From 00e40dba232cf4f9c1943cea01147fb688e94e5e Mon Sep 17 00:00:00 2001 From: Vincent Bernat Date: Sat, 12 Oct 2013 16:00:40 +0200 Subject: [PATCH] seccomp: add support for seccomp through libseccomp The support is only for the monitor process (running as root). It is enabled when the monitor has been initiliazed, before the event loop. The monitor has to open a lot of files and read them (files in /proc, /sys). Moreover, for some files, it has to write to them (for example, stdout, /dev/log and for writing interface aliases). Therefore, there are many registered syscalls. It should be possible to filter more but this would require some efforts. Becuase it is difficult to reliably report errors to the user and we may have to execute arbitrary code because we need to resolve hostnames (and therefore, connect to nscd, LDAP or anything else handled by NSS), this does not seem to be reliable enough, yet. Moreover, displaying failed syscall is a bit hackhish. Maybe we could enable it by default if we change the default behaviour from killing the offending process to just return a failed errno. Or just logging the problem. --- NEWS | 1 + configure.ac | 15 +++- m4/seccomp.m4 | 19 ++++ src/daemon/Makefile.am | 13 +++ src/daemon/lldpd.h | 4 + src/daemon/priv-seccomp.c | 183 ++++++++++++++++++++++++++++++++++++++ src/daemon/priv.c | 8 +- 7 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 m4/seccomp.m4 create mode 100644 src/daemon/priv-seccomp.c diff --git a/NEWS b/NEWS index 7e599264..92e214b8 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,7 @@ lldpd (0.7.7) configurable through `lldpcli`. + Add support for "team" driver (alternative to bond devices). + Preliminary support for DTrace/systemtap. + + Preliminary support for seccomp (for monitor process). * Fixes: + Various bugs related to fixed point number handling (for coordinates in LLDP-MED) diff --git a/configure.ac b/configure.ac index 1982ed68..1e409e14 100644 --- a/configure.ac +++ b/configure.ac @@ -161,7 +161,7 @@ fi # XML AC_ARG_WITH([xml], - AC_HELP_STRING( + AS_HELP_STRING( [--with-xml], [Enable XML output via libxml2 @<:@default=no@:>@] )) @@ -171,7 +171,7 @@ fi # JSON AC_ARG_WITH([json], - AC_HELP_STRING( + AS_HELP_STRING( [--with-json], [Enable JSON output via Jansson @<:@default=no@:>@] )) @@ -179,6 +179,15 @@ if test x"$with_json" = x"yes"; then lldp_CHECK_JANSSON fi +# Seccomp +AC_ARG_WITH([seccomp], + AS_HELP_STRING( + [--with-seccomp], + [Enable seccomp support (with libseccomp) @<:@default=no@:>@]), + [], + [with_seccomp=no]) +lldp_CHECK_SECCOMP + # OS X launchd support lldp_ARG_WITH([launchddaemonsdir], [Directory for launchd configuration file (OSX)], [/Library/LaunchDaemons]) @@ -221,6 +230,7 @@ AM_CONDITIONAL([HAVE_CHECK], [test x"$have_check" = x"yes"]) AM_CONDITIONAL([USE_SNMP], [test x"$with_snmp" = x"yes"]) AM_CONDITIONAL([USE_XML], [test x"$with_xml" = x"yes"]) AM_CONDITIONAL([USE_JSON], [test x"$with_json" = x"yes"]) +AM_CONDITIONAL([USE_SECCOMP], [test x"$with_seccomp" = x"yes"]) AC_OUTPUT if test x"$LIBEVENT_EMBEDDED" = x; then @@ -251,6 +261,7 @@ cat <= 1], [ + AC_SUBST([SECCOMP_LIBS]) + AC_SUBST([SECCOMP_CFLAGS]) + AC_DEFINE_UNQUOTED([USE_SECCOMP], 1, [Define to indicate to enable seccomp support]) + with_seccomp=yes + ], [ + if test x"$with_seccomp" = x"yes"; then + AC_MSG_FAILURE([*** no seccomp support found]) + fi + with_seccomp=no + ]) + fi +]) diff --git a/src/daemon/Makefile.am b/src/daemon/Makefile.am index a0c5bc85..7e405245 100644 --- a/src/daemon/Makefile.am +++ b/src/daemon/Makefile.am @@ -91,6 +91,19 @@ liblldpd_la_CFLAGS += @NETSNMP_CFLAGS@ liblldpd_la_LIBADD += @NETSNMP_LIBS@ endif +# seccomp support +if USE_SECCOMP +BUILT_SOURCES += syscall-names.h +syscall-names.h: + echo "static const char *syscall_names[] = {" > $@ ;\ + echo "#include " | cpp -dM | grep '^#define __NR_' | \ + LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([0-9]+)(.*)/ [\2] = "\1",/p' >> $@ ;\ + echo "};" >> $@ +liblldpd_la_SOURCES += priv-seccomp.c syscall-names.h +liblldpd_la_CFLAGS += @SECCOMP_CFLAGS@ +liblldpd_la_LIBADD += @SECCOMP_LIBS@ +endif + ## lldpd lldpd_SOURCES = main.c lldpd_LDADD = liblldpd.la @LIBEVENT_LDFLAGS@ diff --git a/src/daemon/lldpd.h b/src/daemon/lldpd.h index a568b380..e37528b4 100644 --- a/src/daemon/lldpd.h +++ b/src/daemon/lldpd.h @@ -239,6 +239,10 @@ enum priv_cmd { PRIV_SNMP_SOCKET, }; +/* priv-seccomp.c */ +#ifdef USE_SECCOMP +int priv_seccomp_init(int, int); +#endif /* privsep_io.c */ int may_read(void *, size_t); diff --git a/src/daemon/priv-seccomp.c b/src/daemon/priv-seccomp.c new file mode 100644 index 00000000..79627528 --- /dev/null +++ b/src/daemon/priv-seccomp.c @@ -0,0 +1,183 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "lldpd.h" +#include +#include +#include +#include + +#include "syscall-names.h" +#include + +#ifndef SYS_SECCOMP +# define SYS_SECCOMP 1 +#endif + +#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" +# define REG_SYSCALL 0 +# define ARCH_NR 0 +#endif + +static int monitored = -1; +static int trapped = 0; +/** + * SIGSYS signal handler + * @param nr the signal number + * @param info siginfo_t pointer + * @param void_context handler context + * + * Simple signal handler for SIGSYS displaying the error, killing the child and + * exiting. + * + */ +static void +priv_seccomp_trap_handler(int signal, siginfo_t *info, void *vctx) +{ + ucontext_t *ctx = (ucontext_t *)(vctx); + unsigned int syscall; + + if (trapped) + _exit(161); /* Avoid loops */ + + /* Get details */ + if (info->si_code != SYS_SECCOMP) + return; + if (!ctx) + _exit(161); + syscall = ctx->uc_mcontext.gregs[REG_SYSCALL]; + trapped = 1; + + /* Log them. Technically, `log_warnx()` is not signal safe, but we are + * unlikely to reenter here. */ + log_warnx("seccomp", "invalid syscall attempted: %s(%d)", + (syscall < sizeof(syscall_names))?syscall_names[syscall]:"unknown", + syscall); + + /* Kill children and exit */ + kill(monitored, SIGTERM); + fatalx("invalid syscall not allowed: stop here"); + _exit(161); +} + +/** + * Install a TRAP action signal handler + * + * This function installs the TRAP action signal handler and is based on + * examples from Will Drewry and Kees Cook. Returns zero on success, negative + * values on failure. + * + */ +static int +priv_seccomp_trap_install() +{ + struct sigaction signal_handler = {}; + sigset_t signal_mask; + + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SIGSYS); + + signal_handler.sa_sigaction = &priv_seccomp_trap_handler; + signal_handler.sa_flags = SA_SIGINFO; + if (sigaction(SIGSYS, &signal_handler, NULL) < 0) + return -errno; + if (sigprocmask(SIG_UNBLOCK, &signal_mask, NULL)) + return -errno; + + return 0; +} + +/** + * Initialize seccomp. + * + * @param remote file descriptor to talk with the unprivileged process + * @param monitored monitored child + * @return negative on failures or 0 if everything was setup + */ +int +priv_seccomp_init(int remote, int child) +{ + int rc = -1; + scmp_filter_ctx ctx; + + log_debug("seccomp", "initialize libseccomp filter"); + monitored = child; + if (priv_seccomp_trap_install() < 0) { + log_warn("seccomp", "unable to install SIGSYS handler"); + goto failure_scmp; + } + + if ((ctx = seccomp_init(SCMP_ACT_TRAP)) == NULL) { + log_warnx("seccomp", "unable to initialize libseccomp subsystem"); + goto failure_scmp; + } + + if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, + SCMP_SYS(read), 1, SCMP_CMP(0, SCMP_CMP_EQ, remote))) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, + SCMP_SYS(write), 1, SCMP_CMP(0, SCMP_CMP_EQ, remote))) < 0) { + errno = -rc; + log_warn("seccomp", "unable to allow read/write on remote socket"); + goto failure_scmp; + } + + /* We are far more generic from here. */ + if ((rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0)) < 0 || /* write needed for */ + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(kill), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(bind), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(unlink), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ioctl), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendmsg), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(wait4), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(stat), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0)) < 0 || + /* The following are for resolving addresses */ + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fstat), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 0)) < 0 || + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0)) < 0 || + + (rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0)) < 0) { + errno = -rc; + log_warn("seccomp", "unable to build seccomp rules"); + goto failure_scmp; + } + + if ((rc = seccomp_load(ctx)) < 0) { + errno = -rc; + log_warn("seccomp", "unable to load libseccomp filter"); + goto failure_scmp; + } + +failure_scmp: + seccomp_release(ctx); + return rc; +} diff --git a/src/daemon/priv.c b/src/daemon/priv.c index c8e262d9..a4ff6cd8 100644 --- a/src/daemon/priv.c +++ b/src/daemon/priv.c @@ -342,12 +342,16 @@ static struct dispatch_actions actions[] = { /* Main loop, run as root */ static void -priv_loop() +priv_loop(int remote) { enum priv_cmd cmd; struct dispatch_actions *a; setproctitle("monitor"); +#ifdef USE_SECCOMP + if (priv_seccomp_init(remote, monitored) != 0) + fatal("privsep", "cannot continue without seccomp setup"); +#endif while (!may_read(&cmd, sizeof(enum priv_cmd))) { for (a = actions; a->function != NULL; a++) { if (cmd == a->msg) { @@ -514,7 +518,7 @@ priv_init(const char *chrootdir, int ctl, uid_t uid, gid_t gid) if (waitpid(monitored, &status, WNOHANG) != 0) /* Child is already dead */ _exit(1); - priv_loop(); + priv_loop(pair[1]); exit(0); } } -- 2.39.5