All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Baban" <bbnpreetsingh@gmail.com>
To: yocto-patches@lists.yoctoproject.org
Cc: Richard Purdie <richard.purdie@linuxfoundation.org>,
	Mark Hatle <mark.hatle@amd.com>,
	Randy MacLeod <randy.macleod@windriver.com>,
	Vincent Haupert <mail@vincent-haupert.de>,
	Babanpreet Singh <bbnpreetsingh@gmail.com>
Subject: [pseudo] [PATCH 2/2] tests: Add close_range() test
Date: Wed, 15 Jul 2026 05:41:42 +0000	[thread overview]
Message-ID: <20260715054142.7-3-bbnpreetsingh@gmail.com> (raw)
In-Reply-To: <20260715054142.7-1-bbnpreetsingh@gmail.com>

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 <bbnpreetsingh@gmail.com>
---
 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 <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+/* <linux/close_range.h> 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
-- 
2.43.0



      parent reply	other threads:[~2026-07-15  9:06 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-15  5:41 [pseudo] [PATCH 0/2] close_range: implement it rather than return ENOSYS Baban
2026-07-15  5:41 ` [pseudo] [PATCH 1/2] ports/linux/guts: Implement close_range() instead of returning ENOSYS Baban
2026-07-15  5:41 ` Baban [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260715054142.7-3-bbnpreetsingh@gmail.com \
    --to=bbnpreetsingh@gmail.com \
    --cc=mail@vincent-haupert.de \
    --cc=mark.hatle@amd.com \
    --cc=randy.macleod@windriver.com \
    --cc=richard.purdie@linuxfoundation.org \
    --cc=yocto-patches@lists.yoctoproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.