[PATCH v5] libio: Fix ungetwc operating on byte stream [BZ #33998]

Carlos O'Donell carlos@redhat.com
Thu May 7 14:41:08 GMT 2026


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!*/


-- 
Cheers,
Carlos.



More information about the Libc-alpha mailing list