From patchwork Fri Nov 6 17:47:02 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Marko X-Patchwork-Id: 103322 X-Patchwork-Delegate: jeremy.rosen@smile.fr Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1754.1793987241089242669 for ; Fri, 06 Nov 2026 09:47:22 -0800 Authentication-Results: mx.groups.io; dkim=pass header.i=peter.marko@siemens.com header.s=fm2 header.b=DzEFpeIK; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-256628-20261106174717d01ba3a5020002074d-pjvrkt@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 20261106174717d01ba3a5020002074d for ; Fri, 06 Nov 2026 18:47:18 +0100 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=peter.marko@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc; bh=zsTK4Oqx2PIoZwvqN8t1YqPBMwzhXPVCi9/nPnzemnQ=; b=DzEFpeIKTDu8/LPEo3rEcKryCiI4MS6GM4m7g1ESMib0KX6lBptZ8NAzekOBq9sfcA49XQ fTTTpo+awM7UtvNOKroS5pbartTsurjaYy0RT+Ge0Q9MIGH/zlKW4g8TrlsHiG50Wmuz5UnW 6/V24uAL9BHKA2IbDieAnpncqKFji6mygVQQpR0qS9YxzoJpVALW16x3jgdOvmYBTucri9FF 7BPOCPOp/Kp6GbywzePku32PHPSNaxQ4oSCdIhURWSszRkzxoKkob21sBHBhxwpSjbeqS7xd Z8dyJo1waP9eB1vkGxsG1FPOkC7Wmw06WJX2phrTF/5KyUJnUVMIc3vg==; From: Peter Marko To: openembedded-core@lists.openembedded.org Cc: Peter Marko Subject: [scarthgap][PATCH] glibc: patch CVE-2026-86805 and CVE-2026-95818 Date: Fri, 6 Nov 2026 18:47:02 +0100 Message-ID: <20261106174702.378945-1-peter.marko@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-256628:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by lin-tor-korg-lkml-alma10-1 with HTTPS for ; Fri, 06 Nov 2026 17:47:30 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/249711 From: Peter Marko Pick patch [1] which is [2] backported to 2.41 (oldest release still receiving security updates) which is mentioned in [3] and [4]. On request, all testing code was removed from the patch. [1] https://sourceware.org/git/?p=glibc.git;a=commit;h=629565b0c91dc326ac622b05982f21a374cd03c4 [2] https://sourceware.org/git/?p=glibc.git;a=commit;h=ed0c137b97eb940b4b64981e84ed806d3276edd9 [3] https://security-tracker.debian.org/tracker/CVE-2026-86805 [4] https://security-tracker.debian.org/tracker/CVE-2026-95818 Signed-off-by: Peter Marko --- .../0049-CVE-2026-86805-CVE-2026-95818.patch | 267 ++++++++++++++++++ meta/recipes-core/glibc/glibc_2.39.bb | 1 + 2 files changed, 268 insertions(+) create mode 100644 meta/recipes-core/glibc/glibc/0049-CVE-2026-86805-CVE-2026-95818.patch diff --git a/meta/recipes-core/glibc/glibc/0049-CVE-2026-86805-CVE-2026-95818.patch b/meta/recipes-core/glibc/glibc/0049-CVE-2026-86805-CVE-2026-95818.patch new file mode 100644 index 00000000000..dde4f8b901f --- /dev/null +++ b/meta/recipes-core/glibc/glibc/0049-CVE-2026-86805-CVE-2026-95818.patch @@ -0,0 +1,267 @@ +From 629565b0c91dc326ac622b05982f21a374cd03c4 Mon Sep 17 00:00:00 2001 +From: Adhemerval Zanella +Date: Mon, 17 Aug 2026 15:15:32 -0300 +Subject: [PATCH] elf: Open the normalized $ORIGIN rpath in AT_SECURE programs + (BZ 34360) + +For AT_SECURE programs the loader honors $ORIGIN in DT_RPATH only when the +expansion is rooted in a trusted directory, but it validated the lexically +normalized path while opening the raw expansion. As "a/b/../c" only names +"a/c" when "b" is not a symlink, an attacker who controls a component of +$ORIGIN -- e.g. by hard-linking the setuid binary into an attacker-owned +directory -- can make the opened path escape the trusted directory even +though the check passed, loading an attacker-controlled object. + +Normalize the expansion in place and open that, so the path that is opened +is exactly the path that was validated. _dl_normalize_path rewrites the +string in place without ever advancing its write cursor past its read +cursor or appending, so it stays within the original storage. + +Add elf/tst-origin-secure as a regression test. + +Reviewed-by: Florian Weimer +(cherry picked from commit ed0c137b97eb940b4b64981e84ed806d3276edd9) + +CVE: CVE-2026-86805 +CVE: CVE-2026-95818 +Upstream-Status: Backport [https://sourceware.org/git/?p=glibc.git;a=commit;h=629565b0c91dc326ac622b05982f21a374cd03c4] +Omit the regression tests and their test harness prerequisites from this backport. +Signed-off-by: Peter Marko +--- + elf/dl-load.c | 74 +++++++++---------------- + elf/dl-path-normalize.h | 118 ++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 143 insertions(+), 49 deletions(-) + create mode 100644 elf/dl-path-normalize.h + +diff --git a/elf/dl-load.c b/elf/dl-load.c +index 7b21f1dffa..0d141a61c1 100644 +--- a/elf/dl-load.c ++++ b/elf/dl-load.c +@@ -32,6 +32,7 @@ + #include + #include + #include ++#include + + /* Type for the buffer we put the ELF header and hopefully the program + header. This buffer does not really have to be too large. In most +@@ -124,54 +125,24 @@ static const size_t system_dirs_len[] = + }; + #define nsystem_dirs_len array_length (system_dirs_len) + ++/* Return true if the normalized path NPATH of length NLEN is rooted in one of ++ the trusted system directories. The system_dirs entries carry a trailing ++ '/'; NPATH matches an entry when it shares the entry's leading component ++ sequence and then either ends or continues with '/'. For instance, ++ "/lib64" and "/lib64/x" match "/lib64/" but "/lib64x" does not. */ + static bool +-is_trusted_path_normalize (const char *path, size_t len) ++path_is_trusted (const char *npath, size_t nlen) + { +- if (len == 0) +- return false; +- +- char *npath = (char *) alloca (len + 2); +- char *wnp = npath; +- while (*path != '\0') +- { +- if (path[0] == '/') +- { +- if (path[1] == '.') +- { +- if (path[2] == '.' && (path[3] == '/' || path[3] == '\0')) +- { +- while (wnp > npath && *--wnp != '/') +- ; +- path += 3; +- continue; +- } +- else if (path[2] == '/' || path[2] == '\0') +- { +- path += 2; +- continue; +- } +- } +- +- if (wnp > npath && wnp[-1] == '/') +- { +- ++path; +- continue; +- } +- } +- +- *wnp++ = *path++; +- } +- +- if (wnp == npath || wnp[-1] != '/') +- *wnp++ = '/'; +- + const char *trun = system_dirs; + + for (size_t idx = 0; idx < nsystem_dirs_len; ++idx) + { +- if (wnp - npath >= system_dirs_len[idx] +- && memcmp (trun, npath, system_dirs_len[idx]) == 0) +- /* Found it. */ ++ /* Compare against the entry without its trailing '/'. */ ++ size_t dirlen = system_dirs_len[idx] - 1; ++ ++ if (nlen >= dirlen ++ && memcmp (trun, npath, dirlen) == 0 ++ && (npath[dirlen] == '/' || npath[dirlen] == '\0')) + return true; + + trun += system_dirs_len[idx] + 1; +@@ -361,16 +332,21 @@ _dl_dst_substitute (struct link_map *l, const char *input, char *result) + checked for trust, the authors of the binaries themselves are + trusted to have designed this correctly. Only $ORIGIN is tested in + this way because it may be manipulated in some ways with hard +- links. */ +- if (__glibc_unlikely (check_for_trusted) +- && !is_trusted_path_normalize (result, wp - result)) +- { +- *result = '\0'; +- return result; +- } ++ links. ++ ++ _dl_normalize_path replaces the expansion with its normalized form ++ in place, so that the path that is opened is exactly the path that ++ was validated. */ + + *wp = '\0'; + ++ if (__glibc_unlikely (check_for_trusted)) ++ { ++ size_t nlen = _dl_normalize_path (result); ++ if (!path_is_trusted (result, nlen)) ++ *result = '\0'; ++ } ++ + return result; + } + +diff --git a/elf/dl-path-normalize.h b/elf/dl-path-normalize.h +new file mode 100644 +index 0000000000..ca4b14698c +--- /dev/null ++++ b/elf/dl-path-normalize.h +@@ -0,0 +1,118 @@ ++/* In-place lexical path normalization for the dynamic loader. ++ Copyright (C) 2026 Free Software Foundation, Inc. ++ 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 ++ . */ ++ ++#ifndef _DL_PATH_NORMALIZE_H ++#define _DL_PATH_NORMALIZE_H ++ ++#include ++#include ++ ++/* Lexically normalize the null-terminated PATH in place and return the ++ length of the result (excluding the terminating NUL byte): ++ ++ - Runs of '/' are collapsed to a single '/'. ++ ++ - "." components are removed. ++ ++ - A ".." component removes the preceding component if there is one and it ++ is not itself a preserved "..". In an absolute path a surplus ".." at ++ the root is dropped ("/../a" normalizes to "/a"), in a relative path ++ leading ".." components are preserved ("../a" stays "../a", "a/../../b" ++ normalizes to "../b"). ++ ++ - The result has no trailing '/' except for the root path "/" itself ++ ("/a/" normalizes to "/a"). ++ ++ - The result is empty if and only if every component cancels or is removed ++ ("", ".", "a/.." all normalize to ""). ++ ++ The PATH is written in place, and the internal write cursor never runs ++ ahead of the read cursor. Only bytes within the strlen (PATH) + 1 storage ++ are accessed. */ ++static inline size_t ++_dl_normalize_path (char *path) ++{ ++ /* The root '/' of an absolute path is not removed. */ ++ char *pstart = path + (path[0] == '/'); ++ const char *rnp = pstart; ++ char *wnp = pstart; ++ /* End of the prefix a ".." may not remove. Either the root '/', or, for ++ relative paths, the original start of the string extended by any ++ preserved leading ".." components. */ ++ char *limit = pstart; ++ ++ while (*rnp != '\0') ++ { ++ /* Collapse consecutive separators. */ ++ if (*rnp == '/') ++ { ++ ++rnp; ++ continue; ++ } ++ ++ /* [RNP, REND) is the next input component. */ ++ const char *rend = rnp; ++ while (*rend != '\0' && *rend != '/') ++ ++rend; ++ size_t clen = rend - rnp; ++ ++ /* Drop '.' component. */ ++ if (clen == 1 && rnp[0] == '.') ++ ; ++ else if (clen == 2 && rnp[0] == '.' && rnp[1] == '.') ++ { ++ if (wnp > limit) ++ { ++ /* Remove the last component along with the '/' separating it ++ from its predecessor (the root '/' of an absolute path is ++ retained). */ ++ while (wnp > limit && wnp[-1] != '/') ++ --wnp; ++ if (wnp > pstart) ++ --wnp; ++ } ++ else if (pstart == path) ++ { ++ /* No component is left and the original path is relative: ++ keep the unresolvable ".." (it becomes part of the ++ preserved prefix). */ ++ if (wnp > pstart) ++ *wnp++ = '/'; ++ *wnp++ = '.'; ++ *wnp++ = '.'; ++ limit = wnp; ++ } ++ /* Otherwise the path is absolute and the surplus ".." at the ++ root is dropped ("/../a" normalizes to "/a"). */ ++ } ++ else ++ { ++ if (wnp > pstart) ++ *wnp++ = '/'; ++ memmove (wnp, rnp, clen); ++ wnp += clen; ++ } ++ ++ rnp = rend; ++ } ++ ++ *wnp = '\0'; ++ return wnp - path; ++} ++ ++#endif /* _DL_PATH_NORMALIZE_H */ diff --git a/meta/recipes-core/glibc/glibc_2.39.bb b/meta/recipes-core/glibc/glibc_2.39.bb index 09ac0fc53f7..1598249b7b3 100644 --- a/meta/recipes-core/glibc/glibc_2.39.bb +++ b/meta/recipes-core/glibc/glibc_2.39.bb @@ -77,6 +77,7 @@ SRC_URI = "${GLIBC_GIT_URI};branch=${SRCBRANCH};name=glibc \ file://0042-CVE-2026-8674.patch \ file://0047-CVE-2026-89092.patch \ file://0048-CVE-2026-97399.patch \ + file://0049-CVE-2026-86805-CVE-2026-95818.patch \ " S = "${WORKDIR}/git" B = "${WORKDIR}/build-${TARGET_SYS}"