#include <sys/param.h>
#include <sys/capsicum.h>
#include <sys/cpuset.h>
#include <sys/ktrace.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/sysent.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <machine/sysarch.h>
#include <netinet/in.h>
#include <atf-c.h>
#include <capsicum_helpers.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <sysdecode.h>
#define CHILD_REQUIRE(exp) do { \
if (!(exp)) \
child_fail_require(__FILE__, __LINE__, \
#exp " not met\n"); \
} while (0)
#define CHILD_REQUIRE_EQ(actual, expected) do { \
__typeof__(expected) _e = expected; \
__typeof__(actual) _a = actual; \
if (_e != _a) \
child_fail_require(__FILE__, __LINE__, #actual \
" (%jd) == " #expected " (%jd) not met\n", \
(intmax_t)_a, (intmax_t)_e); \
} while (0)
static __dead2 void
child_fail_require(const char *file, int line, const char *fmt, ...)
{
va_list ap;
char buf[1024];
snprintf(buf, sizeof(buf), "%s:%d: ", file, line);
write(STDERR_FILENO, buf, strlen(buf));
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
write(STDERR_FILENO, buf, strlen(buf));
va_end(ap);
_exit(32);
}
static void *
xmalloc(size_t size)
{
void *p;
p = malloc(size);
ATF_REQUIRE(p != NULL);
return (p);
}
static enum sysdecode_abi
syscallabi(u_int sv_flags)
{
switch (sv_flags & SV_ABI_MASK) {
case SV_ABI_FREEBSD:
return (SYSDECODE_ABI_FREEBSD);
case SV_ABI_LINUX:
#ifdef __LP64__
if ((sv_flags & SV_ILP32) != 0)
return (SYSDECODE_ABI_LINUX32);
#endif
return (SYSDECODE_ABI_LINUX);
}
return (SYSDECODE_ABI_UNKNOWN);
}
static int
trace_child(int cpid, int facility, int status)
{
int error, fd;
ATF_REQUIRE((fd = open("ktrace.out",
O_RDONLY | O_CREAT | O_TRUNC, 0600)) != -1);
ATF_REQUIRE_MSG(ktrace("ktrace.out", KTROP_SET, facility, cpid) != -1,
"ktrace failed: %s", strerror(errno));
ATF_REQUIRE(kill(cpid, SIGUSR1) != -1);
ATF_REQUIRE(waitpid(cpid, &error, 0) != -1);
ATF_REQUIRE(WIFEXITED(error));
ATF_REQUIRE_EQ(WEXITSTATUS(error), status);
return (fd);
}
static void
cap_trace_child(pid_t cpid, struct ktr_cap_fail *v, int numv)
{
struct ktr_header header;
ssize_t n;
int fd;
fd = trace_child(cpid, KTRFAC_CAPFAIL, 0);
for (int i = 0; i < numv; ++i) {
ATF_REQUIRE((n = read(fd, &header, sizeof(header))) != -1);
ATF_REQUIRE_EQ(n, sizeof(header));
ATF_REQUIRE_EQ(header.ktr_len, sizeof(*v));
ATF_REQUIRE_EQ(header.ktr_pid, cpid);
ATF_REQUIRE((n = read(fd, v + i,
sizeof(*v))) != -1);
ATF_REQUIRE_EQ(n, sizeof(*v));
}
ATF_REQUIRE(close(fd) != -1);
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_not_capable);
ATF_TC_BODY(ktrace__cap_not_capable, tc)
{
struct ktr_cap_fail violation;
cap_rights_t rights;
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
cap_rights_init(&rights, CAP_READ);
CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) != -1);
CHILD_REQUIRE(caph_enter() != -1);
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(write(STDIN_FILENO, &pid, sizeof(pid)) == -1);
CHILD_REQUIRE_EQ(errno, ENOTCAPABLE);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_NOTCAPABLE);
ATF_REQUIRE(cap_rights_is_set(&violation.cap_data.cap_needed,
CAP_WRITE));
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_increase_rights);
ATF_TC_BODY(ktrace__cap_increase_rights, tc)
{
struct ktr_cap_fail violation;
cap_rights_t rights;
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
cap_rights_init(&rights, CAP_READ);
CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) != -1);
CHILD_REQUIRE(caph_enter() != -1);
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
cap_rights_set(&rights, CAP_WRITE);
CHILD_REQUIRE(caph_rights_limit(STDIN_FILENO, &rights) == -1);
CHILD_REQUIRE_EQ(errno, ENOTCAPABLE);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_INCREASE);
ATF_REQUIRE(cap_rights_is_set(&violation.cap_data.cap_needed,
CAP_WRITE));
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_syscall);
ATF_TC_BODY(ktrace__cap_syscall, tc)
{
struct kinfo_file kinf;
struct ktr_cap_fail violation[2];
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(chdir(".") != -1);
kinf.kf_structsize = sizeof(struct kinfo_file);
CHILD_REQUIRE(fcntl(STDIN_FILENO, F_KINFO, &kinf) != -1);
exit(0);
}
cap_trace_child(pid, violation, 2);
ATF_REQUIRE_EQ(violation[0].cap_type, CAPFAIL_SYSCALL);
error = syscallabi(violation[0].cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[0].cap_code),
"chdir");
ATF_REQUIRE_EQ(violation[1].cap_type, CAPFAIL_SYSCALL);
error = syscallabi(violation[1].cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[1].cap_code),
"fcntl");
ATF_REQUIRE_EQ(violation[1].cap_data.cap_int, F_KINFO);
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_signal);
ATF_TC_BODY(ktrace__cap_signal, tc)
{
struct ktr_cap_fail violation;
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(kill(getppid(), SIGCONT) != -1);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_SIGNAL);
error = syscallabi(violation.cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code),
"kill");
ATF_REQUIRE_EQ(violation.cap_data.cap_int, SIGCONT);
}
ATF_TC(ktrace__cap_proto);
ATF_TC_HEAD(ktrace__cap_proto, tc)
{
atf_tc_set_md_var(tc, "require.user", "root");
}
ATF_TC_BODY(ktrace__cap_proto, tc)
{
struct ktr_cap_fail violation;
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(close(socket(AF_INET, SOCK_RAW,
IPPROTO_ICMP)) != -1);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_PROTO);
error = syscallabi(violation.cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code),
"socket");
ATF_REQUIRE_EQ(violation.cap_data.cap_int, IPPROTO_ICMP);
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_sockaddr);
ATF_TC_BODY(ktrace__cap_sockaddr, tc)
{
struct sockaddr_in addr = { }, *saddr;
struct ktr_cap_fail violation;
sigset_t set = { };
pid_t pid;
int error, sfd;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1);
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ATF_REQUIRE(bind(sfd, (const struct sockaddr *)&addr,
sizeof(addr)) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(sendto(sfd, NULL, 0, 0,
(const struct sockaddr *)&addr, sizeof(addr)) != -1);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_SOCKADDR);
error = syscallabi(violation.cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code),
"sendto");
saddr = (struct sockaddr_in *)&violation.cap_data.cap_sockaddr;
ATF_REQUIRE_EQ(saddr->sin_family, AF_INET);
ATF_REQUIRE_EQ(saddr->sin_port, htons(5000));
ATF_REQUIRE_EQ(saddr->sin_addr.s_addr, htonl(INADDR_LOOPBACK));
close(sfd);
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_namei);
ATF_TC_BODY(ktrace__cap_namei, tc)
{
struct ktr_cap_fail violation[2];
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(close(openat(AT_FDCWD, "ktrace.out",
O_RDONLY | O_CREAT)) != -1);
CHILD_REQUIRE(close(openat(-1, "/", O_RDONLY)) != -1);
exit(0);
}
cap_trace_child(pid, violation, 2);
ATF_REQUIRE_EQ(violation[0].cap_type, CAPFAIL_NAMEI);
error = syscallabi(violation[0].cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[0].cap_code),
"openat");
ATF_REQUIRE_STREQ(violation[0].cap_data.cap_path, "AT_FDCWD");
ATF_REQUIRE_EQ(violation[1].cap_type, CAPFAIL_NAMEI);
error = syscallabi(violation[1].cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation[1].cap_code),
"openat");
ATF_REQUIRE_STREQ(violation[1].cap_data.cap_path, "/");
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_cpuset);
ATF_TC_BODY(ktrace__cap_cpuset, tc)
{
struct ktr_cap_fail violation;
cpuset_t cpuset_mask = { };
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CPU_SET(0, &cpuset_mask);
CHILD_REQUIRE(cpuset_setaffinity(CPU_LEVEL_WHICH, CPU_WHICH_PID,
getppid(), sizeof(cpuset_mask), &cpuset_mask) != -1);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_CPUSET);
error = syscallabi(violation.cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code),
"cpuset_setaffinity");
}
ATF_TC_WITHOUT_HEAD(ktrace__cap_shm_open);
ATF_TC_BODY(ktrace__cap_shm_open, tc)
{
struct ktr_cap_fail violation;
sigset_t set = { };
pid_t pid;
int error;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
CHILD_REQUIRE(shm_open("/ktrace_shm", O_RDWR | O_CREAT,
0600) != -1);
CHILD_REQUIRE(shm_unlink("/ktrace_shm") != -1);
exit(0);
}
cap_trace_child(pid, &violation, 1);
ATF_REQUIRE_EQ(violation.cap_type, CAPFAIL_NAMEI);
error = syscallabi(violation.cap_svflags);
ATF_REQUIRE_STREQ(sysdecode_syscallname(error, violation.cap_code),
"shm_open2");
ATF_REQUIRE_STREQ(violation.cap_data.cap_path, "/ktrace_shm");
}
ATF_TC(ktrace__setuid_exec);
ATF_TC_HEAD(ktrace__setuid_exec, tc)
{
atf_tc_set_md_var(tc, "require.user", "unprivileged");
}
ATF_TC_BODY(ktrace__setuid_exec, tc)
{
struct ktr_header header;
struct ktr_syscall *syscall;
sigset_t set = { };
off_t off, off1;
ssize_t n;
pid_t pid;
int error, fd;
ATF_REQUIRE(sigaddset(&set, SIGUSR1) != -1);
ATF_REQUIRE(sigprocmask(SIG_BLOCK, &set, NULL) != -1);
ATF_REQUIRE((pid = fork()) != -1);
if (pid == 0) {
CHILD_REQUIRE(sigwait(&set, &error) != -1);
CHILD_REQUIRE_EQ(error, SIGUSR1);
execve("/usr/bin/su", (char *[]){ "su", "whoami", NULL }, NULL);
_exit(0);
}
fd = trace_child(pid, KTRFAC_SYSCALL, 1);
n = read(fd, &header, sizeof(header));
ATF_REQUIRE(n >= 0);
ATF_REQUIRE_EQ((size_t)n, sizeof(header));
ATF_REQUIRE_EQ(header.ktr_pid, pid);
ATF_REQUIRE(header.ktr_len >= (int)sizeof(*syscall));
syscall = xmalloc(header.ktr_len);
n = read(fd, syscall, header.ktr_len);
ATF_REQUIRE(n >= 0);
ATF_REQUIRE_EQ(n, header.ktr_len);
if (syscall->ktr_code == SYS_sigwait) {
free(syscall);
n = read(fd, &header, sizeof(header));
ATF_REQUIRE(n >= 0);
ATF_REQUIRE_EQ((size_t)n, sizeof(header));
ATF_REQUIRE_EQ(header.ktr_pid, pid);
ATF_REQUIRE(header.ktr_len >= (int)sizeof(*syscall));
syscall = xmalloc(header.ktr_len);
n = read(fd, syscall, header.ktr_len);
ATF_REQUIRE(n >= 0);
ATF_REQUIRE_EQ(n, header.ktr_len);
}
ATF_REQUIRE_EQ(syscall->ktr_code, SYS_execve);
free(syscall);
off = lseek(fd, 0, SEEK_CUR);
ATF_REQUIRE(off != -1);
off1 = lseek(fd, 0, SEEK_END);
ATF_REQUIRE(off1 != -1);
ATF_REQUIRE_EQ(off, off1);
ATF_REQUIRE(close(fd) == 0);
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, ktrace__cap_not_capable);
ATF_TP_ADD_TC(tp, ktrace__cap_increase_rights);
ATF_TP_ADD_TC(tp, ktrace__cap_syscall);
ATF_TP_ADD_TC(tp, ktrace__cap_signal);
ATF_TP_ADD_TC(tp, ktrace__cap_proto);
ATF_TP_ADD_TC(tp, ktrace__cap_sockaddr);
ATF_TP_ADD_TC(tp, ktrace__cap_namei);
ATF_TP_ADD_TC(tp, ktrace__cap_cpuset);
ATF_TP_ADD_TC(tp, ktrace__cap_shm_open);
ATF_TP_ADD_TC(tp, ktrace__setuid_exec);
return (atf_no_error());
}