From patchwork Thu Sep 21 02:14:45 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Wei Gao X-Patchwork-Id: 1837486 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=suse.com header.i=@suse.com header.a=rsa-sha256 header.s=susede1 header.b=KDCg2ttl; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.linux.it (client-ip=2001:1418:10:5::2; helo=picard.linux.it; envelope-from=ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it; receiver=patchwork.ozlabs.org) Received: from picard.linux.it (picard.linux.it [IPv6:2001:1418:10:5::2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Rrf9w1Hdbz1yh6 for ; Thu, 21 Sep 2023 12:14:59 +1000 (AEST) Received: from picard.linux.it (localhost [IPv6:::1]) by picard.linux.it (Postfix) with ESMTP id 8628B3CE3E0 for ; Thu, 21 Sep 2023 04:14:55 +0200 (CEST) X-Original-To: ltp@lists.linux.it Delivered-To: ltp@picard.linux.it Received: from in-6.smtp.seeweb.it (in-6.smtp.seeweb.it [IPv6:2001:4b78:1:20::6]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by picard.linux.it (Postfix) with ESMTPS id 251983C9274 for ; Thu, 21 Sep 2023 04:14:53 +0200 (CEST) Received: from smtp-out2.suse.de (smtp-out2.suse.de [IPv6:2001:67c:2178:6::1d]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by in-6.smtp.seeweb.it (Postfix) with ESMTPS id 7718D140025A for ; Thu, 21 Sep 2023 04:14:51 +0200 (CEST) Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id A1C66205AD; Thu, 21 Sep 2023 02:14:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=susede1; t=1695262489; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version: content-transfer-encoding:content-transfer-encoding; bh=XE42rxZetD2f9HnHfs5rjjijSpkNJ3XlMlWXEoDEhds=; b=KDCg2ttl3a9tGwNpgX8k5g4v40Wvyw+z42WAb1LgQz1YhHG9qhGi5IDkd4u128CcMHAtFi ICOxxxv7LGaJPMkUOynFumuRsIE3uupbRvlESoStjoCkpFYconm39dmvAuHyeIKLwgOHrw lc3PavXup7HkRkqcVbQ09ZPx+nOgU+s= Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id 083E813478; Thu, 21 Sep 2023 02:14:48 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id /DCPNBinC2XubwAAMHmgww (envelope-from ); Thu, 21 Sep 2023 02:14:48 +0000 To: ltp@lists.linux.it Date: Wed, 20 Sep 2023 22:14:45 -0400 Message-Id: <20230921021445.2075-1-wegao@suse.com> X-Mailer: git-send-email 2.35.3 MIME-Version: 1.0 X-Virus-Scanned: clamav-milter 1.0.1 at in-6.smtp.seeweb.it X-Virus-Status: Clean X-Spam-Status: No, score=0.1 required=7.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_NONE,SPF_PASS shortcircuit=no autolearn=disabled version=4.0.0 X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on in-6.smtp.seeweb.it Subject: [LTP] [PATCH v1] seccomp01.c: Add SECCOMP_RET_USER_NOTIF check X-BeenThere: ltp@lists.linux.it X-Mailman-Version: 2.1.29 Precedence: list List-Id: Linux Test Project List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Wei Gao via ltp From: Wei Gao Reply-To: Wei Gao Errors-To: ltp-bounces+incoming=patchwork.ozlabs.org@lists.linux.it Sender: "ltp" 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 --- 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 diff --git a/configure.ac b/configure.ac index 662c4c058..6cea35cb4 100644 --- a/configure.ac +++ b/configure.ac @@ -138,6 +138,7 @@ AC_CHECK_FUNCS_ONCE([ \ renameat \ renameat2 \ sched_getcpu \ + seccomp \ sendmmsg \ sethostid \ setns \ diff --git a/include/lapi/seccomp.h b/include/lapi/seccomp.h index 29819ba6f..cfb3da55d 100644 --- a/include/lapi/seccomp.h +++ b/include/lapi/seccomp.h @@ -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__ */ diff --git a/runtest/syscalls b/runtest/syscalls index 4f1ee1f34..544610d63 100644 --- a/runtest/syscalls +++ b/runtest/syscalls @@ -1242,6 +1242,8 @@ select02 select02 select03 select03 select04 select04 +seccomp01 seccomp01 + semctl01 semctl01 semctl02 semctl02 semctl03 semctl03 diff --git a/testcases/kernel/syscalls/seccomp/.gitignore b/testcases/kernel/syscalls/seccomp/.gitignore new file mode 100644 index 000000000..9196906cf --- /dev/null +++ b/testcases/kernel/syscalls/seccomp/.gitignore @@ -0,0 +1 @@ +seccomp01 diff --git a/testcases/kernel/syscalls/seccomp/Makefile b/testcases/kernel/syscalls/seccomp/Makefile new file mode 100644 index 000000000..49238eee0 --- /dev/null +++ b/testcases/kernel/syscalls/seccomp/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Copyright (c) 2023 Wei Gao + +top_srcdir ?= ../../../.. + +include $(top_srcdir)/include/mk/testcases.mk + +include $(top_srcdir)/include/mk/generic_leaf_target.mk diff --git a/testcases/kernel/syscalls/seccomp/seccomp01.c b/testcases/kernel/syscalls/seccomp/seccomp01.c new file mode 100644 index 000000000..bf23fe8f7 --- /dev/null +++ b/testcases/kernel/syscalls/seccomp/seccomp01.c @@ -0,0 +1,456 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Michael Kerrisk + * Copyright (c) 2023 Wei Gao + */ + +/*\ + * [Description] + * + * Verify seccomp and seccomp_user_notif + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +};