From patchwork Mon Apr 27 17:56:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87013 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 AA767FF886D for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.2119.1777312614201864306 for ; Mon, 27 Apr 2026 10:56:54 -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 63RHugAe876837; Mon, 27 Apr 2026 12:56:43 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 01/11] Only copy xattrs on a rename if it's cross-filesystem Date: Mon, 27 Apr 2026 12:56:31 -0500 Message-Id: <1777312601-1393-2-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3854 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 Mon Apr 27 17:56:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87007 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 4A691FF8868 for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.2116.1777312611172000411 for ; Mon, 27 Apr 2026 10:56:51 -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 63RHugAf876837; Mon, 27 Apr 2026 12:56:43 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 02/11] linkat: Avoid a segmentation fault Date: Mon, 27 Apr 2026 12:56:32 -0500 Message-Id: <1777312601-1393-3-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3849 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 Mon Apr 27 17:56:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87017 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 77686FF8873 for ; Mon, 27 Apr 2026 17:56:59 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2123.1777312617907992030 for ; Mon, 27 Apr 2026 10:56:58 -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 63RHugAg876837; Mon, 27 Apr 2026 12:56:44 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 03/11] test: Add test for linkat chroot path stripping Date: Mon, 27 Apr 2026 12:56:33 -0500 Message-Id: <1777312601-1393-4-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:59 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3860 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" From patchwork Mon Apr 27 17:56:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87016 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 28BFEFF8871 for ; Mon, 27 Apr 2026 17:56:59 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2116.1777312611172297058 for ; Mon, 27 Apr 2026 10:56:51 -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 63RHugAh876837; Mon, 27 Apr 2026 12:56:44 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 04/11] test: Add fts test case Date: Mon, 27 Apr 2026 12:56:34 -0500 Message-Id: <1777312601-1393-5-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:59 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3850 From: Mark Hatle AI-Generated: test cases generated by github copilot (claude opus 6.4) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- test/test-fts.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test-fts.sh | 8 ++++++ 2 files changed, 95 insertions(+) create mode 100644 test/test-fts.c create mode 100755 test/test-fts.sh diff --git a/test/test-fts.c b/test/test-fts.c new file mode 100644 index 0000000..38dfb87 --- /dev/null +++ b/test/test-fts.c @@ -0,0 +1,87 @@ +/* + * Test fts_open and related functions + * SPDX-License-Identifier: LGPL-2.1-only + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +static int failures = 0; + +static void check(const char *desc, int condition) { + if (!condition) { + fprintf(stderr, "FAIL: %s\n", desc); + failures++; + } +} + +int main(void) { + FTS *ftsp; + FTSENT *p; + int fd; + int file_count = 0; + int dir_count = 0; + + /* Create test directory tree */ + mkdir("test_fts_dir", 0755); + mkdir("test_fts_dir/sub1", 0755); + mkdir("test_fts_dir/sub2", 0755); + fd = open("test_fts_dir/file1", O_CREAT | O_WRONLY, 0644); + close(fd); + fd = open("test_fts_dir/sub1/file2", O_CREAT | O_WRONLY, 0644); + close(fd); + + /* Set ownership so we can check pseudo tracks it through fts */ + check("chown file1", chown("test_fts_dir/file1", 100, 200) == 0); + check("chown file2", chown("test_fts_dir/sub1/file2", 300, 400) == 0); + + /* fts_open */ + { + char *paths[] = { "test_fts_dir", NULL }; + ftsp = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR, NULL); + check("fts_open", ftsp != NULL); + } + + /* Walk the tree */ + while ((p = fts_read(ftsp)) != NULL) { + switch (p->fts_info) { + case FTS_F: + file_count++; + if (strcmp(p->fts_name, "file1") == 0) { + check("fts file1 uid", p->fts_statp->st_uid == 100); + check("fts file1 gid", p->fts_statp->st_gid == 200); + } + if (strcmp(p->fts_name, "file2") == 0) { + check("fts file2 uid", p->fts_statp->st_uid == 300); + check("fts file2 gid", p->fts_statp->st_gid == 400); + } + break; + case FTS_D: + dir_count++; + break; + default: + break; + } + } + + check("fts found 2 files", file_count == 2); + check("fts found dirs", dir_count >= 3); /* test_fts_dir, sub1, sub2 */ + + fts_close(ftsp); + + /* Cleanup */ + unlink("test_fts_dir/sub1/file2"); + unlink("test_fts_dir/file1"); + rmdir("test_fts_dir/sub1"); + rmdir("test_fts_dir/sub2"); + rmdir("test_fts_dir"); + + return failures; +} diff --git a/test/test-fts.sh b/test/test-fts.sh new file mode 100755 index 0000000..50413c8 --- /dev/null +++ b/test/test-fts.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# +# Test fts_open and tree walking + +rm -rf test_fts_dir +./test/test-fts From patchwork Mon Apr 27 17:56:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87014 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 B9032FF886E for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.2118.1777312614186923949 for ; Mon, 27 Apr 2026 10:56:54 -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 63RHugAi876837; Mon, 27 Apr 2026 12:56:44 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 05/11] ports/unix: fts_*: Certain functions were incorrectly returning stat data Date: Mon, 27 Apr 2026 12:56:35 -0500 Message-Id: <1777312601-1393-6-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3853 From: Mark Hatle Glibc appears to be using fts private symbols for stat/fstatat/etc. This caused incorrect ownership and permissions data to be returned from fts_read(). It appears that glibc's fts_read calls directly into __fstatat64 instead of using the standard fstatat64 function. This means fts_read() populates FTSENT->fts_statp with real stat data (actual on-disk uid/gid), not pseudo's fake data. fts_read and fts_children wrappers The fix: wrap fts_read() and fts_children() so that after the real libc function returns, we re-query pseudo's database for each entry. This is the same pattern used by nftw where each callback re-stats each file through pseudo to void internal functions returning actual data. AI-Generate: Fix suggested by github copilot (claude opus 6.4) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- ports/unix/guts/fts_children.c | 64 +++++++++++++++++++++++++++++++++++++++ ports/unix/guts/fts_read.c | 68 ++++++++++++++++++++++++++++++++++++++++++ ports/unix/wrapfuncs.in | 2 ++ 3 files changed, 134 insertions(+) create mode 100644 ports/unix/guts/fts_children.c create mode 100644 ports/unix/guts/fts_read.c diff --git a/ports/unix/guts/fts_children.c b/ports/unix/guts/fts_children.c new file mode 100644 index 0000000..f5d1890 --- /dev/null +++ b/ports/unix/guts/fts_children.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026 Yocto Project; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static FTSENT * + * wrap_fts_children(FTS *ftsp, int options) { + * FTSENT * rc = NULL; + */ + + rc = real_fts_children(ftsp, options); + + /* glibc's fts_children calls fstatat internally using glibc-private + * symbols, which bypass pseudo's LD_PRELOAD wrappers. We need to + * re-stat each entry through pseudo so that pseudo-tracked + * ownership and permissions are visible to callers. + */ + FTSENT *p; + for (p = rc; p != NULL; p = p->fts_link) { + if (p->fts_statp && p->fts_path) { + pseudo_msg_t *msg; + int save_errno = errno; + const char *fts_rpath; + PSEUDO_STATBUF buf64; + + switch (p->fts_info) { + case FTS_F: + case FTS_D: + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + /* See fts_read.c: fts_path may already contain + * the chroot prefix from fts_open's path resolution. + * Use it directly in that case. + */ + if (p->fts_path[0] == '/' && pseudo_chroot_len && + !memcmp(p->fts_path, pseudo_chroot, pseudo_chroot_len) && + (p->fts_path[pseudo_chroot_len] == '/' || + p->fts_path[pseudo_chroot_len] == '\0')) { + fts_rpath = p->fts_path; + } else { + fts_rpath = PSEUDO_ROOT_PATH(AT_FDCWD, p->fts_path, AT_SYMLINK_NOFOLLOW); + } + if (fts_rpath) { + pseudo_stat64_from32(&buf64, p->fts_statp); + msg = pseudo_client_op(OP_STAT, 0, -1, -1, fts_rpath, &buf64); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf64, msg); + pseudo_stat32_from64(p->fts_statp, &buf64); + } + } + break; + default: + break; + } + + errno = save_errno; + } + } + +/* return rc; + * } + */ diff --git a/ports/unix/guts/fts_read.c b/ports/unix/guts/fts_read.c new file mode 100644 index 0000000..654b982 --- /dev/null +++ b/ports/unix/guts/fts_read.c @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2026 Yocto Project; see + * guts/COPYRIGHT for information. + * + * SPDX-License-Identifier: LGPL-2.1-only + * + * static FTSENT * + * wrap_fts_read(FTS *ftsp) { + * FTSENT * rc = NULL; + */ + + rc = real_fts_read(ftsp); + + /* glibc's fts_read calls fstatat internally using glibc-private + * symbols, which bypass pseudo's LD_PRELOAD wrappers. We need to + * re-stat the entry through pseudo so that pseudo-tracked + * ownership and permissions are visible to callers. + */ + if (rc && rc->fts_statp && rc->fts_accpath) { + pseudo_msg_t *msg; + int save_errno = errno; + const char *fts_rpath; + PSEUDO_STATBUF buf64; + + switch (rc->fts_info) { + case FTS_F: + case FTS_D: + case FTS_DP: + case FTS_SL: + case FTS_SLNONE: + case FTS_DEFAULT: + /* fts_open passes real (chroot-resolved) paths to real_fts_open, + * so fts_path is already a real filesystem path. We must use + * it directly without PSEUDO_ROOT_PATH, which would + * incorrectly prepend the chroot prefix a second time. + * + * However, for the non-chroot case (or when fts was given + * relative paths that are still relative in fts_path), + * we still need PSEUDO_ROOT_PATH to make them absolute. + */ + if (rc->fts_path[0] == '/' && pseudo_chroot_len && + !memcmp(rc->fts_path, pseudo_chroot, pseudo_chroot_len) && + (rc->fts_path[pseudo_chroot_len] == '/' || + rc->fts_path[pseudo_chroot_len] == '\0')) { + /* Already a real path with chroot prefix */ + fts_rpath = rc->fts_path; + } else { + fts_rpath = PSEUDO_ROOT_PATH(AT_FDCWD, rc->fts_path, AT_SYMLINK_NOFOLLOW); + } + if (fts_rpath) { + pseudo_stat64_from32(&buf64, rc->fts_statp); + msg = pseudo_client_op(OP_STAT, 0, -1, -1, fts_rpath, &buf64); + if (msg && msg->result == RESULT_SUCCEED) { + pseudo_stat_msg(&buf64, msg); + pseudo_stat32_from64(rc->fts_statp, &buf64); + } + } + break; + default: + break; + } + + errno = save_errno; + } + +/* return rc; + * } + */ diff --git a/ports/unix/wrapfuncs.in b/ports/unix/wrapfuncs.in index 7724fc7..5665735 100644 --- a/ports/unix/wrapfuncs.in +++ b/ports/unix/wrapfuncs.in @@ -13,6 +13,8 @@ int access(const char *path, int mode); int faccessat(int dirfd, const char *path, int mode, int flags); int faccessat2(int dirfd, const char *path, int mode, int flags); FTS *fts_open(char * const *path_argv, int options, int (*compar)(const FTSENT **, const FTSENT **)); /* inode64=1 */ +FTSENT *fts_read(FTS *ftsp); /* noignore_path=1, inode64=1 */ +FTSENT *fts_children(FTS *ftsp, int options); /* noignore_path=1, inode64=1 */ int ftw(const char *path, int (*fn)(const char *, const struct stat *, int), int nopenfd); int nftw(const char *path, int (*fn)(const char *, const struct stat *, int, struct FTW *), int nopenfd, int flag); int glob(const char *pattern, int flags, int (*errfunc)(const char *, int), glob_t *pglob); From patchwork Mon Apr 27 17:56:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87015 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 14298FF886F for ; Mon, 27 Apr 2026 17:56:59 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2117.1777312611324752135 for ; Mon, 27 Apr 2026 10:56:51 -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 63RHugAj876837; Mon, 27 Apr 2026 12:56:44 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 06/11] ports/unix: fts_open: Fix chroot behavior Date: Mon, 27 Apr 2026 12:56:36 -0500 Message-Id: <1777312601-1393-7-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:59 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3851 From: Mark Hatle The fts_open wrapper was resolving paths via PSEUDO_ROOT_PATH into rpath_argv, but then used path_argv in the real_fts_open call. Switch this to using the pseudo processed root paths. AI-Generated: AI was used to identify the source of the issue - github copilot (claude opus 4.6) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- ports/unix/guts/fts_open.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ports/unix/guts/fts_open.c b/ports/unix/guts/fts_open.c index ccc9322..6acdf9b 100644 --- a/ports/unix/guts/fts_open.c +++ b/ports/unix/guts/fts_open.c @@ -42,7 +42,8 @@ errno = ENOMEM; rc = NULL; } else { - rc = real_fts_open(path_argv, options, compar); + rpath_argv[args] = NULL; + rc = real_fts_open(rpath_argv, options, compar); } for (i = 0; i < args; ++i) free(rpath_argv[i]); From patchwork Mon Apr 27 17:56:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87008 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 3EA44FF8860 for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.2122.1777312615422348099 for ; Mon, 27 Apr 2026 10:56:55 -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 63RHugAk876837; Mon, 27 Apr 2026 12:56:45 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 07/11] test: Add test cases for canonicalize functions Date: Mon, 27 Apr 2026 12:56:37 -0500 Message-Id: <1777312601-1393-8-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3859 From: Mark Hatle Verify canonicalize, which calls realpath, both in a normal and emulated chroot environment. AI-Generated: test cases generated by github copilot (claude opus 6.4) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- test/test-canonicalize.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++ test/test-canonicalize.sh | 11 +++ 2 files changed, 182 insertions(+) create mode 100644 test/test-canonicalize.c create mode 100755 test/test-canonicalize.sh diff --git a/test/test-canonicalize.c b/test/test-canonicalize.c new file mode 100644 index 0000000..db20f25 --- /dev/null +++ b/test/test-canonicalize.c @@ -0,0 +1,171 @@ +/* + * Test canonicalize_file_name + * SPDX-License-Identifier: LGPL-2.1-only + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +static int failures = 0; + +static void check(const char *desc, int condition) { + if (!condition) { + fprintf(stderr, "FAIL: %s\n", desc); + failures++; + } +} + +static int test_basic(void) { + char *result; + char cwd[PATH_MAX]; + int fd; + + if (!getcwd(cwd, sizeof(cwd))) { + perror("getcwd"); + return 1; + } + + /* Create a test file and symlink */ + fd = open("test_canon_file", O_CREAT | O_WRONLY, 0644); + close(fd); + if (symlink("test_canon_file", "test_canon_link") != 0) { + perror("symlink"); + return 1; + } + + /* canonicalize_file_name on regular file */ + result = canonicalize_file_name("test_canon_file"); + check("canon file", result != NULL); + if (result) { + check("canon file starts with /", result[0] == '/'); + check("canon file contains name", strstr(result, "test_canon_file") != NULL); + free(result); + } + + /* canonicalize_file_name on symlink (should resolve) */ + result = canonicalize_file_name("test_canon_link"); + check("canon link", result != NULL); + if (result) { + check("canon link resolves", strstr(result, "test_canon_file") != NULL); + /* Should NOT contain the link name */ + check("canon link not link", strstr(result, "test_canon_link") == NULL); + free(result); + } + + /* canonicalize_file_name on . */ + result = canonicalize_file_name("."); + check("canon dot", result != NULL); + if (result) { + check("canon dot matches cwd", strcmp(result, cwd) == 0); + free(result); + } + + /* canonicalize_file_name on nonexistent should return NULL */ + result = canonicalize_file_name("test_canon_noexist"); + check("canon noexist NULL", result == NULL); + + unlink("test_canon_link"); + unlink("test_canon_file"); + + return 0; +} + +static int test_chroot(const char *chroot_dir) { + char *result; + char path[PATH_MAX]; + int fd; + + /* Create test file and symlink inside the chroot directory */ + snprintf(path, sizeof(path), "%s/cr_canon_file", chroot_dir); + fd = open(path, O_CREAT | O_WRONLY, 0644); + if (fd < 0) { perror("open chroot file"); return 1; } + close(fd); + + snprintf(path, sizeof(path), "%s/cr_canon_link", chroot_dir); + if (symlink("cr_canon_file", path) != 0) { + perror("symlink in chroot dir"); + return 1; + } + + /* Also create an absolute-target symlink */ + snprintf(path, sizeof(path), "%s/cr_abs_link", chroot_dir); + if (symlink("/cr_canon_file", path) != 0) { + perror("abs symlink in chroot dir"); + return 1; + } + + if (chroot(chroot_dir) != 0) { + perror("chroot"); + return 1; + } + if (chdir("/") != 0) { + perror("chdir /"); + return 1; + } + + /* canonicalize on a file: result must be /cr_canon_file, not + * /real/path/to/chroot/cr_canon_file */ + result = canonicalize_file_name("/cr_canon_file"); + check("chroot: canon abs file", result != NULL); + if (result) { + check("chroot: canon abs file path", strcmp(result, "/cr_canon_file") == 0); + free(result); + } + + /* canonicalize on a relative symlink */ + result = canonicalize_file_name("/cr_canon_link"); + check("chroot: canon rel symlink", result != NULL); + if (result) { + check("chroot: canon rel symlink resolves", strcmp(result, "/cr_canon_file") == 0); + free(result); + } + + /* canonicalize on an absolute-target symlink */ + result = canonicalize_file_name("/cr_abs_link"); + check("chroot: canon abs symlink", result != NULL); + if (result) { + check("chroot: canon abs symlink resolves", strcmp(result, "/cr_canon_file") == 0); + free(result); + } + + /* canonicalize on . should return / */ + result = canonicalize_file_name("."); + check("chroot: canon dot", result != NULL); + if (result) { + check("chroot: canon dot is /", strcmp(result, "/") == 0); + free(result); + } + + /* path traversal: /../cr_canon_file must stay confined */ + result = canonicalize_file_name("/../cr_canon_file"); + check("chroot: canon traverse", result != NULL); + if (result) { + check("chroot: canon traverse resolves", strcmp(result, "/cr_canon_file") == 0); + free(result); + } + + return 0; +} + +int main(int argc, char *argv[]) { + int rc; + + rc = test_basic(); + if (rc) + return rc; + + if (argc > 1) { + rc = test_chroot(argv[1]); + if (rc) + return rc; + } + + return failures; +} diff --git a/test/test-canonicalize.sh b/test/test-canonicalize.sh new file mode 100755 index 0000000..4a1c321 --- /dev/null +++ b/test/test-canonicalize.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# +# Test canonicalize_file_name (basic + chroot) + +CHROOT_DIR=$(mktemp -d "${PWD}/chroot_canon_XXXXXX") +trap "rm -rf '$CHROOT_DIR'" 0 + +rm -f test_canon_file test_canon_link +./test/test-canonicalize "$CHROOT_DIR" From patchwork Mon Apr 27 17:56:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87012 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 9F88CFF886C for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.2121.1777312614338263232 for ; Mon, 27 Apr 2026 10:56:54 -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 63RHugAl876837; Mon, 27 Apr 2026 12:56:45 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 08/11] ports/unix: realpath: Fix chroot processing Date: Mon, 27 Apr 2026 12:56:38 -0500 Message-Id: <1777312601-1393-9-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3855 From: Mark Hatle When running realpath from within a chroot, the returned path must be sanitized to appear as if it's within the chroot. Use the existing pseudo_chroot settings to identify and clear the path. AI-Generated: Fix suggested by github copilot (claude opus 4.6) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- ports/unix/guts/realpath.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ports/unix/guts/realpath.c b/ports/unix/guts/realpath.c index 62a92b2..4f91220 100644 --- a/ports/unix/guts/realpath.c +++ b/ports/unix/guts/realpath.c @@ -30,6 +30,21 @@ *(ep--) = '\0'; } + /* If in a chroot, strip the chroot prefix so the caller sees + * a path relative to the chroot root. + */ + if (pseudo_chroot_len && + (size_t)len >= pseudo_chroot_len && + !memcmp(rname, pseudo_chroot, pseudo_chroot_len) && + (rname[pseudo_chroot_len] == '/' || rname[pseudo_chroot_len] == '\0')) { + rname += pseudo_chroot_len; + len -= pseudo_chroot_len; + if (len == 0) { + rname = "/"; + len = 1; + } + } + if (len >= pseudo_sys_path_max()) { errno = ENAMETOOLONG; return NULL; From patchwork Mon Apr 27 17:56:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87011 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 85393FF886B for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2119.1777312614610159156 for ; Mon, 27 Apr 2026 10:56:54 -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 63RHugAm876837; Mon, 27 Apr 2026 12:56:45 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 09/11] test: Add test symlinkat and related Date: Mon, 27 Apr 2026 12:56:39 -0500 Message-Id: <1777312601-1393-10-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3856 From: Mark Hatle Add a test case that verifies link, linkat, symlink, symlinkat, and readlink. Test both with and without chroot. AI-Generated: test cases generated by github copilot (claude opus 6.4) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- test/test-link-symlink.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++ test/test-link-symlink.sh | 11 +++ 2 files changed, 186 insertions(+) create mode 100644 test/test-link-symlink.c create mode 100755 test/test-link-symlink.sh diff --git a/test/test-link-symlink.c b/test/test-link-symlink.c new file mode 100644 index 0000000..f8a1026 --- /dev/null +++ b/test/test-link-symlink.c @@ -0,0 +1,175 @@ +/* + * Test link, linkat, symlink, symlinkat, readlink, readlinkat + * SPDX-License-Identifier: LGPL-2.1-only + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int failures = 0; + +static void check(const char *desc, int condition) { + if (!condition) { + fprintf(stderr, "FAIL: %s\n", desc); + failures++; + } +} + +static int test_basic(void) { + struct stat st, st2; + char buf[PATH_MAX]; + ssize_t len; + int fd, dirfd; + + /* Create a test file */ + fd = open("test_link_file", O_CREAT | O_WRONLY, 0644); + check("write", write(fd, "hello", 5) == 5); + close(fd); + check("chown", chown("test_link_file", 100, 200) == 0); + + /* symlink */ + check("symlink", symlink("test_link_file", "test_link_sym") == 0); + check("symlink lstat", lstat("test_link_sym", &st) == 0); + check("symlink is link", S_ISLNK(st.st_mode)); + + /* readlink */ + len = readlink("test_link_sym", buf, sizeof(buf)); + check("readlink len", len > 0); + buf[len] = '\0'; + check("readlink value", strcmp(buf, "test_link_file") == 0); + + /* symlink target stat should show chown'd values */ + stat("test_link_sym", &st); + check("symlink target uid", st.st_uid == 100); + check("symlink target gid", st.st_gid == 200); + + /* link (hard link) */ + check("link", link("test_link_file", "test_link_hard") == 0); + stat("test_link_hard", &st); + stat("test_link_file", &st2); + check("hardlink same inode", st.st_ino == st2.st_ino); + check("hardlink uid", st.st_uid == 100); + check("hardlink gid", st.st_gid == 200); + + /* linkat */ + dirfd = open(".", O_RDONLY | O_DIRECTORY); + check("linkat", linkat(dirfd, "test_link_file", dirfd, "test_link_hard2", 0) == 0); + stat("test_link_hard2", &st); + check("linkat uid", st.st_uid == 100); + + /* symlinkat */ + check("symlinkat", symlinkat("test_link_file", dirfd, "test_link_sym2") == 0); + check("symlinkat lstat", fstatat(dirfd, "test_link_sym2", &st, AT_SYMLINK_NOFOLLOW) == 0); + check("symlinkat is link", S_ISLNK(st.st_mode)); + + /* readlinkat */ + len = readlinkat(dirfd, "test_link_sym2", buf, sizeof(buf)); + check("readlinkat len", len > 0); + buf[len] = '\0'; + check("readlinkat value", strcmp(buf, "test_link_file") == 0); + + close(dirfd); + unlink("test_link_sym"); + unlink("test_link_sym2"); + unlink("test_link_hard"); + unlink("test_link_hard2"); + unlink("test_link_file"); + + return 0; +} + +static int test_chroot(const char *chroot_dir) { + struct stat st; + char buf[PATH_MAX]; + char path[PATH_MAX]; + ssize_t len; + int fd, dirfd; + + /* Create a file inside the chroot directory */ + snprintf(path, sizeof(path), "%s/cr_link_file", chroot_dir); + fd = open(path, O_CREAT | O_WRONLY, 0644); + if (fd < 0) { perror("open chroot file"); return 1; } + check("chroot: write", write(fd, "hello", 5) == 5); + close(fd); + check("chroot: chown", chown(path, 100, 200) == 0); + + if (chroot(chroot_dir) != 0) { + perror("chroot"); + return 1; + } + if (chdir("/") != 0) { + perror("chdir /"); + return 1; + } + + /* symlink with absolute target inside chroot. + * The fix in pseudo_util.c strips a doubled chroot prefix + * from absolute symlink targets during path resolution. + * Without the fix, stat through this symlink would fail or + * resolve to the wrong path. + */ + check("chroot: symlink abs", symlink("/cr_link_file", "/cr_sym_abs") == 0); + check("chroot: stat through abs symlink", stat("/cr_sym_abs", &st) == 0); + check("chroot: abs symlink uid", st.st_uid == 100); + check("chroot: abs symlink gid", st.st_gid == 200); + + /* readlink should return the chroot-relative target */ + len = readlink("/cr_sym_abs", buf, sizeof(buf)); + check("chroot: readlink abs len", len > 0); + if (len > 0) { + buf[len] = '\0'; + check("chroot: readlink abs value", strcmp(buf, "/cr_link_file") == 0); + } + + /* symlink with relative target inside chroot */ + check("chroot: symlink rel", symlink("cr_link_file", "/cr_sym_rel") == 0); + check("chroot: stat through rel symlink", stat("/cr_sym_rel", &st) == 0); + check("chroot: rel symlink uid", st.st_uid == 100); + + /* symlinkat with absolute target inside chroot */ + dirfd = open("/", O_RDONLY | O_DIRECTORY); + check("chroot: open dirfd", dirfd >= 0); + check("chroot: symlinkat abs", symlinkat("/cr_link_file", dirfd, "cr_symat_abs") == 0); + check("chroot: stat symlinkat abs", fstatat(dirfd, "cr_symat_abs", &st, 0) == 0); + check("chroot: symlinkat abs uid", st.st_uid == 100); + + /* readlinkat should return the chroot-relative target */ + len = readlinkat(dirfd, "cr_symat_abs", buf, sizeof(buf)); + check("chroot: readlinkat abs len", len > 0); + if (len > 0) { + buf[len] = '\0'; + check("chroot: readlinkat abs value", strcmp(buf, "/cr_link_file") == 0); + } + + /* linkat inside chroot */ + check("chroot: linkat", linkat(dirfd, "cr_link_file", dirfd, "cr_hard", 0) == 0); + check("chroot: stat linkat", fstatat(dirfd, "cr_hard", &st, 0) == 0); + check("chroot: linkat uid", st.st_uid == 100); + + close(dirfd); + return 0; +} + +int main(int argc, char *argv[]) { + int rc; + + rc = test_basic(); + if (rc) + return rc; + + if (argc > 1) { + rc = test_chroot(argv[1]); + if (rc) + return rc; + } + + return failures; +} diff --git a/test/test-link-symlink.sh b/test/test-link-symlink.sh new file mode 100755 index 0000000..f758f37 --- /dev/null +++ b/test/test-link-symlink.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# +# Test link, linkat, symlink, symlinkat, readlink, readlinkat (basic + chroot) + +CHROOT_DIR=$(mktemp -d "${PWD}/chroot_link_XXXXXX") +trap "rm -rf '$CHROOT_DIR'" 0 + +rm -f test_link_file test_link_sym test_link_sym2 test_link_hard test_link_hard2 +./test/test-link-symlink "$CHROOT_DIR" From patchwork Mon Apr 27 17:56:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87010 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 5FD20FF8869 for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2120.1777312614822013327 for ; Mon, 27 Apr 2026 10:56:55 -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 63RHugAn876837; Mon, 27 Apr 2026 12:56:45 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 10/11] pseudo_util.c: Fix symlink processing for symlinkat and related Date: Mon, 27 Apr 2026 12:56:40 -0500 Message-Id: <1777312601-1393-11-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3857 From: Mark Hatle If the symlink is absolute (starts with a '/') we need to ensure that it is still presented as if it was inside of the chroot. This was discovered by the openat2 chroot test case, but only when looking at a symlink and helped indicate that symlinkat (and similar) users were affected. AI-Generated: Fixed by github copilot (claude opus 4.6) Signed-off-by: Mark Hatle Signed-off-by: Mark Hatle --- pseudo_util.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pseudo_util.c b/pseudo_util.c index 3a06027..2b0cc04 100644 --- a/pseudo_util.c +++ b/pseudo_util.c @@ -729,7 +729,21 @@ pseudo_append_element(char *newpath, char *root, size_t allocated, char **pcurre linkbuf[linklen] = '\0'; /* absolute symlink means go back to root */ if (*linkbuf == '/') { + size_t rootlen = root - newpath; current = root; + /* If we're in a chroot (rootlen > 0) and the + * symlink target starts with the chroot prefix, + * strip it. This happens when symlinkat expanded + * an absolute target to include the chroot path + * on disk; without stripping, we'd double-apply + * the chroot prefix during resolution. + */ + if (rootlen > 0 && (size_t)linklen > rootlen && + !memcmp(linkbuf, newpath, rootlen) && + (linkbuf[rootlen] == '/' || linkbuf[rootlen] == '\0')) { + memmove(linkbuf, linkbuf + rootlen, linklen - rootlen + 1); + linklen -= rootlen; + } } else { #ifdef PSEUDO_PORT_LINUX if (is_proc) { From patchwork Mon Apr 27 17:56:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 87009 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 4F2E4FF886A for ; Mon, 27 Apr 2026 17:56:58 +0000 (UTC) Received: from gate.crashing.org (gate.crashing.org [63.228.1.57]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.2121.1777312614934456692 for ; Mon, 27 Apr 2026 10:56:55 -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 63RHugAo876837; Mon, 27 Apr 2026 12:56:46 -0500 From: Mark Hatle To: yocto-patches@lists.yoctoproject.org, richard.purdie@linuxfoundation.org Cc: dburgener@linux.microsoft.com, peter.kjellerstedt@axis.com Subject: [pseudo][PATCH 11/11] Makefile.in: Bump version to 1.9.6 Date: Mon, 27 Apr 2026 12:56:41 -0500 Message-Id: <1777312601-1393-12-git-send-email-mark.hatle@kernel.crashing.org> X-Mailer: git-send-email 1.8.3.1 In-Reply-To: <1777312601-1393-1-git-send-email-mark.hatle@kernel.crashing.org> References: <1777312601-1393-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 ; Mon, 27 Apr 2026 17:56:58 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/3858 Signed-off-by: Mark Hatle --- Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.in b/Makefile.in index 74d3492..b4f5f03 100644 --- a/Makefile.in +++ b/Makefile.in @@ -34,7 +34,7 @@ BITS=@BITS@ ARCH_FLAGS=@ARCH_FLAGS@ MARK64=@MARK64@ RPATH=@RPATH@ -VERSION=1.9.5 +VERSION=1.9.6 LIB=@LIB@ BIN=bin