[v5] libio: Fix ungetwc operating on byte stream [BZ #33998]
Checks
| Context |
Check |
Description |
| redhat-pt-bot/TryBot-apply_patch |
success
|
Patch applied to master at the time it was sent
|
| redhat-pt-bot/TryBot-32bit |
success
|
Build for i686
|
Commit Message
* libio/wgenops.c: When _IO_wdefault_pbackfail attempts to push back one
character, it accidently compare the wchar to push back with the last
char from byte stream, instead of wide stream. Under specific coding,
attacker may exploit this to leak information. This commit fix bug
33998, or CVE-2026-5928.
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
---
Use more convenient functions from support to reduce usage of TEST_*.
Add some comment about the regression test.
---
libio/Makefile | 1 +
libio/bug-wgenops-bz33998.c | 54 +++++++++++++++++++++++++++++++++++++
libio/wgenops.c | 4 +--
3 files changed, 57 insertions(+), 2 deletions(-)
create mode 100644 libio/bug-wgenops-bz33998.c
Comments
On 5/1/26 11:39 PM, Rocket Ma wrote:
> * libio/wgenops.c: When _IO_wdefault_pbackfail attempts to push back one
> character, it accidently compare the wchar to push back with the last
> char from byte stream, instead of wide stream. Under specific coding,
> attacker may exploit this to leak information. This commit fix bug
> 33998, or CVE-2026-5928.
>
> Signed-off-by: Rocket Ma <marocketbd@gmail.com>
I reviewed the comments from Florian in v4, and they are largely applied here.
I also reviewed Rocket Ma's comments about why it isn't easy to have a closed-box test.
The current open-box test which looks at _IO_read_ptr is the most straight forward.
I've pushed the fix.
One last comment I want to make:
- For DCO'd contributions we should not include "(C) 2026" since thsi is not
a copyright statement, and as such I'm going to clean up the 3 of these that
have made it into the tree in the next commit.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> ---
> Use more convenient functions from support to reduce usage of TEST_*.
> Add some comment about the regression test.
> ---
> libio/Makefile | 1 +
> libio/bug-wgenops-bz33998.c | 54 +++++++++++++++++++++++++++++++++++++
> libio/wgenops.c | 4 +--
> 3 files changed, 57 insertions(+), 2 deletions(-)
> create mode 100644 libio/bug-wgenops-bz33998.c
>
> diff --git a/libio/Makefile b/libio/Makefile
> index 93656466df..6e0627bb88 100644
> --- a/libio/Makefile
> +++ b/libio/Makefile
> @@ -84,6 +84,7 @@ tests = \
> bug-ungetwc1 \
> bug-ungetwc2 \
> bug-wfflush \
> + bug-wgenops-bz33998 \
> bug-wmemstream1 \
> bug-wsetpos \
> test-fmemopen \
> diff --git a/libio/bug-wgenops-bz33998.c b/libio/bug-wgenops-bz33998.c
> new file mode 100644
> index 0000000000..cc4067da99
> --- /dev/null
> +++ b/libio/bug-wgenops-bz33998.c
> @@ -0,0 +1,54 @@
> +/* Regression test for ungetwc operating on byte stream (BZ #33998)
> + Copyright (C) 2026 The GNU Toolchain Authors.
> + 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/>. */
> +
> +#include "support/temp_file.h"
> +#include "support/xstdio.h"
> +#include "support/xunistd.h"
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <sys/mman.h>
> +#include <stdio.h>
> +#include <wchar.h>
> +#include <support/check.h>
> +
> +static int
> +do_test (void)
> +{
> + char *filename;
> + int fd = create_temp_file ("tst-bz33998-", &filename);
> + TEST_VERIFY (fd != -1);
> + xwrite (fd, "A", sizeof ("A")); // write "A\0" by design
> + xclose (fd);
> +
> + FILE *fp = xfopen (filename, "r+");
> + TEST_COMPARE (getwc (fp), L'A');
> + /* If the bug is fixed, then ungetwc should not touch byte stream.
> + If the bug is not fixed, ungetwc firstly match last read char, L'A',
> + failed, then the pbackfail branch, matching last read char in byte
> + stream, that is, '\0' (initialized when setup wide stream). */
> + char *old_read_ptr = fp->_IO_read_ptr;
> + TEST_COMPARE (ungetwc (L'\0', fp), L'\0');
> + TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
> +
> + xfclose (fp);
> + free (filename);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/libio/wgenops.c b/libio/wgenops.c
> index 6829477e0c..5f36bc49a1 100644
> --- a/libio/wgenops.c
> +++ b/libio/wgenops.c
> @@ -110,8 +110,8 @@ _IO_wdefault_pbackfail (FILE *fp, wint_t c)
> {
> if (fp->_wide_data->_IO_read_ptr > fp->_wide_data->_IO_read_base
> && !_IO_in_backup (fp)
> - && (wint_t) fp->_IO_read_ptr[-1] == c)
> - --fp->_IO_read_ptr;
> + && (wint_t) fp->_wide_data->_IO_read_ptr[-1] == c)
> + --fp->_wide_data->_IO_read_ptr;
OK. As expected, reads _wide_data.
> else
> {
> /* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
> One last comment I want to make:
> - For DCO'd contributions we should not include "(C) 2026" since thsi is not
> a copyright statement, and as such I'm going to clean up the 3 of these that
> have made it into the tree in the next commit.
Oh, got it. Actually when I looking at glibc wiki, I didn't see this
convention...
Rocket
@@ -84,6 +84,7 @@ tests = \
bug-ungetwc1 \
bug-ungetwc2 \
bug-wfflush \
+ bug-wgenops-bz33998 \
bug-wmemstream1 \
bug-wsetpos \
test-fmemopen \
new file mode 100644
@@ -0,0 +1,54 @@
+/* Regression test for ungetwc operating on byte stream (BZ #33998)
+ Copyright (C) 2026 The GNU Toolchain Authors.
+ 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/>. */
+
+#include "support/temp_file.h"
+#include "support/xstdio.h"
+#include "support/xunistd.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+ char *filename;
+ int fd = create_temp_file ("tst-bz33998-", &filename);
+ TEST_VERIFY (fd != -1);
+ xwrite (fd, "A", sizeof ("A")); // write "A\0" by design
+ xclose (fd);
+
+ FILE *fp = xfopen (filename, "r+");
+ TEST_COMPARE (getwc (fp), L'A');
+ /* If the bug is fixed, then ungetwc should not touch byte stream.
+ If the bug is not fixed, ungetwc firstly match last read char, L'A',
+ failed, then the pbackfail branch, matching last read char in byte
+ stream, that is, '\0' (initialized when setup wide stream). */
+ char *old_read_ptr = fp->_IO_read_ptr;
+ TEST_COMPARE (ungetwc (L'\0', fp), L'\0');
+ TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
+
+ xfclose (fp);
+ free (filename);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -110,8 +110,8 @@ _IO_wdefault_pbackfail (FILE *fp, wint_t c)
{
if (fp->_wide_data->_IO_read_ptr > fp->_wide_data->_IO_read_base
&& !_IO_in_backup (fp)
- && (wint_t) fp->_IO_read_ptr[-1] == c)
- --fp->_IO_read_ptr;
+ && (wint_t) fp->_wide_data->_IO_read_ptr[-1] == c)
+ --fp->_wide_data->_IO_read_ptr;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/