diff options
Diffstat (limited to 'man2/seccomp_unotify.2')
-rw-r--r-- | man2/seccomp_unotify.2 | 318 |
1 files changed, 159 insertions, 159 deletions
diff --git a/man2/seccomp_unotify.2 b/man2/seccomp_unotify.2 index 4fde10db1..32f183704 100644 --- a/man2/seccomp_unotify.2 +++ b/man2/seccomp_unotify.2 @@ -847,21 +847,21 @@ operation (here, to emulate a call to .EX .in +4n 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; /* Cookie from 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); /* No longer needed in supervisor */ - +\& struct seccomp_notif_resp *resp; /* Code to allocate 'resp' omitted */ resp\->id = req\->id; @@ -1252,14 +1252,14 @@ call. .EX $ \fB./seccomp_unotify /tmp/x\fP T: PID = 23168 - +\& T: about to mkdir("/tmp/x") S: got notification (ID 0x17445c4a0f4e0e3c) for PID 23168 S: executing: mkdir("/tmp/x", 0700) S: success! spoofed return = 6 S: sending response (flags = 0; val = 6; error = 0) T: SUCCESS: mkdir(2) returned 6 - +\& T: terminating S: target has terminated; bye .EE @@ -1287,13 +1287,13 @@ call. .EX $ \fB./seccomp_unotify ./sub\fP T: PID = 23204 - +\& T: about to mkdir("./sub") S: got notification (ID 0xddb16abe25b4c12) for PID 23204 S: target can execute system call S: sending response (flags = 0x1; val = 0; error = 0) T: SUCCESS: mkdir(2) returned 0 - +\& T: terminating S: target has terminated; bye .EE @@ -1312,13 +1312,13 @@ call (which is not executed): .EX $ \fB./seccomp_unotify /xxx\fP T: PID = 23178 - +\& T: about to mkdir("/xxx") S: got notification (ID 0xe7dc095d1c524e80) for PID 23178 S: spoofing error response (Operation not supported) S: sending response (flags = 0; val = 0; error = \-95) T: ERROR: mkdir(2): Operation not supported - +\& T: terminating S: target has terminated; bye .EE @@ -1342,14 +1342,14 @@ call. .EX $ \fB./seccomp_unotify /tmp/nosuchdir/b\fP T: PID = 23199 - +\& T: about to mkdir("/tmp/nosuchdir/b") S: got notification (ID 0x8744454293506046) for PID 23199 S: executing: mkdir("/tmp/nosuchdir/b", 0700) S: failure! (errno = 2; No such file or directory) S: sending response (flags = 0; val = 0; error = \-2) T: ERROR: mkdir(2): No such file or directory - +\& T: terminating S: target has terminated; bye .EE @@ -1375,17 +1375,17 @@ This is demonstrated by the following example: .EX $ \fB./seccomp_unotify /bye /tmp/y\fP T: PID = 23185 - +\& T: about to mkdir("/bye") S: got notification (ID 0xa81236b1d2f7b0f4) for PID 23185 S: spoofing error response (Operation not supported) S: sending response (flags = 0; val = 0; error = \-95) S: terminating ********** T: ERROR: mkdir(2): Operation not supported - +\& T: about to mkdir("/tmp/y") T: ERROR: mkdir(2): Function not implemented - +\& T: terminating .EE .in @@ -1416,12 +1416,12 @@ T: terminating #include <sys/types.h> #include <sys/un.h> #include <unistd.h> - +\& #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - +\& /* Send the file descriptor \[aq]fd\[aq] over the connected UNIX domain socket \[aq]sockfd\[aq]. Returns 0 on success, or \-1 on error. */ - +\& static int sendfd(int sockfd, int fd) { @@ -1429,7 +1429,7 @@ sendfd(int sockfd, int fd) 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 \[aq]struct cmsghdr\[aq], use a union to ensure that it is suitably aligned. */ @@ -1438,48 +1438,48 @@ sendfd(int sockfd, int fd) /* Space large enough to hold an \[aq]int\[aq] */ struct cmsghdr align; } controlMsg; - +\& /* The \[aq]msg_name\[aq] 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 \[aq]sockfd\[aq] 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 \[aq]msghdr\[aq] 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) { @@ -1487,7 +1487,7 @@ recvfd(int sockfd) ssize_t nr; struct iovec iov; struct msghdr msgh; - +\& /* Allocate a char buffer for the ancillary data. See the comments in sendfd() */ union { @@ -1495,35 +1495,35 @@ recvfd(int sockfd) struct cmsghdr align; } controlMsg; struct cmsghdr *cmsgp; - +\& /* The \[aq]msg_name\[aq] 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 \[aq]int\[aq] */ iov.iov_len = sizeof(int); - +\& /* Set \[aq]msghdr\[aq] 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 \[aq]cmsghdr\[aq] */ - +\& if (cmsgp == NULL || cmsgp\->cmsg_len != CMSG_LEN(sizeof(int)) || cmsgp\->cmsg_level != SOL_SOCKET @@ -1532,37 +1532,37 @@ recvfd(int sockfd) 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[] = "\etS: target has terminated; bye\en"; - +\& 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 \e BPF_STMT(BPF_LD | BPF_W | BPF_ABS, \e (offsetof(struct seccomp_data, arch))), \e @@ -1571,50 +1571,50 @@ seccomp(unsigned int operation, unsigned int flags, void *args) (offsetof(struct seccomp_data, nr))), \e BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, X32_SYSCALL_BIT, 0, 1), \e 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 = ARRAY_SIZE(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]) { @@ -1623,96 +1623,96 @@ closeSocketPair(int sockPair[2]) 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, \[aq]sockPair[0]\[aq]; (3) calls mkdir(2) for each element of \[aq]argv\[aq]. - +\& 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\en", (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("\enT: about to mkdir(\e"%s\e")\en", *ap); - +\& s = mkdir(*ap, 0700); if (s == \-1) perror("T: ERROR: mkdir(2)"); else printf("T: SUCCESS: mkdir(2) returned %d\en", s); } - +\& printf("\enT: terminating\en"); 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 \[aq]argNum\[aq] in \[aq]req\->data.args[]\[aq]. The pathname is returned in \[aq]path\[aq], a buffer of \[aq]len\[aq] 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 len) @@ -1720,13 +1720,13 @@ getTargetPathname(struct seccomp_notif *req, int notifyFd, 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 @@ -1734,21 +1734,21 @@ getTargetPathname(struct seccomp_notif *req, int notifyFd, 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, len, 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\[aq]s blocked system @@ -1757,12 +1757,12 @@ getTargetPathname(struct seccomp_notif *req, int notifyFd, system call). In that case, we have no guarantees about what we are reading, since the target\[aq]s memory may have been arbitrarily changed by subsequent operations. */ - +\& if (!cookieIsValid(notifyFd, req\->id)) { perror("\etS: notification ID check failed!!!"); return false; } - +\& /* Even if the target\[aq]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 @@ -1770,35 +1770,35 @@ getTargetPathname(struct seccomp_notif *req, int notifyFd, 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\[aq]s responsibility to free the buffers returned via \[aq]req\[aq] and \[aq]resp\[aq]. */ - +\& 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 \[aq]struct seccomp_notif_resp\[aq] is bigger than the @@ -1807,20 +1807,20 @@ allocSeccompNotifBuffers(struct seccomp_notif **req, 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, \[aq]notifyFd\[aq]. */ - +\& static void handleNotifications(int notifyFd) { @@ -1829,45 +1829,45 @@ handleNotifications(int notifyFd) 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 \[aq]*req\[aq] */ - +\& memset(req, 0, sizes.seccomp_notif); if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req) == \-1) { if (errno == EINTR) continue; err(EXIT_FAILURE, "\etS: ioctl\-SECCOMP_IOCTL_NOTIF_RECV"); } - +\& printf("\etS: got notification (ID %#llx) for PID %d\en", 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("\etS: notification contained unexpected " "system call number; bye!!!\en"); 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 @@ -1876,7 +1876,7 @@ handleNotifications(int notifyFd) 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("\etS: spoofing error for invalid pathname (%s)\en", @@ -1884,7 +1884,7 @@ handleNotifications(int notifyFd) } else if (strncmp(path, "/tmp/", strlen("/tmp/")) == 0) { printf("\etS: executing: mkdir(\e"%s\e", %#llo)\en", 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 @@ -1892,10 +1892,10 @@ handleNotifications(int notifyFd) printf("\etS: success! spoofed return = %lld\en", resp\->val); } else { - +\& /* If mkdir() failed in the supervisor, pass the error back to the target */ - +\& resp\->error = \-errno; printf("\etS: failure! (errno = %d; %s)\en", errno, strerror(errno)); @@ -1909,13 +1909,13 @@ handleNotifications(int notifyFd) printf("\etS: spoofing error response (%s)\en", strerror(\-resp\->error)); } - +\& /* Send a response to the notification */ - +\& printf("\etS: sending response " "(flags = %#x; val = %lld; error = %d)\en", resp\->flags, resp\->val, resp\->error); - +\& if (ioctl(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp) == \-1) { if (errno == ENOENT) printf("\etS: response failed with ENOENT; " @@ -1924,79 +1924,79 @@ handleNotifications(int notifyFd) 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 (strcmp(path, "/bye") == 0) break; } - +\& free(req); free(resp); printf("\etS: terminating **********\en"); exit(EXIT_FAILURE); } - +\& /* Implementation of the supervisor process: - +\& (1) obtains the notification file descriptor from \[aq]sockPair[1]\[aq] (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\en"); 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 \[aq]sockPair[0]\[aq] 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); } .EE |