Commit b2433552 authored by Aristeu Rozanski's avatar Aristeu Rozanski Committed by Andrew Morton
Browse files

selftests/memfd: use IPC semaphore instead of SIGSTOP/SIGCONT

selftests/memfd: use IPC semaphore instead of SIGSTOP/SIGCONT

In order to synchronize new processes to test inheritance of memfd_noexec
sysctl, memfd_test sets up the sysctl with a value before creating the new
process.  The new process then sends itself a SIGSTOP in order to wait for
the parent to flip the sysctl value and send a SIGCONT signal.

This would work as intended if it wasn't the fact that the new process is
being created with CLONE_NEWPID, which creates a new PID namespace and the
new process has PID 1 in this namespace.  There're restrictions on sending
signals to PID 1 and, although it's relaxed for other than root PID
namespace, it's biting us here.  In this specific case the SIGSTOP sent by
the new process is ignored (no error to kill() is returned) and it never
stops its execution.  This is usually not noticiable as the parent usually
manages to set the new sysctl value before the child has a chance to run
and the test succeeds.  But if you run the test in a loop, it eventually
reproduces:

	while [ 1 ]; do ./memfd_test >log 2>&1 || break; done; cat log

So this patch replaces the SIGSTOP/SIGCONT synchronization with IPC
semaphore.

Link: https://lkml.kernel.org/r/a7776389-b3d6-4b18-b438-0b0e3ed1fd3b@work


Fixes: 6469b66e ("selftests: improve vm.memfd_noexec sysctl tests")
Signed-off-by: default avatarAristeu Rozanski <aris@redhat.com>
Cc: Aleksa Sarai <cyphar@cyphar.com>
Cc: Shuah Khan <shuah@kernel.org>
Cc: liuye <liuye@kylinos.cn>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent 9333980c
Loading
Loading
Loading
Loading
+105 −8
Original line number Diff line number Diff line
@@ -18,6 +18,9 @@
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <ctype.h>

@@ -39,6 +42,20 @@
		    F_SEAL_EXEC)

#define MFD_NOEXEC_SEAL	0x0008U
union semun {
	int val;
	struct semid_ds *buf;
	unsigned short int *array;
	struct seminfo *__buf;
};

/*
 * we use semaphores on nested wait tasks due the use of CLONE_NEWPID: the
 * child will be PID 1 and can't send SIGSTOP to themselves due special
 * treatment of the init task, so the SIGSTOP/SIGCONT synchronization
 * approach can't be used here.
 */
#define SEM_KEY 0xdeadbeef

/*
 * Default is not to test hugetlbfs
@@ -1333,8 +1350,22 @@ static int sysctl_nested(void *arg)

static int sysctl_nested_wait(void *arg)
{
	/* Wait for a SIGCONT. */
	kill(getpid(), SIGSTOP);
	int sem = semget(SEM_KEY, 1, 0600);
	struct sembuf sembuf;

	if (sem < 0) {
		perror("semget:");
		abort();
	}
	sembuf.sem_num = 0;
	sembuf.sem_flg = 0;
	sembuf.sem_op = 0;

	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		abort();
	}

	return sysctl_nested(arg);
}

@@ -1355,7 +1386,9 @@ static void test_sysctl_sysctl2_failset(void)

static int sysctl_nested_child(void *arg)
{
	int pid;
	int pid, sem;
	union semun semun;
	struct sembuf sembuf;

	printf("%s nested sysctl 0\n", memfd_str);
	sysctl_assert_write("0");
@@ -1389,23 +1422,53 @@ static int sysctl_nested_child(void *arg)
			   test_sysctl_sysctl2_failset);
	join_thread(pid);

	sem = semget(SEM_KEY, 1, IPC_CREAT | 0600);
	if (sem < 0) {
		perror("semget:");
		return 1;
	}
	semun.val = 1;
	sembuf.sem_op = -1;
	sembuf.sem_flg = 0;
	sembuf.sem_num = 0;

	/* Verify that the rules are actually inherited after fork. */
	printf("%s nested sysctl 0 -> 1 after fork\n", memfd_str);
	sysctl_assert_write("0");

	if (semctl(sem, 0, SETVAL, semun) < 0) {
		perror("semctl:");
		return 1;
	}

	pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait,
			   test_sysctl_sysctl1_failset);
	sysctl_assert_write("1");
	kill(pid, SIGCONT);

	/* Allow child to continue */
	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		return 1;
	}
	join_thread(pid);

	printf("%s nested sysctl 0 -> 2 after fork\n", memfd_str);
	sysctl_assert_write("0");

	if (semctl(sem, 0, SETVAL, semun) < 0) {
		perror("semctl:");
		return 1;
	}

	pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait,
			   test_sysctl_sysctl2_failset);
	sysctl_assert_write("2");
	kill(pid, SIGCONT);

	/* Allow child to continue */
	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		return 1;
	}
	join_thread(pid);

	/*
@@ -1415,28 +1478,62 @@ static int sysctl_nested_child(void *arg)
	 */
	printf("%s nested sysctl 2 -> 1 after fork\n", memfd_str);
	sysctl_assert_write("2");

	if (semctl(sem, 0, SETVAL, semun) < 0) {
		perror("semctl:");
		return 1;
	}

	pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait,
			   test_sysctl_sysctl2);
	sysctl_assert_write("1");
	kill(pid, SIGCONT);

	/* Allow child to continue */
	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		return 1;
	}
	join_thread(pid);

	printf("%s nested sysctl 2 -> 0 after fork\n", memfd_str);
	sysctl_assert_write("2");

	if (semctl(sem, 0, SETVAL, semun) < 0) {
		perror("semctl:");
		return 1;
	}

	pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait,
			   test_sysctl_sysctl2);
	sysctl_assert_write("0");
	kill(pid, SIGCONT);

	/* Allow child to continue */
	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		return 1;
	}
	join_thread(pid);

	printf("%s nested sysctl 1 -> 0 after fork\n", memfd_str);
	sysctl_assert_write("1");

	if (semctl(sem, 0, SETVAL, semun) < 0) {
		perror("semctl:");
		return 1;
	}

	pid = spawn_thread(CLONE_NEWPID, sysctl_nested_wait,
			   test_sysctl_sysctl1);
	sysctl_assert_write("0");
	kill(pid, SIGCONT);
	/* Allow child to continue */
	if (semop(sem, &sembuf, 1) < 0) {
		perror("semop:");
		return 1;
	}
	join_thread(pid);

	semctl(sem, 0, IPC_RMID);

	return 0;
}