diff options
-rw-r--r-- | HACKING | 27 | ||||
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | configure.ac | 15 | ||||
-rw-r--r-- | linux_priv.c | 111 | ||||
-rw-r--r-- | memcached.c | 61 | ||||
-rw-r--r-- | memcached.h | 8 | ||||
-rw-r--r-- | t/issue_67.t | 4 | ||||
-rw-r--r-- | t/lib/MemcachedTest.pm | 9 | ||||
-rw-r--r-- | t/misbehave.t | 20 | ||||
-rw-r--r-- | testapp.c | 4 | ||||
-rw-r--r-- | thread.c | 4 |
12 files changed, 270 insertions, 2 deletions
@@ -47,6 +47,33 @@ If you export the environment variable T_MEMD_USE_DAEMON="127.0.0.1:11211" the tests will use an existing daemon at that address. +* Debugging seccomp issues + +If new functionality fails when built with seccomp / drop privileges +support, it can be debugged in one of two ways: + +Run the memcached via strace. For example: + + strace -o /tmp/memcache.strace -f -- ./memcached + less /tmp/memcache.strace + +And look for calls which failed due to access restriction. They will +show up with result: "-1 (errno 13)". Then add them to linux_priv.c. + +Alternatively, change the definition in linux_priv.c to: + + #define DENY_ACTION SCMP_ACT_TRAP + +and the process will crash with a coredump on all policy violations. +In strace output those can be seen as: + + SIGSYS {si_signo=SIGSYS, si_code=SYS_SECCOMP, + si_call_addr=0x358a443454d, si_syscall=__NR_write, + si_arch=AUDIT_ARCH_X86_64} --- + +In that output, the si_syscall shows which operation has been +blocked. In this case that's `write()`. + * Sending patches See current instructions at http://contributing.appspot.com/memcached diff --git a/Makefile.am b/Makefile.am index a5a0930..e56fbf9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,11 @@ if BUILD_SOLARIS_PRIVS memcached_SOURCES += solaris_priv.c endif +if BUILD_LINUX_PRIVS +memcached_SOURCES += linux_priv.c +LDFLAGS += -lseccomp +endif + if ENABLE_SASL memcached_SOURCES += sasl_defs.c endif @@ -61,6 +66,8 @@ memcached_debug_DEPENDENCIES += memcached_debug_dtrace.o CLEANFILES += memcached_dtrace.o memcached_debug_dtrace.o endif +memcached_debug_CFLAGS += -DMEMCACHED_DEBUG + memcached_dtrace.h: memcached_dtrace.d ${DTRACE} -h -s memcached_dtrace.d sed -e 's,void \*,const void \*,g' memcached_dtrace.h | \ @@ -18,6 +18,8 @@ list to ask questions, github issues aren't seen by everyone! ## Dependencies * libevent, http://www.monkey.org/~provos/libevent/ (libevent-dev) +* libseccomp, (optional, linux) - enables process restrictions for better + security. ## Environment diff --git a/configure.ac b/configure.ac index 49068e0..7fa848b 100644 --- a/configure.ac +++ b/configure.ac @@ -82,6 +82,9 @@ fi AM_PROG_CC_C_O AC_PROG_INSTALL +AC_ARG_ENABLE(seccomp, + [AS_HELP_STRING([--enable-seccomp],[Enable seccomp restrictions])]) + AC_ARG_ENABLE(sasl, [AS_HELP_STRING([--enable-sasl],[Enable SASL authentication])]) @@ -563,7 +566,19 @@ AC_CHECK_FUNCS(setppriv, [ ], []) ],[]) +AS_IF([test "x$enable_seccomp" = "xyes" ], [ + AC_CHECK_LIB(seccomp, seccomp_rule_add, [ + AC_DEFINE([HAVE_DROP_PRIVILEGES], 1, + [Define this if you have an implementation of drop_privileges()]) + build_linux_privs=yes + AC_DEFINE([HAVE_DROP_WORKER_PRIVILEGES], 1, + [Define this if you have an implementation of drop_worker_privileges()]) + build_linux_privs=yes + ], []) +]) + AM_CONDITIONAL([BUILD_SOLARIS_PRIVS],[test "$build_solaris_privs" = "yes"]) +AM_CONDITIONAL([BUILD_LINUX_PRIVS],[test "$build_linux_privs" = "yes"]) AC_CHECK_HEADER(umem.h, [ AC_DEFINE([HAVE_UMEM_H], 1, diff --git a/linux_priv.c b/linux_priv.c new file mode 100644 index 0000000..811d6e8 --- /dev/null +++ b/linux_priv.c @@ -0,0 +1,111 @@ +#include "config.h" +#include <seccomp.h> +#include <errno.h> +#include <stdlib.h> +#include "memcached.h" + +// In the future when the system is more tested this could be switched +// to SCMP_ACT_KILL instead. +#define DENY_ACTION SCMP_ACT_ERRNO(EACCES) + +void drop_privileges(void) { + scmp_filter_ctx ctx = seccomp_init(DENY_ACTION); + if (ctx == NULL) { + return; + } + + int rc = 0; + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_wait), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept4), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(accept), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(shmctl), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); + +#ifdef MEMCACHED_DEBUG + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); +#endif + + if (rc != 0) { + goto fail; + } + + rc = seccomp_load(ctx); + if (rc < 0) { + goto fail; + } + +fail: + seccomp_release(ctx); +} + +void drop_worker_privileges(void) { + scmp_filter_ctx ctx = seccomp_init(DENY_ACTION); + if (ctx == NULL) { + return; + } + + int rc = 0; + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigreturn), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_wait), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(epoll_ctl), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpeername), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrusage), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mremap), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0); + + // for spawning the LRU crawler + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(set_robust_list), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(madvise), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); + + // stat + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getsockname), 0); + + if (settings.shutdown_command) { + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tgkill), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettid), 0); + } + + if (settings.relaxed_privileges) { + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0); + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); + } else { + rc |= seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 1, SCMP_A0(SCMP_CMP_EQ, 1)); + } + + if (rc != 0) { + goto fail; + } + + rc = seccomp_load(ctx); + if (rc < 0) { + goto fail; + } + +fail: + seccomp_release(ctx); +} diff --git a/memcached.c b/memcached.c index efa5337..cc64dc8 100644 --- a/memcached.c +++ b/memcached.c @@ -258,6 +258,10 @@ static void settings_init(void) { settings.crawls_persleep = 1000; settings.logger_watcher_buf_size = LOGGER_WATCHER_BUF_SIZE; settings.logger_buf_size = LOGGER_BUF_SIZE; + settings.drop_privileges = true; +#ifdef MEMCACHED_DEBUG + settings.relaxed_privileges = false; +#endif } /* @@ -3841,6 +3845,31 @@ static void process_verbosity_command(conn *c, token_t *tokens, const size_t nto return; } +#ifdef MEMCACHED_DEBUG +static void process_misbehave_command(conn *c) { + int allowed = 0; + + // try opening new TCP socket + int i = socket(AF_INET, SOCK_STREAM, 0); + if (i != -1) { + allowed++; + close(i); + } + + // try executing new commands + system("sleep 0"); + if (i != -1) { + allowed++; + } + + if (allowed) { + out_string(c, "ERROR"); + } else { + out_string(c, "OK"); + } +} +#endif + static void process_slabs_automove_command(conn *c, token_t *tokens, const size_t ntokens) { unsigned int level; double ratio; @@ -4281,6 +4310,11 @@ static void process_command(conn *c, char *command) { process_verbosity_command(c, tokens, ntokens); } else if (ntokens >= 3 && strcmp(tokens[COMMAND_TOKEN].value, "lru") == 0) { process_lru_command(c, tokens, ntokens); +#ifdef MEMCACHED_DEBUG + // commands which exist only for testing the memcached's security protection + } else if (ntokens == 2 && (strcmp(tokens[COMMAND_TOKEN].value, "misbehave") == 0)) { + process_misbehave_command(c); +#endif } else { out_string(c, "ERROR"); } @@ -5606,6 +5640,13 @@ static void usage(void) { " - modern: enables options which will be default in future.\n" " currently: nothing\n" " - no_modern: uses defaults of previous major version (1.4.x)\n" +#ifdef HAVE_DROP_PRIVILEGES + " - no_drop_privileges: Disable drop_privileges in case it causes issues with\n" + " some customisation.\n" +#ifdef MEMCACHED_DEBUG + " - relaxed_privileges: Running tests requires extra privileges.\n" +#endif +#endif ); return; } @@ -5917,6 +5958,10 @@ int main (int argc, char **argv) { INLINE_ASCII_RESP, NO_LRU_CRAWLER, NO_LRU_MAINTAINER, + NO_DROP_PRIVILEGES, +#ifdef MEMCACHED_DEBUG + RELAXED_PRIVILEGES, +#endif }; char *const subopts_tokens[] = { [MAXCONNS_FAST] = "maxconns_fast", @@ -5952,6 +5997,10 @@ int main (int argc, char **argv) { [INLINE_ASCII_RESP] = "inline_ascii_resp", [NO_LRU_CRAWLER] = "no_lru_crawler", [NO_LRU_MAINTAINER] = "no_lru_maintainer", + [NO_DROP_PRIVILEGES] = "no_drop_privileges", +#ifdef MEMCACHED_DEBUG + [RELAXED_PRIVILEGES] = "relaxed_privileges", +#endif NULL }; @@ -6503,6 +6552,14 @@ int main (int argc, char **argv) { start_lru_crawler = false; start_lru_maintainer = false; break; + case NO_DROP_PRIVILEGES: + settings.drop_privileges = false; + break; +#ifdef MEMCACHED_DEBUG + case RELAXED_PRIVILEGES: + settings.relaxed_privileges = true; + break; +#endif default: printf("Illegal suboption \"%s\"\n", subopts_value); return 1; @@ -6801,7 +6858,9 @@ int main (int argc, char **argv) { } /* Drop privileges no longer needed */ - drop_privileges(); + if (settings.drop_privileges) { + drop_privileges(); + } /* Initialize the uriencode lookup table. */ uriencode_init(); diff --git a/memcached.h b/memcached.h index 3bfcb11..3332360 100644 --- a/memcached.h +++ b/memcached.h @@ -381,6 +381,8 @@ struct settings { int idle_timeout; /* Number of seconds to let connections idle */ unsigned int logger_watcher_buf_size; /* size of logger's per-watcher buffer */ unsigned int logger_buf_size; /* size of per-thread logger buffer */ + bool drop_privileges; /* Whether or not to drop unnecessary process privileges */ + bool relaxed_privileges; /* Relax process restrictions when running testapp */ }; extern struct stats stats; @@ -684,6 +686,12 @@ extern void drop_privileges(void); #define drop_privileges() #endif +#if HAVE_DROP_WORKER_PRIVILEGES +extern void drop_worker_privileges(void); +#else +#define drop_worker_privileges() +#endif + /* If supported, give compiler hints for branch prediction. */ #if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) #define __builtin_expect(x, expected_value) (x) diff --git a/t/issue_67.t b/t/issue_67.t index c6b8b4c..e7eeff4 100644 --- a/t/issue_67.t +++ b/t/issue_67.t @@ -45,6 +45,10 @@ sub run_server { my $root = ''; $root = "-u root" if ($< == 0); + + # test build requires more privileges + $args .= " -o relaxed_privileges"; + my $cmd = "$builddir/timedrun 10 $exe $root $args"; unless($childpid) { diff --git a/t/lib/MemcachedTest.pm b/t/lib/MemcachedTest.pm index 947bc43..f4c6313 100644 --- a/t/lib/MemcachedTest.pm +++ b/t/lib/MemcachedTest.pm @@ -14,7 +14,7 @@ my $builddir = getcwd; my @unixsockets = (); @EXPORT = qw(new_memcached sleep mem_get_is mem_gets mem_gets_is mem_stats - supports_sasl free_port); + supports_sasl free_port supports_drop_priv); sub sleep { my $n = shift; @@ -149,6 +149,12 @@ sub supports_sasl { return 0; } +sub supports_drop_priv { + my $output = `$builddir/memcached-debug -h`; + return 1 if $output =~ /no_drop_privileges/i; + return 0; +} + sub new_memcached { my ($args, $passed_port) = @_; my $port = $passed_port; @@ -168,6 +174,7 @@ sub new_memcached { if ($< == 0) { $args .= " -u root"; } + $args .= " -o relaxed_privileges"; my $udpport; if ($args =~ /-l (\S+)/) { diff --git a/t/misbehave.t b/t/misbehave.t new file mode 100644 index 0000000..ccf88f5 --- /dev/null +++ b/t/misbehave.t @@ -0,0 +1,20 @@ +#!/usr/bin/perl + +use strict; +use Test::More; +use FindBin qw($Bin); +use lib "$Bin/lib"; +use MemcachedTest; + +if (supports_drop_priv()) { + plan tests => 1; +} else { + plan skip_all => 'Privilege drop not supported'; + exit 0; +} + +my $server = new_memcached(); +my $sock = $server->sock; + +print $sock "misbehave\r\n"; +is(scalar <$sock>, "OK\r\n", "did not allow misbehaving"); @@ -352,6 +352,10 @@ static pid_t start_server(in_port_t *port_out, bool daemon, int timeout) { #ifdef MESSAGE_DEBUG argv[arg++] = "-vvv"; #endif +#ifdef HAVE_DROP_PRIVILEGES + argv[arg++] = "-o"; + argv[arg++] = "relaxed_privileges"; +#endif argv[arg++] = NULL; assert(execv(argv[0], argv) != -1); } @@ -351,6 +351,10 @@ static void *worker_libevent(void *arg) { abort(); } + if (settings.drop_privileges) { + drop_worker_privileges(); + } + register_thread_initialized(); event_base_loop(me->base, 0); |