@@ -138,6 +138,7 @@ AC_CHECK_FUNCS_ONCE([ \
renameat \
renameat2 \
sched_getcpu \
+ seccomp \
sendmmsg \
sethostid \
setns \
@@ -37,4 +37,11 @@ struct seccomp_data {
};
#endif /* HAVE_LINUX_SECCOMP_H*/
+
+# ifndef HAVE_SECCOMP
+int seccomp(unsigned int operation, unsigned int flags, void *args)
+{
+ return syscall(__NR_seccomp, operation, flags, args);
+}
+# endif /* HAVE_SECCOMP */
#endif /* LAPI_SECCOMP_H__ */
@@ -1242,6 +1242,8 @@ select02 select02
select03 select03
select04 select04
+seccomp01 seccomp01
+
semctl01 semctl01
semctl02 semctl02
semctl03 semctl03
new file mode 100644
@@ -0,0 +1 @@
+seccomp01
new file mode 100644
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0-only
+# Copyright (c) 2023 Wei Gao <wegao@suse.com>
+
+top_srcdir ?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
new file mode 100644
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2023 Michael Kerrisk <mtk.manpages@gmail.com>
+ * Copyright (c) 2023 Wei Gao <wegao@suse.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * Verify seccomp and seccomp_user_notif
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/prctl.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <linux/audit.h>
+#include <sys/syscall.h>
+#include <sys/stat.h>
+#include <linux/filter.h>
+#include <linux/seccomp.h>
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "tst_test.h"
+#include "lapi/seccomp.h"
+
+#define TMP_PREFIX_DIR "/tmp/ltp_test"
+#define CWD_DIR "./abc"
+#define OTHER_DIR "/aa"
+
+static struct tcase {
+ char *dir;
+ int expect_ret;
+ char *desc;
+} tcases[] = {
+ {TMP_PREFIX_DIR, strlen(TMP_PREFIX_DIR), "pathname begins with the prefix /tmp/"},
+ {CWD_DIR, 0, "pathname begins with ./"},
+ {OTHER_DIR, -1, "pathname begins with /abc"},
+};
+
+static int sendfd(int sockfd, int fd)
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data;
+ 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 suitable 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));
+
+ SAFE_SENDMSG(sizeof(int), sockfd, &msgh, 0);
+
+ return 0;
+}
+
+static int recvfd(int sockfd)
+{
+ struct msghdr msgh;
+ struct iovec iov;
+ int data, fd;
+ ssize_t nr;
+
+ /* 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 = SAFE_RECVMSG(sizeof(int), 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;
+}
+
+/* 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)
+{
+ 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, __NR_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.
+ */
+
+ TST_EXP_POSITIVE(seccomp(SECCOMP_SET_MODE_FILTER,
+ SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog));
+ return TST_RET;
+
+}
+
+/* Close a pair of sockets created by socketpair() */
+
+static void closeSocketPair(int sockPair[2])
+{
+ SAFE_CLOSE(sockPair[0]);
+ SAFE_CLOSE(sockPair[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], struct tcase *tc)
+{
+
+ pid_t targetPid = SAFE_FORK();
+
+ if (targetPid > 0) /* In parent, return PID of child */
+ return targetPid;
+
+ /* Child falls through to here */
+
+ tst_res(TINFO, "T: PID = %ld", (long) getpid());
+
+ /* Install seccomp filter(s) */
+
+ TST_EXP_PASS(prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0));
+
+ int notifyFd = installNotifyFilter();
+
+ /* Pass the notification file descriptor to the tracing process over
+ * a UNIX domain socket
+ */
+
+ TST_EXP_PASS(sendfd(sockPair[0], notifyFd));
+
+ /* Notification and socket FDs are no longer needed in target */
+
+ SAFE_CLOSE(notifyFd);
+
+ closeSocketPair(sockPair);
+
+ tst_res(TINFO, "T: about to mkdir(\"%s\")", tc->dir);
+
+ TST_CHECKPOINT_WAIT(0);
+
+ TEST(mkdir(tc->dir, 0700));
+
+ tst_res(TINFO, "T: SUCCESS: mkdir(2) returned %ld", TST_RET);
+ if (TST_RET == tc->expect_ret)
+ tst_res(TPASS, "Case %s PASS", tc->desc);
+ else
+ tst_brk(TBROK | TTERRNO, "Case %s Failed, expect %d but return %ld",
+ tc->desc, tc->expect_ret, TST_RET);
+
+ exit(EXIT_SUCCESS);
+}
+
+
+/* Access the memory of the target process in order to discover the
+ * pathname that was given to mkdir()
+ */
+
+static void getTargetPathname(struct seccomp_notif *req, int notifyFd,
+ char *path)
+{
+ char procMemPath[PATH_MAX];
+
+ snprintf(procMemPath, sizeof(procMemPath), "/proc/%d/mem", req->pid);
+
+ int procMemFd = SAFE_OPEN(procMemPath, O_RDONLY);
+
+ /* Check that the process whose info we are accessing is still alive.
+ * If the SECCOMP_IOCTL_NOTIF_ID_VALID operation (performed
+ * in checkNotificationIdIsValid()) succeeds, we know that the
+ * /proc/PID/mem file descriptor that we opened corresponds to the
+ * process for which we received a notification. If that process
+ * subsequently terminates, then read() on that file descriptor
+ * will return 0 (EOF).
+ */
+
+ SAFE_IOCTL(notifyFd, SECCOMP_IOCTL_NOTIF_ID_VALID, &req->id);
+
+ /* Seek to the location containing the pathname argument (i.e., the
+ * first argument) of the mkdir(2) call and read that pathname
+ */
+
+ SAFE_LSEEK(procMemFd, req->data.args[0], SEEK_SET);
+
+ SAFE_READ(1, procMemFd, path, PATH_MAX);
+
+ SAFE_CLOSE(procMemFd);
+}
+
+/* Handle notifications that arrive via the SECCOMP_RET_USER_NOTIF file
+ * descriptor, 'notifyFd'.
+ */
+
+static void handleNotifications(int notifyFd)
+{
+ struct seccomp_notif_sizes sizes;
+ char path[PATH_MAX];
+
+ TST_EXP_POSITIVE(seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes));
+
+ struct seccomp_notif *req = SAFE_MALLOC(sizes.seccomp_notif);
+
+ struct seccomp_notif_resp *resp = SAFE_MALLOC(sizes.seccomp_notif_resp);
+
+ memset(req, 0, sizes.seccomp_notif);
+
+ TST_CHECKPOINT_WAKE(0);
+
+ SAFE_IOCTL(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req);
+
+ tst_res(TINFO, "S: got notification (ID %#llx) for PID %d",
+ 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 != __NR_mkdir)
+ tst_brk(TBROK, "notification contained unexpected system call number");
+
+ getTargetPathname(req, notifyFd, path);
+
+ /* Prepopulate some fields of the response */
+
+ resp->id = req->id; /* Response includes notification ID */
+ resp->flags = 0;
+ resp->val = 0;
+
+ /* 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 (strncmp(path, "/tmp/", strlen("/tmp/")) == 0) {
+ tst_res(TINFO, "S: executing: mkdir(\"%s\", %#llo)",
+ 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
+ */
+ tst_res(TINFO, "S: success! spoofed return = %lld",
+ resp->val);
+ } else {
+
+ /* If mkdir() failed in the supervisor, pass the error
+ * back to the target
+ */
+
+ resp->error = -errno;
+ tst_res(TINFO, "S: failure! (errno = %d; %s)", errno,
+ strerror(errno));
+ }
+ } else if (strncmp(path, "./", strlen("./")) == 0) {
+ resp->error = resp->val = 0;
+ resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
+ tst_res(TINFO, "S: target can execute system call");
+ } else {
+ resp->error = -EOPNOTSUPP;
+ tst_res(TINFO, "S: spoofing error response (%s)",
+ strerror(-resp->error));
+ }
+
+ /* Send a response to the notification */
+
+ tst_res(TINFO, "S: sending response "
+ "(flags = %#x; val = %lld; error = %d)",
+ resp->flags, resp->val, resp->error);
+
+ SAFE_IOCTL(notifyFd, SECCOMP_IOCTL_NOTIF_SEND, resp);
+
+}
+
+/* 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 = TST_EXP_POSITIVE(recvfd(sockPair[1]));
+
+ closeSocketPair(sockPair); /* We no longer need the socket pair */
+
+ handleNotifications(notifyFd);
+}
+
+static void run(unsigned int n)
+{
+ struct tcase *tc = &tcases[n];
+ int sockPair[2];
+
+ tst_res(TINFO, "Test case %s start", tc->desc);
+
+ SAFE_SOCKETPAIR(AF_UNIX, SOCK_STREAM, 0, sockPair);
+
+ int pid = targetProcess(sockPair, tc);
+
+ supervisor(sockPair);
+
+ SAFE_WAITPID(pid, NULL, 0);
+
+ if (!access(tc->dir, F_OK))
+ SAFE_RMDIR(tc->dir);
+}
+
+static struct tst_test test = {
+ .tcnt = ARRAY_SIZE(tcases),
+ .test = run,
+ .needs_tmpdir = 1,
+ .forks_child = 1,
+ .min_kver = "5.7.19",
+ .needs_checkpoints = 1
+};
This case will report EINVAL error when execute SAFE_IOCTL(notifyFd, SECCOMP_IOCTL_NOTIF_RECV, req) such as 5.6.19, so i put current case's .min_kver = "5.7.19" NOTE: If your old kernel compile env is ubuntu 22.04 LTS, better use old gcc-8 and also apply patch base following link: https://www.spinics.net/lists/kernel/msg3797871.html Signed-off-by: Wei Gao <wegao@suse.com> --- configure.ac | 1 + include/lapi/seccomp.h | 7 + runtest/syscalls | 2 + testcases/kernel/syscalls/seccomp/.gitignore | 1 + testcases/kernel/syscalls/seccomp/Makefile | 8 + testcases/kernel/syscalls/seccomp/seccomp01.c | 456 ++++++++++++++++++ 6 files changed, 475 insertions(+) create mode 100644 testcases/kernel/syscalls/seccomp/.gitignore create mode 100644 testcases/kernel/syscalls/seccomp/Makefile create mode 100644 testcases/kernel/syscalls/seccomp/seccomp01.c