From patchwork Fri Aug 28 09:45:17 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Dongkyun Son X-Patchwork-Id: 142381 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 3EEA84BA799D for ; Fri, 28 Aug 2026 09:45:58 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3EEA84BA799D Authentication-Results: sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=samsung.com header.i=@samsung.com header.a=rsa-sha256 header.s=mail20170921 header.b=YbWP1fTd X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mailout2.samsung.com (mailout2.samsung.com [203.254.224.25]) by sourceware.org (Postfix) with ESMTPS id 7C7E64BA2E20 for ; Fri, 28 Aug 2026 09:45:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7C7E64BA2E20 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=samsung.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=samsung.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 7C7E64BA2E20 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=203.254.224.25 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1787910323; cv=none; b=YvHTClibcZtL46v09QGpbuMRzh6sb2MQ5FPGFFoYrnYaNNM6iZMlgUkVXca8KZAFZqbsSo26t5wsoDAdB38GBL6L0Exl4CF5Hgue4Hk0bg0tT23Ym4vRgHvfbJf4BRXiLU+GL2IfI4SMS///z+hs+6/mo/+dWiv5zU+/53VIxBI= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1787910323; c=relaxed/simple; bh=NPy8gf5TUUSRbeiJJ1Jpfsg3PoQiz6MtWdXDXz75MlI=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=VpqHLmRrHA/sqbBzJdw5dZPWJZ8Mgb7PKbM+az7G63LXfZJGq3S7Ba50w/6tnYVoJEPmFguyvyfsQUNQI/4PzoMNiOgMJenqYTy80rkCC9+SNBVdiNH9Ev7e37+NNRMjRSoMGedDI28XDRD2M1jgnVZ4mcHhX/Z9Wm/f8tbnXL4= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (1024-bit key, unprotected) header.d=samsung.com header.i=@samsung.com header.a=rsa-sha256 header.s=mail20170921 header.b=YbWP1fTd DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7C7E64BA2E20 Received: from epcas1p4.samsung.com (unknown [182.195.41.48]) by mailout2.samsung.com (KnoxPortal) with ESMTP id 20260828094519epoutp0264a9b7b687994fe402487c1a2968d822~P7vZ5wN2c2835728357epoutp02L for ; Fri, 28 Aug 2026 09:45:19 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 mailout2.samsung.com 20260828094519epoutp0264a9b7b687994fe402487c1a2968d822~P7vZ5wN2c2835728357epoutp02L DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=samsung.com; s=mail20170921; t=1787910319; bh=/G5G7zpQz+La2N7YuyAa+AptM4++W71PFOm1AyPaVak=; h=From:To:Cc:Subject:Date:References:From; b=YbWP1fTdRm3g1R7rncL7fbBFeeMehNRL3gqctRinUiwOGPgJtaZyNjSKNVyXEWR+P iqvdyCBfytnqiCRHkht8B0uLxiUnv6GAgVSH5jadfVOhij/jxjm23qZgI9+ViwruNc EA/ObVU/JiBIHa2v7b1/a3z7e7HgIwrO9Hfx2wdE= Received: from epsnrtp03.localdomain (unknown [182.195.42.155]) by epcas1p1.samsung.com (KnoxPortal) with ESMTPS id 20260828094518epcas1p1795a48e153aa44e9165b963d955132fb~P7vZJTrPZ1991919919epcas1p1V; Fri, 28 Aug 2026 09:45:18 +0000 (GMT) Received: from epcas1p1.samsung.com (unknown [182.195.38.98]) by epsnrtp03.localdomain (Postfix) with ESMTP id 4hWYQk0r6Yz3hhTC; Fri, 28 Aug 2026 09:45:18 +0000 (GMT) Received: from epsmtip2.samsung.com (unknown [182.195.34.31]) by epcas1p2.samsung.com (KnoxPortal) with ESMTPA id 20260828094517epcas1p212ab6ad88b4d9ba40670505d309a756d~P7vYMs3S72617126171epcas1p2G; Fri, 28 Aug 2026 09:45:17 +0000 (GMT) Received: from dongkyuns002 (unknown [10.113.112.105]) by epsmtip2.samsung.com (KnoxPortal) with ESMTPA id 20260828094517epsmtip24769a3839a2e252f00872c9e0fe48961~P7vYKDHPl1734317343epsmtip2H; Fri, 28 Aug 2026 09:45:17 +0000 (GMT) From: =?utf-8?b?7IaQ64+Z6regL1Byb2Nlc3MgJiBJbmZyYSBMYWIoU1IpL+yCvOyEseyghA==?= =?utf-8?b?7J6Q?= To: "'Florian Weimer'" Cc: , Subject: [PATCH v3] libio: Fix CVE-2026-18374 heap buffer overflow in ccs= handling Date: Fri, 28 Aug 2026 18:45:17 +0900 Message-ID: <006c01dd36d1$eeef5530$cccdff90$@samsung.com> MIME-Version: 1.0 X-Mailer: Microsoft Outlook 16.0 Thread-Index: Ad020e6CRBG2V0ddTbG8IR0wmUvVSw== Content-Language: ko X-CMS-MailID: 20260828094517epcas1p212ab6ad88b4d9ba40670505d309a756d X-Msg-Generator: CA CMS-TYPE: 101P cpgsPolicy: CPGSC10-361,Y X-CFilter-Loop: Reflected X-CMS-RootMailID: 20260828094517epcas1p212ab6ad88b4d9ba40670505d309a756d References: X-Spam-Status: No, score=-12.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_PASS, SPF_NONE, TXREP shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org When fopen is called with a ,ccs= specification whose value is not empty but becomes empty after strip() (for example "r,ccs=/,..."), the code took the fallback branch __wcsmbs_named_conv (&fcts, ccs[2] == '\0' ? upstr (ccs, cs + 5) : ccs) and upstr() copied from cs + 5 up to the terminating NUL of the whole mode string -- past the ',' delimiter at endp -- into ccs, which is only allocated for endp - (cs + 5) + 3 bytes. This overflows the heap buffer. Checking for an empty ccs= token before strip() (as an earlier attempt did) does not help, because in the reproducer the token is only empty after strip(), not before. Bound the fallback copy to the [cs + 5, endp) charset token so it can no longer read past the delimiter. A specification that is empty after strip() has no valid charset name and keeps failing with EINVAL from __wcsmbs_named_conv. A regression test that reproduces the overflow (and the trivial empty ,ccs= case) is added to libio/tst-fopenloc.c. CVE-2026-18374 - CVSS 4.9 (AV:L/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L) Reported-by: AISLE in partnership with Red Hat Signed-off-by: Dongkyun Son --- Changes since v2 (thanks to Florian Weimer for the review): - v2 was wrong: it rejected a ccs= token that was empty *before* strip(). The actual overflow happens when the token is non-empty but becomes empty *after* strip(), which takes the upstr (ccs, cs + 5) fallback and copies past the ',' delimiter. v3 bounds that fallback to the [cs + 5, endp) token instead. libio/fileops.c | 15 ++++++++++++-- libio/tst-fopenloc.c | 49 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/libio/fileops.c b/libio/fileops.c index 9348d7c3a1..f42588a94f 100644 --- a/libio/fileops.c +++ b/libio/fileops.c @@ -355,8 +355,19 @@ _IO_new_file_fopen (FILE *fp, const char *filename, const char *mode, *((char *) __mempcpy (ccs, cs + 5, endp - (cs + 5))) = '\0'; strip (ccs, ccs); - if (__wcsmbs_named_conv (&fcts, ccs[2] == '\0' - ? upstr (ccs, cs + 5) : ccs) != 0) + /* If strip() reduced the specification to the empty string (leaving + only the "//" that strip() always appends), fall back to the + original charset name. Bound the copy by ENDP so we do not read + past the ',' delimiter and overflow CCS (CVE-2026-18374). */ + if (ccs[2] == '\0') + { + char *wp = ccs; + for (const char *rp = cs + 5; rp < endp; ++rp) + *wp++ = __toupper_l (*rp, _nl_C_locobj_ptr); + *wp = '\0'; + } + + if (__wcsmbs_named_conv (&fcts, ccs) != 0) { /* Something went wrong, we cannot load the conversion modules. This means we cannot proceed since the user explicitly asked diff --git a/libio/tst-fopenloc.c b/libio/tst-fopenloc.c index ea3f7b5265..5ce3bc5abb 100644 --- a/libio/tst-fopenloc.c +++ b/libio/tst-fopenloc.c @@ -85,6 +85,54 @@ do_bz18906 (void) return EXIT_SUCCESS; } +static int +do_cve_2026_18374 (void) +{ + /* CVE-2026-18374 -- a ,ccs= specification that is not empty but becomes + empty after strip() must not make fopen read past the ',' delimiter and + overflow the heap buffer. It has to fail cleanly with EINVAL. */ + + const size_t sz = 8192; + char *ccs = xmalloc (sz); + strcpy (ccs, "r,ccs=/,"); + memset (ccs + 8, 'A', sz - 8 - 1); + ccs[sz - 1] = '\0'; + + errno = 0; + FILE *fp = fopen (inputfile, ccs); + if (fp != NULL) + { + printf ("fopen with empty-after-strip ccs= unexpectedly succeeded\n"); + free (ccs); + fclose (fp); + return 1; + } + if (errno != EINVAL) + { + printf ("expected EINVAL, got %d\n", errno); + free (ccs); + return 1; + } + free (ccs); + + /* Also check the trivially empty ,ccs= case. */ + errno = 0; + fp = fopen (inputfile, "r,ccs="); + if (fp != NULL) + { + printf ("fopen with empty ccs= unexpectedly succeeded\n"); + fclose (fp); + return 1; + } + if (errno != EINVAL) + { + printf ("expected EINVAL, got %d\n", errno); + return 1; + } + + return 0; +} + static int do_test (void) { @@ -110,6 +158,7 @@ do_test (void) TEST_COMPARE (do_bz17916 (), 0); TEST_COMPARE (do_bz18906 (), 0); + TEST_COMPARE (do_cve_2026_18374 (), 0); return EXIT_SUCCESS; }