* [PATCH] libio: Fix wrong reference to byte stream
@ 2026-03-18 5:06 Rocket Ma
2026-03-23 17:17 ` [PATCH v2] " Rocket Ma
0 siblings, 1 reply; 10+ messages in thread
From: Rocket Ma @ 2026-03-18 5:06 UTC (permalink / raw)
To: libc-alpha
* libio/wgenops.c: Fix _IO_wdefault_pbackfail and _IO_wdefault_finish by
deferencing wide stream pointers, should be a mistake (bug 33998[1] and
bug 33999[2])
[1]: https://sourceware.org/bugzilla/show_bug.cgi?id=33998
[2]: https://sourceware.org/bugzilla/show_bug.cgi?id=33999
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
---
libio/wgenops.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/libio/wgenops.c b/libio/wgenops.c
index 064d71266d..3ebbb941f0 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;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
@@ -181,10 +181,10 @@ _IO_wdefault_finish (FILE *fp, int dummy)
for (mark = fp->_markers; mark != NULL; mark = mark->_next)
mark->_sbuf = NULL;
- if (fp->_IO_save_base)
+ if (fp->_wide_data->_IO_save_base)
{
free (fp->_wide_data->_IO_save_base);
- fp->_IO_save_base = NULL;
+ fp->_wide_data->_IO_save_base = NULL;
}
#ifdef _IO_MTSAFE_IO
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2] libio: Fix wrong reference to byte stream
2026-03-18 5:06 [PATCH] libio: Fix wrong reference to byte stream Rocket Ma
@ 2026-03-23 17:17 ` Rocket Ma
2026-04-17 19:57 ` Carlos O'Donell
0 siblings, 1 reply; 10+ messages in thread
From: Rocket Ma @ 2026-03-23 17:17 UTC (permalink / raw)
To: Rocket Ma; +Cc: libc-alpha
* libio/wgenops.c: Fix _IO_wdefault_pbackfail and _IO_wdefault_finish by
deferencing wide stream pointers, should be a mistake (bug 33998[1] and
bug 33999[2])
The regression test attached can not perform fclose, see bug 34020[3]
[1]: https://sourceware.org/bugzilla/show_bug.cgi?id=33998
[2]: https://sourceware.org/bugzilla/show_bug.cgi?id=33999
[3]: https://sourceware.org/bugzilla/show_bug.cgi?id=34020
Signed-off-by: Rocket Ma <marocketbd@gmail.com>
---
libio/Makefile | 1 +
libio/bug-wgenops.c | 30 ++++++++++++++++++++++++++++++
libio/wgenops.c | 8 ++++----
3 files changed, 35 insertions(+), 4 deletions(-)
create mode 100644 libio/bug-wgenops.c
diff --git a/libio/Makefile b/libio/Makefile
index 08e1e0ec25..da838cdecc 100644
--- a/libio/Makefile
+++ b/libio/Makefile
@@ -84,6 +84,7 @@ tests = \
bug-ungetwc1 \
bug-ungetwc2 \
bug-wfflush \
+ bug-wgenops \
bug-wmemstream1 \
bug-wsetpos \
test-fmemopen \
diff --git a/libio/bug-wgenops.c b/libio/bug-wgenops.c
new file mode 100644
index 0000000000..cf67b9d436
--- /dev/null
+++ b/libio/bug-wgenops.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <wchar.h>
+#include <stdlib.h>
+
+#define tst_assert(cond) \
+ if (!(cond)) \
+ { \
+ puts ("Failed assertion: " #cond); \
+ return 1; \
+ }
+
+static int
+do_test (void)
+{
+ wchar_t *buf = NULL;
+ size_t size = 0;
+ FILE *fp = open_wmemstream (&buf, &size);
+ tst_assert (fp != NULL);
+ tst_assert (fputwc (L'A', fp) != WEOF);
+ tst_assert (fflush (fp) == 0);
+
+ tst_assert (fgetwc (fp) == L'A');
+ tst_assert (ungetwc (L'B', fp) == L'B');
+
+ // fclose (fp); // See BZ 34020; flose will free invalid _IO_save_base
+ free (buf);
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/libio/wgenops.c b/libio/wgenops.c
index 064d71266d..3ebbb941f0 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;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
@@ -181,10 +181,10 @@ _IO_wdefault_finish (FILE *fp, int dummy)
for (mark = fp->_markers; mark != NULL; mark = mark->_next)
mark->_sbuf = NULL;
- if (fp->_IO_save_base)
+ if (fp->_wide_data->_IO_save_base)
{
free (fp->_wide_data->_IO_save_base);
- fp->_IO_save_base = NULL;
+ fp->_wide_data->_IO_save_base = NULL;
}
#ifdef _IO_MTSAFE_IO
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] libio: Fix wrong reference to byte stream
2026-03-23 17:17 ` [PATCH v2] " Rocket Ma
@ 2026-04-17 19:57 ` Carlos O'Donell
2026-04-18 9:24 ` [PATCH v3] libio: Fix ungetwc operating on byte stream [BZ #33998] Rocket Ma
2026-04-18 10:17 ` [PATCH v4] " Rocket Ma
0 siblings, 2 replies; 10+ messages in thread
From: Carlos O'Donell @ 2026-04-17 19:57 UTC (permalink / raw)
To: Rocket Ma; +Cc: libc-alpha
On 3/23/26 1:17 PM, Rocket Ma wrote:
> * libio/wgenops.c: Fix _IO_wdefault_pbackfail and _IO_wdefault_finish by
> deferencing wide stream pointers, should be a mistake (bug 33998[1] and
> bug 33999[2])
Please split the patch to only fix one at a time.
For example 33999 is being discussed here with a test case:
https://inbox.sourceware.org/libc-alpha/20260403015721.343918-1-gaoxiang@kylinos.cn/
> The regression test attached can not perform fclose, see bug 34020[3]
Andreas Schwab commented on this one.
>
> [1]: https://sourceware.org/bugzilla/show_bug.cgi?id=33998
> [2]: https://sourceware.org/bugzilla/show_bug.cgi?id=33999
> [3]: https://sourceware.org/bugzilla/show_bug.cgi?id=34020
>
> Signed-off-by: Rocket Ma <marocketbd@gmail.com>
> ---
> libio/Makefile | 1 +
> libio/bug-wgenops.c | 30 ++++++++++++++++++++++++++++++
> libio/wgenops.c | 8 ++++----
> 3 files changed, 35 insertions(+), 4 deletions(-)
> create mode 100644 libio/bug-wgenops.c
>
> diff --git a/libio/Makefile b/libio/Makefile
> index 08e1e0ec25..da838cdecc 100644
> --- a/libio/Makefile
> +++ b/libio/Makefile
> @@ -84,6 +84,7 @@ tests = \
> bug-ungetwc1 \
> bug-ungetwc2 \
> bug-wfflush \
> + bug-wgenops \
> bug-wmemstream1 \
> bug-wsetpos \
> test-fmemopen \
> diff --git a/libio/bug-wgenops.c b/libio/bug-wgenops.c
> new file mode 100644
> index 0000000000..cf67b9d436
> --- /dev/null
> +++ b/libio/bug-wgenops.c
> @@ -0,0 +1,30 @@
May you please add a copyright header for the test case please?
We license the test cases with a matching project license.
May you please add a line at the top that describes the test.
Example:
/* Bug XXXX: Test XXXX does YYYY.
Copyright 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 <stdio.h>
> +#include <wchar.h>
> +#include <stdlib.h>
> +
> +#define tst_assert(cond) \
> + if (!(cond)) \
> + { \
> + puts ("Failed assertion: " #cond); \
> + return 1; \
> + }
Please see the support/* directory and #include <support/check.h> for TEST_VERIFY.
> +
> +static int
> +do_test (void)
> +{
> + wchar_t *buf = NULL;
> + size_t size = 0;
> + FILE *fp = open_wmemstream (&buf, &size);
> + tst_assert (fp != NULL);
These should be TEST_VERIFY(...);
> + tst_assert (fputwc (L'A', fp) != WEOF);
> + tst_assert (fflush (fp) == 0);
> +
> + tst_assert (fgetwc (fp) == L'A');
> + tst_assert (ungetwc (L'B', fp) == L'B');
> +
> + // fclose (fp); // See BZ 34020; flose will free invalid _IO_save_base
> + free (buf);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
OK. Good use of the support test driver.
> diff --git a/libio/wgenops.c b/libio/wgenops.c
> index 064d71266d..3ebbb941f0 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)
OK. This is the relevant change to use the correct buffer.
> + --fp->_wide_data->_IO_read_ptr;
> else
> {
> /* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
> @@ -181,10 +181,10 @@ _IO_wdefault_finish (FILE *fp, int dummy)
> for (mark = fp->_markers; mark != NULL; mark = mark->_next)
> mark->_sbuf = NULL;
>
> - if (fp->_IO_save_base)
> + if (fp->_wide_data->_IO_save_base)
> {
> free (fp->_wide_data->_IO_save_base);
> - fp->_IO_save_base = NULL;
> + fp->_wide_data->_IO_save_base = NULL;
This is being fixed in a different way by Gao Xiang in:
https://inbox.sourceware.org/libc-alpha/20260403015721.343918-1-gaoxiang@kylinos.cn/
> }
>
> #ifdef _IO_MTSAFE_IO
--
Cheers,
Carlos.
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-04-17 19:57 ` Carlos O'Donell
@ 2026-04-18 9:24 ` Rocket Ma
2026-04-18 10:17 ` [PATCH v4] " Rocket Ma
1 sibling, 0 replies; 10+ messages in thread
From: Rocket Ma @ 2026-04-18 9:24 UTC (permalink / raw)
To: Carlos O'Donell; +Cc: libc-alpha
* 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>
---
libio/Makefile | 1 +
libio/bug-wgenops-bz33998.c | 49 +++++++++++++++++++++++++++++++++++++
libio/wgenops.c | 4 +--
3 files changed, 52 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..4ff4817303
--- /dev/null
+++ b/libio/bug-wgenops-bz33998.c
@@ -0,0 +1,49 @@
+/* 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 <unistd.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+
+#ifndef MFD_CLOEXEC
+extern int memfd_create (const char *, int);
+# define MFD_CLOEXEC 1
+#endif
+
+static int
+do_test (void)
+{
+ int fd = memfd_create ("test", MFD_CLOEXEC);
+ TEST_VERIFY (fd != -1);
+ TEST_COMPARE (write (fd, (unsigned char[]){ 'A', 0, 0, 0 }, 4), 4);
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ FILE *fp = fdopen (fd, "r+");
+ TEST_VERIFY (fp != NULL);
+ TEST_COMPARE (getwc (fp), L'A');
+
+ /* if the bug is fixed, then ungetwc should not touch byte stream. */
+ char *old_read_ptr = fp->_IO_read_ptr;
+ TEST_COMPARE (ungetwc (0, fp), L'\0');
+ TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
+
+ 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;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-04-17 19:57 ` Carlos O'Donell
2026-04-18 9:24 ` [PATCH v3] libio: Fix ungetwc operating on byte stream [BZ #33998] Rocket Ma
@ 2026-04-18 10:17 ` Rocket Ma
2026-04-30 16:11 ` Florian Weimer
1 sibling, 1 reply; 10+ messages in thread
From: Rocket Ma @ 2026-04-18 10:17 UTC (permalink / raw)
To: Carlos O'Donell; +Cc: libc-alpha
* 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>
---
Removed redundant macro from previous patch.
---
libio/Makefile | 1 +
libio/bug-wgenops-bz33998.c | 44 +++++++++++++++++++++++++++++++++++++
libio/wgenops.c | 4 ++--
3 files changed, 47 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..b3f750a753
--- /dev/null
+++ b/libio/bug-wgenops-bz33998.c
@@ -0,0 +1,44 @@
+/* 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 <unistd.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <wchar.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+ int fd = memfd_create ("test", MFD_CLOEXEC);
+ TEST_VERIFY (fd != -1);
+ TEST_COMPARE (write (fd, (unsigned char[]){ 'A', 0, 0, 0 }, 4), 4);
+ TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
+ FILE *fp = fdopen (fd, "r+");
+ TEST_VERIFY (fp != NULL);
+ TEST_COMPARE (getwc (fp), L'A');
+
+ /* if the bug is fixed, then ungetwc should not touch byte stream. */
+ char *old_read_ptr = fp->_IO_read_ptr;
+ TEST_COMPARE (ungetwc (0, fp), L'\0');
+ TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
+
+ 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;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
--
2.53.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-04-18 10:17 ` [PATCH v4] " Rocket Ma
@ 2026-04-30 16:11 ` Florian Weimer
2026-05-01 17:24 ` Rocket Ma
2026-05-02 3:39 ` [PATCH v5] " Rocket Ma
0 siblings, 2 replies; 10+ messages in thread
From: Florian Weimer @ 2026-04-30 16:11 UTC (permalink / raw)
To: Rocket Ma; +Cc: Carlos O'Donell, libc-alpha
* Rocket Ma:
> diff --git a/libio/bug-wgenops-bz33998.c b/libio/bug-wgenops-bz33998.c
> new file mode 100644
> index 0000000000..b3f750a753
> --- /dev/null
> +++ b/libio/bug-wgenops-bz33998.c
> @@ -0,0 +1,44 @@
> +/* 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 <unistd.h>
> +#include <sys/mman.h>
> +#include <stdio.h>
> +#include <wchar.h>
> +#include <support/check.h>
> +
> +static int
> +do_test (void)
> +{
> + int fd = memfd_create ("test", MFD_CLOEXEC);
> + TEST_VERIFY (fd != -1);
This may fail on older kernels, so please use create_temp_file from
<support/temp_file.h>.
> + TEST_COMPARE (write (fd, (unsigned char[]){ 'A', 0, 0, 0 }, 4), 4);
> + TEST_COMPARE (lseek (fd, 0, SEEK_SET), 0);
> + FILE *fp = fdopen (fd, "r+");
You could use xwrite, xlseek, xfopen.
> + TEST_VERIFY (fp != NULL);
> + TEST_COMPARE (getwc (fp), L'A');
> +
> + /* if the bug is fixed, then ungetwc should not touch byte stream. */
> + char *old_read_ptr = fp->_IO_read_ptr;
> + TEST_COMPARE (ungetwc (0, fp), L'\0');
Can you please use 0 or L'\0' in both places?
> + TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
You could check that the null character can be read back with fgetwc.
And call xfclose at the end.
> 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;
> else
> {
> /* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
The fix itself looks good to me.
Thanks,
Florian
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-04-30 16:11 ` Florian Weimer
@ 2026-05-01 17:24 ` Rocket Ma
2026-05-02 3:39 ` [PATCH v5] " Rocket Ma
1 sibling, 0 replies; 10+ messages in thread
From: Rocket Ma @ 2026-05-01 17:24 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
> > + TEST_VERIFY (fp->_IO_read_ptr == old_read_ptr);
>
> You could check that the null character can be read back with fgetwc.
> And call xfclose at the end.
In my regression test, the buffer in FILE (byte stream) is "A\0\0\0",
and the buffer in wide FILE (wide stream) is L"A\0\0\0\0", in this
case we can reproduce the error easily, instead of crafting a
complicated test case.[1] In this case, read_ptr of wide stream is 1
out of 4 (A | \0\0\0), leading to next fgetwc returns L'\0' (no new
buffer allocated, read_ptr in byte stream is decreased by 1, no change
on read_ptr in wide stream). If the fix is applied, read_ptr will be
set to the buffer allocated by pbackfail, so L'\0' is returned. The
value returned by fgetwc is always L'\0', so we can not distinguish if
the fix is applied.
[1]: If the buffer in byte stream is "A" instead, then read_ptr[-1] is
'A', like wide stream. Then we can not verify if the bug exists any
more as the bug code path is not entered. Or we set up a locale and
find a character that could be verified if the bug still exists, but
that's a bit hard.
So I still think verifying `fp->_IO_read_ptr == old_read_ptr` is suitable.
Rocket
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v5] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-04-30 16:11 ` Florian Weimer
2026-05-01 17:24 ` Rocket Ma
@ 2026-05-02 3:39 ` Rocket Ma
2026-05-07 14:41 ` Carlos O'Donell
1 sibling, 1 reply; 10+ messages in thread
From: Rocket Ma @ 2026-05-02 3:39 UTC (permalink / raw)
To: Florian Weimer; +Cc: libc-alpha
* 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
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;
else
{
/* Need to handle a filebuf in write mode (switch to read mode). FIXME!*/
--
2.54.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-05-02 3:39 ` [PATCH v5] " Rocket Ma
@ 2026-05-07 14:41 ` Carlos O'Donell
2026-05-07 16:29 ` Rocket Ma
0 siblings, 1 reply; 10+ messages in thread
From: Carlos O'Donell @ 2026-05-07 14:41 UTC (permalink / raw)
To: Rocket Ma, Florian Weimer; +Cc: libc-alpha
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.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v5] libio: Fix ungetwc operating on byte stream [BZ #33998]
2026-05-07 14:41 ` Carlos O'Donell
@ 2026-05-07 16:29 ` Rocket Ma
0 siblings, 0 replies; 10+ messages in thread
From: Rocket Ma @ 2026-05-07 16:29 UTC (permalink / raw)
To: Carlos O'Donell; +Cc: libc-alpha
> 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
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-05-07 16:29 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-18 5:06 [PATCH] libio: Fix wrong reference to byte stream Rocket Ma
2026-03-23 17:17 ` [PATCH v2] " Rocket Ma
2026-04-17 19:57 ` Carlos O'Donell
2026-04-18 9:24 ` [PATCH v3] libio: Fix ungetwc operating on byte stream [BZ #33998] Rocket Ma
2026-04-18 10:17 ` [PATCH v4] " Rocket Ma
2026-04-30 16:11 ` Florian Weimer
2026-05-01 17:24 ` Rocket Ma
2026-05-02 3:39 ` [PATCH v5] " Rocket Ma
2026-05-07 14:41 ` Carlos O'Donell
2026-05-07 16:29 ` Rocket Ma
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).