From patchwork Fri Apr 24 23:02:11 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 86934 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id EFADCFF8850 for ; Fri, 24 Apr 2026 23:02:33 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1798.1777071745919284876 for ; Fri, 24 Apr 2026 16:02:26 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.18.1/8.18.1/Debian-2) with ESMTP id 63ON2Dbv287175; Fri, 24 Apr 2026 18:02:15 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: richard.purdie@linuxfoundation.org, dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [yocto-patches][pseudo][PATCH 1/3] Only copy xattrs on a rename if it's cross-filesystem Date: Fri, 24 Apr 2026 18:02:11 -0500 Message-Id: <1777071733-25858-2-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 23:02:33 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3814 From: Daniel Burgener On same file-system renames, the path update in the db means that the xattrs are already present, and copying results in duplicates. This was previously masked by the bug fixed in d1db9c219abf92f15303486a409292237f1fc790, since that prevented the xattr copying. Now that that bug is fixed, we started observing this issue in 1.9.3. AI-Generated: Code fix by Claude Haiku 4.5. Test code by me. Signed-off-by: Daniel Burgener Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- pseudo_db.c | 4 +++- test/test-xattr.sh | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pseudo_db.c b/pseudo_db.c index 9713e0c..6a33f20 100644 --- a/pseudo_db.c +++ b/pseudo_db.c @@ -2196,7 +2196,9 @@ pdb_update_inode(pseudo_msg_t *msg) { found_existing = !pdb_find_file_path(oldmsg); if (found_existing) { /* we have an existing file entry */ - pdb_copy_xattrs(oldmsg, msg); + if (oldmsg->dev != msg->dev || oldmsg->ino != msg->ino) { + pdb_copy_xattrs(oldmsg, msg); + } } sqlite3_bind_int(update, 1, msg->dev); sqlite3_bind_int64(update, 2, signed_ino(msg->ino)); diff --git a/test/test-xattr.sh b/test/test-xattr.sh index 6ac6b3c..89fbd35 100755 --- a/test/test-xattr.sh +++ b/test/test-xattr.sh @@ -65,6 +65,18 @@ then exit 1 fi +mv f1 f2 +attrs=`getfattr -d -m - f2 | grep -v '^#'` +expected=$'security.dummy="test_f2"\nuser.dummy="test_f1"' +if [ "$attrs" != "$expected" ] +then + #echo "Fail, unpexected getfattr result '$attrs'" + rm -f f1 + rm -f f2 + exit 1 +fi + #echo "Passed." rm -f f1 +rm -f f2 exit 0 From patchwork Fri Apr 24 23:02:12 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 86935 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2F6D0FF885A for ; Fri, 24 Apr 2026 23:02:34 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.1690.1777071749459453281 for ; Fri, 24 Apr 2026 16:02:29 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.18.1/8.18.1/Debian-2) with ESMTP id 63ON2Dbw287175; Fri, 24 Apr 2026 18:02:15 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: richard.purdie@linuxfoundation.org, dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [yocto-patches][pseudo][PATCH 2/3] linkat: Avoid a segmentation fault Date: Fri, 24 Apr 2026 18:02:12 -0500 Message-Id: <1777071733-25858-3-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 23:02:34 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3815 From: Peter Kjellerstedt This avoids the following segmentation fault (in useradd): Program terminated with signal SIGSEGV, Segmentation fault. #0 0x0000795b22562f49 in wrap_linkat (olddirfd=olddirfd@entry=-100, oldname=oldname@entry=0x6016835f1fb0 "/etc/group.3167327", newdirfd=newdirfd@entry=-100, newname=newname@entry=0x6016835f1fd0 "/etc/group.lock", flags=flags@entry=0) at ports/unix/guts/linkat.c:37 37 oldpath = oldname; (gdb) bt #0 0x0000795b22562f49 in wrap_linkat (olddirfd=olddirfd@entry=-100, oldname=oldname@entry=0x6016835f1fb0 "/etc/group.3167327", newdirfd=newdirfd@entry=-100, newname=newname@entry=0x6016835f1fd0 "/etc/group.lock", flags=flags@entry=0) at ports/unix/guts/linkat.c:37 #1 0x0000795b2257b95c in wrap_link (newname=0x6016835f1fd0 "/etc/group.lock", oldname=0x6016835f1fb0 "/etc/group.3167327") at ports/unix/guts/link.c:17 #2 link (oldname=0x6016835f1fb0 "/etc/group.3167327", newname=0x6016835f1fd0 "/etc/group.lock") at pseudo_wrapfuncs.c:8968 #3 0x00006016814d45dd in do_lock_file (file=file@entry=0x6016835f1fb0 "/etc/group.3167327", lock=lock@entry=0x6016835f1fd0 "/etc/group.lock", log=log@entry=true) at ../../sources/shadow-4.19.4/lib/commonio.c:167 #4 0x00006016814d4e88 in commonio_lock_nowait (db=db@entry=0x6016814df9e0 , log=log@entry=true) at ../../sources/shadow-4.19.4/lib/commonio.c:373 #5 0x00006016814d4f09 in commonio_lock (db=db@entry=0x6016814df9e0 ) at ../../sources/shadow-4.19.4/lib/commonio.c:413 #6 0x00006016814cfc9a in gr_lock () at ../../sources/shadow-4.19.4/lib/groupio.c:141 #7 0x00006016814c981f in open_group_files (process_selinux=process_selinux@entry=false) at ../../sources/shadow-4.19.4/src/useradd.c:1797 #8 0x00006016814cacdc in open_files (process_selinux=process_selinux@entry=false) at ../../sources/shadow-4.19.4/src/useradd.c:1759 #9 0x00006016814cc388 in main (argc=, argv=) at ../../sources/shadow-4.19.4/src/useradd.c:2585 (gdb) l 32 if (olddirfd != AT_FDCWD || newdirfd != AT_FDCWD) { 33 errno = ENOSYS; 34 return -1; 35 } 36 #endif 37 oldpath = oldname; 38 if (pseudo_chroot_len && strncmp(oldpath, pseudo_chroot, pseudo_chroot_len) && 39 oldpath[pseudo_chroot_len] == '/') { 40 oldpath += pseudo_chroot_len; 41 } (gdb) p oldname $1 = 0x6016835f1fb0 "/etc/group.3167327" (gdb) p oldpath $2 = 0x6016835f1fb0 "/etc/group.3167327" (gdb) p pseudo_chroot_len $3 = 91 The above code (on line 38) is supposed to check if oldpath is prefixed by pseudo_chroot, but instead it checks the opposite, and then the check for the slash on the next line is made outside oldpath if it is shorter than pseudo_chroot. Signed-off-by: Peter Kjellerstedt Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- ports/unix/guts/linkat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/unix/guts/linkat.c b/ports/unix/guts/linkat.c index e1712a9..60fbf63 100644 --- a/ports/unix/guts/linkat.c +++ b/ports/unix/guts/linkat.c @@ -35,7 +35,7 @@ } #endif oldpath = oldname; - if (pseudo_chroot_len && strncmp(oldpath, pseudo_chroot, pseudo_chroot_len) && + if (pseudo_chroot_len && !strncmp(oldpath, pseudo_chroot, pseudo_chroot_len) && oldpath[pseudo_chroot_len] == '/') { oldpath += pseudo_chroot_len; } From patchwork Fri Apr 24 23:02:13 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 86936 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 48335FF885C for ; Fri, 24 Apr 2026 23:02:34 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.1797.1777071745414275353 for ; Fri, 24 Apr 2026 16:02:25 -0700 Authentication-Results: mx.groups.io; dkim=none (message not signed); spf=pass (domain: kernel.crashing.org, ip: 63.228.1.57, mailfrom: mark.hatle@kernel.crashing.org) Received: from kernel.crashing.org.net (70-99-78-136.nuveramail.net [70.99.78.136] (may be forged)) by gate.crashing.org (8.18.1/8.18.1/Debian-2) with ESMTP id 63ON2Dbx287175; Fri, 24 Apr 2026 18:02:15 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org Cc: richard.purdie@linuxfoundation.org, dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [yocto-patches][pseudo][PATCH 3/3] test: Add test for linkat chroot path stripping Date: Fri, 24 Apr 2026 18:02:13 -0500 Message-Id: <1777071733-25858-4-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777071733-25858-1-git-send-email-mark.hatle@kernel.crashing.org> List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Fri, 24 Apr 2026 23:02:34 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3813 From: Mark Hatle A bug in linkat's chroot path stripping logic used strncmp() without negation, causing it to strip the chroot prefix when the path did NOT match. Also it accessed memory out of bounds when the path was shorter than pseudo_chroot_len. This was fixed in the previous commit. Add a test that exercises this scenario by creating a chroot with a long directory path, then calling link() on short paths (/a -> /b) inside the chroot. The paths are placed at the end of a mapped memory page (via mmap) with the next page unmapped, so the out-of-bounds read at oldpath[pseudo_chroot_len] reliably causes a segfault rather than silently reading adjacent mapped memory. AI-Generated: Test code written by Claude Opus 4.6. Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- test/test-linkat-chroot.c | 69 ++++++++++++++++++++++++++++++++++++++++++++++ test/test-linkat-chroot.sh | 19 +++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 test/test-linkat-chroot.c create mode 100755 test/test-linkat-chroot.sh diff --git a/test/test-linkat-chroot.c b/test/test-linkat-chroot.c new file mode 100644 index 0000000..e02989e --- /dev/null +++ b/test/test-linkat-chroot.c @@ -0,0 +1,69 @@ +/* + * Test that link/linkat inside a chroot does not segfault + * when the path is shorter than the chroot path. + * SPDX-License-Identifier: LGPL-2.1-only + * + * The bug was that linkat's chroot path stripping used strncmp() + * without negation, causing an out-of-bounds read at + * oldpath[pseudo_chroot_len] when the path was shorter than the + * chroot path. To reliably trigger this, we place the path string + * at the end of a mapped page with the next page unmapped, so any + * out-of-bounds access will SIGSEGV. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +/* Place str at the end of a page with the next page unmapped. + * Any read past the string will hit unmapped memory and SIGSEGV. */ +static char *guarded_string(const char *str) { + long pagesize = sysconf(_SC_PAGESIZE); + size_t len = strlen(str) + 1; + char *pages = mmap(NULL, pagesize * 2, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (pages == MAP_FAILED) { + perror("mmap"); + return NULL; + } + if (munmap(pages + pagesize, pagesize) == -1) { + perror("munmap"); + return NULL; + } + char *dest = pages + pagesize - len; + memcpy(dest, str, len); + return dest; +} + +int main(int argc, char *argv[]) { + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + if (chroot(argv[1]) == -1) { + perror("chroot"); + return 1; + } + if (chdir("/") == -1) { + perror("chdir"); + return 1; + } + + char *oldpath = guarded_string("/a"); + char *newpath = guarded_string("/b"); + if (!oldpath || !newpath) + return 1; + + /* link() calls linkat() internally. The short paths are placed + * at page boundaries so that the buggy out-of-bounds read at + * oldpath[pseudo_chroot_len] will SIGSEGV instead of silently + * reading adjacent memory. */ + if (link(oldpath, newpath) == -1) { + perror("link"); + return 1; + } + return 0; +} diff --git a/test/test-linkat-chroot.sh b/test/test-linkat-chroot.sh new file mode 100755 index 0000000..247a6d7 --- /dev/null +++ b/test/test-linkat-chroot.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# +# Test that link/linkat inside a chroot does not segfault +# when the path is shorter than the chroot path. +# SPDX-License-Identifier: LGPL-2.1-only +# + +set -e + +# Create a chroot directory with a deeply nested path +# so pseudo_chroot_len is longer than the filenames used inside +CHROOTDIR=$(pwd)/linkat_chroot_test/deep/nested/path/to/make/it/long +mkdir -p "$CHROOTDIR" +touch "$CHROOTDIR/a" +trap "rm -rf $(pwd)/linkat_chroot_test test-linkat-chroot" 0 + +gcc -o test-linkat-chroot test/test-linkat-chroot.c + +./test-linkat-chroot "$CHROOTDIR"