Add a new fwrite test that exercises buffer overflow

Message ID 20240909194622.2565139-1-tuliom@ascii.art.br
State Superseded
Delegated to: Joseph Myers
Headers
Series 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

Tulio Magno Quites Machado Filho Sept. 9, 2024, 7:46 p.m. UTC
  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

Florian Weimer Sept. 24, 2024, 10:49 a.m. UTC | #1
* 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
  
Joseph Myers Sept. 24, 2024, 9:20 p.m. UTC | #2
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.
  
Tulio Magno Quites Machado Filho Sept. 26, 2024, 1:13 p.m. UTC | #3
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!
  

Patch

diff --git a/stdio-common/Makefile b/stdio-common/Makefile
index ce7f7cdd3b..a0e2753418 100644
--- a/stdio-common/Makefile
+++ b/stdio-common/Makefile
@@ -224,6 +224,7 @@  tests := \
   tst-fseek \
   tst-fwrite \
   tst-fwrite-memstrm \
+  tst-fwrite-overflow \
   tst-fwrite-ro \
   tst-getline \
   tst-getline-enomem \
diff --git a/stdio-common/tst-fwrite-overflow.c b/stdio-common/tst-fwrite-overflow.c
new file mode 100644
index 0000000000..1516e4cd0a
--- /dev/null
+++ b/stdio-common/tst-fwrite-overflow.c
@@ -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>