From 388e0319a0faf48193658c82228133bd1ea24eb6 Mon Sep 17 00:00:00 2001 Message-Id: <388e0319a0faf48193658c82228133bd1ea24eb6.1714723900.git.mdw@distorted.org.uk> From: Mark Wooding Date: Mon, 29 Dec 2008 20:07:04 +0000 Subject: [PATCH] server: Introduce privilege separation. Organization: Straylight/Edgeware From: Mark Wooding During initialization, we fork off a child which retains its root privileges, and maintain communication with it via a Unix-domain socket pair. To open a new tunnel, we send it a request and it responds by passing back the appropriate file descriptor. The helper process running as root is implemented in a separate program, tripe-privhelper. This is done (a) to reduce memory use, (b) to trigger close-on-exec behaviour and (c) to provide a clear boundary in the source code for the parts which still run with superuser privileges. This entails moving our tunnel-open functions into a separate program, and doing the necessary build-system hacking. The changes to existing code aren't as invasive as they at first appear. --- Makefile.am | 1 + configure.ac | 10 ++ debian/tripe.install | 2 + priv/Makefile.am | 55 +++++++ priv/comm.c | 174 +++++++++++++++++++++ priv/helper.c | 307 +++++++++++++++++++++++++++++++++++++ priv/priv.h | 188 +++++++++++++++++++++++ priv/tripe-privhelper.8.in | 89 +++++++++++ server/Makefile.am | 3 +- server/admin.c | 2 + server/peer.c | 4 +- server/privsep.c | 215 ++++++++++++++++++++++++++ server/tests.at | 3 +- server/tripe.c | 1 + server/tripe.h | 71 ++++++++- server/tun-bsd.c | 39 +---- server/tun-linux.c | 36 +---- server/tun-slip.c | 2 +- server/tun-unet.c | 42 +---- vars.am | 6 +- 20 files changed, 1128 insertions(+), 122 deletions(-) create mode 100644 priv/Makefile.am create mode 100644 priv/comm.c create mode 100644 priv/helper.c create mode 100644 priv/priv.h create mode 100644 priv/tripe-privhelper.8.in create mode 100644 server/privsep.c diff --git a/Makefile.am b/Makefile.am index 504ca56b..192dcc5e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -34,6 +34,7 @@ SUBDIRS = SUBDIRS += common SUBDIRS += uslip SUBDIRS += client +SUBDIRS += priv SUBDIRS += server SUBDIRS += proxy SUBDIRS += pkstream diff --git a/configure.ac b/configure.ac index 0eeb1ec5..5b5eaa2c 100644 --- a/configure.ac +++ b/configure.ac @@ -96,6 +96,15 @@ TRIPE_DEFINE_PATH( TRIPE_DEFINE_PATH( [logfile], [FILE], [logging output [[./tripe.log]]], [tripe.log]) +dnl-------------------------------------------------------------------------- +dnl Privilege-separation helper. + +mdw_DEFINE_PATHS([ + AC_DEFINE_UNQUOTED([PRIVSEP_HELPER], + ["mdw_PATH([$libexecdir])/mdw_PROG([tripe-privhelper])"], + [Pathname of privilege-separation helper.]) +]) + dnl-------------------------------------------------------------------------- dnl Other options. @@ -292,6 +301,7 @@ AC_CONFIG_FILES( [common/Makefile] [uslip/Makefile] [client/Makefile] + [priv/Makefile] [server/Makefile] [proxy/Makefile] [pkstream/Makefile] diff --git a/debian/tripe.install b/debian/tripe.install index e68861de..7df89238 100644 --- a/debian/tripe.install +++ b/debian/tripe.install @@ -3,4 +3,6 @@ debian/tmp/usr/sbin/tripe debian/tmp/usr/share/man/man1/tripectl.1 debian/tmp/usr/share/man/man5/tripe-admin.5 debian/tmp/usr/share/man/man8/tripe.8 +debian/tmp/usr/lib/tripe/tripe-privhelper +debian/tmp/usr/share/man/man8/tripe-privhelper.8 debian/tmp/usr/lib/pkgconfig/tripe.pc diff --git a/priv/Makefile.am b/priv/Makefile.am new file mode 100644 index 00000000..7c8ad95e --- /dev/null +++ b/priv/Makefile.am @@ -0,0 +1,55 @@ +### -*-makefile-*- +### +### Makefile for privilege separation +### +### (c) 2008 Straylight/Edgeware +### + +###----- Licensing notice --------------------------------------------------- +### +### This file is part of Trivial IP Encryption (TrIPE). +### +### TrIPE is free software; you can redistribute it and/or modify +### it under the terms of the GNU General Public License as published by +### the Free Software Foundation; either version 2 of the License, or +### (at your option) any later version. +### +### TrIPE is distributed in the hope that it will be useful, +### but WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU General Public License for more details. +### +### You should have received a copy of the GNU General Public License +### along with TrIPE; if not, write to the Free Software Foundation, +### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +include $(top_srcdir)/vars.am + +noinst_LIBRARIES = libpriv.a +libexec_PROGRAMS = tripe-privhelper +man_MANS = + +###-------------------------------------------------------------------------- +### Library. + +libpriv_a_SOURCES = + +## Header. +libpriv_a_SOURCES += priv.h + +## Communications. +libpriv_a_SOURCES += comm.c + +###-------------------------------------------------------------------------- +### Helper. + +## The progam itself. +tripe_privhelper_SOURCES = helper.c +tripe_privhelper_LDADD = $(libpriv) $(libtripe) + +## Manual page. +man_MANS += tripe-privhelper.8 +CLEANFILES += tripe-privhelper.8 +EXTRA_DIST += tripe-privhelper.8.in + +###----- That's all, folks -------------------------------------------------- diff --git a/priv/comm.c b/priv/comm.c new file mode 100644 index 00000000..ad2e518c --- /dev/null +++ b/priv/comm.c @@ -0,0 +1,174 @@ +/* -*-c-*- + * + * Communication between server and helper + * + * (c) 2008 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Trivial IP Encryption (TrIPE). + * + * TrIPE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TrIPE; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "priv.h" + +/*----- Global variables --------------------------------------------------*/ + +int pc_fd = 0; /* File descriptor for comms */ + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @pc_put@ --- * + * + * Arguments: @const void *p@ = pointer to buffer + * @size_t sz@ = size of the buffer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Writes a buffer, handling short writes and other bogosity. + */ + +int pc_put(const void *p, size_t sz) +{ + ssize_t n; + const unsigned char *pp = p; + + while (sz) { + n = write(pc_fd, pp, sz); + if (n < 0) { + if (errno == EINTR) + continue; + return (-1); + } + if (n == 0) { + errno = EIO; + return (-1); + } + pp += n; sz -= n; + } + return (0); +} + +/* --- @pc_puterr@, @pc_putuint@, @pc_putsz@, @pc_puttops@ --- * + * + * Arguments: @int err@ = error number to write + * @uint u@ = unsigned integer to write + * @size_t sz@ = size to write + * @const tunnel_ops *tops@ = tunnel pointer to write + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Sends an error/integer/size/tunnel-ops pointer. + */ + +#define PUT(abbr, type) \ + int pc_put##abbr(type x) { return (pc_put(&x, sizeof(x))); } +COMM_TYPES(PUT) + +/* --- @pc_putstring@ --- * + * + * Arguments: @const char *s@ = pointer to string to write + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Sends a string/error/integer/tunnel-ops pointer. + */ + +int pc_putstring(const char *s) +{ + size_t sz = strlen(s); + + if (pc_putsz(sz) || pc_put(s, sz)) + return (-1); + return (0); +} + +/* --- @pc_get@ --- * + * + * Arguments: @void *p@ = pointer to buffer + * @size_t sz@ = size of the buffer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives a buffer, handling short reads and other bogosity. + */ + +int pc_get(void *p, size_t sz) +{ + ssize_t n; + unsigned char *pp = p; + + while (sz) { + n = read(pc_fd, pp, sz); + if (n < 0) { + if (errno == EINTR) + continue; + else if (errno == ECONNRESET) + errno = -1; + return (-1); + } + if (n == 0) { + errno = -1; + return (-1); + } + pp += n; sz -= n; + } + return (0); +} + +/* --- @pc_geterr@, @pc_getuint@, @pc_getsz@, @pc_getops@ --- * + * + * Arguments: @int *err@ = where to put the error number + * @uint *u@ = where to put the unsigned integer + * @size_t *sz@ = where to put the size + * @const tunnel_ops **tops@ = where to put the tunnel pointer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives an error/integer/size/tunnel-ops pointer. + */ + +#define GET(abbr, type) \ + int pc_get##abbr(type *x) { return (pc_get(x, sizeof(*x))); } +COMM_TYPES(GET) + +/* --- @pc_gettring@ --- * + * + * Arguments: @dstr *d@ = where to pc_put the string + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives a string. + */ + +int pc_getstring(dstr *d) +{ + size_t sz; + + if (pc_getsz(&sz)) + return (-1); + DENSURE(d, sz + 1); + if (pc_get(d->buf + d->len, sz)) + return (-1); + d->len += sz; + d->buf[d->len] = 0; + return (0); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/priv/helper.c b/priv/helper.c new file mode 100644 index 00000000..c1401f12 --- /dev/null +++ b/priv/helper.c @@ -0,0 +1,307 @@ +/* -*-c-*- + * + * Privilege-separated helper + * + * (c) 2008 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Trivial IP Encryption (TrIPE). + * + * TrIPE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TrIPE; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "priv.h" + +/*----- Helper-side utilities ---------------------------------------------*/ + +/* --- @lose@ --- * + * + * Arguments: @const char *excuse@ = what went wrong + * + * Returns: Doesn't. + * + * Use: Reports a fatal error and quits. + */ + +static void lose(const char *excuse) +{ + moan("helper process bailing out: %s; error: %s", + excuse, + errno == -1 ? "Unexpected EOF" : strerror(errno)); + _exit(127); +} + +/*----- Diagnostic functions ----------------------------------------------*/ + +/* --- @trace@ --- * + * + * Arguments: @unsigned mask@ = trace mask to check + * @const char *fmt@ = message format + * @...@ = values for placeholders + * + * Returns: --- + * + * Use: Writes a trace message. + */ + +#ifndef NTRACE + +static void itrace(unsigned mask, const char *fmt, ...) +{ + va_list ap; + dstr d = DSTR_INIT; + + va_start(ap, fmt); + dstr_vputf(&d, fmt, &ap); + if (pc_putuint(PS_TRACE) || + pc_putuint(mask) || + pc_putsz(d.len) || + pc_put(d.buf, d.len)) + lose("write (trace)"); + va_end(ap); + dstr_destroy(&d); +} + +#endif + +/* --- @warn@ --- * + * + * Arguments: @const char *fmt@ = message format + * @...@ = values for placeholders + * + * Returns: --- + * + * Use: Writes a warning message. + */ + +#define A_END ((char *)0) + +static void warn(const char *fmt, ...) +{ + va_list ap; + dstr d = DSTR_INIT, dd = DSTR_INIT; + + va_start(ap, fmt); + while (fmt) { + if (*fmt == '?') { + if (strcmp(fmt, "?ERRNO") == 0) { + dstr_putf(&d, " E%d", errno); + u_quotify(&d, strerror(errno)); + } else + abort(); + } else { + DRESET(&dd); + dstr_vputf(&dd, fmt, &ap); + u_quotify(&d, dd.buf); + } + fmt = va_arg(ap, const char *); + } + va_end(ap); + + if (pc_putuint(PS_WARN) || + pc_putsz(d.len) || + pc_put(d.buf, d.len)) + lose("write (warn)"); + + dstr_destroy(&d); + dstr_destroy(&dd); +} + +/*----- Tunnel drivers ----------------------------------------------------*/ + +/* --- @topen_DRIVER@ --- * + * + * Arguments: @char **ifn@ = where to put the interface name + * + * Returns: A file descriptor, or @-1@ on failure. + * + * Use: Opens a tunnel device. + */ + +#ifdef TUN_LINUX + +#include +#include +#include + +static int topen_linux(char **ifn) +{ + int fd; + struct ifreq iff; + + if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { + warn("TUN", "-", "linux", + "open-error", "/dev/net/tun", "?ERRNO", + A_END); + return (-1); + } + memset(&iff, 0, sizeof(iff)); + iff.ifr_name[0] = 0; + iff.ifr_flags = IFF_TUN | IFF_NO_PI; + if (ioctl(fd, TUNSETIFF, &iff) < 0) { + warn("TUN", "-", "linux", "config-error", "?ERRNO", A_END); + close(fd); + return (-1); + } + iff.ifr_name[IFNAMSIZ - 1] = 0; + *ifn = xstrdup(iff.ifr_name); + return (fd); +} + +#endif + +#ifdef TUN_BSD + +static int topen_bsd(char **ifn) +{ + int fd; + unsigned n; + char buf[16]; + + n = 0; + for (;;) { + sprintf(buf, "/dev/tun%u", n); + if ((fd = open(buf, O_RDWR)) >= 0) + break; + switch (errno) { + case EBUSY: + T( itrace(T_PRIVSEP, "tunnel device %u busy: skipping", n); ) + break; + case ENOENT: + warn("TUN", "-", "bsd", "no-tunnel-devices", A_END); + return (-1); + default: + warn("TUN", "-", "open-error", "%s", buf, "?ERRNO", A_END); + break; + } + n++; + } + return (fd); +} + +#endif + +#ifdef TUN_UNET + +#include +#include +#include + +static int topen_unet(char **ifn) +{ + int fd; + int f; + struct unet_info uni; + + if ((fd = open("/dev/unet", O_RDWR)) < 0) { + warn("TUN", "-", "unet", "open-error", "/dev/unet", "?ERRNO", A_END); + goto fail_0; + } + if ((f = ioctl(fd, UNIOCGIFFLAGS)) < 0 || + ioctl(fd, UNIOCSIFFLAGS, f | IFF_POINTOPOINT)) { + warn("TUN", "-", "unet", "config-error", "?ERRNO", A_END); + goto fail_1; + } + if (ioctl(fd, UNIOCGINFO, &uni)) { + warn("TUN", "-", "unet", "getinfo-error", "?ERRNO", A_END); + goto fail_1; + } + *ifn = xstrdup(uni.uni_ifname); + return (fd); + +fail_1: + close(fd); +fail_0: + return (-1); +} + +#endif + +static const struct tunnel { + const char *name; + int (*open)(char **); +} tunnels[] = { +#ifdef TUN_LINUX + { "linux", topen_linux }, +#endif +#ifdef TUN_BSD + { "bsd", topen_bsd }, +#endif +#ifdef TUN_UNET + { "unet", topen_unet }, +#endif + { 0, 0 } +}; + +/*----- Helper process core -----------------------------------------------*/ + +int main(int argc, char *argv[]) +{ + struct sockaddr_un sun; + socklen_t slen = sizeof(sun); + unsigned rq; + dstr d = DSTR_INIT; + const struct tunnel *t; + char *ifn = 0; + int fd; + ssize_t n; + + ego(argv[0]); + if (argc != 1 || + getpeername(0, (struct sockaddr *)&sun, &slen) || + sun.sun_family != AF_UNIX) + die(EXIT_FAILURE, "please do not run this program again."); + + for (;;) { + if (pc_getuint(&rq)) { + if (errno == -1) break; + else lose("read (main)"); + } + switch (rq) { + case PS_TUNRQ: + DRESET(&d); + if (pc_getstring(&d)) lose("read (tunnel)"); + for (t = tunnels;; t++) { + if (!t->name) lose("unknown tunnel"); + if (strcmp(d.buf, t->name) == 0) break; + } + T( itrace(T_PRIVSEP, + "privsep: received request for %s tunnel", + t->name); ) + if ((fd = t->open(&ifn)) < 0) + goto err; + rq = PS_TUNFD; + n = fdpass_send(pc_fd, fd, &rq, sizeof(rq)); close(fd); + if (n < 0) { xfree(ifn); goto err; } + else if (n < sizeof(rq)) lose("partial write (fd-pass)"); + if (pc_putstring(ifn)) lose("write (ifname)"); + xfree(ifn); + break; + err: + if (pc_putuint(PS_TUNERR) || pc_puterr(errno)) lose("write (error)"); + break; + default: + lose("bad request"); + break; + } + } + _exit(0); +} + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/priv/priv.h b/priv/priv.h new file mode 100644 index 00000000..225075a6 --- /dev/null +++ b/priv/priv.h @@ -0,0 +1,188 @@ +/* -*-c-*- + * + * Privilege separation definitions + * + * (c) 2008 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Trivial IP Encryption (TrIPE). + * + * TrIPE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TrIPE; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef PRIV_H +#define PRIV_H + +#ifdef __cplusplus + extern "C" { +#endif + +/*----- Header files ------------------------------------------------------*/ + +#include "config.h" + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include "util.h" + +#undef sun + +/*----- Protocol ----------------------------------------------------------*/ + +/* --- Notes --- * + * + * The protocol is synchronous. The socket is not marked as nonblocking; + * instead we just trust the helper to respond in good time; this is + * reasonable since it's not doing anything complicated. The helper is + * completely trusted. + * + * The protocol works like this. Messages begin with a request code which is + * a single @unsigned int@. The server sends a request @PS_TUNRQ@ to the + * helper, followed by a @const tunnel_ops *@ referring to the tunnel driver + * of interest. The server responds with a sequence of @PS_TRACE@ and/or + * @PS_WARN@ messages, followed by either a @PS_TUNFD@ carrying a file + * descriptor, or a @PS_TUNERR@ followed by an integer @errno@ code. + * + * If all else fails, the helper process will just quit. + */ + +enum { + PS_TUNRQ, /* Request (@tunnel_ops *@) */ + PS_TUNFD, /* Tunnel descriptor (string) */ + PS_TUNERR, /* Error (@int errno@) */ +#ifndef NTRACE + PS_TRACE, /* Trace (@unsigned mask@, string) */ +#endif + PS_WARN, /* Warning (string) */ +}; + +/*----- Tracing definitions -----------------------------------------------*/ + +#define T_PRIVSEP 512u + +/*----- Global variables --------------------------------------------------*/ + +extern int pc_fd; /* File descriptor for comms */ + +/*----- Functions provided ------------------------------------------------*/ + +#define COMM_TYPES(_) \ + _(err, int) \ + _(uint, unsigned int) \ + _(sz, size_t) + +/* --- @put@ --- * + * + * Arguments: @const void *p@ = pointer to buffer + * @size_t sz@ = size of the buffer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Writes a buffer, handling short writes and other bogosity. + */ + +extern int pc_put(const void */*p*/, size_t /*sz*/); + +/* --- @puterr@, @putuint@, @putsz@, @puttops@ --- * + * + * Arguments: @int err@ = error number to write + * @uint u@ = unsigned integer to write + * @size_t sz@ = size to write + * @const tunnel_ops *tops@ = tunnel pointer to write + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Sends an error/integer/size/tunnel-ops pointer. + */ + +#define DECL(abbr, type) extern int pc_put##abbr(type /*x*/); +COMM_TYPES(DECL) +#undef DECL + +/* --- @putstring@ --- * + * + * Arguments: @const char *s@ = pointer to string to write + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Sends a string/error/integer/tunnel-ops pointer. + */ + +extern int pc_putstring(const char */*s*/); + +/* --- @get@ --- * + * + * Arguments: @void *p@ = pointer to buffer + * @size_t sz@ = size of the buffer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives a buffer, handling short reads and other bogosity. + */ + +extern int pc_get(void */*p*/, size_t /*sz*/); + +/* --- @geterr@, @getuint@, @getsz@, @getops@ --- * + * + * Arguments: @int *err@ = where to put the error number + * @uint *u@ = where to put the unsigned integer + * @size_t *sz@ = where to put the size + * @const tunnel_ops **tops@ = where to put the tunnel pointer + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives an error/integer/size/tunnel-ops pointer. + */ + +#define DECL(abbr, type) extern int pc_get##abbr(type */*x*/); +COMM_TYPES(DECL) +#undef DECL + +/* --- @gettring@ --- * + * + * Arguments: @dstr *d@ = where to put the string + * + * Returns: Zero on success, @-1@ on error (and @errno@ set). + * + * Use: Receives a string. + */ + +extern int pc_getstring(dstr */*d*/); + +/*----- That's all, folks -------------------------------------------------*/ + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/priv/tripe-privhelper.8.in b/priv/tripe-privhelper.8.in new file mode 100644 index 00000000..ea4a43fd --- /dev/null +++ b/priv/tripe-privhelper.8.in @@ -0,0 +1,89 @@ +.\" -*-nroff-*- +.\". +.\" Manual for the server +.\" +.\" (c) 2008 Straylight/Edgeware +.\" +. +.\"----- Licensing notice --------------------------------------------------- +.\" +.\" This file is part of Trivial IP Encryption (TrIPE). +.\" +.\" TrIPE is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" TrIPE is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with TrIPE; if not, write to the Free Software Foundation, +.\" Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +. +.\"-------------------------------------------------------------------------- +.so ../defs.man.in \" @@@PRE@@@ +. +.\"-------------------------------------------------------------------------- +.TH tripe-privhelper 8 "28 April 2008" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption" +. +.\"-------------------------------------------------------------------------- +.SH "NAME" +. +tripe-privhelper \- privilege-separation helper program +. +.\"-------------------------------------------------------------------------- +.SH "SYNOPSIS" +. +This program communicates using a binary protocol over a Unix-domain +socket on file descriptor 0. It is not intended to be run +interactively. +. +.\"-------------------------------------------------------------------------- +.SH "DESCRIPTION" +. +The +.BR tripe (8) +server usually needs superuser privileges in order to open new tunnel +devices, through which it collects and emits network packets. In order +to prevent the whole system needing to be run as root, the server splits +off a child process and then drops its privileges; the child process +runs this program. +.PP +The +.B tripe-privhelper +program reads requests for tunnel devices on file descriptor 0 and +responds with appropriate file descriptors (using Unix-domain socket +file descriptor passing: see +.BR unix (7)) +for correctly configured tunnel devices. +. +.\"-------------------------------------------------------------------------- +.SH "BUGS" +. +The objective of the privilege separation model is to reduce the attack +surface for the code running with superuser privileges down to a simple +binary protocol. There may still be bugs in the small program which +runs as root. +.PP +The `unprivileged' portion of the server still runs with the ability to +read and write arbitrary data on tunnel devices. In particular, if +compromised, it can inject arbitrary packets into the network. This is +unfortunately inherent in the nature of a VPN server. +. +.\"-------------------------------------------------------------------------- +.SH "SEE ALSO" +. +.BR tripe (8). +.PP +.IR "The Trivial IP Encryption Protocol" , +.IR "The Wrestlers Protocol" . +. +.\"-------------------------------------------------------------------------- +.SH "AUTHOR" +. +Mark Wooding, +. +.\"----- That's all, folks -------------------------------------------------- diff --git a/server/Makefile.am b/server/Makefile.am index e033ab0c..f4fb936f 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -34,7 +34,7 @@ man_MANS = sbin_PROGRAMS += tripe tripe_SOURCES = -tripe_LDADD = $(libtripe) $(catacomb_LIBS) +tripe_LDADD = $(libpriv) $(libtripe) $(catacomb_LIBS) ## Main header file. tripe_SOURCES += tripe.h @@ -47,6 +47,7 @@ tripe_SOURCES += keyset.c tripe_SOURCES += keyexch.c tripe_SOURCES += chal.c tripe_SOURCES += peer.c +tripe_SOURCES += privsep.c tripe_SOURCES += admin.c tripe_SOURCES += tripe.c diff --git a/server/admin.c b/server/admin.c index 4e87e48c..ccd49b73 100644 --- a/server/admin.c +++ b/server/admin.c @@ -40,6 +40,7 @@ const trace_opt tr_opts[] = { { 'x', T_KEYEXCH, "key exchange" }, { 'm', T_KEYMGMT, "key management" }, { 'l', T_CHAL, "challenge management" }, + { 'v', T_PRIVSEP, "privilege separation" }, { 'p', T_PACKET, "packet contents" }, { 'c', T_CRYPTO, "crypto details" }, { 'A', T_ALL, "all of the above" }, @@ -521,6 +522,7 @@ void a_quit(void) close(sock.fd); unlink(sockname); FOREACH_PEER(p, { p_destroy(p); }); + ps_quit(); exit(0); } diff --git a/server/peer.c b/server/peer.c index 3cb12ea2..4bb2c82c 100644 --- a/server/peer.c +++ b/server/peer.c @@ -742,9 +742,9 @@ peer *p_create(peerspec *spec) p->ifname = 0; memset(&p->st, 0, sizeof(stats)); p->st.t_start = time(0); - if (!tops->open) + if (!(tops->flags & TUNF_PRIVOPEN)) fd = -1; - else if ((fd = tops->open(&p->ifname)) < 0) + else if ((fd = ps_tunfd(tops, &p->ifname)) < 0) goto tidy_2; if ((p->t = tops->create(p, fd, &p->ifname)) == 0) goto tidy_3; diff --git a/server/privsep.c b/server/privsep.c new file mode 100644 index 00000000..5bc2b892 --- /dev/null +++ b/server/privsep.c @@ -0,0 +1,215 @@ +/* -*-c-*- + * + * Privilege separation communication protocol + * + * (c) 2008 Straylight/Edgeware + */ + +/*----- Licensing notice --------------------------------------------------* + * + * This file is part of Trivial IP Encryption (TrIPE). + * + * TrIPE is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * TrIPE is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with TrIPE; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/*----- Header files ------------------------------------------------------*/ + +#include "tripe.h" +#include "priv.h" + +/*----- Static variables --------------------------------------------------*/ + +static pid_t kid = -1; + +/*----- Fetching a tunnel file descriptor ---------------------------------*/ + +/* --- @ps_tunfd@ --- * + * + * Arguments: @const tunnel_ops *tops@ = pointer to tunnel operations + * @char **ifn@ = where to put the interface name + * + * Returns: The file descriptor, or @-1@ on error. + * + * Use: Fetches a file descriptor for a tunnel driver. + */ + +int ps_tunfd(const tunnel_ops *tops, char **ifn) +{ + unsigned code; + ssize_t n; + dstr d = DSTR_INIT; + int fd; + + if (pc_fd == -1) { + a_warn("PRIVSEP", "helper-died", A_END); + return (-1); + } + T( trace(T_PRIVSEP, + "privsep: requesting descriptor for %s tunnel", + tops->name); ) + if (pc_putuint(PS_TUNRQ) || pc_putstring(tops->name)) { + a_warn("PRIVSEP", "helper-write-error", "?ERRNO", A_END); + goto lose; + } + for (;;) { + n = fdpass_recv(pc_fd, &fd, &code, sizeof(code)); + if (n < 0) goto readlose; + if (n < sizeof(code)) { + a_warn("PRIVSEP", "helper-short-read", A_END); + goto lose; + } + switch (code) { + case PS_TUNFD: + if (fd == -1) { + a_warn("PRIVSEP", "no-fd-from-helper", A_END); + goto lose; + } + if (pc_getstring(&d)) { close(fd); goto readlose; } + *ifn = xstrdup(d.buf); + T( trace(T_PRIVSEP, + "privsep: received winning descriptor for %s", + *ifn); ) + goto done; + case PS_TUNERR: + if (pc_geterr(&errno)) goto readlose; + T( trace(T_PRIVSEP, "privsep: helper lost: %s", strerror(errno)); ) + fd = -1; + goto done; +#ifndef NTRACE + case PS_TRACE: + if (pc_getuint(&code) || pc_getstring(&d)) goto readlose; + trace(code, "%s", d.buf); + DRESET(&d); + break; +#endif + case PS_WARN: + if (pc_getstring(&d)) goto readlose; + a_warn("*%s", d.buf, A_END); + DRESET(&d); + break; + default: + a_warn("PRIVSEP", "unknown-response-code", "%u", code, A_END); + goto lose; + } + } +done: + dstr_destroy(&d); + return (fd); + +readlose: + a_warn("PRIVSEP", "helper-read-error", "?ERRNO", A_END); +lose: + dstr_destroy(&d); + close(pc_fd); + pc_fd = -1; + return (-1); +} + +/*----- Main code ---------------------------------------------------------*/ + +/* --- @reap@ --- * + * + * Arguments: @int sig@ = signal number (always @SIGCHLD@; ignored) + * + * Returns: --- + * + * Use: Notices and reports child process death. + */ + +static void reap(int sig) +{ + pid_t k; + int st; + + for (;;) { + k = waitpid(-1, &st, WNOHANG); + if (k < 0) { + switch (errno) { + case EINTR: + break; + default: + a_warn("SERVER", "waitpid-error", "?ERRNO", A_END); + case ECHILD: + return; + } + } + if (!k) + return; + if (k == kid) { + if (WIFEXITED(st)) + a_warn("PRIVSEP", "child-exited", "%d", WEXITSTATUS(st), A_END); + else if (WIFSIGNALED(st)) + a_warn("PRIVSEP", "child-killed", "%d", WTERMSIG(st), A_END); + else + a_warn("PRIVSEP", "child-died", "%d", st, A_END); + kid = -1; + } + } +} + +/* --- @ps_split@ --- * + * + * Arguments: @int detachp@ = whether to detach the child from its terminal + * + * Returns: --- + * + * Use: Separates off the privileged tunnel-opening service from the + * rest of the server. + */ + +void ps_split(int detachp) +{ + pid_t kid; + int fd[2]; + const char *helper; + + if (socketpair(PF_UNIX, SOCK_STREAM, 0, fd)) { + die(EXIT_FAILURE, + "failed to create socket pair for privilege separation: %s", + strerror(errno)); + } + helper = getenv("TRIPE_PRIVHELPER"); + if (!helper) helper = PRIVSEP_HELPER; + fdflags(fd[0], 0, 0, FD_CLOEXEC, FD_CLOEXEC); + fdflags(fd[1], 0, 0, FD_CLOEXEC, FD_CLOEXEC); + signal(SIGCHLD, reap); + kid = fork(); + if (kid == 0) { + signal(SIGCHLD, SIG_DFL); + if (detachp) detachtty(); + if (dup2(fd[0], 0) < 0) goto lose; + close(fd[0]); close(fd[1]); + execl(helper, helper, (char *)0); + lose: + fprintf(stderr, "helper: failed to run helper: %s\n", strerror(errno)); + _exit(127); + } + T( trace(T_PRIVSEP, "privsep: forked child successfully"); ) + close(fd[0]); + pc_fd = fd[1]; +} + +/* --- @ps_quit@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Detaches from the helper process. + */ + +void ps_quit(void) { if (pc_fd != -1) close(pc_fd); } + +/*----- That's all, folks -------------------------------------------------*/ diff --git a/server/tests.at b/server/tests.at index 1a3bc4c5..393a6616 100644 --- a/server/tests.at +++ b/server/tests.at @@ -34,7 +34,8 @@ m4_define([SETUPDIR], [ ## Running standard programs with useful options. m4_define([TRIPE], - [$abs_top_builddir/server/tripe -F -d. -aadmin -p0 -b127.0.0.1 -talice]) + [env TRIPE_PRIVHELPER=$abs_top_builddir/priv/tripe-privhelper \ + $abs_top_builddir/server/tripe -F -d. -aadmin -p0 -b127.0.0.1 -talice]) m4_define([TRIPECTL], [$abs_top_builddir/client/tripectl -d. -aadmin]) m4_define([USLIP], [$abs_top_builddir/uslip/tripe-uslip]) diff --git a/server/tripe.c b/server/tripe.c index ba59dfea..a2731c38 100644 --- a/server/tripe.c +++ b/server/tripe.c @@ -288,6 +288,7 @@ int main(int argc, char *argv[]) af |= AF_FOREGROUND; a_create(STDIN_FILENO, STDOUT_FILENO, af); } + ps_split(f & f_daemon); a_init(csock, u, g); u_setugid(u, g); km_init(kr_priv, kr_pub, tag_priv); diff --git a/server/tripe.h b/server/tripe.h index 404bad0f..ab15787f 100644 --- a/server/tripe.h +++ b/server/tripe.h @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -103,6 +105,7 @@ #include #include +#include "priv.h" #include "protocol.h" #include "slip.h" #include "util.h" @@ -122,8 +125,9 @@ #define T_KEYEXCH 64u #define T_KEYMGMT 128u #define T_CHAL 256u +/* T_PRIVSEP in priv.h */ -#define T_ALL 511u +#define T_ALL 1023u /* --- Units --- */ @@ -287,8 +291,9 @@ struct peer; typedef struct tunnel_ops { const char *name; /* Name of this tunnel driver */ + unsigned flags; /* Various interesting flags */ +#define TUNF_PRIVOPEN 1u /* Need helper to open file */ void (*init)(void); /* Initializes the system */ - int (*open)(char **/*ifn*/); /* Open tunnel and report ifname */ tunnel *(*create)(struct peer */*p*/, int /*fd*/, char **/*ifn*/); /* Initializes a new tunnel */ void (*setifname)(tunnel */*t*/, const char */*ifn*/); @@ -994,6 +999,68 @@ extern void *am_find(addrmap */*m*/, const addr */*a*/, extern void am_remove(addrmap */*m*/, void */*i*/); +/*----- Privilege separation ----------------------------------------------*/ + +/* --- @ps_trace@ --- * + * + * Arguments: @unsigned mask@ = trace mask to check + * @const char *fmt@ = message format + * @...@ = values for placeholders + * + * Returns: --- + * + * Use: Writes a trace message. + */ + +T( extern void ps_trace(unsigned /*mask*/, const char */*fmt*/, ...); ) + +/* --- @ps_warn@ --- * + * + * Arguments: @const char *fmt@ = message format + * @...@ = values for placeholders + * + * Returns: --- + * + * Use: Writes a warning message. + */ + +extern void ps_warn(const char */*fmt*/, ...); + +/* --- @ps_tunfd@ --- * + * + * Arguments: @const tunnel_ops *tops@ = pointer to tunnel operations + * @char **ifn@ = where to put the interface name + * + * Returns: The file descriptor, or @-1@ on error. + * + * Use: Fetches a file descriptor for a tunnel driver. + */ + +extern int ps_tunfd(const tunnel_ops */*tops*/, char **/*ifn*/); + +/* --- @ps_split@ --- * + * + * Arguments: @int detachp@ = whether to detach the child from its terminal + * + * Returns: --- + * + * Use: Separates off the privileged tunnel-opening service from the + * rest of the server. + */ + +extern void ps_split(int /*detachp*/); + +/* --- @ps_quit@ --- * + * + * Arguments: --- + * + * Returns: --- + * + * Use: Detaches from the helper process. + */ + +extern void ps_quit(void); + /*----- Peer management ---------------------------------------------------*/ /* --- @p_txstart@ --- * diff --git a/server/tun-bsd.c b/server/tun-bsd.c index 200b5d93..bfa528b2 100644 --- a/server/tun-bsd.c +++ b/server/tun-bsd.c @@ -82,43 +82,6 @@ static void t_read(int fd, unsigned mode, void *v) static void t_init(void) { return; } -/* --- @t_open@ --- * - * - * Arguments: @char **ifn@ = where to put the interface name - * - * Returns: A file descriptor, or @-1@ on failure. - * - * Use: Opens a tunnel device. This will run with root privileges - * even if the rest of the server has dropped them. - */ - -static int t_open(char **ifn) -{ - int fd; - unsigned n; - char buf[16]; - - n = 0; - for (;;) { - sprintf(buf, "/dev/tun%u", n); - if ((fd = open(buf, O_RDWR)) >= 0) - break; - switch (errno) { - case EBUSY: - T( trace(T_TUNNEL, "tunnel device %u busy: skipping", n); ) - break; - case ENOENT: - a_warn("TUN", "-", "bsd", "no-tunnel-devices", A_END); - return (-1); - default: - a_warn("TUN", "-", "open-error", "%s", buf, "?ERRNO", A_END); - break; - } - n++; - } - return (fd); -} - /* --- @t_create@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -176,8 +139,8 @@ static void t_destroy(tunnel *t) const tunnel_ops tun_bsd = { "bsd", + TUNF_PRIVOPEN, t_init, - t_open, t_create, 0, t_inject, diff --git a/server/tun-linux.c b/server/tun-linux.c index 11c7f37a..b8705802 100644 --- a/server/tun-linux.c +++ b/server/tun-linux.c @@ -88,40 +88,6 @@ static void t_read(int fd, unsigned mode, void *v) static void t_init(void) { return; } -/* --- @t_open@ --- * - * - * Arguments: @char **ifn@ = where to put the interface name - * - * Returns: A file descriptor, or @-1@ on failure. - * - * Use: Opens a tunnel device. This will run with root privileges - * even if the rest of the server has dropped them. - */ - -static int t_open(char **ifn) -{ - int fd; - struct ifreq iff; - - if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { - a_warn("TUN", "-", "linux", - "open-error", "/dev/net/tun", "?ERRNO", - A_END); - return (-1); - } - memset(&iff, 0, sizeof(iff)); - iff.ifr_name[0] = 0; - iff.ifr_flags = IFF_TUN | IFF_NO_PI; - if (ioctl(fd, TUNSETIFF, &iff) < 0) { - a_warn("TUN", "-", "linux", "config-error", "?ERRNO", A_END); - close(fd); - return (-1); - } - iff.ifr_name[IFNAMSIZ - 1] = 0; - *ifn = xstrdup(iff.ifr_name); - return (fd); -} - /* --- @t_create@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -179,8 +145,8 @@ static void t_destroy(tunnel *t) const tunnel_ops tun_linux = { "linux", + TUNF_PRIVOPEN, t_init, - t_open, t_create, 0, t_inject, diff --git a/server/tun-slip.c b/server/tun-slip.c index 4afef753..40830f9d 100644 --- a/server/tun-slip.c +++ b/server/tun-slip.c @@ -440,8 +440,8 @@ static void t_destroy(tunnel *t) const tunnel_ops tun_slip = { "slip", - t_init, 0, + t_init, t_create, t_setifname, t_inject, diff --git a/server/tun-unet.c b/server/tun-unet.c index 88ce83d0..d4b4cf34 100644 --- a/server/tun-unet.c +++ b/server/tun-unet.c @@ -32,7 +32,7 @@ #ifdef TUN_UNET # include -# include +# include # include #endif @@ -88,44 +88,6 @@ static void t_read(int fd, unsigned mode, void *v) static void t_init(void) { return; } -/* --- @t_open@ --- * - * - * Arguments: @char **ifn@ = where to put the interface name - * - * Returns: A file descriptor, or @-1@ on failure. - * - * Use: Opens a tunnel device. This will run with root privileges - * even if the rest of the server has dropped them. - */ - -static int t_open(char **ifn) -{ - int fd; - int f; - struct unet_info uni; - - if ((fd = open("/dev/unet", O_RDWR)) < 0) { - a_warn("TUN", "-", "unet", "open-error", "/dev/unet", "?ERRNO", A_END); - goto fail_0; - } - if ((f = ioctl(fd, UNIOCGIFFLAGS)) < 0 || - ioctl(fd, UNIOCSIFFLAGS, f | IFF_POINTOPOINT)) { - a_warn("TUN", "-", "unet", "config-error", "?ERRNO", A_END); - goto fail_1; - } - if (ioctl(t->f.fd, UNIOCGINFO, &uni)) { - a_warn("TUN", "-", "unet", "getinfo-error", "?ERRNO", A_END); - goto fail_1; - } - *ifn = xstrdup(uni.uni_ifname); - return (fd); - -fail_1: - close(fd); -fail_0: - return (-1); -} - /* --- @t_create@ --- * * * Arguments: @peer *p@ = pointer to peer block @@ -183,8 +145,8 @@ static void t_destroy(tunnel *t) const tunnel_ops tun_unet = { "unet", + TUNF_PRIVOPEN, t_init, - t_open, t_create, 0, t_inject, diff --git a/vars.am b/vars.am index 5780686e..6eb59dbf 100644 --- a/vars.am +++ b/vars.am @@ -39,15 +39,17 @@ SUFFIXES = TRIPE_INCLUDES = \ -I$(top_builddir)/config.h \ - -I$(top_srcdir)/common + -I$(top_srcdir)/common \ + -I$(top_srcdir)/priv CPPFLAGS += $(TRIPE_INCLUDES) ###-------------------------------------------------------------------------- ### Miscellanous useful definitions. -## Library of common code. +## Libraries of common code. libtripe = $(top_builddir)/common/libtripe.a +libpriv = $(top_builddir)/priv/libpriv.a ## Create a directory if it doesn't exist. mkdir_p = $(top_srcdir)/config/install-sh -d -- [mdw]