All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/4] selftests/core: fix close_range_test build after XFAIL removal
       [not found] <https://lore.kernel.org/linux-fsdevel/20201217213303.722643-1-christian.brauner@ubuntu.com>
@ 2020-12-18 14:54 ` Christian Brauner
  2020-12-18 14:54 ` [PATCH 2/4] selftests/core: handle missing syscall number for close_range Christian Brauner
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 4+ messages in thread
From: Christian Brauner @ 2020-12-18 14:54 UTC (permalink / raw)
  To: linux-fsdevel; +Cc: Giuseppe Scrivano, Tobias Klauser, Christian Brauner

From: Tobias Klauser <tklauser@distanz.ch>

XFAIL was removed in commit 9847d24af95c ("selftests/harness: Refactor
XFAIL into SKIP") and its use in close_range_test was already replaced
by commit 1d44d0dd61b6 ("selftests: core: use SKIP instead of XFAIL in
close_range_test.c"). However, commit 23afeaeff3d9 ("selftests: core:
add tests for CLOSE_RANGE_CLOEXEC") introduced usage of XFAIL in
TEST(close_range_cloexec). Use SKIP there as well.

Cc: Giuseppe Scrivano <gscrivan@redhat.com>
Fixes: 23afeaeff3d9 ("selftests: core: add tests for CLOSE_RANGE_CLOEXEC")
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Acked-by: Christian Brauner <christian.brauner@ubuntu.com>
Link: https://lore.kernel.org/r/20201218112428.13662-1-tklauser@distanz.ch
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
 tools/testing/selftests/core/close_range_test.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c
index 9625d2f2188a..c97dd1a7abd6 100644
--- a/tools/testing/selftests/core/close_range_test.c
+++ b/tools/testing/selftests/core/close_range_test.c
@@ -241,7 +241,7 @@ TEST(close_range_cloexec)
 		fd = open("/dev/null", O_RDONLY);
 		ASSERT_GE(fd, 0) {
 			if (errno == ENOENT)
-				XFAIL(return, "Skipping test since /dev/null does not exist");
+				SKIP(return, "Skipping test since /dev/null does not exist");
 		}
 
 		open_fds[i] = fd;
@@ -250,9 +250,9 @@ TEST(close_range_cloexec)
 	ret = sys_close_range(1000, 1000, CLOSE_RANGE_CLOEXEC);
 	if (ret < 0) {
 		if (errno == ENOSYS)
-			XFAIL(return, "close_range() syscall not supported");
+			SKIP(return, "close_range() syscall not supported");
 		if (errno == EINVAL)
-			XFAIL(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC");
+			SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC");
 	}
 
 	/* Ensure the FD_CLOEXEC bit is set also with a resource limit in place.  */
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 2/4] selftests/core: handle missing syscall number for close_range
       [not found] <https://lore.kernel.org/linux-fsdevel/20201217213303.722643-1-christian.brauner@ubuntu.com>
  2020-12-18 14:54 ` [PATCH 1/4] selftests/core: fix close_range_test build after XFAIL removal Christian Brauner
@ 2020-12-18 14:54 ` Christian Brauner
  2020-12-18 14:54 ` [PATCH 3/4] selftests/core: add test for CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC Christian Brauner
  2020-12-18 14:54 ` [PATCH 4/4] selftests/core: add regression " Christian Brauner
  3 siblings, 0 replies; 4+ messages in thread
From: Christian Brauner @ 2020-12-18 14:54 UTC (permalink / raw)
  To: linux-fsdevel; +Cc: Giuseppe Scrivano, Christian Brauner

This improves the syscall number handling in the close_range()
selftests. This should handle any architecture.

Cc: Giuseppe Scrivano <gscrivan@redhat.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
 .../testing/selftests/core/close_range_test.c  | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c
index c97dd1a7abd6..bc592a1372bb 100644
--- a/tools/testing/selftests/core/close_range_test.c
+++ b/tools/testing/selftests/core/close_range_test.c
@@ -17,7 +17,23 @@
 #include "../clone3/clone3_selftests.h"
 
 #ifndef __NR_close_range
-#define __NR_close_range -1
+	#if defined __alpha__
+		#define __NR_close_range 546
+	#elif defined _MIPS_SIM
+		#if _MIPS_SIM == _MIPS_SIM_ABI32	/* o32 */
+			#define __NR_close_range (436 + 4000)
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_NABI32	/* n32 */
+			#define __NR_close_range (436 + 6000)
+		#endif
+		#if _MIPS_SIM == _MIPS_SIM_ABI64	/* n64 */
+			#define __NR_close_range (436 + 5000)
+		#endif
+	#elif defined __ia64__
+		#define __NR_close_range (436 + 1024)
+	#else
+		#define __NR_close_range 436
+	#endif
 #endif
 
 #ifndef CLOSE_RANGE_UNSHARE
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 3/4] selftests/core: add test for CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC
       [not found] <https://lore.kernel.org/linux-fsdevel/20201217213303.722643-1-christian.brauner@ubuntu.com>
  2020-12-18 14:54 ` [PATCH 1/4] selftests/core: fix close_range_test build after XFAIL removal Christian Brauner
  2020-12-18 14:54 ` [PATCH 2/4] selftests/core: handle missing syscall number for close_range Christian Brauner
@ 2020-12-18 14:54 ` Christian Brauner
  2020-12-18 14:54 ` [PATCH 4/4] selftests/core: add regression " Christian Brauner
  3 siblings, 0 replies; 4+ messages in thread
From: Christian Brauner @ 2020-12-18 14:54 UTC (permalink / raw)
  To: linux-fsdevel; +Cc: Giuseppe Scrivano, Christian Brauner

Add a test to verify that CLOSE_RANGE_UNSHARE works correctly when combined
with CLOSE_RANGE_CLOEXEC for the single-threaded case.

Cc: Giuseppe Scrivano <gscrivan@redhat.com>
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
 .../testing/selftests/core/close_range_test.c | 70 +++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c
index bc592a1372bb..862444f1c244 100644
--- a/tools/testing/selftests/core/close_range_test.c
+++ b/tools/testing/selftests/core/close_range_test.c
@@ -313,5 +313,75 @@ TEST(close_range_cloexec)
 	}
 }
 
