From patchwork Wed Jul 15 05:41:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Baban X-Patchwork-Id: 92465 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 582ECC44508 for ; Wed, 15 Jul 2026 09:06:46 +0000 (UTC) Received: from mail-pf1-f177.google.com (mail-pf1-f177.google.com [209.85.210.177]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.480.1784094105435895567 for ; Tue, 14 Jul 2026 22:41:45 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=Li5difgn; spf=pass (domain: gmail.com, ip: 209.85.210.177, mailfrom: bbnpreetsingh@gmail.com) Received: by mail-pf1-f177.google.com with SMTP id d2e1a72fcca58-845c92bc464so1684581b3a.2 for ; Tue, 14 Jul 2026 22:41:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784094105; x=1784698905; darn=lists.yoctoproject.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6MfFuRyPRnk8cGj7U0aOW5yv28tMt3ff4BwJD3L/xOg=; b=Li5difgn3vChBNBbtPNtK9Fmmen0upfPzsYKks30HrFcxmd4Mk9kjuAWEUehvl8voc tfYBV7dr6112Vn90pVf8s3UII/fvM1/y/QJ1Jl8W0pQf2x0+ZyM4pZng9echjs4YY0jc cDwlW9F3Qy0AaX+yTgVTnwGGnK+esMhSLoWv/QqBNpb4jneC9hnry9EhGVJi+od0JxBe hAw6rvOR3ZcpkiQnoU63pLG/n0/exMRWN8TB1Y2ryvyDOMkQhfmDhPq5VMB3pRKrIsPK r2wJde2X9H+sIMS/jDrUkomdmhJHRGgUvis/dpEXQmbbEbz8P8XtlYXSbUXr6gmhuX8X mgyw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784094105; x=1784698905; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6MfFuRyPRnk8cGj7U0aOW5yv28tMt3ff4BwJD3L/xOg=; b=F/4IusP8VErYwD2CztTsXQaq8kl6rd5IDuDi4XYS6sWnNOGkyqOhSzHlzAzW2xIDN0 E8WM7CzOt5I1rOwhQTgUUp7Lzg9rfk1vC6Aqgm0EX/QApwKhkB5LiaFNhYFrb+Q+dGtJ 7xX/jCkKene+DkaV8kyriWlFdQiDMdPSeGflQvLgs8BbOe0dhLfxR3XDMYS61px3Z2pA JZn3n9FhJ3dtO35zqtyyANfKTeo6Fd8j8bNZXN7kfIYqLCPGJDBAfEkCOjGtxLV1R/uZ VWfa9JDnAUsoBW2m5YhC0Xp0ed/A60u2iBGsEPm6wCTZufWO931U9YQsZeHucTFvoEMA 6cAA== X-Gm-Message-State: AOJu0YwCI6GH3sKaipEYxXIufW8FUbM5rzhxKsosqvB6eiVQwoWoeAEl Rs190tVnce/wQskJ4kmjJWjVn7mFWN1DyROOywd8GNcaiZDfdFkdg/jhnkYntpHd6mM= X-Gm-Gg: AfdE7cn/ZmK+dDVo4AL4WQCV5bTyYgwi9nKjW/dpWCI5M1nTGhL6wPzLqcUcCVT5Df/ Sj7o9xl0PI3Q8eS8t/ed+TjIIBCxeW7PB2i5vdSnCTu6oB3uE9djvOHRm67YusxL72Im4QiMIwR 8DdmPwSyjfmxByM0eCjcQbhb7DxL8YIAks3nXUbsMDLZiKbHql4hfLsCix55hNiwsv0xJheIG9u ZcQmdazUo+XC49FjBGvrVNDADmnwQIlI7l1rvdDkRRho7twBqClyC87DHkHpkR+epQPlU5/IYmr q1cmz4oAVBi9kTIkwLctf8hGzEIVE1Aq9NiWf2MbAUoJotNl3RjlPuGZFidU5sMx31gx6KOZWPl K6rfzILh7xv25wRoJEBuVa3lB5bnbodtina9+QrNTUY10KXLD4BlxyuX9MomeNIqE3WEhtDMq1q lWi2CmVfnE99jX X-Received: by 2002:a05:6a00:1741:b0:845:e1a3:107e with SMTP id d2e1a72fcca58-848896eb340mr15376983b3a.52.1784094104673; Tue, 14 Jul 2026 22:41:44 -0700 (PDT) Received: from ydev.. ([108.180.130.139]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-84a4f264989sm2550668b3a.13.2026.07.14.22.41.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 22:41:44 -0700 (PDT) From: "Baban" To: yocto-patches@lists.yoctoproject.org Cc: Richard Purdie , Mark Hatle , Randy MacLeod , Vincent Haupert , Babanpreet Singh Subject: [pseudo] [PATCH 1/2] ports/linux/guts: Implement close_range() instead of returning ENOSYS Date: Wed, 15 Jul 2026 05:41:41 +0000 Message-ID: <20260715054142.7-2-bbnpreetsingh@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260715054142.7-1-bbnpreetsingh@gmail.com> References: <20260715054142.7-1-bbnpreetsingh@gmail.com> MIME-Version: 1.0 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 ; Wed, 15 Jul 2026 09:06:46 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4479 close_range() has been wrapped since 35433e6 ("ports/linux/guts: Add close_range wrapper for glibc 2.34"), but the wrapper only sets ENOSYS, on the grounds that callers have to handle that anyway. That was true when it was written. It no longer is: systemd's close_all_fds() has used close_range() since v247, and v260 raised its kernel baseline to 5.10, dropped the /proc fallback and began treating a failure as fatal. safe_fork_full() aborts the child when it fails, so under pseudo every fork+exec dies, eg for "pseudo systemd-repart" spawning mkfs: Failed to close all file descriptors: Function not implemented '(mkfs)' failed with exit status 1. Implement it the way closefrom() already is, per 21ff2fb ("ports/linux/ guts: Add closefrom support for glibc 2.34"): a client side op works out the first fd above every descriptor pseudo keeps for itself, closes the ones below it by hand while stepping around its own, and hands that fd back so the caller can turn the kernel loose on the rest. close_range() has a top end where closefrom() does not, so both the manual loop and the path table cleanup are bounded by maxfd. The flags are dealt with before any of that. CLOSE_RANGE_UNSHARE has to take effect first, or descriptors would be closed for everyone still sharing the table rather than just for us. CLOSE_RANGE_CLOEXEC closes nothing, and pseudo's own fds are close-on-exec already, so there is nothing to protect and it can go straight to the kernel. An unknown flag or an inverted range is refused before anything is touched, matching the kernel: otherwise a call which should have failed cleanly takes descriptors with it on the way out. A range starting above INT_MAX cannot hold any of pseudo's own fds and is passed through, since the client op takes the low end as an int. [YOCTO #16339] AI-Generated: Uses Claude (claude-opus-4-8) Signed-off-by: Babanpreet Singh --- enums/op.in | 1 + ports/linux/guts/close_range.c | 55 +++++++++++++++++++++++++++---- ports/linux/portdefs.h | 16 +++++++++ pseudo_client.c | 60 ++++++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 7 deletions(-) diff --git a/enums/op.in b/enums/op.in index 5b5e21b..5013892 100644 --- a/enums/op.in +++ b/enums/op.in @@ -28,3 +28,4 @@ set-xattr, 0 create-xattr, 1 replace-xattr, 1 closefrom, 0 +close-range, 0 diff --git a/ports/linux/guts/close_range.c b/ports/linux/guts/close_range.c index 4bd2fe1..15258be 100644 --- a/ports/linux/guts/close_range.c +++ b/ports/linux/guts/close_range.c @@ -6,14 +6,55 @@ * int close_range(unsigned int lowfd, unsigned int maxfd, int flags) * int rc = -1; */ + pseudo_msg_t *msg; - (void) lowfd; - (void) maxfd; - (void) flags; - /* for now pretend the kernel doesn't support it regardless - which users are supposed to be able to handle */ - errno = ENOSYS; - rc = -1; + /* The kernel rejects both of these outright, so check before doing + * anything: otherwise a call which should have failed cleanly takes + * descriptors with it on the way out. + */ + if (flags & ~(CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC)) { + errno = EINVAL; + return -1; + } + if (lowfd > maxfd) { + errno = EINVAL; + return -1; + } + + /* CLOSE_RANGE_UNSHARE has to take effect before anything is closed: + * while the descriptor table is still shared, closing a descriptor + * would close it for everyone sharing the table, not just for us. + */ + if (flags & CLOSE_RANGE_UNSHARE) { + if (unshare(CLONE_FILES) == -1) + return -1; + flags &= ~CLOSE_RANGE_UNSHARE; + } + + /* CLOSE_RANGE_CLOEXEC closes nothing, it only marks descriptors, and + * pseudo's own are close-on-exec already (pseudo_fd() sets that on + * every one of them), so there is nothing here to protect. + */ + if (flags & CLOSE_RANGE_CLOEXEC) + return real_close_range(lowfd, maxfd, flags); + + /* Descriptors are ints, so a range starting above INT_MAX cannot hold + * any of pseudo's own and there is nothing to step around. Worth its + * own case because pseudo_client_op() takes the low end as an int. + */ + if (lowfd > INT_MAX) + return real_close_range(lowfd, maxfd, flags); + + /* Same shape as closefrom(): the client op closes the descriptors its + * own are mixed in with by hand, stepping around the ones pseudo needs + * to keep, and hands back the first fd the kernel can safely be turned + * loose on. + */ + msg = pseudo_client_op(OP_CLOSE_RANGE, 0, lowfd, -1, 0, 0, maxfd); + if (maxfd >= (unsigned int) msg->fd) + rc = real_close_range(msg->fd, maxfd, flags); + else + rc = 0; /* return rc; * } diff --git a/ports/linux/portdefs.h b/ports/linux/portdefs.h index 19bb232..1f1a41a 100644 --- a/ports/linux/portdefs.h +++ b/ports/linux/portdefs.h @@ -35,6 +35,22 @@ GLIBC_COMPAT_SYMBOL(memcpy,2.0); #include #include +/* close_range()'s flags, and unshare(), are only declared by glibc under + * _GNU_SOURCE, which pseudo does not build with. is + * not an option either: it is absent on hosts with pre-5.9 kernel headers, + * the same problem SYS_openat2 has below. Both values are kernel ABI. + */ +#ifndef CLOSE_RANGE_UNSHARE +#define CLOSE_RANGE_UNSHARE (1U << 1) +#endif +#ifndef CLOSE_RANGE_CLOEXEC +#define CLOSE_RANGE_CLOEXEC (1U << 2) +#endif +#ifndef CLONE_FILES +#define CLONE_FILES 0x00000400 +#endif +extern int unshare(int flags); + #ifndef _STAT_VER #if defined (__aarch64__) || defined (__riscv) #define _STAT_VER 0 diff --git a/pseudo_client.c b/pseudo_client.c index 7041366..1acd948 100644 --- a/pseudo_client.c +++ b/pseudo_client.c @@ -993,6 +993,28 @@ pseudo_client_closefrom(int fd) { } } +/* Like pseudo_client_closefrom(), but bounded: close_range() has a top end, + * so entries above it have to be left alone. + */ +static void +pseudo_client_close_range(int lowfd, unsigned int maxfd) { + int i, top; + + if (lowfd < 0 || lowfd >= nfds) + return; + + top = (maxfd >= (unsigned int) nfds) ? nfds - 1 : (int) maxfd; + for (i = lowfd; i <= top; ++i) { + free(fd_paths[i]); + fd_paths[i] = 0; + + if (i < linked_nfds) { + free(linked_fd_paths[i]); + linked_fd_paths[i] = 0; + } + } +} + /* spawn server */ static int client_spawn_server(void) { @@ -1633,6 +1655,7 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path static size_t alloced_len = 0; int strip_slash; int startfd, i; + unsigned int close_range_maxfd = 0; #ifdef PSEUDO_PROFILING struct timeval tv1_op, tv2_op; @@ -1753,6 +1776,13 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path } #endif + if (op == OP_CLOSE_RANGE) { + va_list ap; + va_start(ap, buf); + close_range_maxfd = va_arg(ap, unsigned int); + va_end(ap); + } + if (op == OP_RENAME) { va_list ap; if (!path) { @@ -1959,6 +1989,36 @@ pseudo_client_op(pseudo_op_t op, int access, int fd, int dirfd, const char *path msg.fd = startfd; do_request = 0; break; + case OP_CLOSE_RANGE: + /* no request needed */ + startfd = fd; + if (pseudo_util_debug_fd > startfd) + startfd = pseudo_util_debug_fd + 1; + if (pseudo_localstate_dir_fd > startfd) + startfd = pseudo_localstate_dir_fd + 1; + if (pseudo_pwd_fd > startfd) + startfd = pseudo_pwd_fd + 1; + if (pseudo_grp_fd > startfd) + startfd = pseudo_grp_fd + 1; + if (connect_fd > startfd) + startfd = connect_fd + 1; + /* the fds below startfd are the ones our own are mixed in + * with, so close those by hand and skip the ones we need + */ + for (i = fd; i < startfd && (unsigned int) i <= close_range_maxfd; ++i) { + if (i == pseudo_util_debug_fd || i == pseudo_localstate_dir_fd || i == pseudo_pwd_fd || + i == pseudo_grp_fd || i == connect_fd) + continue; + pseudo_client_close(i); + close(i); + } + if (close_range_maxfd >= (unsigned int) startfd) + pseudo_client_close_range(startfd, close_range_maxfd); + /* tell the caller to start at startfd instead of fd */ + result = &msg; + msg.fd = startfd; + do_request = 0; + break; case OP_CLOSE: /* no request needed */ if (fd >= 0) { From patchwork Wed Jul 15 05:41:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Baban X-Patchwork-Id: 92464 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 5F760C44507 for ; Wed, 15 Jul 2026 09:06:36 +0000 (UTC) Received: from mail-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.182]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.481.1784094106041579388 for ; Tue, 14 Jul 2026 22:41:46 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gmail.com header.s=20251104 header.b=cleeu0Z6; spf=pass (domain: gmail.com, ip: 209.85.210.182, mailfrom: bbnpreetsingh@gmail.com) Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-8487b7b4066so5010744b3a.1 for ; Tue, 14 Jul 2026 22:41:45 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784094105; x=1784698905; darn=lists.yoctoproject.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=1HmKLCW9+ZXJqHBltPu8fin/Ezulz9iFCmh7iTNMcqw=; b=cleeu0Z66lBOgnAQS+rzJAFyPyu38BkVB/GB1scHTqIQLJIdeC2wH+SKQBW8qLFQ/n dDo+dBb9IHVnMfky4T1ayGv8W6K4esLI8cA0SaW0XpT3rXENAThMi6Rz76BK94UWz0lo DDI/zLQAUOx3Ql9DpK24Rd3cQ6WMWvH/8V4r8fOEHRt/j8C9H1VeRCYiTqvQa7J1O6Fo euNId8Qp4kJP2Uti1YqFK6+cvJGtvFaeolEcmDxORxxQl9SOPq92F4cBOR3y9nyQUZwS NZJW3LljT2PaIoktMwIXhn4KhN/Q+MpW05wUNE8zgK/eHu7gIV6zd/toEmBqLKBxHCV2 CgIw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784094105; x=1784698905; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=1HmKLCW9+ZXJqHBltPu8fin/Ezulz9iFCmh7iTNMcqw=; b=qrL1wK4GHbpyfsBmDobu4dvwVvg7Q60JlVj2eZEJKNrulJtPEgw8yOZq3+WYSZDdd0 5LQIC/WH4nMmauxCRPOb5UpVx0F2+sUYm0qxbFU7SDIQ960GAOH4IQ/2JmEPCViGETxg guuA7hKcv2eqruHkdXufwOad6LoupvImkFH3/n+Bh/p3wSb4MFxj2InPYmXyZ3W6j3It Q85PXSyqmPqbJgzUAo6f+wJoWDNBL1Dlit9cl0qg4dLFyjeemwZB29p18IGKQtMaT/Ca 5ui/qKHkctJa7E2pwWG8wAgYqEMe7M2FTsrLoBsuh11FkaAbrZ6TRy7AUPrdzKPH245y gJUQ== X-Gm-Message-State: AOJu0Yx5dTISUWNdD5tpDhSHYVHJ6F5un4uvt1YqtbwKgXqkOEYLTQua /8iKLrSZzk9RXgD48g27GH236fycsSETHV79A2BohWzK/dqT9cMwZIj99t2xsaKhMJ4= X-Gm-Gg: AfdE7cnblUR7WEaJURZiVx1KqamtSQkkCFxsS7E16zIlPrltMXt16xN0+PPWmONr2CF G9FSRCIMu9FNf9Ot+CBJ/8M4xKiTPZUPj/YK3gJfe3oMQ9vdAO5W2M03waZRAdp/vnDQDKFw7+E 0HIYNiq62wRwY//xI65Cz6SpRUruuBwDBrPGSBkSR9FyPEUJQmW6dKUx5v7kbEB1jVIFuiVT8WV SqFXsMrqqdO7NgK6LRRMPyH5PU1HvB4vwv8CzABJxZR09nlbwv5L5cjv91Xmy7b+oBnBGwFWswa qmDloJZre3BRRBndyIyX2s1NyD4S+WqQafjkXZnJymJCw08cqqIbR3xLYCqVgJEhyoqcwQTWdbf I9LJ588xLD/n5DjyIV59Flf4GSk27G1VvYN5Kd67pJKiXLzFdTfKTLBlGBLbLDpIMTKwWF4ew0C qRnGpnRDrbXAoh X-Received: by 2002:aa7:8891:0:b0:847:b2e4:9f68 with SMTP id d2e1a72fcca58-84a671da3aamr1125803b3a.9.1784094105303; Tue, 14 Jul 2026 22:41:45 -0700 (PDT) Received: from ydev.. ([108.180.130.139]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-84a4f264989sm2550668b3a.13.2026.07.14.22.41.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Jul 2026 22:41:45 -0700 (PDT) From: "Baban" To: yocto-patches@lists.yoctoproject.org Cc: Richard Purdie , Mark Hatle , Randy MacLeod , Vincent Haupert , Babanpreet Singh Subject: [pseudo] [PATCH 2/2] tests: Add close_range() test Date: Wed, 15 Jul 2026 05:41:42 +0000 Message-ID: <20260715054142.7-3-bbnpreetsingh@gmail.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260715054142.7-1-bbnpreetsingh@gmail.com> References: <20260715054142.7-1-bbnpreetsingh@gmail.com> MIME-Version: 1.0 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 ; Wed, 15 Jul 2026 09:06:36 -0000 X-Groupsio-URL: https://lists.yoctoproject.org/g/yocto-patches/message/4477 Covers the ENOSYS regression itself, that the wrapper agrees with the kernel about bad arguments, and the part that actually needs the care: pseudo keeps its own descriptors in the range, including the connection to its server, and has to come out of a close_range(3, ~0U, 0) still tracking ownership. close_range() needs a 5.9 kernel, so the shell wrapper probes for it with PSEUDO_DISABLED=1 before running anything. Asking with pseudo out of the way keeps a wrapper which has gone back to returning ENOSYS from passing itself off as an old kernel and quietly skipping the test it is supposed to fail. [YOCTO #16339] AI-Generated: Uses Claude (claude-opus-4-8) Signed-off-by: Babanpreet Singh --- test/test-close-range.c | 167 +++++++++++++++++++++++++++++++++++++++ test/test-close-range.sh | 16 ++++ 2 files changed, 183 insertions(+) create mode 100644 test/test-close-range.c create mode 100755 test/test-close-range.sh diff --git a/test/test-close-range.c b/test/test-close-range.c new file mode 100644 index 0000000..61f729b --- /dev/null +++ b/test/test-close-range.c @@ -0,0 +1,167 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-only + * + * close_range() was stubbed out to return ENOSYS unconditionally, on the + * assumption that callers all cope with that. Current systemd does not: it + * dropped its /proc fallback once its kernel baseline reached 5.10, so every + * fork+exec under pseudo failed. Check the wrapper works, that it agrees + * with the kernel about bad arguments, and that pseudo still functions after + * a caller closes every descriptor from 3 up. + */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include + +/* is missing on hosts with pre-5.9 kernel headers */ +#ifndef CLOSE_RANGE_CLOEXEC +#define CLOSE_RANGE_CLOEXEC (1U << 2) +#endif + +/* matches test-openat2-syscall: the shell wrapper turns 77 into "skipped" */ +#define SKIP 77 + +#define TESTFILE "test-close-range-file" + +static int open_null(void) { + int fd = open("/dev/null", O_RDONLY); + if (fd < 0) + perror("open /dev/null"); + return fd; +} + +static int is_closed(int fd) { + return fcntl(fd, F_GETFD) == -1 && errno == EBADF; +} + +/* Is close_range() there at all? The wrapper runs this with PSEUDO_DISABLED + * set, so the answer is the kernel's and a stubbed wrapper cannot pass + * itself off as an old kernel. + */ +static int probe(void) { + int fd = open_null(); + + if (fd < 0) + return 1; + if (close_range(fd, fd, 0) == -1) { + int err = errno; + close(fd); + return (err == ENOSYS) ? SKIP : 1; + } + return 0; +} + +int main(int argc, char **argv) { + struct stat st; + int a, b, c; + + if (argc > 1 && !strcmp(argv[1], "--probe")) + return probe(); + + /* the reported bug: this used to fail with ENOSYS every time */ + a = open_null(); + if (a < 0) + return 1; + if (close_range(a, a, 0) == -1) { + fprintf(stderr, "close_range(%d, %d, 0): %s\n", a, a, strerror(errno)); + return 1; + } + if (!is_closed(a)) { + fprintf(stderr, "close_range() left fd %d open\n", a); + return 1; + } + + /* a bounded range closes all of itself */ + a = open_null(); + b = open_null(); + c = open_null(); + if (a < 0 || b < 0 || c < 0) + return 1; + if (close_range(a, c, 0) == -1) { + perror("close_range(a, c, 0)"); + return 1; + } + if (!is_closed(a) || !is_closed(b) || !is_closed(c)) { + fprintf(stderr, "close_range(%d, %d, 0) left descriptors open\n", a, c); + return 1; + } + + /* CLOSE_RANGE_CLOEXEC marks descriptors, it does not close them */ + a = open_null(); + if (a < 0) + return 1; + if (close_range(a, a, CLOSE_RANGE_CLOEXEC) == -1) { + perror("close_range(a, a, CLOSE_RANGE_CLOEXEC)"); + return 1; + } + if (is_closed(a)) { + fprintf(stderr, "CLOSE_RANGE_CLOEXEC closed fd %d\n", a); + return 1; + } + if (!(fcntl(a, F_GETFD) & FD_CLOEXEC)) { + fprintf(stderr, "CLOSE_RANGE_CLOEXEC did not set FD_CLOEXEC on fd %d\n", a); + return 1; + } + close(a); + + /* bad arguments are refused, and nothing is closed on the way out */ + a = open_null(); + if (a < 0) + return 1; + if (close_range(a, a, 0x80) != -1 || errno != EINVAL) { + fprintf(stderr, "close_range() with an unknown flag should fail EINVAL\n"); + return 1; + } + if (is_closed(a)) { + fprintf(stderr, "a rejected close_range() closed fd %d anyway\n", a); + return 1; + } + if (close_range(a + 1, a, 0) != -1 || errno != EINVAL) { + fprintf(stderr, "close_range() with lowfd > maxfd should fail EINVAL\n"); + return 1; + } + close(a); + + /* a range entirely above INT_MAX holds no descriptors at all, but it + * still has to be accepted rather than mangled on the way through + */ + if (close_range((unsigned int) INT_MAX + 1, ~0U, 0) == -1) { + fprintf(stderr, "close_range(INT_MAX+1, ~0U, 0): %s\n", strerror(errno)); + return 1; + } + + /* And the point of the care in the wrapper: pseudo keeps descriptors + * of its own in this range and has to come out of it still working. + */ + a = open(TESTFILE, O_CREAT | O_WRONLY, 0644); + if (a < 0) { + perror("open " TESTFILE); + return 1; + } + close(a); + if (chown(TESTFILE, 0, 0) == -1) { + perror("chown " TESTFILE); + return 1; + } + if (close_range(3, ~0U, 0) == -1) { + fprintf(stderr, "close_range(3, ~0U, 0): %s\n", strerror(errno)); + return 1; + } + if (stat(TESTFILE, &st) == -1) { + perror("stat after close_range"); + return 1; + } + if (st.st_uid != 0 || st.st_gid != 0) { + fprintf(stderr, "pseudo lost track after close_range: uid %d, gid %d\n", + (int) st.st_uid, (int) st.st_gid); + return 1; + } + unlink(TESTFILE); + + return 0; +} diff --git a/test/test-close-range.sh b/test/test-close-range.sh new file mode 100755 index 0000000..65be016 --- /dev/null +++ b/test/test-close-range.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.1-only +# + +# close_range() needs a 5.9 kernel. Ask with pseudo out of the way, so that a +# wrapper which has gone back to returning ENOSYS cannot pass itself off as an +# old kernel and quietly skip the test it is supposed to fail. +PSEUDO_DISABLED=1 ./test/test-close-range --probe +case $? in +0) ;; +77) exit 255 ;; +*) exit 1 ;; +esac + +./test/test-close-range