From 22a59d2d8b48cb2762362bd71e24293a53bca09a Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Tue, 3 Dec 2019 09:17:35 -0500 Subject: [PATCH 1/2] Add a basic seccomp profile to zhm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent zhm from doing some evil things while it runs (e.g., ptracing) by implementing a basic seccomp-bpf filter. The filter still allows a lot of potentially dangerous operations (e.g., unlink(2)), but this is a good start. The filter is based partly on a close reading of the zhm and libhesiod source code and partly on empirical evidence from running zhm under strace. I’ve run zhm with this filter for several days without incident, but some edge cases (e.g., server failover) are still untested. configure decides whether or not to enable seccomp by looking for libseccomp. By default, it treats seccomp as an enhancement and enables it opportunistically. Builders can force seccomp to be enabled or disabled by passing --with-seccomp or --without-seccomp, respectively, to configure. --- configure.ac | 22 +++++++++- zhm/Makefile.in | 3 +- zhm/zhm.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index dd0dc5aa..cb0d568e 100644 --- a/configure.ac +++ b/configure.ac @@ -264,7 +264,27 @@ if test "x$with_ares" != "xno"; then AC_MSG_ERROR(libcares not found))) fi AC_SUBST(ARES_LIBS) - + +AC_ARG_WITH(seccomp, + [AS_HELP_STRING([--without-seccomp], [Disable seccomp]) +AS_HELP_STRING([--with-seccomp=PREFIX], [Specify location of libseccomp])], + [seccomp="$withval"], [seccomp=maybe]) +AS_IF([test "x$seccomp" != "xno"], [ + AS_IF([test "x$seccomp" != "xyes" && test "x$seccomp" != "xmaybe"], [ + CPPFLAGS="$CPPFLAGS -I$seccomp/include" + LDFLAGS="$LDFLAGS -I$seccomp/lib" + ]) + AC_CHECK_LIB(seccomp, seccomp_init, [ + SECCOMP_LIBS="-lseccomp" + AC_DEFINE(HAVE_SECCOMP, 1, + [Define to compile with libseccomp support.]) + ], [ + AS_IF([test "x$seccomp" != "xmaybe"], + AC_MSG_ERROR([libseccomp not found])) + ]) +]) +AC_SUBST(SECCOMP_LIBS) + AC_PROG_GCC_TRADITIONAL AC_FUNC_VPRINTF AC_FUNC_GETPGRP diff --git a/zhm/Makefile.in b/zhm/Makefile.in index 77bf2c0a..3284bd34 100644 --- a/zhm/Makefile.in +++ b/zhm/Makefile.in @@ -33,13 +33,14 @@ CFLAGS=@CFLAGS@ ALL_CFLAGS=${CFLAGS} -I${top_srcdir}/h -I${BUILDTOP}/h ${CPPFLAGS} LDFLAGS=@LDFLAGS@ HESIOD_LIBS=@HESIOD_LIBS@ +SECCOMP_LIBS=@SECCOMP_LIBS@ OBJS= timer.o queue.o zhm.o zhm_client.o zhm_server.o all: zhm zhm.8 zhm: ${OBJS} ${LIBZEPHYR} - ${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${HESIOD_LIBS} -lcom_err + ${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${HESIOD_LIBS} -lcom_err ${SECCOMP_LIBS} zhm.8: ${srcdir}/zhm.8.in Makefile ${editman} ${srcdir}/$@.in > $@.tmp diff --git a/zhm/zhm.c b/zhm/zhm.c index ec4696b7..8386cec5 100644 --- a/zhm/zhm.c +++ b/zhm/zhm.c @@ -6,6 +6,7 @@ * $Id$ * * Copyright (c) 1987,1991 by the Massachusetts Institute of Technology. + * Copyright 2019 Google LLC. * For copying and distribution information, see the file * "mit-copyright.h". */ @@ -13,6 +14,10 @@ #include "zhm.h" #include +#ifdef HAVE_SECCOMP +#include +#endif + static const char rcsid_hm_c[] = "$Id$"; #ifdef HAVE_HESIOD @@ -49,6 +54,9 @@ static void init_hm(void); #ifndef DEBUG static void detach(void); #endif +#ifdef HAVE_SECCOMP +static int set_seccomp_enforcing(void); +#endif static void send_stats(ZNotice_t *, struct sockaddr_in *); static char *strsave(const char *); @@ -164,6 +172,14 @@ main(int argc, DPR2("zephyr server port: %u\n", ntohs(serv_sin.sin_port)); DPR2("zephyr client port: %u\n", ntohs(cli_port)); +#ifdef HAVE_SECCOMP + if (set_seccomp_enforcing()) { + printf("Unable to enable seccomp; exiting.\n"); + exit(ZERR_INTERNAL); + } + DPR("Seccomp enabled.\n"); +#endif + /* Main loop */ for (;;) { /* Wait for incoming packets or queue timeouts. */ @@ -519,6 +535,103 @@ stats_malloc(size_t size) return p; } +#ifdef HAVE_SECCOMP +static int +set_seccomp_enforcing(void) +{ + scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); + if (ctx == NULL) { + DPR("seccomp_init failed."); + return 1; + } + int r = 0; + +#define ALLOW_SYSCALL(syscall) \ + do { \ + if ((r = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(syscall), \ + 0)) < 0) { \ + Zperr(-r); \ + goto out; \ + } \ + } while (0) + + /* The main ZHM loop fundamentally consists of a select(2), a ZReceivePacket + * (a recvfrom(2)), and a ZSendPacket (a sendto(2)). */ + ALLOW_SYSCALL(select); + ALLOW_SYSCALL(recvfrom); + ALLOW_SYSCALL(sendto); + + /* If stuff breaks, we need to log with syslog(3). */ + ALLOW_SYSCALL(openat); + ALLOW_SYSCALL(fstat); + ALLOW_SYSCALL(read); + ALLOW_SYSCALL(lseek); + ALLOW_SYSCALL(close); + ALLOW_SYSCALL(getpid); + ALLOW_SYSCALL(socket); + ALLOW_SYSCALL(connect); + ALLOW_SYSCALL(sendto); + + /* We might also use com_err, which manipulates the terminal during its + * writes. */ + ALLOW_SYSCALL(write); + ALLOW_SYSCALL(ioctl); + + /* Exiting the process is okay. */ + ALLOW_SYSCALL(exit_group); + ALLOW_SYSCALL(exit); + + /* We might exit in response to a signal. */ + ALLOW_SYSCALL(rt_sigreturn); +#ifdef __NR_sigreturn + ALLOW_SYSCALL(sigreturn); +#endif + + /* When it's time to exit, we need to remove the PID file. */ + ALLOW_SYSCALL(unlink); + +#ifdef DEBUG + /* Logging stuff to stderr (with DPR and DPR2) is okay. write(2) has already + * been allowed above, so don't add it again. */ + /* ALLOW_SYSCALL(write); */ +#endif + +#ifdef HAVE_HESIOD + /* If a Zephyr server goes offline; we need to query Hesiod for a new + * server. Some of these syscalls have already been allowed above, so don't + * add them again. */ + ALLOW_SYSCALL(brk); + ALLOW_SYSCALL(getuid); + ALLOW_SYSCALL(geteuid); + ALLOW_SYSCALL(getgid); + ALLOW_SYSCALL(getegid); + /* ALLOW_SYSCALL(openat); */ + /* ALLOW_SYSCALL(fstat); */ + ALLOW_SYSCALL(mmap); + /* ALLOW_SYSCALL(close); */ + /* ALLOW_SYSCALL(getpid); */ + ALLOW_SYSCALL(stat); + /* ALLOW_SYSCALL(read); */ + /* ALLOW_SYSCALL(socket); */ + /* ALLOW_SYSCALL(connect); */ + ALLOW_SYSCALL(poll); + /* ALLOW_SYSCALL(sendto); */ + /* ALLOW_SYSCALL(recvfrom); */ +#endif + +#undef ALLOW_SYSCALL + + if ((r = seccomp_load(ctx)) < 0) { + Zperr(-r); + goto out; + } + +out: + seccomp_release(ctx); + return r; +} +#endif + static void send_stats(ZNotice_t *notice, struct sockaddr_in *sin) From aa8af5bb174bd392249eb2d30f23ed04ebc8b158 Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Tue, 3 Dec 2019 09:31:00 -0500 Subject: [PATCH 2/2] debian: Enable seccomp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit 22a59d2d8b48cb2762362bd71e24293a53bca09a added basic seccomp support to zhm. Since zhm currently runs as root and handles network traffic, any sandboxing we can get is a good idea. Take a dependency on libseccomp-dev and enable seccomp support for zhm. This breaks building on kFreeBSD, but this package doesn’t currently build there anyway, so it’s not making things any worse. --- debian/control | 3 ++- debian/rules | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/debian/control b/debian/control index bf03797a..d1df79d7 100644 --- a/debian/control +++ b/debian/control @@ -6,7 +6,8 @@ Build-Depends: debhelper (>= 5), libc-ares-dev, libkrb5-dev (>= 1.2.2-4), comerr-dev, ss-dev, libreadline-dev | libreadline5-dev, libx11-dev, libxt-dev, x11proto-core-dev, libncurses5-dev, bison, libhesiod-dev, autotools-dev, python (>= 2.5), python-central, - autoconf, libtool, automake, git-core | git, devscripts + autoconf, libtool, automake, git-core | git, devscripts, + libseccomp-dev Build-Conflicts: autoconf2.13 Standards-Version: 3.9.2.0 Homepage: http://zephyr.1ts.org/ diff --git a/debian/rules b/debian/rules index 8f575bbb..daed056a 100755 --- a/debian/rules +++ b/debian/rules @@ -17,7 +17,8 @@ PACKAGES:=-plibzephyr4 -pzephyr-clients -pzephyr-server -plibzephyr-dev -plibzep export DH_OPTIONS CONFIGURE_ROOT=--prefix=/usr --mandir=\$${prefix}/share/man \ --infodir=\$${prefix}/share/info --sysconfdir=/etc --datadir=/etc \ - --with-cares=/usr --with-hesiod=/usr --enable-cmu-zwgcplus + --with-cares=/usr --with-hesiod=/usr --with-seccomp=/usr \ + --enable-cmu-zwgcplus CONFIGURE_krb5=--with-krb5=/usr CONFIGURE_krb45=--with-krb4=/usr --with-krb5=/usr CONFIGURE_krb=--with-krb4=/usr