Created
July 8, 2019 17:58
-
-
Save Blub/84758c3bd28adc10751b9ee510ddc4f5 to your computer and use it in GitHub Desktop.
lxc seccomp notify proxy example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _GNU_SOURCE | |
# define _GNU_SOURCE | |
#endif | |
#include <errno.h> | |
#include <stdarg.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/epoll.h> | |
#include <sys/socket.h> | |
#include <sys/syscall.h> | |
#include <sys/types.h> | |
#include <sys/un.h> | |
#include <seccomp.h> | |
static struct seccomp_notif_sizes gSizes; | |
struct seccomp_notify_proxy_msg { | |
uint64_t __reserved; | |
pid_t monitor_pid; | |
pid_t init_pid; | |
struct seccomp_notif_sizes sizes; | |
uint64_t cookie_len; | |
/* followed by: seccomp_notif, seccomp_notif_resp, cookie */ | |
}; | |
static void _Noreturn | |
bail(const char *fmt, ...) { | |
va_list ap; | |
va_start(ap, fmt); | |
vfprintf(stderr, fmt, ap); | |
va_end(ap); | |
fprintf(stderr, "\n"); | |
exit(1); | |
} | |
static void | |
warn(const char *fmt, ...) { | |
va_list ap; | |
va_start(ap, fmt); | |
vfprintf(stderr, fmt, ap); | |
va_end(ap); | |
fprintf(stderr, "\n"); | |
} | |
static void _Noreturn | |
sysbail(const char *fmt, ...) { | |
int err = errno; | |
va_list ap; | |
va_start(ap, fmt); | |
vfprintf(stderr, fmt, ap); | |
va_end(ap); | |
fprintf(stderr, ": %s\n", strerror(err)); | |
exit(1); | |
} | |
static void | |
syswarn(const char *fmt, ...) { | |
int err = errno; | |
va_list ap; | |
va_start(ap, fmt); | |
vfprintf(stderr, fmt, ap); | |
va_end(ap); | |
fprintf(stderr, ": %s\n", strerror(err)); | |
} | |
static int | |
must_listen_on_path(const char *path) | |
{ | |
struct sockaddr_un addr = { | |
.sun_family = AF_UNIX, | |
.sun_path = {0}, | |
}; | |
size_t pathlen = strlen(path); | |
if (pathlen >= sizeof(addr.sun_path)) | |
bail("path too long"); | |
memcpy(addr.sun_path, path, pathlen); | |
int sock = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0); | |
if (sock < 0) | |
sysbail("failed to create socket"); | |
(void)unlink(path); | |
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) | |
sysbail("failed to bind to path"); | |
if (listen(sock, 10) != 0) | |
sysbail("failed to listen on socket"); | |
return sock; | |
} | |
static void | |
must_register_in(int epfd, int add_fd) | |
{ | |
int ret = epoll_ctl( | |
epfd, | |
EPOLL_CTL_ADD, | |
add_fd, | |
&((struct epoll_event) { | |
.events = EPOLLIN | EPOLLERR, | |
.data = ((union epoll_data){ | |
.fd = add_fd, | |
}), | |
}) | |
); | |
if (ret != 0) | |
sysbail("failed to add server socket to epoll"); | |
} | |
static void | |
handle_server(int epfd, int server) | |
{ | |
struct sockaddr_storage ss = {0}; | |
socklen_t length = sizeof(ss); | |
int ret = accept4(server, (struct sockaddr*)&ss, &length, SOCK_CLOEXEC); | |
if (ret < 0) | |
sysbail("failed to accept() client"); | |
must_register_in(epfd, ret); | |
} | |
static void | |
handle_client(int epfd, int client) | |
{ | |
(void)epfd; | |
(void)client; | |
printf("got client message\n"); | |
struct seccomp_notify_proxy_msg msg = {0}; | |
struct seccomp_notif notif; | |
struct seccomp_notif_resp resp; | |
char cookie[64]; | |
const size_t full_proxy_msg_size = sizeof(msg) + sizeof(notif) + sizeof(resp); | |
struct iovec iov[4] = { | |
{ .iov_base = &msg, .iov_len = sizeof(msg) }, | |
{ .iov_base = ¬if, .iov_len = sizeof(notif) }, | |
{ .iov_base = &resp, .iov_len = sizeof(resp) }, | |
{ .iov_base = cookie, .iov_len = sizeof(cookie) }, | |
}; | |
struct msghdr msghdr = { 0 }; | |
msghdr.msg_iov = iov; | |
msghdr.msg_iovlen = sizeof(iov) / sizeof(iov[0]); | |
// skipping the memfd for this test | |
// but include MSG_CMSG_CLOEXEC to not forget it later | |
ssize_t got = recvmsg(client, &msghdr, MSG_TRUNC | MSG_CMSG_CLOEXEC | MSG_NOSIGNAL); | |
if (got < 0) { | |
syswarn("recvmsg() failed, dropping client"); | |
close(client); | |
return; | |
} | |
if ((size_t)got < full_proxy_msg_size) { | |
warn("client sent incomplete packet, dropping"); | |
close(client); | |
return; | |
} | |
if (msg.__reserved != 0) { | |
warn("client has non-zero reserved fields, dropping"); | |
close(client); | |
return; | |
} | |
if (msg.sizes.seccomp_notif != gSizes.seccomp_notif | |
|| msg.sizes.seccomp_notif_resp != gSizes.seccomp_notif_resp | |
|| msg.sizes.seccomp_data != gSizes.seccomp_data) | |
{ | |
warn("client uses different seccomp sizes, dropping"); | |
close(client); | |
return; | |
} | |
printf(" syscall = %i\n", notif.data.nr); | |
switch (notif.data.nr) { | |
case SYS_mknod: | |
case SYS_mknodat: | |
// Let's use a distinct code for testing: | |
resp.val = -1; | |
resp.error = -ENOENT; | |
resp.flags = 0; | |
break; | |
default: | |
resp.val = -1; | |
resp.error = -ENOSYS; | |
resp.flags = 0; | |
break; | |
} | |
// reset msghdr: | |
memset(&msghdr, 0, sizeof(msghdr)); | |
msghdr.msg_iov = iov; | |
msghdr.msg_iovlen = (sizeof(iov) / sizeof(iov[0])) - 1; // without cookie | |
ssize_t sent = sendmsg(client, &msghdr, MSG_NOSIGNAL); | |
if (sent < 0) { | |
syswarn("failed to send seccomp response, dropping client"); | |
close(client); | |
return; | |
} | |
if ((size_t)sent != full_proxy_msg_size) { | |
warn("short sendmsg() call?: %zi != %zi", sent, full_proxy_msg_size); | |
close(client); | |
return; | |
} | |
printf(" sent response\n"); | |
} | |
int | |
main(int argc, char **argv) { | |
if (syscall(SYS_seccomp, SECCOMP_GET_NOTIF_SIZES, 0, &gSizes) != 0) | |
sysbail("failed to query seccomp notify struct sizes"); | |
if (gSizes.seccomp_notif != sizeof(struct seccomp_notif) | |
|| gSizes.seccomp_notif_resp != sizeof(struct seccomp_notif_resp) | |
|| gSizes.seccomp_data != sizeof(struct seccomp_data)) | |
{ | |
bail("seccomp notify struct size mismatch, don't know how to talk to kernel"); | |
} | |
if (argc != 2) | |
bail("usage: %s SOCKET_PATH", argv[0]); | |
const char *path = argv[1]; | |
int server = must_listen_on_path(path); | |
int epfd = epoll_create1(EPOLL_CLOEXEC); | |
if (epfd < 0) | |
sysbail("failed to create epoll fd"); | |
must_register_in(epfd, server); | |
const int max_events = 16; | |
struct epoll_event events[max_events]; | |
for (;;) { | |
int evcount = epoll_wait(epfd, events, max_events, -1); | |
if (evcount < 0) { | |
if (errno == EINTR) | |
continue; | |
sysbail("epoll_wait error"); | |
} | |
for (int i = 0; i != evcount; ++i) { | |
struct epoll_event *ev = &events[i]; | |
int fd = ev->data.fd; | |
if (fd == server) { | |
if (ev->events & EPOLLHUP) | |
bail("server socket hang up?"); | |
if (ev->events & EPOLLERR) | |
bail("server socket error"); | |
if (ev->events & EPOLLIN) | |
handle_server(epfd, fd); | |
else | |
warn("epic epoll failure"); | |
} else { | |
handle_client(epfd, fd); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment