summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HACKING27
-rw-r--r--Makefile.am7
-rw-r--r--README.md2
-rw-r--r--configure.ac15
-rw-r--r--linux_priv.c111
-rw-r--r--memcached.c61
-rw-r--r--memcached.h8
-rw-r--r--t/issue_67.t4
-rw-r--r--t/lib/MemcachedTest.pm9
-rw-r--r--t/misbehave.t20
-rw-r--r--testapp.c4
-rw-r--r--thread.c4
12 files changed, 270 insertions, 2 deletions
diff --git a/HACKING b/HACKING
index 60bfa9f..b8b62bd 100644
--- a/HACKING
+++ b/HACKING
@@ -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 | \
diff --git a/README.md b/README.md
index 86b85f3..935e4cd 100644
--- a/README.md
+++ b/README.md
@@ -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");
diff --git a/testapp.c b/testapp.c
index 7e3fe0e..ea8081b 100644
--- a/testapp.c
+++ b/testapp.c
@@ -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);
}
diff --git a/thread.c b/thread.c
index 6a8cd97..b02b0f9 100644
--- a/thread.c
+++ b/thread.c
@@ -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);