use seccomp-filter to reduce privileges

This commit is contained in:
leitner
2015-05-08 00:26:37 +00:00
parent 2479167b37
commit ed9c3d238e

View File

@@ -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 <sys/prctl.h>
#include <linux/unistd.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#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