[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