Skip to content

Commit a240a79

Browse files
committed
Merge tag 'seccomp-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux
Pull seccomp update from Kees Cook: - Fix race with WAIT_KILLABLE_RECV (Johannes Nixdorf) * tag 'seccomp-v6.18-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux: selftests/seccomp: Add a test for the WAIT_KILLABLE_RECV fast reply race seccomp: Fix a race with WAIT_KILLABLE_RECV if the tracer replies too fast
2 parents 50157ea + b0c9bfb commit a240a79

2 files changed

Lines changed: 136 additions & 7 deletions

File tree

kernel/seccomp.c

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ static void seccomp_handle_addfd(struct seccomp_kaddfd *addfd, struct seccomp_kn
11391139
static bool should_sleep_killable(struct seccomp_filter *match,
11401140
struct seccomp_knotif *n)
11411141
{
1142-
return match->wait_killable_recv && n->state == SECCOMP_NOTIFY_SENT;
1142+
return match->wait_killable_recv && n->state >= SECCOMP_NOTIFY_SENT;
11431143
}
11441144

11451145
static int seccomp_do_user_notification(int this_syscall,
@@ -1186,13 +1186,11 @@ static int seccomp_do_user_notification(int this_syscall,
11861186

11871187
if (err != 0) {
11881188
/*
1189-
* Check to see if the notifcation got picked up and
1190-
* whether we should switch to wait killable.
1189+
* Check to see whether we should switch to wait
1190+
* killable. Only return the interrupted error if not.
11911191
*/
1192-
if (!wait_killable && should_sleep_killable(match, &n))
1193-
continue;
1194-
1195-
goto interrupted;
1192+
if (!(!wait_killable && should_sleep_killable(match, &n)))
1193+
goto interrupted;
11961194
}
11971195

11981196
addfd = list_first_entry_or_null(&n.addfd,

tools/testing/selftests/seccomp/seccomp_bpf.c

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <linux/filter.h>
2525
#include <sys/prctl.h>
2626
#include <sys/ptrace.h>
27+
#include <sys/time.h>
2728
#include <sys/user.h>
2829
#include <linux/prctl.h>
2930
#include <linux/ptrace.h>
@@ -3547,6 +3548,10 @@ static void signal_handler(int signal)
35473548
perror("write from signal");
35483549
}
35493550

3551+
static void signal_handler_nop(int signal)
3552+
{
3553+
}
3554+
35503555
TEST(user_notification_signal)
35513556
{
35523557
pid_t pid;
@@ -4819,6 +4824,132 @@ TEST(user_notification_wait_killable_fatal)
48194824
EXPECT_EQ(SIGTERM, WTERMSIG(status));
48204825
}
48214826

4827+
/* Ensure signals after the reply do not interrupt */
4828+
TEST(user_notification_wait_killable_after_reply)
4829+
{
4830+
int i, max_iter = 100000;
4831+
int listener, status;
4832+
int pipe_fds[2];
4833+
pid_t pid;
4834+
long ret;
4835+
4836+
ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
4837+
ASSERT_EQ(0, ret)
4838+
{
4839+
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
4840+
}
4841+
4842+
listener = user_notif_syscall(
4843+
__NR_dup, SECCOMP_FILTER_FLAG_NEW_LISTENER |
4844+
SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV);
4845+
ASSERT_GE(listener, 0);
4846+
4847+
/*
4848+
* Used to count invocations. One token is transferred from the child
4849+
* to the parent per syscall invocation, the parent tries to take
4850+
* one token per successful RECV. If the syscall is restarted after
4851+
* RECV the parent will try to get two tokens while the child only
4852+
* provided one.
4853+
*/
4854+
ASSERT_EQ(pipe(pipe_fds), 0);
4855+
4856+
pid = fork();
4857+
ASSERT_GE(pid, 0);
4858+
4859+
if (pid == 0) {
4860+
struct sigaction new_action = {
4861+
.sa_handler = signal_handler_nop,
4862+
.sa_flags = SA_RESTART,
4863+
};
4864+
struct itimerval timer = {
4865+
.it_value = { .tv_usec = 1000 },
4866+
.it_interval = { .tv_usec = 1000 },
4867+
};
4868+
char c = 'a';
4869+
4870+
close(pipe_fds[0]);
4871+
4872+
/* Setup the sigaction with SA_RESTART */
4873+
if (sigaction(SIGALRM, &new_action, NULL)) {
4874+
perror("sigaction");
4875+
exit(1);
4876+
}
4877+
4878+
/*
4879+
* Kill with SIGALRM repeatedly, to try to hit the race when
4880+
* handling the syscall.
4881+
*/
4882+
if (setitimer(ITIMER_REAL, &timer, NULL) < 0)
4883+
perror("setitimer");
4884+
4885+
for (i = 0; i < max_iter; ++i) {
4886+
int fd;
4887+
4888+
/* Send one token per iteration to catch repeats. */
4889+
if (write(pipe_fds[1], &c, sizeof(c)) != 1) {
4890+
perror("write");
4891+
exit(1);
4892+
}
4893+
4894+
fd = syscall(__NR_dup, 0);
4895+
if (fd < 0) {
4896+
perror("dup");
4897+
exit(1);
4898+
}
4899+
close(fd);
4900+
}
4901+
4902+
exit(0);
4903+
}
4904+
4905+
close(pipe_fds[1]);
4906+
4907+
for (i = 0; i < max_iter; ++i) {
4908+
struct seccomp_notif req = {};
4909+
struct seccomp_notif_addfd addfd = {};
4910+
struct pollfd pfd = {
4911+
.fd = pipe_fds[0],
4912+
.events = POLLIN,
4913+
};
4914+
char c;
4915+
4916+
/*
4917+
* Try to receive one token. If it failed, one child syscall
4918+
* was restarted after RECV and needed to be handled twice.
4919+
*/
4920+
ASSERT_EQ(poll(&pfd, 1, 1000), 1)
4921+
kill(pid, SIGKILL);
4922+
4923+
ASSERT_EQ(read(pipe_fds[0], &c, sizeof(c)), 1)
4924+
kill(pid, SIGKILL);
4925+
4926+
/*
4927+
* Get the notification, reply to it as fast as possible to test
4928+
* whether the child wrongly skips going into the non-preemptible
4929+
* (TASK_KILLABLE) state.
4930+
*/
4931+
do
4932+
ret = ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req);
4933+
while (ret < 0 && errno == ENOENT); /* Accept interruptions before RECV */
4934+
ASSERT_EQ(ret, 0)
4935+
kill(pid, SIGKILL);
4936+
4937+
addfd.id = req.id;
4938+
addfd.flags = SECCOMP_ADDFD_FLAG_SEND;
4939+
addfd.srcfd = 0;
4940+
ASSERT_GE(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), 0)
4941+
kill(pid, SIGKILL);
4942+
}
4943+
4944+
/*
4945+
* Wait for the process to exit, and make sure the process terminated
4946+
* with a zero exit code..
4947+
*/
4948+
EXPECT_EQ(waitpid(pid, &status, 0), pid);
4949+
EXPECT_EQ(true, WIFEXITED(status));
4950+
EXPECT_EQ(0, WEXITSTATUS(status));
4951+
}
4952+
48224953
struct tsync_vs_thread_leader_args {
48234954
pthread_t leader;
48244955
};

0 commit comments

Comments
 (0)