Add a new fwrite test that exercises buffer overflow
Checks
Context |
Check |
Description |
redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
linaro-tcwg-bot/tcwg_glibc_build--master-arm |
success
|
Build passed
|
redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
linaro-tcwg-bot/tcwg_glibc_check--master-arm |
success
|
Test passed
|
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 |
success
|
Test passed
|
Commit Message
From: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
Exercises fwrite's internal buffer when doing a file operation.
The new test, exercises 2 overflow behaviors:
1. Call fwrite multiple times making usage of fwrite's internal buffer.
The total number of bytes written is larger than fwrite's internal
buffer, forcing an automatic flush.
2. Call fwrite a single time with an amount of data that is larger than
fwrite's internal buffer.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
---
stdio-common/Makefile | 1 +
stdio-common/tst-fwrite-overflow.c | 123 +++++++++++++++++++++++++++++
2 files changed, 124 insertions(+)
create mode 100644 stdio-common/tst-fwrite-overflow.c
Comments
* Tulio Magno Quites Machado Filho:
> From: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
>
> Exercises fwrite's internal buffer when doing a file operation.
> The new test, exercises 2 overflow behaviors:
>
> 1. Call fwrite multiple times making usage of fwrite's internal buffer.
> The total number of bytes written is larger than fwrite's internal
> buffer, forcing an automatic flush.
>
> 2. Call fwrite a single time with an amount of data that is larger than
> fwrite's internal buffer.
>
> Reviewed-by: Carlos O'Donell <carlos@redhat.com>
Joseph, would you be able to review this, considering your recent work
on fread testing?
Thanks,
Florian
On Mon, 9 Sep 2024, Tulio Magno Quites Machado Filho wrote:
> From: Tulio Magno Quites Machado Filho <tuliom@redhat.com>
>
> Exercises fwrite's internal buffer when doing a file operation.
> The new test, exercises 2 overflow behaviors:
>
> 1. Call fwrite multiple times making usage of fwrite's internal buffer.
> The total number of bytes written is larger than fwrite's internal
> buffer, forcing an automatic flush.
>
> 2. Call fwrite a single time with an amount of data that is larger than
> fwrite's internal buffer.
It might also be useful to test cases where the size parameter passed to
fwrite (the size of an individual item for it to write) is more than one
byte.
> +/* Length of the buffers in bytes. */
> +#define RWBUF_SIZE 2 * BUFSIZ
Use parentheses in the macro definition, (2 * BUFSIZ).
> + for (written = 0; written < nmemb; written += to_write)
> + {
> + if (written + block <= nmemb)
> + {
> + to_write = block;
> + }
> + else
> + {
> + to_write = nmemb - written;
> + }
We don't generally put braces around individual statements like that.
> + out = malloc (RWBUF_SIZE);
> + TEST_VERIFY_EXIT (out != NULL);
This can use xmalloc.
> + in = malloc (RWBUF_SIZE);
> + TEST_VERIFY_EXIT (in != NULL);
Likewise.
> + for (i = 0; i < RWBUF_SIZE / 2; i++)
> + in[i] = i % 0xff;
Why is this only filling in half of buf?
> + for (i = 0; nmemb[i] != 0; i++)
> + {
> + for (j = 0; block[j] != 0; j++)
> + {
> + /* Run a test with an array of nmemb bytes. Write at most block
> + bytes per fwrite call. */
> + test_one_rw (in, nmemb[i], block[j]);
> + }
Similar comment to the above about braces round a single statement.
Joseph Myers <josmyers@redhat.com> writes:
> On Mon, 9 Sep 2024, Tulio Magno Quites Machado Filho wrote:
>> + for (i = 0; i < RWBUF_SIZE / 2; i++)
>> + in[i] = i % 0xff;
>
> Why is this only filling in half of buf?
Oops. This should be RWBUF_SIZE.
I fixed this as well as the other things and will send a new version.
Thanks!
@@ -224,6 +224,7 @@ tests := \
tst-fseek \
tst-fwrite \
tst-fwrite-memstrm \
+ tst-fwrite-overflow \
tst-fwrite-ro \
tst-getline \
tst-getline-enomem \
new file mode 100644
@@ -0,0 +1,123 @@
+/* Test the overflow of fwrite's internal buffer.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+/* stdio.h provides BUFSIZ, which is the size of fwrite's internal buffer. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/temp_file.h>
+#include <support/xstdio.h>
+#include <support/xunistd.h>
+
+/* Length of the buffers in bytes. */
+#define RWBUF_SIZE 2 * BUFSIZ
+
+void
+test_one_rw (const char *in, size_t nmemb, size_t block)
+{
+ int fd;
+ FILE *f;
+ char *out;
+ size_t written, to_write;
+
+ printf ("Testing with nmemb = %zd, block = %zd\n", nmemb, block);
+
+ TEST_VERIFY_EXIT (nmemb <= RWBUF_SIZE);
+ /* Ensure fwrite's internal buffer will overflow. */
+ TEST_VERIFY_EXIT (nmemb > BUFSIZ);
+
+ /* Create a temporary file and open it for reading and writing. */
+ fd = create_temp_file ("tst-fwrite-overflow", NULL);
+ TEST_VERIFY_EXIT (fd != -1);
+ f = fdopen (fd, "w+");
+ TEST_VERIFY_EXIT (f != NULL);
+
+ /* Call fwrite() as many times as needed, until all data is written,
+ limiting the amount of data written per call to block bytes. */
+ for (written = 0; written < nmemb; written += to_write)
+ {
+ if (written + block <= nmemb)
+ {
+ to_write = block;
+ }
+ else
+ {
+ to_write = nmemb - written;
+ }
+ /* Check if fwrite() returns the expected value. No errors are
+ expected. */
+ TEST_COMPARE (fwrite (in + written, 1, to_write, f), to_write);
+ TEST_COMPARE (ferror (f), 0);
+ }
+
+ /* Ensure all the data is flushed to file. */
+ TEST_COMPARE (fflush (f), 0);
+
+ /* We have to check if the contents in the file are correct. Go back to
+ the beginning of the file. */
+ rewind (f);
+ /* Try to allocate a buffer and save the contents of the generated file to
+ it. */
+ out = malloc (RWBUF_SIZE);
+ TEST_VERIFY_EXIT (out != NULL);
+ TEST_COMPARE (fread (out, 1, nmemb, f), nmemb);
+
+ /* Ensure the output has the expected contents. */
+ TEST_COMPARE (memcmp (out, in, nmemb), 0);
+
+ xfclose (f);
+ free (out);
+}
+
+static int
+do_test (void)
+{
+ char * in;
+ int i, j;
+ size_t nmemb[] = {BUFSIZ + 1, RWBUF_SIZE, 0};
+ /* Maximum number of bytes written for each fwrite call. */
+ size_t block[] = {100, 1024, 2047, 0};
+ /* The largest block must fit entirely in fwrite's buffer. */
+ _Static_assert (2047 < BUFSIZ,
+ "a block must fit in fwrite's internal buffer");
+
+ in = malloc (RWBUF_SIZE);
+ TEST_VERIFY_EXIT (in != NULL);
+ for (i = 0; i < RWBUF_SIZE / 2; i++)
+ in[i] = i % 0xff;
+
+ for (i = 0; nmemb[i] != 0; i++)
+ {
+ for (j = 0; block[j] != 0; j++)
+ {
+ /* Run a test with an array of nmemb bytes. Write at most block
+ bytes per fwrite call. */
+ test_one_rw (in, nmemb[i], block[j]);
+ }
+ /* Run a test that overflows fwrite's internal buffer in a single call.
+ This call should not use the buffer and should be written directly
+ to the file. */
+ test_one_rw (in, nmemb[i], nmemb[i]);
+ }
+
+ free (in);
+ return 0;
+}
+
+#include <support/test-driver.c>