seccomp_unotify(2) System Calls Manual seccomp_unotify(2) seccomp_unotify - seccomp (libc -lc) #include #include #include int seccomp(unsigned int operation, unsigned int flags, void *args); #include int ioctl(int fd, SECCOMP_IOCTL_NOTIF_RECV, struct seccomp_notif *req); int ioctl(int fd, SECCOMP_IOCTL_NOTIF_SEND, struct seccomp_notif_resp *resp); int ioctl(int fd, SECCOMP_IOCTL_NOTIF_ID_VALID, __u64 *id); int ioctl(int fd, SECCOMP_IOCTL_NOTIF_ADDFD, struct seccomp_notif_addfd *addfd); (seccomp). SECCOMP_FILTER_FLAG_NEW_LISTENER SECCOMP_RET_USER_NOTIF SECCOMP_GET_NOTIF_SIZES seccomp(2) ioctl(2) ( ). seccomp . seccomp . (NOTES). ( ) seccomp (target) (supervisor). . seccomp . ( seccomp .) : (1) seccomp : o flags seccomp(2) SECCOMP_FILTER_FLAG_NEW_LISTENER. seccomp(2) () "" . seccomp "" . o seccomp SECCOMP_RET_USER_NOTIF. . (2) ( ) . ( SCM_RIGHTS unix(7)). pidfd_getfd(2). (3) . seccomp_notif. SECCOMP_GET_NOTIF_SIZES seccomp(2) seccomp_notif_sizes. seccomp_notif_sizes.seccomp_notif . seccomp_notif_sizes.seccomp_notif_resp ( struct seccomp_notif_resp) ( ). (4) seccomp. SECCOMP_RET_USER_NOTIF () ( ) . (5) SECCOMP_RET_USER_NOTIF. SECCOMP_IOCTL_NOTIF_RECV ioctl(2) . seccomp_notif . ( (NOTES) select(2) poll(2) epoll(7).) (6) seccomp_notif SECCOMP_IOCTL_NOTIF_RECV ( seccomp_data) seccomp. . "" (cookie) SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOCTL_NOTIF_SEND . . ( seccomp.) /proc/tid/mem ( proc(5)) . ( SECCOMP_IOCTL_NOTIF_ID_VALID ioctl(2) .) seccomp. (7) ( seccomp SECCOMP_RET_USER_NOTIF). . . . (8) . errno . SECCOMP_IOCTL_NOTIF_SEND ioctl(2) seccomp_notif_resp . seccomp_notif SECCOMP_IOCTL_NOTIF_RECV. . seccomp_notif SECCOMP_IOCTL_NOTIF_RECV . (9) . SECCOMP_USER_NOTIF_FLAG_CONTINUE . IOCTL ioctl(2) seccomp. ( ) ioctl(2) seccomp(2) SECCOMP_FILTER_FLAG_NEW_LISTENER. SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOCTL_NOTIF_RECV ( 5.0) . . ioctl(2) . . struct seccomp_notif { __u64 id; /* */ __u32 pid; /* TID */ __u32 flags; /* (0) */ struct seccomp_data data; /* seccomp(2) */ }; : id . seccomp . o SECCOMP_IOCTL_NOTIF_ID_VALID ioctl(2) . o seccomp_notif_resp SECCOMP_IOCTL_NOTIF_SEND. pid . flags . . data seccomp_data . seccomp. seccomp(2) . 0 -1 errno . : EINVAL ( 5.5) seccomp_notif . ENOENT () . SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOCTL_NOTIF_ID_VALID ( 5.0) SECCOMP_IOCTL_NOTIF_RECV ( ). ioctl(2) (id) SECCOMP_IOCTL_NOTIF_RECV. pid SECCOMP_IOCTL_NOTIF_RECV . (1) . seccomp_notif (TID) ( pid ). (2) . (3) TID . (4) open(2) /proc/tid/mem TID 1 ( ) 1. . open(2) SECCOMP_IOCTL_NOTIF_ID_VALID . ( read(2) 0 ). SECCOMP_IOCTL_NOTIF_ID_VALID. ( ) 0. ( ) -1 errno ENOENT. SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOCTL_NOTIF_SEND ( 5.0) . ioctl(2) : struct seccomp_notif_resp { __u64 id; /* */ __s64 val; /* */ __s32 error; /* 0 () */ __u32 flags; /* */ }; : id SECCOMP_IOCTL_NOTIF_RECV. . val . error (errno) . flags : SECCOMP_USER_NOTIF_FLAG_CONTINUE ( 5.5) . : o . flags SECCOMP_USER_NOTIF_FLAG_CONTINUE error val . seccomp ( ) . SECCOMP_USER_NOTIF_FLAG_CONTINUE . o . seccomp_notif_resp. : o flags SECCOMP_USER_NOTIF_FLAG_CONTINUE. o error 0 "" "" . val. -1 errno error . o val "" . error . 0 -1 errno . : EINPROGRESS . EINVAL flags. EINVAL flags SECCOMP_USER_NOTIF_FLAG_CONTINUE error val . ENOENT . SECCOMP_IOCTL_NOTIF_ADDFD SECCOMP_IOCTL_NOTIF_ADDFD ( 5.9) . SCM_RIGHTS unix(7) . SECCOMP_IOCTL_NOTIF_ADDFD ( socket(2) openat(2)) . ( ) . ( open(2)). . (LSM) SCM_RIGHTS . (cgroup) 1 (classid netprioidx) . ioctl(2) : struct seccomp_notif_addfd { __u64 id; /* */ __u32 flags; /* */ __u32 srcfd; /* */ __u32 newfd; /* 0 */ __u32 newfd_flags; /* */ }; : id ( ) SECCOMP_IOCTL_NOTIF_RECV. flags . : SECCOMP_ADDFD_FLAG_SETFD newfd. SECCOMP_ADDFD_FLAG_SEND ( 5.14) SECCOMP_IOCTL_NOTIF_ADDFD SECCOMP_IOCTL_NOTIF_SEND . errno 0 . . srcfd . newfd . SECCOMP_ADDFD_FLAG_SETFD . . LSM srcfd newfd . SECCOMP_ADDFD_FLAG_SETFD 0 . newfd_flags . : O_CLOEXEC . ioctl(2) . ( socket(2)) (resp.val) SECCOMP_IOCTL_NOTIF_SEND. -1 errno . : EBADF RLIMIT_NOFILE ( getrlimit(2)). EBUSY SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOCTL_NOTIF_ADDFD . EINPROGRESS id ( SECCOMP_IOCTL_NOTIF_RECV) ( SECCOMP_IOCTL_NOTIF_SEND). EINVAL flags newfd_flags newfd SECCOMP_ADDFD_FLAG_SETFD flags. EMFILE newfd /proc/sys/fs/nr_open. ENOENT . ( ) SECCOMP_ADDFD_FLAG_SETFD ( openat(2)): int fd, removeFd; fd = openat(req->data.args[0], path, req->data.args[2], req->data.args[3]); struct seccomp_notif_addfd addfd; addfd.id = req->id; /* SECCOMP_IOCTL_NOTIF_RECV */ addfd.srcfd = fd; addfd.newfd = 0; addfd.flags = 0; addfd.newfd_flags = O_CLOEXEC; targetFd = ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd); close(fd); /* */ struct seccomp_notif_resp *resp; /* 'resp' */ resp->id = req->id; resp->error = 0; /* "" */ resp->val = targetFd; resp->flags = 0; ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp); ( ) . SECCOMP_USER_NOTIF_FLAG_CONTINUE ioctl(2). mount(2) ("") ( "" ) . ( tmpfs(5)) mount(2) . select()/poll()/epoll seccomp(2) SECCOMP_FILTER_FLAG_NEW_LISTENER poll(2) epoll(7) select(2). : o . SECCOMP_IOCTL_NOTIF_RECV ioctl(2) EINTR . o ( SECCOMP_IOCTL_NOTIF_RECV ioctl(2)) SECCOMP_IOCTL_NOTIF_SEND ioctl(2). o waitpid(2) ( ) ( select(2) POLLHUP/EPOLLHUP poll(2)/ epoll_wait(2)). SECCOMP_USER_NOTIF_FLAG_CONTINUE . ( ). : . SECCOMP_USER_NOTIF_FLAG_CONTINUE . . " " "" . seccomp(2) prctl(2) SECCOMP_RET_USER_NOTIF ( seccomp(2)). seccomp ! . . /proc/tid/mem ioctl(2) SECCOMP_IOCTL_NOTIF_ID_VALID /proc/tid/mem ( ). ioctl(2) . mount(2) : (1) (func()) mount(2) . (2) /proc/tid/mem SECCOMP_IOCTL_NOTIF_ID_VALID (). (3) mount(2). (4) . (5) func()) ( func()). (6) . (7) mount(2) . : . SECCOMP_IOCTL_NOTIF_ID_VALID. . "". . ( accept(2)) . . . . accept(2) accept(2) ( ) bind(2). . SECCOMP_IOCTL_NOTIF_ID_VALID . accept(2) poll(2) ( accept(2) ) ( ). ( ) . SA_RESTART : (1) sigaction(2) SA_RESTART. (2) seccomp . (3) . (4) () ioctl(2) SECCOMP_IOCTL_NOTIF_SEND ENOENT. . . . signal(7) SA_RESTART. SECCOMP_IOCTL_NOTIF_ADDFD SECCOMP_ADDFD_FLAG_SEND . SECCOMP_IOCTL_NOTIF_RECV ioctl(2) ioctl(2) ( ). ( ) . "". seccomp SECCOMP_RET_USER_NOTIF mkdir(2). mkdir(2) . . mkdir(2). ( /proc/pid/mem) mkdir(2) : o "/tmp/" mkdir(2) . . o "./" ( ) SECCOMP_USER_NOTIF_FLAG_CONTINUE mkdir(2) . o mkdir(2) EOPNOTSUPP (" "). "/bye" . seccomp. ( "T:") ( "S:"). /tmp/x. mkdir(2) . $ ./seccomp_unotify /tmp/x; T: PID = 23168 T: mkdir("/tmp/x") S: (ID 0x17445c4a0f4e0e3c) PID 23168 S: : mkdir("/tmp/x", 0700) S: ! = 6 S: (flags = 0; val = 6; error = 0) T: : mkdir(2) 6 T: S: 6 ( /tmp/x) mkdir(2) 0 . ./sub. "./" SECCOMP_USER_NOTIF_FLAG_CONTINUE () mkdir(2) . $ ./seccomp_unotify ./sub; T: PID = 23204 T: mkdir("./sub") S: (ID 0xddb16abe25b4c12) PID 23204 S: S: (flags = 0x1; val = 0; error = 0) T: : mkdir(2) 0 T: S: "." "/tmp/" (EOPNOTSUPP " ") mkdir(2) ( ): $ ./seccomp_unotify /xxx; T: PID = 23178 T: mkdir("/xxx") S: (ID 0xe7dc095d1c524e80) PID 23178 S: ( ) S: (flags = 0; val = 0; error = -95) T: : mkdir(2): T: S: /tmp/nosuchdir/b. mkdir(2) /tmp/nosuchdir . mkdir(2) . $ ./seccomp_unotify /tmp/nosuchdir/b; T: PID = 23199 T: mkdir("/tmp/nosuchdir/b") S: (ID 0x8744454293506046) PID 23199 S: : mkdir("/tmp/nosuchdir/b", 0700) S: ! (errno = 2; ) S: (flags = 0; val = 0; error = -2) T: : mkdir(2): T: S: mkdir(2) "/bye" ( EOPNOTSUPP). mkdir(2) seccomp SECCOMP_RET_USER_NOTIF ENOSYS (" "). : $ ./seccomp_unotify /bye /tmp/y; T: PID = 23185 T: mkdir("/bye") S: (ID 0xa81236b1d2f7b0f4) PID 23185 S: ( ) S: (flags = 0; val = 0; error = -95) S: ********** T: : mkdir(2): T: mkdir("/tmp/y") T: : mkdir(2): T: #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define streq(...) (strcmp(__VA_ARGS__) == 0) /* Send the file descriptor 'fd' over the connected UNIX domain socket 'sockfd'. Returns 0 on success, or -1 on error. */ static int sendfd(int sockfd, int fd) { int data; struct iovec iov; struct msghdr msgh; struct cmsghdr *cmsgp; /* Allocate a char array of suitable size to hold the ancillary data. However, since this buffer is in reality a 'struct cmsghdr', use a union to ensure that it is suitably aligned. */ union { char buf[CMSG_SPACE(sizeof(int))]; /* Space large enough to hold an 'int' */ struct cmsghdr align; } controlMsg; /* The 'msg_name' field can be used to specify the address of the destination socket when sending a datagram. However, we do not need to use this field because 'sockfd' is a connected socket. */ msgh.msg_name = NULL; msgh.msg_namelen = 0; /* On Linux, we must transmit at least one byte of real data in order to send ancillary data. We transmit an arbitrary integer whose value is ignored by recvfd(). */ msgh.msg_iov = &iov; msgh.msg_iovlen = 1; iov.iov_base = &data; iov.iov_len = sizeof(int); data = 12345; /* Set 'msghdr' fields that describe ancillary data */ msgh.msg_control = controlMsg.buf; msgh.msg_controllen = sizeof(controlMsg.buf); /* Set up ancillary data describing file descriptor to send */ cmsgp = CMSG_FIRSTHDR(&msgh); cmsgp->cmsg_level = SOL_SOCKET; cmsgp->cmsg_type = SCM_RIGHTS; cmsgp->cmsg_len = CMSG_LEN(sizeof(int)); memcpy(CMSG_DATA(cmsgp), &fd, sizeof(int)); /* Send real plus ancillary data */ if (sendmsg(sockfd, &msgh, 0) == -1) return -1; return 0; } /* Receive a file descriptor on a connected UNIX domain socket. Returns the received file descriptor on success, or -1 on error. */ static int recvfd(int sockfd) { int data, fd; ssize_t nr; struct iovec iov; struct msghdr msgh; /* Allocate a char buffer for the ancillary data. See the comments in sendfd() */ union { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } controlMsg; struct cmsghdr *cmsgp; /* The 'msg_name' field can be used to obtain the address of the sending socket. However, we do not need this information. */ msgh.msg_name = NULL; msgh.msg_namelen = 0; /* Specify buffer for receiving real data */ msgh.msg_iov = &iov; msgh.msg_iovlen = 1; iov.iov_base = &data; /* Real data is an 'int' */ iov.iov_len = sizeof(int); /* Set 'msghdr' fields that describe ancillary data */ msgh.msg_control = controlMsg.buf; msgh.msg_controllen = sizeof(controlMsg.buf); /* Receive real plus ancillary data; real data is ignored */ nr = recvmsg(sockfd, &msgh, 0); if (nr == -1) return -1; cmsgp = CMSG_FIRSTHDR(&msgh); /* Check the validity of the 'cmsghdr' */ if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int)) || cmsgp->cmsg_level != SOL_SOCKET || cmsgp->cmsg_type != SCM_RIGHTS) { errno = EINVAL; return -1; } /* Return the received file descriptor to our caller */ memcpy(&fd, CMSG_DATA(cmsgp), sizeof(int)); return fd; } static void sigchldHandler(int sig) { char msg[] = "\tS: target has terminated; bye\n"; write(STDOUT_FILENO, msg, sizeof(msg) - 1); _exit(EXIT_SUCCESS); } static int seccomp(unsigned int operation, unsigned int flags, void *args) { return syscall(SYS_seccomp, operation, flags, args); } /* The following is the x86-64-specific BPF boilerplate code for checking that the BPF program is running on the right architecture + ABI. At completion of these instructions, the accumulator contains the system call number. */ /* For the x32 ABI, all system call numbers have bit 30 set */ #define X32_SYSCALL_BIT 0x40000000 #define X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR \ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \ (offsetof(struct seccomp_data, arch))), \ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 2), \ BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \ (offsetof(struct seccomp_data, nr))), \ BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, X32_SYSCALL_BIT, 0, 1), \ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS) /* installNotifyFilter() installs a seccomp filter that generates user-space notifications (SECCOMP_RET_USER_NOTIF) when the process calls mkdir(2); the filter allows all other system calls. The function return value is a file descriptor from which the user-space notifications can be fetched. */ static int installNotifyFilter(void) { int notifyFd; struct sock_filter filter[] = { X86_64_CHECK_ARCH_AND_LOAD_SYSCALL_NR, /* mkdir() triggers notification to user-space supervisor */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_mkdir, 0, 1), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_USER_NOTIF), /* Every other system call is allowed */ BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), }; struct sock_fprog prog = { .len = countof(filter), .filter = filter, }; /* Install the filter with the SECCOMP_FILTER_FLAG_NEW_LISTENER flag; as a result, seccomp() returns a notification file descriptor. */ notifyFd = seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); if (notifyFd == -1) err(EXIT_FAILURE, "seccomp-install-notify-filter"); return notifyFd; } /* Close a pair of sockets created by socketpair() */ static void closeSocketPair(int sockPair[2]) { if (close(sockPair[0]) == -1) err(EXIT_FAILURE, "closeSocketPair-close-0"); if (close(sockPair[1]) == -1) err(EXIT_FAILURE, "closeSocketPair-close-1"); } /* Implementation of the target process. Create a child process that: (1) installs a seccomp filter with the SECCOMP_FILTER_FLAG_NEW_LISTENER flag; (2) writes the seccomp notification file descriptor returned from the previous step onto the UNIX domain socket, 'sockPair[0]'; (3) calls mkdir(2) for each element of 'argv'. The function return value in the parent is the PID of the child process; the child does not return from this function. */ static pid_t targetProcess(int sockPair[2], char *argv[]) { int notifyFd, s; pid_t targetPid; targetPid = fork(); if (targetPid == -1) err(EXIT_FAILURE, "fork"); if (targetPid > 0) /* In parent, return PID of child */ return targetPid; /* Child falls through to here */ printf("T: PID = %ld\n", (long) getpid()); /* Install seccomp filter(s) */ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) err(EXIT_FAILURE, "prctl"); notifyFd = installNotifyFilter(); /* Pass the notification file descriptor to the tracing process over a UNIX domain socket */ if (sendfd(sockPair[0], notifyFd) == -1) err(EXIT_FAILURE, "sendfd"); /* Notification and socket FDs are no longer needed in target */ if (close(notifyFd) == -1) err(EXIT_FAILURE, "close-target-notify-fd"); closeSocketPair(sockPair); /* Perform a mkdir() call for each of the command-line arguments */ for (char **ap = argv; *ap != NULL; ap++) { printf("\nT: about to mkdir(\"%s\")\n", *ap); s = mkdir(*ap, 0700); if (s == -1) perror("T: ERROR: mkdir(2)"); else printf("T: SUCCESS: mkdir(2) returned %d\n", s); } printf("\nT: terminating\n"); exit(EXIT_SUCCESS); } /* Check that the notification ID provided by a SECCOMP_IOCTL_NOTIF_RECV operation is still valid. It will no longer be valid if the target process has terminated or is no longer blocked in the system call that generated the notification (because it was interrupted by a signal). This operation can be used when doing such things as accessing /proc/PID files in the target process in order to avoid TOCTOU race conditions where the PID that is returned by SECCOMP_IOCTL_NOTIF_RECV terminates and is reused by another process. */ static bool cookieIsValid(int notifyFd, uint64_t id) { return ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_ID_VALID, &id) == 0; } /* Access the memory of the target process in order to fetch the pathname referred to by the system call argument 'argNum' in 'req->data.args[]'. The pathname is returned in 'path', a buffer of 'size' bytes allocated by the caller. Returns true if the pathname is successfully fetched, and false otherwise. For possible causes of failure, see the comments below. */ static bool getTargetPathname(struct seccomp_notif *req, int notifyFd, int argNum, char *path, size_t size) { int procMemFd; char procMemPath[PATH_MAX]; ssize_t nread; snprintf(procMemPath, sizeof(procMemPath), "/proc/%d/mem", req->pid); procMemFd = open(procMemPath, O_RDONLY | O_CLOEXEC); if (procMemFd == -1) return false; /* Check that the process whose info we are accessing is still alive and blocked in the system call that caused the notification. If the SECCOMP_IOCTL_NOTIF_ID_VALID operation (performed in cookieIsValid()) succeeded, we know that the /proc/PID/mem file descriptor that we opened corresponded to the process for which we received a notification. If that process subsequently terminates, then read() on that file descriptor will return 0 (EOF). */ if (!cookieIsValid(notifyFd, req->id)) { close(procMemFd); return false; } /* Read bytes at the location containing the pathname argument */ nread = pread(procMemFd, path, size, req->data.args[argNum]); close(procMemFd); if (nread <= 0) return false; /* Once again check that the notification ID is still valid. The case we are particularly concerned about here is that just before we fetched the pathname, the target's blocked system call was interrupted by a signal handler, and after the handler returned, the target carried on execution (past the interrupted system call). In that case, we have no guarantees about what we are reading, since the target's memory may have been arbitrarily changed by subsequent operations. */ if (!cookieIsValid(notifyFd, req->id)) { perror("\tS: notification ID check failed!!!"); return false; } /* Even if the target's system call was not interrupted by a signal, we have no guarantees about what was in the memory of the target process. (The memory may have been modified by another thread, or even by an external attacking process.) We therefore treat the buffer returned by pread() as untrusted input. The buffer should contain a terminating null byte; if not, then we will trigger an error for the target process. */ if (strnlen(path, nread) < nread) return true; return false; } /* Allocate buffers for the seccomp user-space notification request and response structures. It is the caller's responsibility to free the buffers returned via 'req' and 'resp'. */ static void allocSeccompNotifBuffers(struct seccomp_notif **req, struct seccomp_notif_resp **resp, struct seccomp_notif_sizes *sizes) { size_t resp_size; /* Discover the sizes of the structures that are used to receive notifications and send notification responses, and allocate buffers of those sizes. */ if (seccomp(SECCOMP_GET_NOTIF_SIZES, 0, sizes) == -1) err(EXIT_FAILURE, "seccomp-SECCOMP_GET_NOTIF_SIZES"); *req = malloc(sizes->seccomp_notif); if (*req == NULL) err(EXIT_FAILURE, "malloc-seccomp_notif"); /* When allocating the response buffer, we must allow for the fact that the user-space binary may have been built with user-space headers where 'struct seccomp_notif_resp' is bigger than the response buffer expected by the (older) kernel. Therefore, we allocate a buffer that is the maximum of the two sizes. This ensures that if the supervisor places bytes into the response structure that are past the response size that the kernel expects, then the supervisor is not touching an invalid memory location. */ resp_size = sizes->seccomp_notif_resp; if (sizeof(struct seccomp_notif_resp) > resp_size) resp_size = sizeof(struct seccomp_notif_resp); *resp = malloc(resp_size); if (*resp == NULL) err(EXIT_FAILURE, "malloc-seccomp_notif_resp"); } /* Handle notifications that arrive via the SECCOMP_RET_USER_NOTIF file descriptor, 'notifyFd'. */ static void handleNotifications(int notifyFd) { bool pathOK; char path[PATH_MAX]; struct seccomp_notif *req; struct seccomp_notif_resp *resp; struct seccomp_notif_sizes sizes; allocSeccompNotifBuffers(&req, &resp, &sizes); /* Loop handling notifications */ for (;;) { /* Wait for next notification, returning info in '*req' */ memset(req, 0, sizes.seccomp_notif); if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req) == -1) { if (errno == EINTR) continue; err(EXIT_FAILURE, "\tS: ioctl-SECCOMP_IOCTL_NOTIF_RECV"); } printf("\tS: got notification (ID %#llx) for PID %d\n", req->id, req->pid); /* The only system call that can generate a notification event is mkdir(2). Nevertheless, we check that the notified system call is indeed mkdir() as kind of future-proofing of this code in case the seccomp filter is later modified to generate notifications for other system calls. */ if (req->data.nr != SYS_mkdir) { printf("\tS: notification contained unexpected " "system call number; bye!!!\n"); exit(EXIT_FAILURE); } pathOK = getTargetPathname(req, notifyFd, 0, path, sizeof(path)); /* Prepopulate some fields of the response */ resp->id = req->id; /* Response includes notification ID */ resp->flags = 0; resp->val = 0; /* If getTargetPathname() failed, trigger an EINVAL error response (sending this response may yield an error if the failure occurred because the notification ID was no longer valid); if the directory is in /tmp, then create it on behalf of the supervisor; if the pathname starts with '.', tell the kernel to let the target process execute the mkdir(); otherwise, give an error for a directory pathname in any other location. */ if (!pathOK) { resp->error = -EINVAL; printf("\tS: spoofing error for invalid pathname (%s)\n", strerror(-resp->error)); } else if (strncmp(path, "/tmp/", strlen("/tmp/")) == 0) { printf("\tS: executing: mkdir(\"%s\", %#llo)\n", path, req->data.args[1]); if (mkdir(path, req->data.args[1]) == 0) { resp->error = 0; /* "Success" */ resp->val = strlen(path); /* Used as return value of mkdir() in target */ printf("\tS: success! spoofed return = %lld\n", resp->val); } else { /* If mkdir() failed in the supervisor, pass the error back to the target */ resp->error = -errno; printf("\tS: failure! (errno = %d; %s)\n", errno, strerror(errno)); } } else if (strncmp(path, "./", strlen("./")) == 0) { resp->error = resp->val = 0; resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; printf("\tS: target can execute system call\n"); } else { resp->error = -EOPNOTSUPP; printf("\tS: spoofing error response (%s)\n", strerror(-resp->error)); } /* Send a response to the notification */ printf("\tS: sending response " "(flags = %#x; val = %lld; error = %d)\n", resp->flags, resp->val, resp->error); if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp) == -1) { if (errno == ENOENT) printf("\tS: response failed with ENOENT; " "perhaps target process's syscall was " "interrupted by a signal?\n"); else perror("ioctl-SECCOMP_IOCTL_NOTIF_SEND"); } /* If the pathname is just "/bye", then the supervisor breaks out of the loop and terminates. This allows us to see what happens if the target process makes further calls to mkdir(2). */ if (streq(path, "/bye")) break; } free(req); free(resp); printf("\tS: terminating **********\n"); exit(EXIT_FAILURE); } /* Implementation of the supervisor process: (1) obtains the notification file descriptor from 'sockPair[1]' (2) handles notifications that arrive on that file descriptor. */ static void supervisor(int sockPair[2]) { int notifyFd; notifyFd = recvfd(sockPair[1]); if (notifyFd == -1) err(EXIT_FAILURE, "recvfd"); closeSocketPair(sockPair); /* We no longer need the socket pair */ handleNotifications(notifyFd); } int main(int argc, char *argv[]) { int sockPair[2]; struct sigaction sa; setbuf(stdout, NULL); if (argc < 2) { fprintf(stderr, "At least one pathname argument is required\n"); exit(EXIT_FAILURE); } /* Create a UNIX domain socket that is used to pass the seccomp notification file descriptor from the target process to the supervisor process. */ if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockPair) == -1) err(EXIT_FAILURE, "socketpair"); /* Create a child process--the "target"--that installs seccomp filtering. The target process writes the seccomp notification file descriptor onto 'sockPair[0]' and then calls mkdir(2) for each directory in the command-line arguments. */ (void) targetProcess(sockPair, &argv[optind]); /* Catch SIGCHLD when the target terminates, so that the supervisor can also terminate. */ sa.sa_handler = sigchldHandler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); if (sigaction(SIGCHLD, &sa, NULL) == -1) err(EXIT_FAILURE, "sigaction"); supervisor(sockPair); exit(EXIT_SUCCESS); } ioctl(2) pidfd_getfd(2) pidfd_open(2) seccomp(2) samples/seccomp/user-trap.c. 3 . . : . 6.18 22 2026 seccomp_unotify(2)