+TEST(close_range_cloexec_unshare)
+{
+	int i, ret;
+	int open_fds[101];
+	struct rlimit rlimit;
+
+	for (i = 0; i < ARRAY_SIZE(open_fds); i++) {
+		int fd;
+
+		fd = open("/dev/null", O_RDONLY);
+		ASSERT_GE(fd, 0) {
+			if (errno == ENOENT)
+				SKIP(return, "Skipping test since /dev/null does not exist");
+		}
+
+		open_fds[i] = fd;
+	}
+
+	ret = sys_close_range(1000, 1000, CLOSE_RANGE_CLOEXEC);
+	if (ret < 0) {
+		if (errno == ENOSYS)
+			SKIP(return, "close_range() syscall not supported");
+		if (errno == EINVAL)
+			SKIP(return, "close_range() doesn't support CLOSE_RANGE_CLOEXEC");
+	}
+
+	/* Ensure the FD_CLOEXEC bit is set also with a resource limit in place.  */
+	ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &rlimit));
+	rlimit.rlim_cur = 25;
+	ASSERT_EQ(0, setrlimit(RLIMIT_NOFILE, &rlimit));
+
+	/* Set close-on-exec for two ranges: [0-50] and [75-100].  */
+	ret = sys_close_range(open_fds[0], open_fds[50],
+			      CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
+	ASSERT_EQ(0, ret);
+	ret = sys_close_range(open_fds[75], open_fds[100],
+			      CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
+	ASSERT_EQ(0, ret);
+
+	for (i = 0; i <= 50; i++) {
+		int flags = fcntl(open_fds[i], F_GETFD);
+
+		EXPECT_GT(flags, -1);
+		EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);
+	}
+
+	for (i = 51; i <= 74; i++) {
+		int flags = fcntl(open_fds[i], F_GETFD);
+
+		EXPECT_GT(flags, -1);
+		EXPECT_EQ(flags & FD_CLOEXEC, 0);
+	}
+
+	for (i = 75; i <= 100; i++) {
+		int flags = fcntl(open_fds[i], F_GETFD);
+
+		EXPECT_GT(flags, -1);
+		EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);
+	}
+
+	/* Test a common pattern.  */
+	ret = sys_close_range(3, UINT_MAX,
+			      CLOSE_RANGE_CLOEXEC | CLOSE_RANGE_UNSHARE);
+	for (i = 0; i <= 100; i++) {
+		int flags = fcntl(open_fds[i], F_GETFD);
+
+		EXPECT_GT(flags, -1);
+		EXPECT_EQ(flags & FD_CLOEXEC, FD_CLOEXEC);
+	}
+}
 
 TEST_HARNESS_MAIN
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH 4/4] selftests/core: add regression test for CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC
       [not found] <https://lore.kernel.org/linux-fsdevel/20201217213303.722643-1-christian.brauner@ubuntu.com>
                   ` (2 preceding siblings ...)
  2020-12-18 14:54 ` [PATCH 3/4] selftests/core: add test for CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC Christian Brauner
