[COMMITTED 2.42 2/2] stdio-common: Fix buffer overflow in scanf %mc [BZ #34008]

Aurelien Jarno aurelien@aurel32.net
Sat May 9 11:54:37 GMT 2026


Hi Florian,

Thanks for looking into that. I have done the backport and checked that 
the new test passes, so I though that the fix was fine for that branch.

On 2026-05-09 12:01, Florian Weimer wrote:
> * Aurelien Jarno:
> 
> > From: Rocket Ma <marocketbd@gmail.com>
> >
> > * stdio-common/vfscanf-internal.c: When enlarging allocated buffer with
> > format %mc or %mC, glibc allocates one byte less, leading to
> > user-controlled one byte overflow. This commit fixes BZ #34008, or
> > CVE-2026-5450.
> >
> > Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> > Signed-off-by: Rocket Ma <marocketbd@gmail.com>
> > Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
> > (cherry picked from commit 839898777226a3ed88c0859f25ffe712519b4ead)
> 
> I believe scanf %mc remains non-functional on glibc 2.42 and earlier due
> to bug 12701:
> 
>   Bug 12701 - scanf accepts non-matching input 
>   <https://sourceware.org/bugzilla/show_bug.cgi?id=12701>
> 
> The fix that changes %Nc not to accept partial matches is unfortunately
> not backportable because it breaks some applications.
> 
> Without further changes, there is no way for the application to know how
> much memory glibc has allocated after a failed match.

Do you mean the following commits two commits that are not in 2.42?
b52ecff316: Reject significands w/o digits in scanf [BZ #12701]
2b16c76609: Reject insufficient character data in scanf [BZ #12701]

The others commit listed in that bug report are included in 2.42.

> Consider this example program:
> 
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> 
> int
> main (void)
> {
>   {
>     char *volatile p = malloc (40);
>     memset (p, 'X', 40);
>     free (p);
>     printf ("old p: %p\n", p);
>   }
>   char *p = NULL;
>   printf ("sscanf: %d\n",
>           sscanf ("YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY", "%40mc", &p));
>   printf ("new p: %p\np: ", p);
>   fwrite (p, 1, 40, stdout);
>   putchar ('\n');
> }
> 
>
> With valgrind, we can see that the allocation size is 32, not 40:
> 
> ==504594== 
> old p: 0x4a6c040
> sscanf: 1
> new p: 0x4a6c560
> ==504594== Invalid read of size 1
> ==504594==    at 0x48E07AD: _IO_new_file_xsputn (fileops.c:1280)
> ==504594==    by 0x48E07AD: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1258)
> ==504594==    by 0x48D32CA: fwrite (iofwrite.c:44)
> ==504594==    by 0x40113A: main (sscanf-mc.c:18)
> ==504594==  Address 0x4a6c587 is 7 bytes after a block of size 32 alloc'd
> ==504594==    at 0x48488E1: realloc (vg_replace_malloc.c:1741)
> ==504594==    by 0x48BA102: __vfscanf_internal (vfscanf-internal.c:896)
> ==504594==    by 0x48ABCD0: __isoc23_sscanf (isoc23_sscanf.c:31)
> ==504594==    by 0x401100: main (sscanf-mc.c:15)
> ==504594== 
> ==504594== Invalid read of size 1
> ==504594==    at 0x485024E: mempcpy (vg_replace_strmem.c:1715)
> ==504594==    by 0x48E069E: _IO_new_file_xsputn (fileops.c:1297)
> ==504594==    by 0x48E069E: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1258)
> ==504594==    by 0x48D32CA: fwrite (iofwrite.c:44)
> ==504594==    by 0x40113A: main (sscanf-mc.c:18)
> ==504594==  Address 0x4a6c580 is 0 bytes after a block of size 32 alloc'd
> ==504594==    at 0x48488E1: realloc (vg_replace_malloc.c:1741)
> ==504594==    by 0x48BA102: __vfscanf_internal (vfscanf-internal.c:896)
> ==504594==    by 0x48ABCD0: __isoc23_sscanf (isoc23_sscanf.c:31)
> ==504594==    by 0x401100: main (sscanf-mc.c:15)
> ==504594== 
> ==504594== Invalid read of size 1
> ==504594==    at 0x4850240: mempcpy (vg_replace_strmem.c:1715)
> ==504594==    by 0x48E069E: _IO_new_file_xsputn (fileops.c:1297)
> ==504594==    by 0x48E069E: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1258)
> ==504594==    by 0x48D32CA: fwrite (iofwrite.c:44)
> ==504594==    by 0x40113A: main (sscanf-mc.c:18)
> ==504594==  Address 0x4a6c581 is 1 bytes after a block of size 32 alloc'd
> ==504594==    at 0x48488E1: realloc (vg_replace_malloc.c:1741)
> ==504594==    by 0x48BA102: __vfscanf_internal (vfscanf-internal.c:896)
> ==504594==    by 0x48ABCD0: __isoc23_sscanf (isoc23_sscanf.c:31)
> ==504594==    by 0x401100: main (sscanf-mc.c:15)
> ==504594== 
> p: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY
>
> And without valgrind, it becomes clear that the application sees
> uninitialized memory:
> 
> old p: 0x14b65310
> sscanf: 1
> new p: 0x14b65310
> p: YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXX

Indeed, thanks for the example.

> I think we need always allocate the expected size, and clear the tail
> that was not matched.

Do you mean we need a different fix for 2.42 and older? In that case I 
guess it's better I just revert the commit for now.

Regards
Aurelien

-- 
Aurelien Jarno                          GPG: 4096R/1DDD8C9B
aurelien@aurel32.net                     http://aurel32.net


More information about the Libc-stable mailing list