From patchwork Thu Nov 12 18:47:09 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 103938 Received: from PH8PR06CU001.outbound.protection.outlook.com (PH8PR06CU001.outbound.protection.outlook.com [40.107.209.2]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15.1794524087263168300 for ; Thu, 12 Nov 2026 14:54:47 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@amd.com header.s=selector1 header.b=Kpp4XsF2; spf=permerror, err=parse error for token &{10 18 %{i}._ip.%{h}._ehlo.%{d}._spf.vali.email}: invalid domain name (domain: amd.com, ip: 40.107.209.2, mailfrom: mark.hatle@amd.com) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=AYmlzl8Opc7kwe4tYq2sZWuq791hC1dDYnuvuQP2lk8FL7po6JOKWnHhjSBAGFvCMaC10qX3Lb0qyFD/uQhwWudi+GQUUOCZhuKly9sF9eLD+Y6/5emJA4DGjbJQ2C5IikQ3DaPrL1lxaP7rTVMFFGSvHc8Tqvf2ykiNPn+hlGsfSSwPq9Thw86zE+ERFClB7kAJ/2T2daH9Mguxzpy3ez4mVVb+vEL8BmM4ySIy9BTbPtMCdVEA+cDoq74xvRG+t9durPY0iMdPIBnC8xyZ4VQgNBP1ZEOpFtOzBTANtNvfV/G7z3xGnipNvTqpqDTSUA/bYKhWobRIds9UvHTepw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=ktuqdMWCbQmoWKsyp6SWg7CL/Fk63y9n5q9hrN0lLF4=; b=Msq5Q3YLPwWExv4GUAfhA6EYYe0ypqG+tYF9oNFITEk5aPSduDMUMvuicOPDObjpJkW+8UXXPmiz07uNQFOj0qpt/yjx90lgsMok0QY03DWWxv80r6xFBZYrW6CyaP4QjwlHUl0aOvau/EEc5mZEEp50s6DlLy2etwW2KACbcKKgZbwFT+tboaY3jLFAqlY5SWywcWsmm0J2PzwDtZx71/0MyYAFpvXY5GBx+cm3g+7MHYbRpEYxbrRsG96LWZKoLHmR1y1QEkM84cVg/Ncrf7+h8MJytTDY/1SyTa87nYJLhEYzUxgqllaTGH8Y+P215NEG3k+HM3yDvZVBBKnVdA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=lists.yoctoproject.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=ktuqdMWCbQmoWKsyp6SWg7CL/Fk63y9n5q9hrN0lLF4=; b=Kpp4XsF2rivJfMbVymFUedqvwnIVgfim22M9ykHkflLFbspbFd3mGVtjlhDFruKGc91PxJLCkZ3miDd/pxwmfzYLpEm+ktkVtmumEQefbUJhkgjFGZcSVoCKh0bh16w2sSiv+11aHcO1s7PgXGAFhB3nb2nsqYTnn+DkVx7xaoE= Received: from BLAPR03CA0023.namprd03.prod.outlook.com (2603:10b6:208:32b::28) by CH3PR12MB9316.namprd12.prod.outlook.com (2603:10b6:610:1ce::21) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.615.10; Thu, 12 Nov 2026 22:51:31 +0000 Received: from CY8PEPF00000053.namprd05.prod.outlook.com (2603:10b6:208:32b:cafe::6d) by BLAPR03CA0023.outlook.office365.com (2603:10b6:208:32b::28) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.592.10 via Frontend Transport; Thu, 12 Nov 2026 22:51:31 +0000 X-MS-Exchange-Authentication-Results: mx.microsoft.com 1; spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by CY8PEPF00000053.mail.protection.outlook.com (10.167.247.57) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.615.4 via Frontend Transport; Thu, 12 Nov 2026 22:50:57 +0000 Received: from satlexmb10.amd.com (10.181.42.219) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.53; Thu, 12 Nov 2026 12:47:10 -0600 Received: from satlexmb07.amd.com (10.181.42.216) by satlexmb10.amd.com (10.181.42.219) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.53; Thu, 12 Nov 2026 12:47:10 -0600 Received: from xcolc230100.xilinx.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server id 15.2.2562.53 via Frontend Transport; Thu, 12 Nov 2026 12:47:10 -0600 From: Mark Hatle To: Subject: [pseudo][PATCH RFC 1/2] ports/linux: Pass an empty openat() path to the kernel Date: Thu, 12 Nov 2026 11:47:09 -0700 Message-ID: <20261112184710.549482-2-mark.hatle@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20261112184710.549482-1-mark.hatle@amd.com> References: <20261112184710.549482-1-mark.hatle@amd.com> MIME-Version: 1.0 Return-Path: mark.hatle@amd.com X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: CY8PEPF00000053:EE_|CH3PR12MB9316:EE_ X-MS-Office365-Filtering-Correlation-Id: cc6d262d-d8b9-4307-daea-08df410f6a9f X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|82310400026|36860700016|1800799024|376014|23010399003|18002099003|22082099003|6133799003|261009223027099003|11063799006|56012099006|261021135927099003|10067099003|261102123227099003|261022105027099003|261026121527099003|261022115727099003|261028144727099003|261021121427099003|261026140227099003|261021123027099003; X-Microsoft-Antispam-Message-Info: IpoJf4siobdg91X9t75SFHkFbHZHXdzg9lRcfsKXee8mfI7DiwVlWOtxnow52CQ+GxZ9KpzgsVBqFisGJi46EI7h1NQbGzFeQPu5Q/wpUOTg4iVaV1IvyGKvvNlh7wHm7ZwsOcOMUtcqLipW/cJ4Z4cWRjt1sYJ/F1bissMEseYs+bKpyvpyu2sjI1u7FWfrznoboCsccp/RcTFDX/i1ALJJHIHezOdkD0wMlkljlg7hyHRZvjecdKR1G+E72B8IVTkSEGbpiYVNEPWYwhGEOm2hD0imkhoLUU0m2gMXu7onY/4Le7T8626EnLfPPkn0d3NZxgKFDGFZ1zgi20hj5xWZ4MRfcnYrvUhOkj4ehFsxAnmPEdq6GzbgOIiJGvemq97nB74Gbzr+l+8RGt38UpVIreYwEV8T8ipX0aPnj0ISU9pG3ESlWUCuXOKH4deVgYfrVOocHXm9jS2deGq926bTmAfomYeiEPr6gAjOijtGginikvCDvhqgn68TUGFsI0RdEsZdYZ4oCY8QGo6RJ9mCLJrcg6aZrujaAFaO9RTGvKot3FxKN78SvKuTDQJU3t3UuHqXnjNoI5ybWSoLAW8Wos89Gp2CkQW/ybCMFhWyxYJg3C0KY83BRx25x2rMfsS6Y6wpPF2H1ngVijdnrBGxpIghjjFGlQcicKvvV7BtQ4blsJ3W2pHApvlYHVAij+WC7mKJq5wfTNoNuTAHYA== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(82310400026)(36860700016)(1800799024)(376014)(23010399003)(18002099003)(22082099003)(6133799003)(261009223027099003)(11063799006)(56012099006)(261021135927099003)(10067099003)(261102123227099003)(261022105027099003)(261026121527099003)(261022115727099003)(261028144727099003)(261021121427099003)(261026140227099003)(261021123027099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: lwxuu3EIQUXaV9PN00dt9g/me8jVMLnVfp8TZ5+UDaapYZf0A9t06/Qd/qKfJGSiuiLqmZa3Z4V35GcVpicEwUOsaHIoqoWs/nxDwRkslu98mSEoef6eng5pVuP120nHnTkfhtWak1VWX4VQvQcwi8KdyPzfLZlLX52IzNgNf+5lH9kuRDubLyx5feUjbrYkIW9avo1t+ZqwYCq8D3CUyfdn7L4hGId9yFf6QjVY5aivQDHUBlxrLzBtjQBU2cISu/o8uAo1uHXW8//ueQAUs7XuvE5hsMPeJwDZ+YIBgae2At4ltJFYhYOxXXj1uXtse2GS1P6dwyEEdobM0Jnsut/PDDf2WEvOCGkUbh7Yhlo/Cl1rRtghVxSjzwoXeVKVo/A0ELFk587GE8toGmKja35fo8FyrOroiBexA8NYPrnxrNrfjU9lh2EZ5/G8WmRg X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 12 Nov 2026 22:50:57.8234 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: cc6d262d-d8b9-4307-daea-08df410f6a9f X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: CY8PEPF00000053.namprd05.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: CH3PR12MB9316 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 ; Thu, 12 Nov 2026 22:54:55 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/5336 openat() wrappers resolved an empty path to dirfd's recorded path, so openat(dirfd, "", ...) opened dirfd's path instead of failing with ENOENT. Linux 7.2 adds O_EMPTYPATH, with which an empty path reopens dirfd itself, e.g. to turn an O_PATH descriptor into a usable one; resolving it to a path opens the wrong file if dirfd's file was renamed or removed. Add a makewrappers option, keep_empty_path, to leave an empty path unchanged, and use it for the openat() wrappers so the kernel decides what an empty path means. The guts look at dirfd with AT_EMPTY_PATH and record dirfd's path for the new descriptor. [YOCTO #16327] Reported-by: Paul Barker AI-Generated: Uses GitHub Copilot Signed-off-by: Mark Hatle --- makewrappers | 13 ++++-- ports/linux/guts/openat.c | 21 +++++++--- ports/linux/wrapfuncs.in | 8 ++-- test/test-openat-emptypath.c | 79 +++++++++++++++++++++++++++++++++++ test/test-openat-emptypath.sh | 12 ++++++ 5 files changed, 121 insertions(+), 12 deletions(-) create mode 100644 test/test-openat-emptypath.c create mode 100755 test/test-openat-emptypath.sh diff --git a/makewrappers b/makewrappers index 303ae32..84dc563 100755 --- a/makewrappers +++ b/makewrappers @@ -262,6 +262,10 @@ class Function: # In those cases, use a suffix ('_int') for the path variable names instead of # overwriting them. This means the wrapper needs to handle paths itself. self.preserve_paths = False + # Leave an empty path unchanged instead of resolving it to dirfd's + # path, for functions where only a flag (e.g. O_EMPTYPATH) makes + # an empty path name dirfd. + self.keep_empty_path = False function, comments = line.split(';') comment = re.search(r'/\* *(.*) *\*/', comments) @@ -427,9 +431,12 @@ class Function: if self.preserve_paths and self.noignore_path: # path_int is only read by the ignore_paths() check continue - fix_paths.append( - "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" % - (pathvar, prefix, self.dirfd, path, self.flags)) + munge = "%s = pseudo_root_path(__func__, __LINE__, %s%s, %s, %s);" % \ + (pathvar, prefix, self.dirfd, path, self.flags) + if self.keep_empty_path: + # see above for the asm() + munge = "asm(\"\" : \"+r\"(%s));\n\t\tif (!%s || *%s)\n\t\t\t%s" % (path, path, path, munge) + fix_paths.append(munge) return "\n\t\t".join(fix_paths) def ignore_paths(self): diff --git a/ports/linux/guts/openat.c b/ports/linux/guts/openat.c index 9d32283..e0075fe 100644 --- a/ports/linux/guts/openat.c +++ b/ports/linux/guts/openat.c @@ -13,6 +13,17 @@ int existed = 1; int save_errno; sigset_t local_saved_sigmask; + const char *record_path = path; + int stat_flags = (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0; + + /* An empty path is left for the kernel, which only accepts it with + * O_EMPTYPATH, to reopen dirfd. Look at and record dirfd instead. + */ + if (path && !*path) { + stat_flags |= AT_EMPTY_PATH; + record_path = pseudo_root_path(__func__, __LINE__, dirfd, + dirfd == AT_FDCWD ? "." : "", 0); + } /* mask out mode bits appropriately */ mode = mode & ~pseudo_umask; @@ -47,7 +58,7 @@ /* note "else" in #ifdef O_TMPFILE above */ if (flags & O_CREAT) { save_errno = errno; - rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + rc = base_fstatat(dirfd, path, &buf, stat_flags); existed = (rc != -1); if (!existed) pseudo_debug(PDBGF_FILE, "openat_creat: %s -> 0%o\n", path, mode); @@ -60,7 +71,7 @@ */ if (!(flags & O_NONBLOCK) && ((flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_RDWR)) { save_errno = errno; - rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + rc = base_fstatat(dirfd, path, &buf, stat_flags); if (rc != -1 && S_ISFIFO(buf.st_mode)) { overly_magic_nonblocking = 1; } @@ -105,7 +116,7 @@ return rc; } #endif - stat_rc = base_fstatat(dirfd, path, &buf, (flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + stat_rc = base_fstatat(dirfd, path, &buf, stat_flags); pseudo_debug(PDBGF_FILE, "openat(path %s), flags %o, stat rc %d, stat mode %o\n", path, flags, stat_rc, buf.st_mode); @@ -122,11 +133,11 @@ } pseudo_client_path_unlock(path_lock_fd); } - pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, &buf); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, record_path, &buf); } else { pseudo_debug(PDBGF_FILE, "openat (fd %d, path %d/%s, flags %d) succeeded, but stat failed (%s).\n", rc, dirfd, path, flags, strerror(errno)); - pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, path, 0); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(flags), rc, dirfd, record_path, 0); } errno = save_errno; } diff --git a/ports/linux/wrapfuncs.in b/ports/linux/wrapfuncs.in index 1874b60..dbbf2f7 100644 --- a/ports/linux/wrapfuncs.in +++ b/ports/linux/wrapfuncs.in @@ -8,8 +8,8 @@ int __fxstat(int ver, int fd, struct stat *buf); int lchmod(const char *path, mode_t mode); /* flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */ int lchown(const char *path, uid_t owner, gid_t group); /* flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */ int __fxstatat(int ver, int dirfd, const char *path, struct stat *buf, int flags); /* path_lock=shared */ -int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */ -int __openat_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */ +int openat(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */ +int __openat_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */ int open_tree(int dirfd, const char *path, unsigned int flags); /* preserve_paths=1, noignore_path=1 */ int open_tree_attr(int dirfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t size); /* preserve_paths=1, noignore_path=1*/ int name_to_handle_at(int dirfd, const char *name, struct file_handle *handle, int *mount_id, int flags); @@ -24,8 +24,8 @@ int fcntl64(int fd, int cmd, ...{struct flock *lock}); /* noignore_path=1 */ char *canonicalize_file_name(const char *filename); int eaccess(const char *path, int mode); int open64(const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */ -int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */ -int __openat64_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1 */ +int openat64(int dirfd, const char *path, int flags, ...{mode_t mode}); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */ +int __openat64_2(int dirfd, const char *path, int flags); /* flags=((flags&O_NOFOLLOW)||((flags&(O_CREAT|O_EXCL))==(O_CREAT|O_EXCL))), noignore_path=1, keep_empty_path=1 */ int creat64(const char *path, mode_t mode); int stat(const char *path, struct stat *buf); /* real_func=pseudo_stat, path_lock=shared */ int lstat(const char *path, struct stat *buf); /* real_func=pseudo_lstat, flags=AT_SYMLINK_NOFOLLOW, path_lock=shared */ diff --git a/test/test-openat-emptypath.c b/test/test-openat-emptypath.c new file mode 100644 index 0000000..076f1fd --- /dev/null +++ b/test/test-openat-emptypath.c @@ -0,0 +1,79 @@ +/* + * Test opening an empty path, which only names dirfd with O_EMPTYPATH + * (Linux 7.2), the way the kernel does. + * SPDX-License-Identifier: LGPL-2.1-only + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include + +#ifndef O_EMPTYPATH +#define O_EMPTYPATH (1 << 26) +#endif + +static int failed; + +#define FAIL(...) do { printf(__VA_ARGS__); failed = 1; } while (0) + +/* fd should be dir, with pseudo knowing its path */ +static void +check_reopened(const char *how, int fd) { + struct stat st; + + if (fd == -1) { + if (errno == ENOENT || errno == EINVAL) + printf("%s: O_EMPTYPATH not supported by the kernel, skipping\n", how); + else + FAIL("%s: %s\n", how, strerror(errno)); + return; + } + if (fstatat(fd, "file", &st, 0) == -1) + FAIL("%s: fstatat: %s\n", how, strerror(errno)); + else if (st.st_uid != 123 || st.st_gid != 456) + FAIL("%s: file owned by %d:%d, expected 123:456\n", + how, (int) st.st_uid, (int) st.st_gid); + close(fd); +} + +int +main(int argc, char *argv[]) { + char path[4096]; + int dfd, pfd, fd; + + if (argc != 2) { + fprintf(stderr, "usage: %s \n", argv[0]); + return 2; + } + snprintf(path, sizeof(path), "%s/file", argv[1]); + if (chown(path, 123, 456) == -1) { + perror("chown"); + return 1; + } + dfd = open(argv[1], O_RDONLY | O_DIRECTORY); + pfd = open(argv[1], O_PATH | O_DIRECTORY); + if (dfd == -1 || pfd == -1) { + perror("open"); + return 1; + } + + fd = openat(dfd, "", O_RDONLY); + if (fd != -1 || errno != ENOENT) + FAIL("openat without O_EMPTYPATH: expected ENOENT, got %d (%s)\n", + fd, strerror(errno)); + if (fd != -1) + close(fd); + + check_reopened("openat", openat(pfd, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); + if (fchdir(dfd) == -1) { + perror("fchdir"); + return 1; + } + check_reopened("openat AT_FDCWD", openat(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); + + return failed; +} diff --git a/test/test-openat-emptypath.sh b/test/test-openat-emptypath.sh new file mode 100755 index 0000000..ead5191 --- /dev/null +++ b/test/test-openat-emptypath.sh @@ -0,0 +1,12 @@ +#!/bin/sh +# +# Test opening an empty path, with and without O_EMPTYPATH. +# SPDX-License-Identifier: LGPL-2.1-only + +dir=$(pwd)/openat_emptypath_test +trap 'rm -rf "$dir"' 0 +rm -rf "$dir" +mkdir -p "$dir" +touch "$dir/file" + +./test/test-openat-emptypath "$dir" From patchwork Thu Nov 12 18:47:10 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mark Hatle X-Patchwork-Id: 103910 Received: from MW6PR02CU001.outbound.protection.outlook.com (MW6PR02CU001.outbound.protection.outlook.com [52.101.48.68]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.3540.1794509284394130964 for ; Thu, 12 Nov 2026 10:48:04 -0800 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@amd.com header.s=selector1 header.b=V+pzGfO6; spf=permerror, err=parse error for token &{10 18 %{i}._ip.%{h}._ehlo.%{d}._spf.vali.email}: invalid domain name (domain: amd.com, ip: 52.101.48.68, mailfrom: mark.hatle@amd.com) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=PEPrKRCKbByx9ty2N1SZiJ88ZFcw8pOK4+iSLkRpByPLMoeuPC9iQSpGuHBNtrZ8NXpVG8q07YVH53xyuIyuYE5lWR1Wx/LgCIwek7E3LjF+zBy3vVyT24VGVx7smpDBGBHwDYVqXgTUuGiWXfgGvV7PpAvaUy5JR2J5FQg5fEHmLtvqjmcBl8thdM4dRyy09SN0ii3KImzcI2vN7GFTYM0Piw/TuCHbmBmPKJwdgMOdquUwo1oo4o+BloVFib87uDhIgH2vGS2XCdUS519J2yIjh7x70B8OHZW8TFC/C7P/4VtywOFqhV7SEgBUheHeJAuHtgyX93BT1XtTGRh8gw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=/fxfjVG2pM4N6sbxFXR17hlBQT1s5SDMA0K153KeHWE=; b=C34IBbfwoLYfjtPB47Fg4BKRvrCC/OkxFpJ3sPozOEZNDt8Sr6vWN9kwtJMhUBeF2Z5jxDuKzYCRuPSmFmVBEeUUfpydRRMOiuOe3+gNx2G8WJGCdV6v1qiL0SisiUd6XFqSQRRy4aDrY06DCvMlnLRS1XRXc8YlkTFcwJEKdhOpCCn9ALHWg4klwnt/XHReNL7Nu0oY7plZ9eUFgwsXkKVmwdDQ7yXqk70gtWhTptPhFevOQd/DnSNuZiPeoo8nFbGasif5bgE9BZC0ShhnpvU0+y8kiXFKMd5ZUV+zT4bRxVV/fMAWiPd7WK3/42xViN8ua5Q3uXWLfBuBnqobTw== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=lists.yoctoproject.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=/fxfjVG2pM4N6sbxFXR17hlBQT1s5SDMA0K153KeHWE=; b=V+pzGfO6QctwwqzlzkvH578xjFrPdQW3nNmPUOaAoKL59VDE20hPnn77o8XYHqTGPyzDY+L3/Z4yPZb4O0iM2futfbEKySQJm2VkSiQz8R+IZNB2bajdGw7+TL0Nbl0snewMJNJb4YzJdJC3/WEHP0EbC+/Rvu0OaXXU8DTnFb4= Received: from BN9PR03CA0527.namprd03.prod.outlook.com (2603:10b6:408:131::22) by PH9PR12MB409658.namprd12.prod.outlook.com (2603:10b6:510:412::13) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.615.8; Thu, 12 Nov 2026 18:47:57 +0000 Received: from BL6PEPF0006C3E1.namprd02.prod.outlook.com (2603:10b6:408:131:cafe::29) by BN9PR03CA0527.outlook.office365.com (2603:10b6:408:131::22) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.21.615.9 via Frontend Transport; Thu, 12 Nov 2026 18:47:56 +0000 X-MS-Exchange-Authentication-Results: mx.microsoft.com 1; spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by BL6PEPF0006C3E1.mail.protection.outlook.com (10.167.249.231) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.21.615.4 via Frontend Transport; Thu, 12 Nov 2026 18:47:40 +0000 Received: from satlexmb07.amd.com (10.181.42.216) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.53; Thu, 12 Nov 2026 12:47:10 -0600 Received: from xcolc230100.xilinx.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server id 15.2.2562.53 via Frontend Transport; Thu, 12 Nov 2026 12:47:10 -0600 From: Mark Hatle To: Subject: [pseudo][PATCH RFC 2/2] openat2: Look at dirfd for an empty path Date: Thu, 12 Nov 2026 11:47:10 -0700 Message-ID: <20261112184710.549482-3-mark.hatle@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20261112184710.549482-1-mark.hatle@amd.com> References: <20261112184710.549482-1-mark.hatle@amd.com> MIME-Version: 1.0 Return-Path: mark.hatle@amd.com X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: BL6PEPF0006C3E1:EE_|PH9PR12MB409658:EE_ X-MS-Office365-Filtering-Correlation-Id: 3612aea4-44ad-45d7-9938-08df40ed6dfc X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|36860700016|82310400026|1800799024|23010399003|376014|22082099003|18002099003|3023799007|261026135527099003|11063799006|261026140227099003|261021123027099003|261028144727099003|261026130827099003|261026121527099003|261021130227099003|261021133027099003|261021135927099003|261021121427099003|56012099006|261022105027099003|261009223027099003|261022113327099003|261022115727099003|261102123227099003|261022075727099003|10067099003; X-Microsoft-Antispam-Message-Info: vX6NQuXzcHaJmxSTRzRLXkbEXdUed784XFFCNQtC7NdDsKjS4l4xffQiqKboZuE+IfecHY7D3kEN0rRlDbtHa59c26fC4TdyONnIcN8LJdsOr6I4HOFBNZHrzd7Sc/XURxZHAgeBFktbfyF74ocHq5FgI21mvn6fiDj0HFs3TuO3aYy6Kql0bO9/EplpAcCu8NW9t6t/NUEskiJydZh2tDFolw9fp0ptGWBeSM19FpvoodOgJnDZIY6+xSCwp41QvGscKgAktY3//oeVQ+pLMqpt4aiJjozgEB3FW3OHYWa5skTUGBbtp20iYCADSK2At6A4mbVxdpmq/CO1VksU1ODn8nazUeI98bdQUJl8CwQ2hBgKneVvwz0N+JZxlBxWnjeSCizsHxM5h3isQzQeq8Wqfflmp4Y/LY9HiDyAvlQqvSo0rglLCEMr0GwSgaSQhwR8c+fH5wQraQyuxkXaUtQ2FJcrfRKNTBRGz30wCQAcFJvacYy1QCzA+0KcL3hdqKC7An56FYa3brAd6y6d0MkKnrWxdg/bU6hLXsPHwkVqP6Jpk1OTS9S0x/RY3Wrdr49Uc3vwm5XWU3Zq3MYT0LiGxvFyZuHTsc+CxVZpS2jRKoRaIxaA4qf0DaP2R8Q8lRcfUd6Uy3d+Obeq34sCfdWchJJ7ZfaMiolZy0kltXAsZ4QhaaydvgDLlEHRLhbNIji8nFFs9HPLh2Hz7oRd+w== X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(36860700016)(82310400026)(1800799024)(23010399003)(376014)(22082099003)(18002099003)(3023799007)(261026135527099003)(11063799006)(261026140227099003)(261021123027099003)(261028144727099003)(261026130827099003)(261026121527099003)(261021130227099003)(261021133027099003)(261021135927099003)(261021121427099003)(56012099006)(261022105027099003)(261009223027099003)(261022113327099003)(261022115727099003)(261102123227099003)(261022075727099003)(10067099003);DIR:OUT;SFP:1101; X-MS-Exchange-AntiSpam-MessageData-ChunkCount: 1 X-MS-Exchange-AntiSpam-MessageData-0: Pb6kUiuUmX1mrla4rRZBmAmLKeDZRX06GDGU5rMk3fv1VDfRV2WWv+IJWeSgw0jp25gibi8QeKneUfJkQA8p49AC7kq7T+CJ2DcEZMwapcssVKwCQ6KnwqBQnMFmD3CeatN01hhDNdIqrW0BP2dygBwc14vpcVRuzZ354rWU/teDOVaO8m/g1G8Oa9QNIjtCGVrCN4BldD8QY98p/CBnFxZ09wDRsTgwFbi0WtL8X87jg3QVRPGDE+rN44Yw9qCA9ceZYUeUZpm/sCYOxBRvktG5GIC6z9NjLOvjyZZNtkro29b4qHC305QYK21I+K4NNekV5yOQz6SfrPqpQMlPwi7RgxwCyW58cBl8bNrKjEoBxfJnKDTL5+iAbIBZth4gbhEyjPI2gjyEj/sWyIFOokQACeEQSteH7VnfQDUgh1Du4/S01WUj3C2BDhU234+z X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 12 Nov 2026 18:47:40.6173 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 3612aea4-44ad-45d7-9938-08df40ed6dfc X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: BL6PEPF0006C3E1.namprd02.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH9PR12MB409658 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 ; Thu, 12 Nov 2026 18:48:12 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/5334 openat2() passes an empty path to the kernel, which with Linux 7.2's O_EMPTYPATH reopens dirfd, but pseudo then looked at and recorded dirfd's recorded path. With AT_FDCWD that path is empty, so *at() calls relative to the new descriptor failed, and for a descriptor on a symlink it described the target instead. Look at dirfd itself with AT_EMPTY_PATH, as openat() now does, and record dirfd's path, or the working directory's for AT_FDCWD. [YOCTO #16327] Reported-by: Paul Barker AI-Generated: Uses GitHub Copilot Signed-off-by: Mark Hatle --- ports/linux/openat2/guts/openat2.c | 24 +++++++++++++++++++----- test/test-openat-emptypath.c | 30 ++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/ports/linux/openat2/guts/openat2.c b/ports/linux/openat2/guts/openat2.c index bb4a125..30a51ab 100644 --- a/ports/linux/openat2/guts/openat2.c +++ b/ports/linux/openat2/guts/openat2.c @@ -15,6 +15,8 @@ sigset_t local_saved_sigmask; struct open_how my_how; char *pseudo_path; + const char *stat_path, *record_path; + int stat_flags; /* Validate parameters */ if (!how || size < sizeof(struct open_how)) { @@ -41,6 +43,18 @@ memcpy(&my_how, how, size); + stat_path = record_path = pseudo_path; + stat_flags = (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0; + /* An empty path is passed to the kernel, which only accepts it with + * O_EMPTYPATH, to reopen dirfd. Look at and record dirfd instead. + */ + if (path && !*path) { + stat_path = ""; + stat_flags |= AT_EMPTY_PATH; + record_path = pseudo_root_path(__func__, __LINE__, dirfd, + dirfd == AT_FDCWD ? "." : "", 0); + } + /* mask out mode bits appropriately */ my_how.mode = my_how.mode & ~pseudo_umask; @@ -81,7 +95,7 @@ /* note "else" in #ifdef O_TMPFILE above */ if (my_how.flags & O_CREAT) { save_errno = errno; - rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + rc = base_fstatat(dirfd, stat_path, &buf, stat_flags); existed = (rc != -1); if (!existed) pseudo_debug(PDBGF_FILE, "openat2_creat: %s -> 0%lld\n", pseudo_path, my_how.mode); @@ -94,7 +108,7 @@ */ if (!(my_how.flags & O_NONBLOCK) && ((my_how.flags & (O_WRONLY | O_RDONLY | O_RDWR)) != O_RDWR)) { save_errno = errno; - rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + rc = base_fstatat(dirfd, stat_path, &buf, stat_flags); if (rc != -1 && S_ISFIFO(buf.st_mode)) { overly_magic_nonblocking = 1; } @@ -151,7 +165,7 @@ return rc; } #endif - stat_rc = base_fstatat(dirfd, pseudo_path, &buf, (my_how.flags & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); + stat_rc = base_fstatat(dirfd, stat_path, &buf, stat_flags); pseudo_debug(PDBGF_FILE, "openat2(path %s), flags %lld, stat rc %d, stat mode %o\n", pseudo_path, my_how.flags, stat_rc, buf.st_mode); @@ -168,11 +182,11 @@ } pseudo_client_path_unlock(path_lock_fd); } - pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, pseudo_path, &buf); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, record_path, &buf); } else { pseudo_debug(PDBGF_FILE, "openat2 (fd %d, path %d/%s, flags %lld) succeeded, but stat failed (%s).\n", rc, dirfd, pseudo_path, my_how.flags, strerror(errno)); - pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, pseudo_path, 0); + pseudo_client_op(OP_OPEN, PSEUDO_ACCESS(my_how.flags), rc, dirfd, record_path, 0); } errno = save_errno; } diff --git a/test/test-openat-emptypath.c b/test/test-openat-emptypath.c index 076f1fd..7ebec12 100644 --- a/test/test-openat-emptypath.c +++ b/test/test-openat-emptypath.c @@ -1,6 +1,6 @@ /* - * Test opening an empty path, which only names dirfd with O_EMPTYPATH - * (Linux 7.2), the way the kernel does. + * Test opening an empty path with openat() and openat2(), which only + * names dirfd with O_EMPTYPATH (Linux 7.2), the way the kernel does. * SPDX-License-Identifier: LGPL-2.1-only */ #define _GNU_SOURCE @@ -10,8 +10,19 @@ #include #include #include +#include #include +#if __has_include () +# include +#else +struct open_how { + unsigned long long flags; + unsigned long long mode; + unsigned long long resolve; +}; +#endif + #ifndef O_EMPTYPATH #define O_EMPTYPATH (1 << 26) #endif @@ -20,6 +31,13 @@ static int failed; #define FAIL(...) do { printf(__VA_ARGS__); failed = 1; } while (0) +static int +do_openat2(int dirfd, const char *path, unsigned long long flags) { + struct open_how how = { .flags = flags }; + + return syscall(SYS_openat2, dirfd, path, &how, sizeof(how)); +} + /* fd should be dir, with pseudo knowing its path */ static void check_reopened(const char *how, int fd) { @@ -67,13 +85,21 @@ main(int argc, char *argv[]) { fd, strerror(errno)); if (fd != -1) close(fd); + fd = do_openat2(dfd, "", O_RDONLY); + if (fd != -1 || errno != ENOENT) + FAIL("openat2 without O_EMPTYPATH: expected ENOENT, got %d (%s)\n", + fd, strerror(errno)); + if (fd != -1) + close(fd); check_reopened("openat", openat(pfd, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); + check_reopened("openat2", do_openat2(pfd, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); if (fchdir(dfd) == -1) { perror("fchdir"); return 1; } check_reopened("openat AT_FDCWD", openat(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); + check_reopened("openat2 AT_FDCWD", do_openat2(AT_FDCWD, "", O_RDONLY | O_DIRECTORY | O_EMPTYPATH)); return failed; }