Skip to content

Instantly share code, notes, and snippets.

@Blub
Created July 8, 2019 17:58
Show Gist options
  • Save Blub/84758c3bd28adc10751b9ee510ddc4f5 to your computer and use it in GitHub Desktop.
Save Blub/84758c3bd28adc10751b9ee510ddc4f5 to your computer and use it in GitHub Desktop.
lxc seccomp notify proxy example
#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 = &notif, .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