Skip to content

Commit

Permalink
selftests/seccomp: Test SECCOMP_IOCTL_NOTIF_ADDFD
Browse files Browse the repository at this point in the history
Test whether we can add file descriptors in response to notifications.
This injects the file descriptors via notifications, and then uses kcmp
to determine whether or not it has been successful.

It also includes some basic sanity checking for arguments.

Signed-off-by: Sargun Dhillon <[email protected]>
Cc: Al Viro <[email protected]>
Cc: Chris Palmer <[email protected]>
Cc: Christian Brauner <[email protected]>
Cc: Jann Horn <[email protected]>
Cc: Kees Cook <[email protected]>
Cc: Robert Sesek <[email protected]>
Cc: Tycho Andersen <[email protected]>
Cc: Matt Denton <[email protected]>
Cc: [email protected]
Cc: [email protected]
Link: https://lore.kernel.org/r/[email protected]
Co-developed-by: Kees Cook <[email protected]>
Signed-off-by: Kees Cook <[email protected]>
  • Loading branch information
sargun authored and kees committed Jul 14, 2020
1 parent 7cf97b1 commit c97aedc
Showing 1 changed file with 229 additions and 0 deletions.
229 changes: 229 additions & 0 deletions tools/testing/selftests/seccomp/seccomp_bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/kcmp.h>
#include <sys/resource.h>

#include <unistd.h>
#include <sys/syscall.h>
Expand Down Expand Up @@ -173,7 +174,9 @@ struct seccomp_metadata {

#ifndef SECCOMP_FILTER_FLAG_NEW_LISTENER
#define SECCOMP_FILTER_FLAG_NEW_LISTENER (1UL << 3)
#endif

#ifndef SECCOMP_RET_USER_NOTIF
#define SECCOMP_RET_USER_NOTIF 0x7fc00000U

#define SECCOMP_IOC_MAGIC '!'
Expand Down Expand Up @@ -209,6 +212,39 @@ struct seccomp_notif_sizes {
};
#endif

#ifndef SECCOMP_IOCTL_NOTIF_ADDFD
/* On success, the return value is the remote process's added fd number */
#define SECCOMP_IOCTL_NOTIF_ADDFD SECCOMP_IOW(3, \
struct seccomp_notif_addfd)

/* valid flags for seccomp_notif_addfd */
#define SECCOMP_ADDFD_FLAG_SETFD (1UL << 0) /* Specify remote fd */

struct seccomp_notif_addfd {
__u64 id;
__u32 flags;
__u32 srcfd;
__u32 newfd;
__u32 newfd_flags;
};
#endif

struct seccomp_notif_addfd_small {
__u64 id;
char weird[4];
};
#define SECCOMP_IOCTL_NOTIF_ADDFD_SMALL \
SECCOMP_IOW(3, struct seccomp_notif_addfd_small)

struct seccomp_notif_addfd_big {
union {
struct seccomp_notif_addfd addfd;
char buf[sizeof(struct seccomp_notif_addfd) + 8];
};
};
#define SECCOMP_IOCTL_NOTIF_ADDFD_BIG \
SECCOMP_IOWR(3, struct seccomp_notif_addfd_big)

#ifndef PTRACE_EVENTMSG_SYSCALL_ENTRY
#define PTRACE_EVENTMSG_SYSCALL_ENTRY 1
#define PTRACE_EVENTMSG_SYSCALL_EXIT 2
Expand Down Expand Up @@ -3761,6 +3797,199 @@ TEST(user_notification_filter_empty_threaded)
EXPECT_GT((pollfd.revents & POLLHUP) ?: 0, 0);
}

TEST(user_notification_addfd)
{
pid_t pid;
long ret;
int status, listener, memfd, fd;
struct seccomp_notif_addfd addfd = {};
struct seccomp_notif_addfd_small small = {};
struct seccomp_notif_addfd_big big = {};
struct seccomp_notif req = {};
struct seccomp_notif_resp resp = {};
/* 100 ms */
struct timespec delay = { .tv_nsec = 100000000 };

memfd = memfd_create("test", 0);
ASSERT_GE(memfd, 0);

ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
ASSERT_EQ(0, ret) {
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
}

/* Check that the basic notification machinery works */
listener = user_notif_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0);

pid = fork();
ASSERT_GE(pid, 0);

if (pid == 0) {
if (syscall(__NR_getppid) != USER_NOTIF_MAGIC)
exit(1);
exit(syscall(__NR_getppid) != USER_NOTIF_MAGIC);
}

ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);

addfd.srcfd = memfd;
addfd.newfd = 0;
addfd.id = req.id;
addfd.flags = 0x0;

/* Verify bad newfd_flags cannot be set */
addfd.newfd_flags = ~O_CLOEXEC;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EINVAL);
addfd.newfd_flags = O_CLOEXEC;

/* Verify bad flags cannot be set */
addfd.flags = 0xff;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EINVAL);
addfd.flags = 0;

/* Verify that remote_fd cannot be set without setting flags */
addfd.newfd = 1;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EINVAL);
addfd.newfd = 0;

/* Verify small size cannot be set */
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD_SMALL, &small), -1);
EXPECT_EQ(errno, EINVAL);

/* Verify we can't send bits filled in unknown buffer area */
memset(&big, 0xAA, sizeof(big));
big.addfd = addfd;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD_BIG, &big), -1);
EXPECT_EQ(errno, E2BIG);


/* Verify we can set an arbitrary remote fd */
fd = ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd);
/*
* The child has fds 0(stdin), 1(stdout), 2(stderr), 3(memfd),
* 4(listener), so the newly allocated fd should be 5.
*/
EXPECT_EQ(fd, 5);
EXPECT_EQ(filecmp(getpid(), pid, memfd, fd), 0);

/* Verify we can set an arbitrary remote fd with large size */
memset(&big, 0x0, sizeof(big));
big.addfd = addfd;
fd = ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD_BIG, &big);
EXPECT_EQ(fd, 6);

/* Verify we can set a specific remote fd */
addfd.newfd = 42;
addfd.flags = SECCOMP_ADDFD_FLAG_SETFD;
fd = ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd);
EXPECT_EQ(fd, 42);
EXPECT_EQ(filecmp(getpid(), pid, memfd, fd), 0);

/* Resume syscall */
resp.id = req.id;
resp.error = 0;
resp.val = USER_NOTIF_MAGIC;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);

/*
* This sets the ID of the ADD FD to the last request plus 1. The
* notification ID increments 1 per notification.
*/
addfd.id = req.id + 1;

/* This spins until the underlying notification is generated */
while (ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd) != -1 &&
errno != -EINPROGRESS)
nanosleep(&delay, NULL);

memset(&req, 0, sizeof(req));
ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);
ASSERT_EQ(addfd.id, req.id);

resp.id = req.id;
resp.error = 0;
resp.val = USER_NOTIF_MAGIC;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);

/* Wait for child to finish. */
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(true, WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status));

close(memfd);
}

TEST(user_notification_addfd_rlimit)
{
pid_t pid;
long ret;
int status, listener, memfd;
struct seccomp_notif_addfd addfd = {};
struct seccomp_notif req = {};
struct seccomp_notif_resp resp = {};
const struct rlimit lim = {
.rlim_cur = 0,
.rlim_max = 0,
};

memfd = memfd_create("test", 0);
ASSERT_GE(memfd, 0);

ret = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
ASSERT_EQ(0, ret) {
TH_LOG("Kernel does not support PR_SET_NO_NEW_PRIVS!");
}

/* Check that the basic notification machinery works */
listener = user_notif_syscall(__NR_getppid,
SECCOMP_FILTER_FLAG_NEW_LISTENER);
ASSERT_GE(listener, 0);

pid = fork();
ASSERT_GE(pid, 0);

if (pid == 0)
exit(syscall(__NR_getppid) != USER_NOTIF_MAGIC);


ASSERT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, &req), 0);

ASSERT_EQ(prlimit(pid, RLIMIT_NOFILE, &lim, NULL), 0);

addfd.srcfd = memfd;
addfd.newfd_flags = O_CLOEXEC;
addfd.newfd = 0;
addfd.id = req.id;
addfd.flags = 0;

/* Should probably spot check /proc/sys/fs/file-nr */
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EMFILE);

addfd.newfd = 100;
addfd.flags = SECCOMP_ADDFD_FLAG_SETFD;
EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd), -1);
EXPECT_EQ(errno, EBADF);

resp.id = req.id;
resp.error = 0;
resp.val = USER_NOTIF_MAGIC;

EXPECT_EQ(ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, &resp), 0);

/* Wait for child to finish. */
EXPECT_EQ(waitpid(pid, &status, 0), pid);
EXPECT_EQ(true, WIFEXITED(status));
EXPECT_EQ(0, WEXITSTATUS(status));

close(memfd);
}

/*
* TODO:
* - expand NNP testing
Expand Down

0 comments on commit c97aedc

Please sign in to comment.