@ 2020-12-18 14:54 ` Christian Brauner
  3 siblings, 0 replies; 4+ messages in thread
From: Christian Brauner @ 2020-12-18 14:54 UTC (permalink / raw)
  To: linux-fsdevel; +Cc: Giuseppe Scrivano, Christian Brauner

This test is a minimalized version of the reproducer given by syzbot
(cf. [1]).

After introducing CLOSE_RANGE_CLOEXEC syzbot reported a crash when
CLOSE_RANGE_CLOEXEC is specified in conjunction with
CLOSE_RANGE_UNSHARE.  When CLOSE_RANGE_UNSHARE is specified the caller
will receive a private file descriptor table in case their file
descriptor table is currently shared. When the caller requests that all
file descriptors are supposed to be operated on via e.g. a call like
close_range(3, ~0U) and the caller shares their file descriptor table
then the kernel will only copy all files in the range from 0 to 3 and no
others.
The original bug used the maximum of the old file descriptor table not
the new one. In order to test this bug we need to first create a huge
large gap in the fd table. When we now call CLOSE_RANGE_UNSHARE with a
shared fd table and and with ~0U as upper bound the kernel will only
copy up to fd1 file descriptors into the new fd table. If max_fd in the
close_range() codepaths isn't correctly set when requesting
CLOSE_RANGE_CLOEXEC with all of these fds we will see NULL pointer
derefs!

This test passes on a fixed kernel.

Cc: Giuseppe Scrivano <gscrivan@redhat.com>
[1]: https://syzkaller.appspot.com/text?tag=KernelConfig&x=db720fe37a6a41d8
Link: syzbot+96cfd2b22b3213646a93@syzkaller.appspotmail.com
Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
---
 tools/testing/selftests/core/Makefile         |   2 +-
 .../testing/selftests/core/close_range_test.c | 231 +++++++++++++++++-
 2 files changed, 230 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/core/Makefile b/tools/testing/selftests/core/Makefile
index f6f2d6f473c6..5ceb3ba1ca9c 100644
--- a/tools/testing/selftests/core/Makefile
+++ b/tools/testing/selftests/core/Makefile
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
-CFLAGS += -g -I../../../../usr/include/
+CFLAGS += -g -I../../../../usr/include/ -pthread
 
 TEST_GEN_PROGS := close_range_test
 
diff --git a/tools/testing/selftests/core/close_range_test.c b/tools/testing/selftests/core/close_range_test.c
index 862444f1c244..65f071d8fd16 100644
--- a/tools/testing/selftests/core/close_range_test.c
+++ b/tools/testing/selftests/core/close_range_test.c
@@ -3,15 +3,22 @@
 #define _GNU_SOURCE
 #include <errno.h>
 #include <fcntl.h>
-#include <linux/kernel.h>
 #include <limits.h>
+#include <linux/futex.h>
+#include <pthread.h>
+#include <signal.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 #include <syscall.h>
 #include <unistd.h>
-#include <sys/resource.h>
 
 #include "../kselftest_harness.h"
 #include "../clone3/clone3_selftests.h"
@@ -384,4 +391,224 @@ TEST(close_range_cloexec_unshare)
 	}
 }
 
+static uint64_t current_time_ms(void)
+{
+	struct timespec ts;
+
+	if (clock_gettime(CLOCK_MONOTONIC, &ts))
+		exit(EXIT_FAILURE);
+
+	return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
+}
+
+static void thread_start(void *(*fn)(void *), void *arg)
+{
+	int i;
+	pthread_t th;
+	pthread_attr_t attr;
+
+	pthread_attr_init(&attr);
+	pthread_attr_setstacksize(&attr, 128 << 10);
+
+	for (i = 0; i < 100; i++) {
+		if (pthread_create(&th, &attr, fn, arg) == 0) {
+			pthread_attr_destroy(&attr);
+			return;
+		}
+
+		if (errno == EAGAIN) {
+			usleep(50);
+			continue;
+		}
+
+		break;
+	}
+
+	exit(EXIT_FAILURE);
+}
+
+static void event_init(int *state)
+{
+	*state = 0;
+}
+
+static void event_reset(int *state)
+{
+	*state = 0;
+}
+
+static void event_set(int *state)
+{
+	if (*state)
+		exit(EXIT_FAILURE);
+
+	__atomic_store_n(state, 1, __ATOMIC_RELEASE);
+	syscall(SYS_futex, state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
+}
+
+static void event_wait(int *state)
+{
+	while (!__atomic_load_n(state, __ATOMIC_ACQUIRE))
+		syscall(SYS_futex, state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
+}
+
+static int event_isset(int *state)
+{
+	return __atomic_load_n(state, __ATOMIC_ACQUIRE);
+}
+
+static int event_timedwait(int *state, uint64_t timeout)
+{
+	uint64_t start = current_time_ms();
+	uint64_t now = start;
+	for (;;) {
+		struct timespec ts;
+		uint64_t remain = timeout - (now - start);
+
+		ts.tv_sec = remain / 1000;
+		ts.tv_nsec = (remain % 1000) * 1000 * 1000;
+
+		syscall(SYS_futex, state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, &ts);
+
+		if (__atomic_load_n(state, __ATOMIC_ACQUIRE))
+			return 1;
+
+		now = current_time_ms();
+		if (now - start > timeout)
+			return 0;
+	}
+}
+
+struct thread_t {
+	int created;
+	int call;
+	int ready;
+	int done;
+};
+
+static struct thread_t threads[4];
+static int running;
+
+static void thread_close_range_call(int call)
+{
+	int fd = 0;
+
+	switch (call) {
+	case 0:
+		fd = openat(-1, "/dev/null", 0, 0);
+		if (fd < 0)
+			fd = 0;
+		break;
+	case 1:
+		sys_close_range(fd, -1, CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC);
+		break;
+	}
+}
+
+static void *thread_close_range(void *arg)
+{
+	struct thread_t *th = (struct thread_t *)arg;
+	for (;;) {
+		event_wait(&th->ready);
+		event_reset(&th->ready);
+		thread_close_range_call(th->call);
+		__atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED);
+		event_set(&th->done);
+	}
+	return 0;
+}
+
+static void threaded_close_range(void)
+{
+	int i, fd, call, thread;
+	for (call = 0; call < 2; call++) {
+		for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0])); thread++) {
+			struct thread_t *th = &threads[thread];
+			if (!th->created) {
+				th->created = 1;
+				event_init(&th->ready);
+				event_init(&th->done);
+				event_set(&th->done);
+				thread_start(thread_close_range, th);
+			}
+
+			if (!event_isset(&th->done))
+				continue;
+
+			event_reset(&th->done);
+			th->call = call;
+			__atomic_fetch_add(&running, 1, __ATOMIC_RELAXED);
+			event_set(&th->ready);
+			event_timedwait(&th->done, 45);
+			break;
+		}
+	}
+
+	for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++)
+		usleep(1000);
+
+	for (fd = 3; fd < 30; fd++)
+		close(fd);
+}
+
+/*
+ * Regression test for syzbot+96cfd2b22b3213646a93@syzkaller.appspotmail.com
+ */
+TEST(close_range_cloexec_unshare_threaded_syzbot)
+{
+	int iter;
+	int fd1, fd2, fd3;
+
+	/*
+	 * Create a huge gap in the fd table. When we now call
+	 * CLOSE_RANGE_UNSHARE with a shared fd table and and with ~0U as upper
+	 * bound the kernel will only copy up to fd1 file descriptors into the
+	 * new fd table. If max_fd in the close_range() codepaths isn't
+	 * correctly set when requesting CLOSE_RANGE_CLOEXEC with all of these
+	 * fds we will see NULL pointer derefs!
+	 */
+	fd1 = open("/dev/null", O_RDWR);
+	EXPECT_GT(fd1, 0);
+
+	fd3 = dup2(fd1, 1000);
+	EXPECT_GT(fd3, 0);
+
+	for (iter = 0; iter <= 1000; iter++) {
+		pid_t pid;
+		int status;
+		uint64_t start;
+
+		pid = fork();
+		if (pid < 0)
+			exit(EXIT_FAILURE);
+		if (pid == 0) {
+			EXPECT_EQ(prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0), 0);
+			setpgrp();
+
+			threaded_close_range();
+			exit(EXIT_SUCCESS);
+		}
+
+		status = 0;
+		start = current_time_ms();
+		for (;;) {
+			if (waitpid(-1, &status, WNOHANG | __WALL) == pid)
+				break;
+
+			usleep(1000);
+
+			if (current_time_ms() - start < 5 * 1000)
+				continue;
+
+			kill(pid, SIGKILL);
+
+			EXPECT_EQ(waitpid(pid, &status, 0), pid);
+
+			EXPECT_EQ(true, WIFEXITED(status));
+
+			EXPECT_EQ(0, WEXITSTATUS(status));
+		}
+	}
+}
+
 TEST_HARNESS_MAIN
-- 
2.29.2


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2020-12-18 14:55 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <https://lore.kernel.org/linux-fsdevel/20201217213303.722643-1-christian.brauner@ubuntu.com>
2020-12-18 14:54 ` [PATCH 1/4] selftests/core: fix close_range_test build after XFAIL removal Christian Brauner
2020-12-18 14:54 ` [PATCH 2/4] selftests/core: handle missing syscall number for close_range Christian Brauner
2020-12-18 14:54 ` [PATCH 3/4] selftests/core: add test for CLOSE_RANGE_UNSHARE | CLOSE_RANGE_CLOEXEC Christian Brauner
2020-12-18 14:54 ` [PATCH 4/4] selftests/core: add regression " Christian Brauner

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.