clatd: implement seccomp system call protection

but only enable it in enforcing mode on aarch64,
since that gets good test coverage via Pixel
on GoogleGuest ipv6-only wifi network
and (for example) T-Mobile US cellular.

For other architectures this will only result
in (automatically ratelimitted to at most 5/s)
audit logs for any 'unusual' system calls.

Test: TreeHugger, manually on Pixel on GoogleGuest wifi
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: Id14c6d9db8d7b4652c7358cac101a82ef09843e0
This commit is contained in:
Maciej Żenczykowski 2025-03-14 12:44:50 -07:00
commit 5ae193ae36
2 changed files with 136 additions and 2 deletions

View file

@ -48,7 +48,7 @@ struct tun_data;
// plus some extra just-in-case headroom, because it doesn't hurt.
#define MAXDUMPLEN (64 + MAXMTU)
#define CLATD_VERSION "1.7"
#define CLATD_VERSION "1.8"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

View file

@ -18,12 +18,17 @@
#include <arpa/inet.h>
#include <errno.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <netinet/in.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/personality.h>
#include <sys/prctl.h>
#include <sys/utsname.h>
#include <unistd.h>
@ -53,6 +58,131 @@ void print_help() {
printf("-w [write socket descriptor number]\n");
}
// Load the architecture identifier (AUDIT_ARCH_* constant)
#define BPF_SECCOMP_LOAD_AUDIT_ARCH \
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, arch))
// Load the system call number
#define BPF_SECCOMP_LOAD_SYSCALL_NR \
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, nr))
#if __BYTE_ORDER == __LITTLE_ENDIAN
// Load the system call argument n, where n is [0..5]
#define BPF_SECCOMP_LOAD_SYSCALL_ARG_LO32(n) \
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]))
#define BPF_SECCOMP_LOAD_SYSCALL_ARG_HI32(n) \
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, offsetof(struct seccomp_data, args[n]) + 4)
#else
#error "Not a little endian architecture?"
#endif
// Allow the system call
#define BPF_SECCOMP_ALLOW BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
// Allow (but 'audit' log) the system call
#define BPF_SECCOMP_LOG BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_LOG)
// Reject the system call (kill thread)
#define BPF_SECCOMP_KILL BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL)
// Note arguments to BPF_JUMP(opcode, operand, true_offset, false_offset)
// If not equal, jump over count instructions
#define BPF_JUMP_IF_NOT_EQUAL(v, count) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), 0, (count))
// If equal, jump over count instructions
#define BPF_JUMP_IF_EQUAL(v, count) \
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, (v), (count), 0)
// *TWO* instructions: compare and if not equal jump over the allow statement
#define BPF2_SECCOMP_ALLOW_IF_EQUAL(v) \
BPF_JUMP_IF_NOT_EQUAL((v), 1), \
BPF_SECCOMP_ALLOW
// *TWO* instructions: compare and if not equal jump over the log statement
#define BPF2_SECCOMP_LOG_IF_EQUAL(v) \
BPF_JUMP_IF_NOT_EQUAL((v), 1), \
BPF_SECCOMP_LOG
// *TWO* instructions: compare and if equal jump over the kill statement
#define BPF2_SECCOMP_KILL_IF_NOT_EQUAL(v) \
BPF_JUMP_IF_EQUAL((v), 1), \
BPF_SECCOMP_KILL
// Android only supports the following 5 little endian architectures
#if defined(__aarch64__) && defined(__LP64__)
#define MY_AUDIT_ARCH AUDIT_ARCH_AARCH64
#elif defined(__arm__) && defined(__ILP32__)
#define MY_AUDIT_ARCH AUDIT_ARCH_ARM
#elif defined(__i386__) && defined(__ILP32__)
#define MY_AUDIT_ARCH AUDIT_ARCH_I386
#elif defined(__x86_64__) && defined(__LP64__)
#define MY_AUDIT_ARCH AUDIT_ARCH_X86_64
#elif defined(__riscv) && defined(__LP64__)
#define MY_AUDIT_ARCH AUDIT_ARCH_RISCV64
#else
#error "Unknown AUDIT_ARCH_* architecture."
#endif
void enable_seccomp(void) {
static const struct sock_filter filter[] = {
BPF_SECCOMP_LOAD_AUDIT_ARCH,
BPF2_SECCOMP_KILL_IF_NOT_EQUAL(MY_AUDIT_ARCH),
BPF_SECCOMP_LOAD_SYSCALL_NR, // aarch64
// main event loop:
// ppoll ( read sendmsg | recvmsg writev )
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_ppoll), // 73
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_read), // 63
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_sendmsg), // 211
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_recvmsg), // 212
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_writev), // 66
// logging: getuid writev
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_getuid), // 174
// inbound signal (SIGTERM) processing
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_rt_sigreturn), // 139
// sleep(n)
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_nanosleep), // 101
// _exit(0)
BPF2_SECCOMP_ALLOW_IF_EQUAL(__NR_exit_group), // 94
#if defined(__aarch64__)
// Pixels are aarch64 - if we break clatd functionality on them,
// we *will* notice on GoogleGuest WiFi network (which is ipv6 only)
BPF_SECCOMP_KILL,
#else
// All other architectures: generate audit lines visible in dmesg and logcat
BPF_SECCOMP_LOG,
#endif
};
static const struct sock_fprog prog = {
.len = (unsigned short)ARRAY_SIZE(filter),
.filter = (struct sock_filter *)filter,
};
// https://man7.org/linux/man-pages/man2/PR_SET_NO_NEW_PRIVS.2const.html
// required to allow non-privileged seccomp filter installation
int rv = prctl(PR_SET_NO_NEW_PRIVS, 1L, 0L, 0L, 0L);
if (rv) {
logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) = %d [%d]", rv, errno);
exit(1);
}
// https://man7.org/linux/man-pages/man2/PR_SET_SECCOMP.2const.html
// but see also https://man7.org/linux/man-pages/man2/seccomp.2.html
rv = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0L, 0L);
if (rv) {
logmsg(ANDROID_LOG_FATAL, "prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog, 0, 0) = %d [%d]", rv, errno);
exit(1);
}
}
/* function: main
* allocate and setup the tun device, then run the event loop
*/
@ -241,6 +371,8 @@ int main(int argc, char **argv) {
// TODO: actually perform true DAD
send_dad(tunnel.write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
enable_seccomp(); // WARNING: from this point forward very limited system calls available.
event_loop(&tunnel);
if (sigterm) {
@ -255,5 +387,7 @@ int main(int argc, char **argv) {
logmsg(ANDROID_LOG_INFO, "Clatd on %s %s SIGTERM", uplink_interface,
sigterm ? "received" : "timed out waiting for");
}
return 0;
// Using _exit() here avoids 4 mprotect() syscalls triggered via 'exit(0)' or 'return 0'
_exit(0);
}