Spaces:
Running
Running
File size: 6,146 Bytes
3052732 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | /* Primitive-only seccomp user-notification qualification harness.
*
* This is deliberately outside the supervisor state machine. It proves the
* kernel can hold execve/execveat/fexecve before entry and that listener loss
* does not turn the boundary into an allow path. It is not a release helper.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <signal.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
extern char **environ;
static int send_fd(int socket_fd, int descriptor) {
char byte = 'F';
struct iovec iov = {.iov_base = &byte, .iov_len = 1};
char control[CMSG_SPACE(sizeof(int))];
memset(control, 0, sizeof(control));
struct msghdr message = {.msg_iov = &iov, .msg_iovlen = 1,
.msg_control = control, .msg_controllen = sizeof(control)};
struct cmsghdr *header = CMSG_FIRSTHDR(&message);
header->cmsg_level = SOL_SOCKET;
header->cmsg_type = SCM_RIGHTS;
header->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(header), &descriptor, sizeof(int));
return sendmsg(socket_fd, &message, 0) == 1 ? 0 : -1;
}
static int receive_fd(int socket_fd) {
char byte = 0;
struct iovec iov = {.iov_base = &byte, .iov_len = 1};
char control[CMSG_SPACE(sizeof(int))];
memset(control, 0, sizeof(control));
struct msghdr message = {.msg_iov = &iov, .msg_iovlen = 1,
.msg_control = control, .msg_controllen = sizeof(control)};
if (recvmsg(socket_fd, &message, 0) != 1) return -1;
struct cmsghdr *header = CMSG_FIRSTHDR(&message);
if (!header || header->cmsg_level != SOL_SOCKET || header->cmsg_type != SCM_RIGHTS) return -1;
int descriptor = -1;
memcpy(&descriptor, CMSG_DATA(header), sizeof(int));
return descriptor;
}
static int install_filter(void) {
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, SYS_execve, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_execveat, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
};
struct sock_fprog program = {.len = sizeof(filter) / sizeof(filter[0]), .filter = filter};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) return -1;
return (int)syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER,
SECCOMP_FILTER_FLAG_NEW_LISTENER, &program);
}
static void attempt_exec(const char *mode, const char *marker) {
char command[512];
snprintf(command, sizeof(command), "printf ENTERED > %s", marker);
char *const argv[] = {"/bin/sh", "-c", command, NULL};
if (strcmp(mode, "execveat") == 0) {
syscall(SYS_execveat, AT_FDCWD, "/bin/sh", argv, environ, 0);
} else if (strcmp(mode, "fexecve") == 0) {
int descriptor = open("/bin/sh", O_PATH | O_CLOEXEC);
if (descriptor >= 0) {
syscall(SYS_execveat, descriptor, "", argv, environ, AT_EMPTY_PATH);
close(descriptor);
}
} else {
execve("/bin/sh", argv, environ);
}
_exit(errno == 0 ? 127 : 100 + (errno & 63));
}
static int child_main(int socket_fd, const char *mode, const char *marker, int fanout) {
int listener = install_filter();
if (listener < 0 || send_fd(socket_fd, listener) < 0) _exit(2);
close(listener);
close(socket_fd);
if (fanout) {
pid_t children[16];
int count = fanout > 16 ? 16 : fanout;
for (int i = 0; i < count; ++i) {
children[i] = fork();
if (children[i] == 0) attempt_exec(mode, marker);
if (children[i] < 0) _exit(3);
}
for (int i = 0; i < count; ++i) waitpid(children[i], NULL, 0);
} else {
attempt_exec(mode, marker);
}
_exit(0);
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "usage: %s MODE MARKER [allow|deny|close|fanout=N]\n", argv[0]);
return 64;
}
const char *mode = argv[1];
const char *marker = argv[2];
int allow = argc > 3 && strcmp(argv[3], "allow") == 0;
int close_listener = argc > 3 && strcmp(argv[3], "close") == 0;
int fanout = 0;
if (argc > 3 && strncmp(argv[3], "fanout=", 7) == 0) fanout = atoi(argv[3] + 7);
int channel[2];
if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, channel) < 0) return 2;
pid_t child = fork();
if (child < 0) return 2;
if (child == 0) {
close(channel[0]);
child_main(channel[1], mode, marker, fanout);
}
close(channel[1]);
int listener = receive_fd(channel[0]);
if (listener < 0) return 3;
int expected = fanout ? (fanout > 16 ? 16 : fanout) : 1;
if (close_listener) {
close(listener);
} else {
for (int i = 0; i < expected; ++i) {
struct seccomp_notif *request = calloc(1, sizeof(*request));
struct seccomp_notif_resp *response = calloc(1, sizeof(*response));
if (!request || !response || ioctl(listener, SECCOMP_IOCTL_NOTIF_RECV, request) < 0) return 4;
if (ioctl(listener, SECCOMP_IOCTL_NOTIF_ID_VALID, &request->id) < 0) return 5;
response->id = request->id;
if (allow) response->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE;
else response->error = -EPERM;
if (ioctl(listener, SECCOMP_IOCTL_NOTIF_SEND, response) < 0) return 6;
free(request);
free(response);
}
close(listener);
}
close(channel[0]);
int status = 0;
waitpid(child, &status, 0);
if (!WIFEXITED(status)) return 7;
if (close_listener) return access(marker, F_OK) == 0 ? 8 : 0;
if (allow) return access(marker, F_OK) == 0 ? 0 : 9;
return access(marker, F_OK) == 0 ? 10 : 0;
}
|