[PATCH 2/3] samples: seccomp: simplify user-trap sample

From: Jann Horn
Date: Mon Nov 02 2020 - 15:40:40 EST


Now that the blocking unotify API returns when all users of the filter are
gone, get rid of the helper task that kills the blocked task.

Note that since seccomp only tracks whether tasks are completely gone, not
whether they have exited, we need to handle SIGCHLD while blocked on
SECCOMP_IOCTL_NOTIF_RECV. Alternatively we could also set SIGCHLD to
SIG_IGN and let the kernel autoreap exiting children.

Signed-off-by: Jann Horn <jannh@xxxxxxxxxx>
---
samples/seccomp/user-trap.c | 163 +++++++++++++++++++-----------------
1 file changed, 87 insertions(+), 76 deletions(-)

diff --git a/samples/seccomp/user-trap.c b/samples/seccomp/user-trap.c
index b9e666f15998..92d0f9ba58b6 100644
--- a/samples/seccomp/user-trap.c
+++ b/samples/seccomp/user-trap.c
@@ -15,6 +15,7 @@
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/ioctl.h>
+#include <sys/prctl.h>
#include <sys/ptrace.h>
#include <sys/mount.h>
#include <linux/limits.h>
@@ -198,6 +199,21 @@ static int handle_req(struct seccomp_notif *req,
return ret;
}

+static pid_t worker;
+static int worker_status;
+
+static void sigchld_handler(int sig, siginfo_t *info, void *ctx)
+{
+ if (info->si_pid != worker) {
+ fprintf(stderr, "SIGCHLD from unknown process\n");
+ return;
+ }
+ if (waitpid(worker, &worker_status, 0) != worker) {
+ perror("waitpid");
+ exit(1);
+ }
+}
+
static void set_blocking(int fd)
{
int file_status_flags = fcntl(fd, F_GETFL);
@@ -211,14 +227,26 @@ static void set_blocking(int fd)

int main(void)
{
- int sk_pair[2], ret = 1, status, listener;
- pid_t worker = 0 , tracer = 0;
+ int sk_pair[2], ret = 1, listener;
+ struct seccomp_notif *req;
+ struct seccomp_notif_resp *resp;
+ struct seccomp_notif_sizes sizes;
+ pid_t parent = getpid();

if (socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair) < 0) {
perror("socketpair");
return 1;
}

+ struct sigaction sigchld_act = {
+ .sa_sigaction = sigchld_handler,
+ .sa_flags = SA_SIGINFO
+ };
+ if (sigaction(SIGCHLD, &sigchld_act, NULL)) {
+ perror("sigaction");
+ return 1;
+ }
+
worker = fork();
if (worker < 0) {
perror("fork");
@@ -226,6 +254,14 @@ int main(void)
}

if (worker == 0) {
+ /* quit if the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL)) {
+ perror("PR_SET_PDEATHSIG");
+ exit(1);
+ }
+ if (getppid() != parent)
+ exit(1);
+
listener = user_trap_syscall(__NR_mount,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
if (listener < 0) {
@@ -283,87 +319,69 @@ int main(void)
*/
listener = recv_fd(sk_pair[0]);
if (listener < 0)
- goto out_kill;
+ goto close_pair;

set_blocking(listener);

- /*
- * Fork a task to handle the requests. This isn't strictly necessary,
- * but it makes the particular writing of this sample easier, since we
- * can just wait ofr the tracee to exit and kill the tracer.
- */
- tracer = fork();
- if (tracer < 0) {
- perror("fork");
- goto out_kill;
+ if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) < 0) {
+ perror("seccomp(GET_NOTIF_SIZES)");
+ goto out_close;
}

- if (tracer == 0) {
- struct seccomp_notif *req;
- struct seccomp_notif_resp *resp;
- struct seccomp_notif_sizes sizes;
+ req = malloc(sizes.seccomp_notif);
+ if (!req)
+ goto out_close;
+
+ resp = malloc(sizes.seccomp_notif_resp);
+ if (!resp)
+ goto out_req;
+ memset(resp, 0, sizes.seccomp_notif_resp);
+
+ while (1) {
+ memset(req, 0, sizes.seccomp_notif);
+ if (ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, req)) {
+ if (errno == ENOTCONN) {
+ /* The child process went away. */
+ break;
+ } else if (errno == EINTR) {
+ continue;
+ }

- if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes) < 0) {
- perror("seccomp(GET_NOTIF_SIZES)");
- goto out_close;
+ perror("ioctl recv");
+ goto out_resp;
}

- req = malloc(sizes.seccomp_notif);
- if (!req)
- goto out_close;
-
- resp = malloc(sizes.seccomp_notif_resp);
- if (!resp)
- goto out_req;
- memset(resp, 0, sizes.seccomp_notif_resp);
+ if (handle_req(req, resp, listener) < 0)
+ goto out_resp;

- while (1) {
- memset(req, 0, sizes.seccomp_notif);
- if (ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, req)) {
- if (errno == ENOTCONN)
- exit(0);
-
- perror("ioctl recv");
- goto out_resp;
- }
-
- if (handle_req(req, resp, listener) < 0)
- goto out_resp;
-
- /*
- * ENOENT here means that the task may have gotten a
- * signal and restarted the syscall. It's up to the
- * handler to decide what to do in this case, but for
- * the sample code, we just ignore it. Probably
- * something better should happen, like undoing the
- * mount, or keeping track of the args to make sure we
- * don't do it again.
- */
- if (ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0 &&
- errno != ENOENT) {
- perror("ioctl send");
- goto out_resp;
- }
+ /*
+ * ENOENT here means that the task may have gotten a
+ * signal and restarted the syscall. It's up to the
+ * handler to decide what to do in this case, but for
+ * the sample code, we just ignore it. Probably
+ * something better should happen, like undoing the
+ * mount, or keeping track of the args to make sure we
+ * don't do it again.
+ */
+ if (ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, resp) < 0 &&
+ errno != ENOENT) {
+ perror("ioctl send");
+ goto out_resp;
}
+ }
+ ret = 0;
+
out_resp:
- free(resp);
+ free(resp);
out_req:
- free(req);
+ free(req);
out_close:
- close(listener);
- exit(1);
- }
-
close(listener);

- if (waitpid(worker, &status, 0) != worker) {
- perror("waitpid");
- goto out_kill;
- }
-
if (umount2("/tmp/foo", MNT_DETACH) < 0 && errno != EINVAL) {
perror("umount2");
- goto out_kill;
+ ret = 1;
+ goto close_pair;
}

if (remove("/tmp/foo") < 0 && errno != ENOENT) {
@@ -371,19 +389,12 @@ int main(void)
exit(1);
}

- if (!WIFEXITED(status) || WEXITSTATUS(status)) {
+ if (!WIFEXITED(worker_status) || WEXITSTATUS(worker_status)) {
fprintf(stderr, "worker exited nonzero\n");
- goto out_kill;
+ ret = 1;
+ goto close_pair;
}

- ret = 0;
-
-out_kill:
- if (tracer > 0)
- kill(tracer, SIGKILL);
- if (worker > 0)
- kill(worker, SIGKILL);
-
close_pair:
close(sk_pair[0]);
close(sk_pair[1]);
--
2.29.1.341.ge80a0c044ae-goog