Merge branch 'next-seccomp' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security
Pull seccomp updates from James Morris: - Add SECCOMP_RET_USER_NOTIF - seccomp fixes for sparse warnings and s390 build (Tycho) * 'next-seccomp' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris/linux-security: seccomp, s390: fix build for syscall type change seccomp: fix poor type promotion samples: add an example of seccomp user trap seccomp: add a return code to trap to userspace seccomp: switch system call argument type to void * seccomp: hoist struct seccomp_data recalculation higher
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
* Test code for seccomp bpf.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
|
||||
/*
|
||||
@@ -40,10 +41,12 @@
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/times.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include "../kselftest_harness.h"
|
||||
|
||||
@@ -133,6 +136,10 @@ struct seccomp_data {
|
||||
#define SECCOMP_GET_ACTION_AVAIL 2
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_GET_NOTIF_SIZES
|
||||
#define SECCOMP_GET_NOTIF_SIZES 3
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_FILTER_FLAG_TSYNC
|
||||
#define SECCOMP_FILTER_FLAG_TSYNC (1UL << 0)
|
||||
#endif
|
||||
@@ -154,6 +161,44 @@ struct seccomp_metadata {
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER
|
||||
#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
|
||||
|
||||
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U
|
||||
|
||||
#define SECCOMP_IOC_MAGIC '!'
|
||||
#define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr)
|
||||
#define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOW(nr, type) _IOW(SECCOMP_IOC_MAGIC, nr, type)
|
||||
#define SECCOMP_IOWR(nr, type) _IOWR(SECCOMP_IOC_MAGIC, nr, type)
|
||||
|
||||
/* Flags for seccomp notification fd ioctl. */
|
||||
#define SECCOMP_IOCTL_NOTIF_RECV SECCOMP_IOWR(0, struct seccomp_notif)
|
||||
#define SECCOMP_IOCTL_NOTIF_SEND SECCOMP_IOWR(1, \
|
||||
struct seccomp_notif_resp)
|
||||
#define SECCOMP_IOCTL_NOTIF_ID_VALID SECCOMP_IOR(2, __u64)
|
||||
|
||||
struct seccomp_notif {
|
||||
__u64 id;
|
||||
__u32 pid;
|
||||
__u32 flags;
|
||||
struct seccomp_data data;
|
||||
};
|
||||
|
||||
struct seccomp_notif_resp {
|
||||
__u64 id;
|
||||
__s64 val;
|
||||
__s32 error;
|
||||
__u32 flags;
|
||||
};
|
||||
|
||||
struct seccomp_notif_sizes {
|
||||
__u16 seccomp_notif;
|
||||
__u16 seccomp_notif_resp;
|
||||
__u16 seccomp_data;
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifndef seccomp
|
||||
int seccomp(unsigned int op, unsigned int flags, void *args)
|
||||
{
|
||||
@@ -2077,7 +2122,8 @@ TEST(detect_seccomp_filter_flags)
|
||||
{
|
||||
unsigned int flags[] = { SECCOMP_FILTER_FLAG_TSYNC,
|
||||
SECCOMP_FILTER_FLAG_LOG,
|
||||
SECCOMP_FILTER_FLAG_SPEC_ALLOW };
|
||||
SECCOMP_FILTER_FLAG_SPEC_ALLOW,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER };
|
||||
unsigned int flag, all_flags;
|
||||
int i;
|
||||
long ret;
|
||||
@@ -2938,6 +2984,403 @@ skip:
|
||||
ASSERT_EQ(0, kill(pid, SIGKILL));
|
||||
}
|
||||
|
||||
static int user_trap_syscall(int nr, unsigned int flags)
|
||||
{
|
||||
struct sock_filter filter[] = {
|
||||
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
|
||||
offsetof(struct seccomp_data, nr)),
|
||||
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, nr, 0, 1),
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_USER_NOTIF),
|
||||
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
|
||||
};
|
||||
|
||||
struct sock_fprog prog = {
|
||||
.len = (unsigned short)ARRAY_SIZE(filter),
|
||||
.filter = filter,
|
||||
};
|
||||
|
||||
return seccomp(SECCOMP_SET_MODE_FILTER, flags, &prog);
|
||||
}
|
||||
|
||||
#define USER_NOTIF_MAGIC 116983961184613L
|
||||
TEST(user_notification_basic)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int status, listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
struct pollfd pollfd;
|
||||
|
||||
struct sock_filter filter[] = {
|
||||
BPF_STMT(BPF_RET|BPF_K, SECCOMP_RET_ALLOW),
|
||||
};
|
||||
struct sock_fprog prog = {
|
||||
.len = (unsigned short)ARRAY_SIZE(filter),
|
||||
.filter = filter,
|
||||
};
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
/* Check that we get -ENOSYS with no listener attached */
|
||||
if (pid == 0) {
|
||||
if (user_trap_syscall(__NR_getpid, 0) < 0)
|
||||
exit(1);
|
||||
ret = syscall(__NR_getpid);
|
||||
exit(ret >= 0 || errno != ENOSYS);
|
||||
}
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
/* Add some no-op filters so for grins. */
|
||||
EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
|
||||
EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
|
||||
EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
|
||||
EXPECT_EQ(seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog), 0);
|
||||
|
||||
/* Check that the basic notification machinery works */
|
||||
listener = user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
/* Installing a second listener in the chain should EBUSY */
|
||||
EXPECT_EQ(user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER),
|
||||
-1);
|
||||
EXPECT_EQ(errno, EBUSY);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
ret = syscall(__NR_getpid);
|
||||
exit(ret != USER_NOTIF_MAGIC);
|
||||
}
|
||||
|
||||
pollfd.fd = listener;
|
||||
pollfd.events = POLLIN | POLLOUT;
|
||||
|
||||
EXPECT_GT(poll(&pollfd, 1, -1), 0);
|
||||
EXPECT_EQ(pollfd.revents, POLLIN);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
pollfd.fd = listener;
|
||||
pollfd.events = POLLIN | POLLOUT;
|
||||
|
||||
EXPECT_GT(poll(&pollfd, 1, -1), 0);
|
||||
EXPECT_EQ(pollfd.revents, POLLOUT);
|
||||
|
||||
EXPECT_EQ(req.data.nr, __NR_getpid);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
/* check that we make sure flags == 0 */
|
||||
resp.flags = 1;
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1);
|
||||
EXPECT_EQ(errno, EINVAL);
|
||||
|
||||
resp.flags = 0;
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(user_notification_kill_in_middle)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
/*
|
||||
* Check that nothing bad happens when we kill the task in the middle
|
||||
* of a syscall.
|
||||
*/
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
ret = syscall(__NR_getpid);
|
||||
exit(ret != USER_NOTIF_MAGIC);
|
||||
}
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id), 0);
|
||||
|
||||
EXPECT_EQ(kill(pid, SIGKILL), 0);
|
||||
EXPECT_EQ(waitpid(pid, NULL, 0), pid);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id), -1);
|
||||
|
||||
resp.id = req.id;
|
||||
ret = ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp);
|
||||
EXPECT_EQ(ret, -1);
|
||||
EXPECT_EQ(errno, ENOENT);
|
||||
}
|
||||
|
||||
static int handled = -1;
|
||||
|
||||
static void signal_handler(int signal)
|
||||
{
|
||||
if (write(handled, "c", 1) != 1)
|
||||
perror("write from signal");
|
||||
}
|
||||
|
||||
TEST(user_notification_signal)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int status, listener, sk_pair[2];
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
char c;
|
||||
|
||||
ASSERT_EQ(socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair), 0);
|
||||
|
||||
listener = user_trap_syscall(__NR_gettid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
close(sk_pair[0]);
|
||||
handled = sk_pair[1];
|
||||
if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
|
||||
perror("signal");
|
||||
exit(1);
|
||||
}
|
||||
/*
|
||||
* ERESTARTSYS behavior is a bit hard to test, because we need
|
||||
* to rely on a signal that has not yet been handled. Let's at
|
||||
* least check that the error code gets propagated through, and
|
||||
* hope that it doesn't break when there is actually a signal :)
|
||||
*/
|
||||
ret = syscall(__NR_gettid);
|
||||
exit(!(ret == -1 && errno == 512));
|
||||
}
|
||||
|
||||
close(sk_pair[1]);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
EXPECT_EQ(kill(pid, SIGUSR1), 0);
|
||||
|
||||
/*
|
||||
* Make sure the signal really is delivered, which means we're not
|
||||
* stuck in the user notification code any more and the notification
|
||||
* should be dead.
|
||||
*/
|
||||
EXPECT_EQ(read(sk_pair[0], &c, 1), 1);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = -EPERM;
|
||||
resp.val = 0;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), -1);
|
||||
EXPECT_EQ(errno, ENOENT);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = -512; /* -ERESTARTSYS */
|
||||
resp.val = 0;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(user_notification_closed_listener)
|
||||
{
|
||||
pid_t pid;
|
||||
long ret;
|
||||
int status, listener;
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid,
|
||||
SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
EXPECT_GE(listener, 0);
|
||||
|
||||
/*
|
||||
* Check that we get an ENOSYS when the listener is closed.
|
||||
*/
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
if (pid == 0) {
|
||||
close(listener);
|
||||
ret = syscall(__NR_getpid);
|
||||
exit(ret != -1 && errno != ENOSYS);
|
||||
}
|
||||
|
||||
close(listener);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that a pid in a child namespace still shows up as valid in ours.
|
||||
*/
|
||||
TEST(user_notification_child_pid_ns)
|
||||
{
|
||||
pid_t pid;
|
||||
int status, listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
ASSERT_EQ(unshare(CLONE_NEWPID), 0);
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(req.pid, pid);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
close(listener);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that a pid in a sibling (i.e. unrelated) namespace shows up as 0, i.e.
|
||||
* invalid.
|
||||
*/
|
||||
TEST(user_notification_sibling_pid_ns)
|
||||
{
|
||||
pid_t pid, pid2;
|
||||
int status, listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0) {
|
||||
ASSERT_EQ(unshare(CLONE_NEWPID), 0);
|
||||
|
||||
pid2 = fork();
|
||||
ASSERT_GE(pid2, 0);
|
||||
|
||||
if (pid2 == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
exit(WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
/* Create the sibling ns, and sibling in it. */
|
||||
EXPECT_EQ(unshare(CLONE_NEWPID), 0);
|
||||
EXPECT_EQ(errno, 0);
|
||||
|
||||
pid2 = fork();
|
||||
EXPECT_GE(pid2, 0);
|
||||
|
||||
if (pid2 == 0) {
|
||||
ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
/*
|
||||
* The pid should be 0, i.e. the task is in some namespace that
|
||||
* we can't "see".
|
||||
*/
|
||||
ASSERT_EQ(req.pid, 0);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
close(listener);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
|
||||
EXPECT_EQ(waitpid(pid2, &status, 0), pid2);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(user_notification_fault_recv)
|
||||
{
|
||||
pid_t pid;
|
||||
int status, listener;
|
||||
struct seccomp_notif req = {};
|
||||
struct seccomp_notif_resp resp = {};
|
||||
|
||||
listener = user_trap_syscall(__NR_getpid, SECCOMP_FILTER_FLAG_NEW_LISTENER);
|
||||
ASSERT_GE(listener, 0);
|
||||
|
||||
pid = fork();
|
||||
ASSERT_GE(pid, 0);
|
||||
|
||||
if (pid == 0)
|
||||
exit(syscall(__NR_getpid) != USER_NOTIF_MAGIC);
|
||||
|
||||
/* Do a bad recv() */
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, NULL), -1);
|
||||
EXPECT_EQ(errno, EFAULT);
|
||||
|
||||
/* We should still be able to receive this notification, though. */
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
|
||||
EXPECT_EQ(req.pid, pid);
|
||||
|
||||
resp.id = req.id;
|
||||
resp.error = 0;
|
||||
resp.val = USER_NOTIF_MAGIC;
|
||||
|
||||
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);
|
||||
|
||||
EXPECT_EQ(waitpid(pid, &status, 0), pid);
|
||||
EXPECT_EQ(true, WIFEXITED(status));
|
||||
EXPECT_EQ(0, WEXITSTATUS(status));
|
||||
}
|
||||
|
||||
TEST(seccomp_get_notif_sizes)
|
||||
{
|
||||
struct seccomp_notif_sizes sizes;
|
||||
|
||||
EXPECT_EQ(seccomp(SECCOMP_GET_NOTIF_SIZES, 0, &sizes), 0);
|
||||
EXPECT_EQ(sizes.seccomp_notif, sizeof(struct seccomp_notif));
|
||||
EXPECT_EQ(sizes.seccomp_notif_resp, sizeof(struct seccomp_notif_resp));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* - add microbenchmarks
|
||||
|
Reference in New Issue
Block a user