From ed9c3d238e6b7d39831dabb4af3aba4424d7679a Mon Sep 17 00:00:00 2001 From: leitner Date: Fri, 8 May 2015 00:26:37 +0000 Subject: [PATCH] use seccomp-filter to reduce privileges --- tinyldap.c | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 176 insertions(+), 7 deletions(-) diff --git a/tinyldap.c b/tinyldap.c index b6b9cae..8c5abdb 100644 --- a/tinyldap.c +++ b/tinyldap.c @@ -47,6 +47,8 @@ #define debug 0 #endif +const char journalfilename[]="journal"; + #define HUGE_SIZE_FOR_SANITY_CHECKS 1024*1024 /* basic operation: the whole data file is mmapped read-only at the beginning and stays there. */ @@ -1725,7 +1727,7 @@ authfailure: /* 3. apply modifications to record to get new record */ if (!applymodreq(hn,&mr,&sre)) { /* 4. write record to journal */ - int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600); + int fd=open(journalfilename,O_WRONLY|O_APPEND|O_CREAT,0600); if (fd==-1) err=operationsError; else { @@ -1786,7 +1788,7 @@ authfailure: } if (err==success) { /* 3. write record to journal */ - int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600); + int fd=open(journalfilename,O_WRONLY|O_APPEND|O_CREAT,0600); if (fd==-1) err=operationsError; else { @@ -1858,7 +1860,7 @@ authfailure: } if (err==success) { /* 3. write record to journal */ - int fd=open("journal",O_WRONLY|O_APPEND|O_CREAT,0600); + int fd=open(journalfilename,O_WRONLY|O_APPEND|O_CREAT,0600); if (fd==-1) err=operationsError; else { @@ -2050,7 +2052,7 @@ static void readjournal() { ldif_parse_callback=parse_callback; mduptab_init(&attributes); mduptab_init(&classes); - if (ldif_parse("journal",0,&ss_journal)) { + if (ldif_parse(journalfilename,0,&ss_journal)) { buffer_putsflush(buffer_2,"Failed to parse journal!\n"); exit(1); } @@ -2083,7 +2085,7 @@ resetjournal: return; } /* the data file did not change. Maybe the journal did. */ - if (stat("journal",&new_journal)==-1) { + if (stat(journalfilename,&new_journal)==-1) { /* no journal; that means: * a) there never was one, totaly read-only data * b) there was one, but it has now been incorporated into the main database @@ -2108,7 +2110,7 @@ resetjournal: int notkosher=0; if (new_journal.st_size>ss_journal.st_size && ss_journal.st_size>2) { int fd; - fd=open("journal",O_RDONLY); + fd=open(journalfilename,O_RDONLY); if (fd!=-1) { char buf[2]; lseek(fd,ss_journal.st_size-2,SEEK_SET); @@ -2122,7 +2124,7 @@ resetjournal: buffer_putsflush(buffer_2,"Unsanctioned journal editing detected! Re-reading journal.\n"); goto resetjournal; } - if (ldif_parse("journal",ss_journal.st_size,&ss_journal)) { + if (ldif_parse(journalfilename,ss_journal.st_size,&ss_journal)) { buffer_putsflush(buffer_2,"Failed to parse journal!\n"); exit(1); } @@ -2329,6 +2331,169 @@ static void answerwithjournal(struct SearchRequest* sr,long messageid,int out) { } } +#if !defined(NO_SECCOMP) && defined(__linux__) && (defined(__i386__) || defined(__x86_64__)) && !defined(STANDALONE) +#define SECCOMP +#endif +#ifdef SECCOMP + +#include +#include +#include +#include +#include + +#ifndef SECCOMP_MODE_FILTER +# define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ +# define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +# define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ +# define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; +#endif +#ifndef SYS_SECCOMP +# define SYS_SECCOMP 1 +#endif + +#define syscall_nr (offsetof(struct seccomp_data, nr)) + +#if defined(__i386__) +# define REG_SYSCALL REG_EAX +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define REG_SYSCALL REG_RAX +# define ARCH_NR AUDIT_ARCH_X86_64 +#else +# error "Platform does not support seccomp filter yet" +#endif + +#define ALLOW_SYSCALL(name) \ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW) + +static int install_syscall_filter(void) { + /* Linux allows a process to restrict itself (and potential children) + * in what syscalls can be issued. The mechanism is called + * seccomp-filter or "seccomp mode 2". It works by reusing the + * Berkeley Packet Filter, which is meant for PCAP-style packet + * filtering expressions like "only TCP packets, please". But it is + * really a bytecode that has to be passed inside an array, and each + * instruction is constructed using scary looking macros. The basics + * are not so bad, however. We have two registers, one accumulator + * and one index register (which is not used in this part of the + * code), and instead of a network packet we are operating on a + * certain struct with the syscall info, which is called seccomp_data + * (reproduced above). */ + struct sock_filter filter[] = { + /* validate architecture to avoid x32-on-x86_64 syscall aliasing shenanigans */ + + /* BPF_LD = load, BPF_W = word, BPF_ABS = absolute offset */ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, arch)), + /* BPF_JMP+BPF_JEQ+BPF_K = compare accumulator to constant (in our + * case, ARCH_NR), and skip the next instruction if equal */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), + /* "return SECCOMP_RET_KILL", tell seccomp to kill the process */ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), + + /* load the syscall number */ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, nr)), + + /* and now a list of allowed syscalls */ + ALLOW_SYSCALL(rt_sigreturn), +#ifdef __NR_sigreturn + ALLOW_SYSCALL(sigreturn), +#endif + ALLOW_SYSCALL(exit_group), + ALLOW_SYSCALL(exit), + + ALLOW_SYSCALL(read), + ALLOW_SYSCALL(write), + + /* we need a special case for open. + * we want open to succeed, but only if it's on the journal */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_open, 0, 8), + /* it's open(2). Load first argument into accumulator (first + * argument is filename, second is mode). */ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, args[0])), + /* make sure it is journalfilename */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (uintptr_t)journalfilename, 0, 1), + /* "return SECCOMP_RET_ALLOW", tell seccomp to allow the syscall */ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), + /* If we get here, it's not journalfilename. The only other file we allow is the data + * file, and that is only opened read-only. So check that mode is O_RDONLY */ + BPF_STMT(BPF_LD+BPF_W+BPF_ABS, offsetof(struct seccomp_data, args[1])), + /* mode & O_ACCMODE */ + BPF_STMT(BPF_ALU+BPF_AND+BPF_K, O_ACCMODE), + /* if (mode & O_ACCMODE) == O_RDONLY goto ALLOW */ + BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, O_RDONLY, 0, 1), + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), + /* otherwise kill the process */ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL), + + ALLOW_SYSCALL(close), + + /* for syslog() */ +#ifdef __NR__llseek + ALLOW_SYSCALL(_llseek), +#else + ALLOW_SYSCALL(lseek), +#endif +#ifdef __NR_fstat64 + ALLOW_SYSCALL(fstat64), + ALLOW_SYSCALL(stat64), +#else + ALLOW_SYSCALL(fstat), + ALLOW_SYSCALL(stat), +#endif + + /* for reading from the socket */ +#ifdef DEBUG + ALLOW_SYSCALL(poll), +#endif + + /* for malloc / calloc */ +#ifdef __dietlibc__ + ALLOW_SYSCALL(mmap), + ALLOW_SYSCALL(mremap), +#else + ALLOW_SYSCALL(brk), + ALLOW_SYSCALL(mmap), +#endif + +#ifdef __NR_socketcall + ALLOW_SYSCALL(socketcall), +#else + ALLOW_SYSCALL(setsockopt), +#endif + + /* if none of these syscalls matched, kill the process */ + BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL) + }; + struct sock_fprog prog = { + .len = (unsigned short)(sizeof(filter)/sizeof(filter[0])), + .filter = filter + }; + + /* see linux/Documentation/prctl/no_new_privs.txt */ + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + /* if this fails, we are running on an ancient kernel without + * seccomp support; nothing we can do about it, really. */ + return -1; + } + + /* see linux/Documentation/prctl/seccomp_filter.txt */ + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + /* if this happens, we are running on a kernel without seccomp + * filters support; nothing we can do about it, really. */ + return -1; + } + return 0; +} +#endif + /* _ _ __ ___ __ _(_)_ __ @@ -2348,6 +2513,10 @@ int main(int argc,char* argv[]) { map_datafile(argc>1?argv[1]:"data"); +#ifdef SECCOMP + install_syscall_filter(); +#endif + readjournal(); #if 0