From: Vincent Bernat Date: Fri, 7 Nov 2008 10:38:27 +0000 (+0100) Subject: Start privilege separation X-Git-Tag: 0.2~26 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=b5562b23b33a84fc469a81295add8b65b563b24a;p=thirdparty%2Flldpd.git Start privilege separation --- diff --git a/configure.ac b/configure.ac index bd3f9563..669c4dd5 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,22 @@ AC_ARG_WITH(snmp, ) AM_CONDITIONAL([USE_SNMP], [test "${with_snmp}" != "no"]) +AC_ARG_WITH(privsep-user, + AC_HELP_STRING([--with-privsep-user], + [Which user to use for privilege separation]), + AC_DEFINE_UNQUOTED([PRIVSEP_USER], "$withval", [User for privilege separation]), + AC_DEFINE_UNQUOTED([PRIVSEP_USER], "lldpd", [User for privilege separation])) +AC_ARG_WITH(privsep-group, + AC_HELP_STRING([--with-privsep-group], + [Which group to use for privilege separation]), + AC_DEFINE_UNQUOTED([PRIVSEP_GROUP], "$withval", [Group for privilege separation]), + AC_DEFINE_UNQUOTED([PRIVSEP_GROUP], "lldpd", [Group for privilege separation])) +AC_ARG_WITH(privsep-chroot, + AC_HELP_STRING([--with-privsep-chroot], + [Which directory to use to chroot lldpd]), + AC_DEFINE_UNQUOTED([PRIVSEP_CHROOT], "$withval", [Chroot directory]), + AC_DEFINE_UNQUOTED([PRIVSEP_CHROOT], "/var/run/lldpd", [Chroot directory])) + # Checks for header files. AC_CHECK_DECLS([TAILQ_FIRST, TAILQ_NEXT, TAILQ_FOREACH, TAILQ_EMPTY],[],[],[[#include ]]) AC_CHECK_DECL([PACKET_ORIGDEV],[],[],[[#include ]]) diff --git a/debian/control b/debian/control index 70292a31..ca0551ff 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,7 @@ Standards-Version: 3.8.0 Package: lldpd Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} +Depends: ${shlibs:Depends}, ${misc:Depends}, adduser Description: implementation of IEEE 802.1ab (LLDP) This implementation provides LLDP sending and reception, supports VLAN and includes an SNMP subagent that can interface to an SNMP diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 00000000..5a59b314 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,13 @@ +#!/bin/sh -e + +if ! ([ "$1" = "configure" ] || [ "$1" = "reconfigure" ]); then + exit 0 +fi + +adduser --system --disabled-password --disabled-login --home /var/run/lldpd \ + --no-create-home --quiet --group lldpd +[ -d /var/run/lldpd ] || mkdir -p /var/run/lldpd + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 00000000..cd94b7c7 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,16 @@ +#!/bin/sh -e + +#DEBHELPER# + +case "$1" in + purge) + deluser --system lldpd || true + delgroup --system lldpd || true + + rm -rf /var/run/lldpd + ;; + *) + ;; +esac + +exit 0 diff --git a/src/Makefile.am b/src/Makefile.am index 5696b8ab..ebae44b1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ sbin_PROGRAMS = lldpd lldpctl COMMON = log.c ctl.c lldpd.h lldp.h cdp.h compat.h sonmp.h llc.h edp.h -lldpd_SOURCES = lldpd.c lldp.c cdp.c sonmp.c edp.c iov.c features.c client.c $(COMMON) +lldpd_SOURCES = lldpd.c lldp.c cdp.c sonmp.c edp.c iov.c features.c client.c priv.c $(COMMON) lldpctl_SOURCES = lldpctl.c $(COMMON) lldpd_LDADD = @LIBOBJS@ diff --git a/src/ctl.c b/src/ctl.c index 0c74d8d9..6c2be325 100644 --- a/src/ctl.c +++ b/src/ctl.c @@ -23,7 +23,7 @@ #include int -ctl_create(struct lldpd *cfg, char *name) +ctl_create(char *name) { int s; struct sockaddr_un su; @@ -41,7 +41,6 @@ ctl_create(struct lldpd *cfg, char *name) rc = errno; close(s); errno = rc; return -1; } - TAILQ_INIT(&cfg->g_clients); return s; } @@ -139,9 +138,8 @@ ctl_close(struct lldpd *cfg, int c) } void -ctl_cleanup(int c, char *name) +ctl_cleanup(char *name) { - close(c); if (unlink(name) == -1) LLOG_WARN("unable to unlink %s", name); } diff --git a/src/lldpd.c b/src/lldpd.c index 3d0253dd..02b46095 100644 --- a/src/lldpd.c +++ b/src/lldpd.c @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -133,7 +132,6 @@ int lldpd_iface_switchto(struct lldpd *, short int, struct lldpd_hardware *); struct lldpd_hardware *lldpd_port_add(struct lldpd *, struct ifaddrs *); void lldpd_loop(struct lldpd *); -void lldpd_hangup(int); void lldpd_shutdown(int); void lldpd_exit(); void lldpd_send_all(struct lldpd *); @@ -478,6 +476,7 @@ lldpd_port_add_vlan(struct lldpd *cfg, struct ifaddrs *ifa) free(vif); return NULL; } + /* Find the real interface */ vif->vif_real = NULL; TAILQ_FOREACH(hardware, &cfg->g_hardware, h_entries) { @@ -658,13 +657,14 @@ lldpd_port_add(struct lldpd *cfg, struct ifaddrs *ifa) break; } if (ethc.port == PORT_AUI) port->p_mau_type = LLDP_DOT3_MAU_AUI; - } + } else + LLOG_INFO("unable to get eth info for %s", hardware->h_ifname); if (!INTERFACE_OPENED(hardware)) { if (lldpd_iface_init(cfg, hardware) != 0) { + LLOG_WARN("unable to initialize %s", hardware->h_ifname); lldpd_vlan_cleanup(&hardware->h_lport); - free(hardware->h_lladdr); free(hardware->h_proto_macs); free(hardware); return (NULL); @@ -1184,19 +1184,19 @@ lldpd_loop(struct lldpd *cfg) int f; char status; struct utsname *un; - struct hostent *hp; + char *hp; /* Set system name and description */ if ((un = (struct utsname*)malloc(sizeof(struct utsname))) == NULL) fatal(NULL); if (uname(un) != 0) fatal("failed to get system information"); - if ((hp = gethostbyname(un->nodename)) == NULL) + if ((hp = priv_gethostbyname()) == NULL) fatal("failed to get system name"); free(cfg->g_lchassis.c_name); free(cfg->g_lchassis.c_descr); if (asprintf(&cfg->g_lchassis.c_name, "%s", - hp->h_name) == -1) + hp) == -1) fatal("failed to set system name"); if (asprintf(&cfg->g_lchassis.c_descr, "%s %s %s %s", un->sysname, un->release, un->version, un->machine) == -1) @@ -1205,7 +1205,7 @@ lldpd_loop(struct lldpd *cfg) /* Check forwarding */ cfg->g_lchassis.c_cap_enabled = 0; - if ((f = open("/proc/sys/net/ipv4/ip_forward", 0)) >= 0) { + if ((f = priv_open("/proc/sys/net/ipv4/ip_forward")) >= 0) { if ((read(f, &status, 1) == 1) && (status == '1')) cfg->g_lchassis.c_cap_enabled = LLDP_CAP_ROUTER; close(f); @@ -1295,15 +1295,6 @@ lldpd_loop(struct lldpd *cfg) lldpd_recv_all(cfg); } -void -lldpd_hangup(int sig) -{ - /* Re-execute */ - LLOG_INFO("sighup received, reloading"); - lldpd_exit(); - execv(saved_argv[0], saved_argv); -} - void lldpd_shutdown(int sig) { @@ -1319,7 +1310,8 @@ lldpd_exit() { struct lldpd_hardware *hardware; struct lldpd_vif *vif; - ctl_cleanup(gcfg->g_ctl, LLDPD_CTL_SOCKET); + close(gcfg->g_ctl); + priv_ctl_cleanup(); TAILQ_FOREACH(hardware, &gcfg->g_hardware, h_entries) { if (INTERFACE_OPENED(hardware)) lldpd_iface_close(gcfg, hardware); @@ -1386,6 +1378,11 @@ main(int argc, char *argv[]) } log_init(debug); + priv_init( +#ifdef USE_SNMP + snmp +#endif +); if (probe == 0) probe = LLDPD_TTL; @@ -1429,34 +1426,22 @@ main(int argc, char *argv[]) #endif /* USE_SNMP */ /* Create socket */ - if ((cfg->g_ctl = ctl_create(cfg, LLDPD_CTL_SOCKET)) == -1) - fatal("unable to create control socket " LLDPD_CTL_SOCKET); + if ((cfg->g_ctl = priv_ctl_create(cfg)) == -1) + fatalx("unable to create control socket " LLDPD_CTL_SOCKET); + TAILQ_INIT(&cfg->g_clients); - if (!debug && daemon(0, 0) != 0) { - ctl_cleanup(cfg->g_ctl, LLDPD_CTL_SOCKET); - fatal("failed to detach daemon"); - } gcfg = cfg; + if (!debug) { + priv_fork(); + } if (atexit(lldpd_exit) != 0) { - ctl_cleanup(cfg->g_ctl, LLDPD_CTL_SOCKET); + close(cfg->g_ctl); + priv_ctl_cleanup(); fatal("unable to set exit function"); } - if (!debug) { - int pid; - char *spid; - if ((pid = open(LLDPD_PID_FILE, - O_TRUNC | O_CREAT | O_WRONLY)) == -1) - fatal("unable to open pid file " LLDPD_PID_FILE); - if (asprintf(&spid, "%d\n", getpid()) == -1) - fatal("unable to create pid file " LLDPD_PID_FILE); - if (write(pid, spid, strlen(spid)) == -1) - fatal("unable to write pid file " LLDPD_PID_FILE); - free(spid); - close(pid); - } /* Signal handling */ - signal(SIGHUP, lldpd_hangup); + signal(SIGHUP, lldpd_shutdown); signal(SIGINT, lldpd_shutdown); signal(SIGTERM, lldpd_shutdown); diff --git a/src/lldpd.h b/src/lldpd.h index 38e6d4fe..10986240 100644 --- a/src/lldpd.h +++ b/src/lldpd.h @@ -260,9 +260,9 @@ int edp_send(PROTO_SEND_SIG); int edp_decode(PROTO_DECODE_SIG); /* ctl.c */ -int ctl_create(struct lldpd *, char *); +int ctl_create(char *); int ctl_connect(char *); -void ctl_cleanup(int, char *); +void ctl_cleanup(char *); int ctl_accept(struct lldpd *, int); int ctl_close(struct lldpd *, int); void ctl_msg_init(struct hmsg *, enum hmsg_type); @@ -324,4 +324,12 @@ void client_handle_get_port_related(struct lldpd *, struct hmsg *, void client_handle_shutdown(struct lldpd *, struct hmsg *, struct hmsg *); +/* priv.c */ +void priv_init(); +void priv_fork(); +int priv_ctl_create(); +void priv_ctl_cleanup(); +char *priv_gethostbyname(); +int priv_open(char*); + #endif /* _LLDPD_H */ diff --git a/src/priv.c b/src/priv.c new file mode 100644 index 00000000..d97ad161 --- /dev/null +++ b/src/priv.c @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2008 Vincent Bernat + * + * Permission to use, copy, modify, and 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. + */ + +/* This file contains code for privilege separation. When an error arises in + * monitor (which is running as root), it just stops instead of trying to + * recover. This module also contains proxies to privileged operations. In this + * case, error can be non fatal. */ + +#include "lldpd.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int remote; /* Other side */ +int monitored; /* Child */ + +/* Message to be sent between monitor and child. The convention is that both + * ends agree on the content (value) which depends on the message and on the + * direction. */ +struct priv_msg { + enum { + PRIV_FORK, + PRIV_CREATE_CTL_SOCKET, + PRIV_DELETE_CTL_SOCKET, + PRIV_GET_HOSTNAME, + PRIV_OPEN, + } msg; + union { + int integer; + char iface[IFNAMSIZ]; + char buf[1024]; + } value; +}; + +int +priv_send(struct priv_msg *msg) +{ + if (write(remote, msg, sizeof(struct priv_msg)) != + sizeof(struct priv_msg)) { + LLOG_WARN("unable to send message"); + errno = EPIPE; + return -1; + } + if (read(remote, msg, sizeof(struct priv_msg)) != + sizeof(struct priv_msg)) { + LLOG_WARN("unable to get answer"); + errno = EPIPE; + return -1; + } + return 0; +} + +/* Run as root */ +void +priv_send_back(struct priv_msg *msg) +{ + if (write(remote, msg, sizeof(struct priv_msg)) != + sizeof(struct priv_msg)) { + fatal("unable to send message"); + } +} + +/* Run as root */ +void +priv_send_fd(int fd) +{ + struct msghdr msg = {0}; + struct cmsghdr *cmsg; + char buf[CMSG_SPACE(sizeof(int))]; + + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + msg.msg_controllen = cmsg->cmsg_len; + if (sendmsg(remote, &msg, 0) == -1) { + LLOG_WARN("unable to send file descriptor %d", fd); + fatal(NULL); + } +} + +int +priv_get_fd() +{ + struct msghdr msg; + struct cmsghdr *cmsg; + char buf[CMSG_SPACE(sizeof(int))]; + + memset(&msg, 0, sizeof(struct msghdr)); + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + if (recvmsg(remote, &msg, 0) == -1) { + LLOG_WARN("unable to receive file descriptor"); + return -1; + } + if ((cmsg = CMSG_FIRSTHDR(&msg)) == NULL) { + LLOG_WARNX("no file descriptor in received message"); + return -1; + } + if (CMSG_NXTHDR(&msg, cmsg) != NULL) { + LLOG_WARNX("more than one file descriptor received"); + return -1; + } + if ((cmsg->cmsg_level != SOL_SOCKET) || + (cmsg->cmsg_type != SCM_RIGHTS)) { + LLOG_WARNX("unknown control data received (%d, %d)", + cmsg->cmsg_level, cmsg->cmsg_type); + return -1; + } + return (*(int *)CMSG_DATA(cmsg)); +} + +/* Proxies */ + +/* Proxy for fork */ +void +priv_fork() +{ + struct priv_msg(msg); + msg.msg = PRIV_FORK; + priv_send(&msg); +} + +/* Proxy for ctl_create, no argument since this is the monitor that decides the + * location of the socket */ +int +priv_ctl_create() +{ + struct priv_msg msg; + msg.msg = PRIV_CREATE_CTL_SOCKET; + if ((priv_send(&msg) == -1) || + (msg.value.integer == -1)) + return -1; + return priv_get_fd(); +} + +/* Proxy for ctl_cleanup */ +void +priv_ctl_cleanup() +{ + struct priv_msg msg; + msg.msg = PRIV_DELETE_CTL_SOCKET; + priv_send(&msg); +} + +/* Proxy for gethostbyname */ +char * +priv_gethostbyname() +{ + static struct priv_msg msg; + msg.msg = PRIV_GET_HOSTNAME; + if (priv_send(&msg) == -1) + fatal("unable to get hostname"); + return msg.value.buf; +} + +/* Proxy for open */ +int +priv_open(char *file) +{ + struct priv_msg msg; + msg.msg = PRIV_OPEN; + if (strlen(file) >= sizeof(msg.value.buf)) { + errno = ENAMETOOLONG; + return -1; + } + strlcpy(msg.value.buf, file, sizeof(msg.value.buf)); + if ((priv_send(&msg) == -1) || + (msg.value.integer == -1)) + return -1; + return priv_get_fd(); +} + +void +priv_fork_daemon(struct priv_msg *msg) +{ + int pid; + char *spid; + if (daemon(0, 0) != 0) + fatal("failed to detach daemon"); + if ((pid = open(LLDPD_PID_FILE, + O_TRUNC | O_CREAT | O_WRONLY)) == -1) + fatal("unable to open pid file " LLDPD_PID_FILE); + if (asprintf(&spid, "%d\n", getpid()) == -1) + fatal("unable to create pid file " LLDPD_PID_FILE); + if (write(pid, spid, strlen(spid)) == -1) + fatal("unable to write pid file " LLDPD_PID_FILE); + free(spid); + close(pid); +} + +void +priv_create_ctl_socket(struct priv_msg *msg) +{ + if ((msg->value.integer = + ctl_create(LLDPD_CTL_SOCKET)) == -1) { + LLOG_WARN("unable to create control socket"); + priv_send_back(msg); + } else { + priv_send_back(msg); + priv_send_fd(msg->value.integer); + close(msg->value.integer); + } +} + +void +priv_delete_ctl_socket(struct priv_msg *msg) +{ + ctl_cleanup(LLDPD_CTL_SOCKET); + priv_send_back(msg); +} + +void +priv_get_hostname(struct priv_msg *msg) +{ + struct utsname un; + struct hostent *hp; + if (uname(&un) != 0) + fatal("failed to get system information"); + if ((hp = gethostbyname(un.nodename)) == NULL) + fatal("failed to get system name"); + strlcpy(msg->value.buf, hp->h_name, sizeof(msg->value.buf)); + priv_send_back(msg); +} + +void +priv_open_readonly(struct priv_msg *msg) +{ + char* authorized[] = { + "/proc/sys/net/ipv4/ip_forward", + NULL + }; + char **f; + int fd; + + for (f=authorized; *f != NULL; f++) { + if (strncmp(msg->value.buf, *f, + sizeof(msg->value.buf)) == 0) + continue; + } + msg->value.buf[sizeof(msg->value.buf) - 1] = '\0'; + if (f == NULL) { + LLOG_WARNX("not authorized to open %s", msg->value.buf); + msg->value.integer = -1; + priv_send_back(msg); + return; + } + if ((fd = open(*f, 0)) == -1) { + msg->value.integer = -1; + priv_send_back(msg); + return; + } + msg->value.integer = fd; + priv_send_back(msg); + priv_send_fd(fd); + close(fd); +} + +struct dispatch_actions { + int msg; + void(*function)(struct priv_msg *); +}; + +struct dispatch_actions actions[] = { + {PRIV_FORK, priv_fork_daemon}, + {PRIV_CREATE_CTL_SOCKET, priv_create_ctl_socket}, + {PRIV_DELETE_CTL_SOCKET, priv_delete_ctl_socket}, + {PRIV_GET_HOSTNAME, priv_get_hostname}, + {PRIV_OPEN, priv_open_readonly}, + {0, NULL} +}; + +/* Main loop, run as root */ +void +priv_loop() +{ + struct priv_msg msg; + struct dispatch_actions *a; + + while (read(remote, &msg, sizeof(struct priv_msg)) == + sizeof(struct priv_msg)) { + for (a = actions; a->function != NULL; a++) { + if (msg.msg == a->msg) { + a->function(&msg); + break; + } + } + if (a->function == NULL) + fatal("bogus message received"); + } + /* Should never be there */ +} + +void +priv_exit() +{ + int status; + int rc; + if ((rc = waitpid(monitored, &status, WNOHANG)) == 0) { + LLOG_DEBUG("killing child"); + kill(monitored, SIGTERM); + } + if ((rc = waitpid(monitored, &status, WNOHANG)) == -1) + _exit(0); + LLOG_DEBUG("waiting for child %d to terminate", monitored); +} + +void +priv_shutdown(int sig) +{ + LLOG_DEBUG("received signal %d, exiting", sig); + priv_exit(); +} + +/* Initialization */ +void +#ifdef USE_SNMP +priv_init(int snmp) +#else +priv_init() +#endif +{ + int pair[2]; + struct passwd *user; + uid_t uid; + struct group *group; + gid_t gid; + + /* Create socket pair */ + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + fatal("unable to create socket pair for privilege separation"); + + /* Get users */ + if ((user = getpwnam(PRIVSEP_USER)) == NULL) + fatal("no " PRIVSEP_USER " user for privilege separation"); + uid = user->pw_uid; + if ((group = getgrnam(PRIVSEP_GROUP)) == NULL) + fatal("no " PRIVSEP_GROUP " group for privilege separation"); + gid = group->gr_gid; + + /* Spawn off monitor */ + if ((monitored = fork()) < 0) + fatal("unable to fork monitor"); + switch (monitored) { + case 0: + /* We are in the children, drop privileges */ + if (chroot(PRIVSEP_CHROOT) == -1) + fatal("unable to chroot"); + if ((setgid(gid) == -1) || (setuid(uid) == -1)) + fatal("unable to drop privileges"); + remote = pair[0]; + close(pair[1]); + break; + default: + /* We are in the monitor */ + remote = pair[1]; + close(pair[0]); + if (atexit(priv_exit) != 0) + fatal("unable to set exit function"); + signal(SIGHUP, priv_shutdown); + signal(SIGTERM, priv_shutdown); + signal(SIGINT, priv_shutdown); + signal(SIGCHLD, priv_shutdown); + priv_loop(); + exit(0); + } + +}