Page MenuHomeFreeBSD

D23022.id.diff
No OneTemporary

D23022.id.diff

diff --git a/usr.sbin/inetd/builtins.c b/usr.sbin/inetd/builtins.c
--- a/usr.sbin/inetd/builtins.c
+++ b/usr.sbin/inetd/builtins.c
@@ -37,6 +37,7 @@
#include <sys/uio.h>
#include <sys/utsname.h>
+#include <capsicum_helpers.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
@@ -73,12 +74,12 @@
struct biltin biltins[] = {
/* Echo received data */
- { "echo", SOCK_STREAM, BIF_FORK, -1, echo_stream },
+ { "echo", SOCK_STREAM, BIF_CAPENTER, -1, echo_stream },
{ "echo", SOCK_DGRAM, 0, 1, echo_dg },
/* Internet /dev/null */
- { "discard", SOCK_STREAM, BIF_FORK, -1, discard_stream },
- { "discard", SOCK_DGRAM, 0, 1, discard_dg },
+ { "discard", SOCK_STREAM, BIF_CAPENTER, -1, discard_stream },
+ { "discard", SOCK_DGRAM, BIF_CAPENTER, 1, discard_dg },
/* Return 32 bit time since 1900 */
{ "time", SOCK_STREAM, 0, -1, machtime_stream },
@@ -89,9 +90,14 @@
{ "daytime", SOCK_DGRAM, 0, 1, daytime_dg },
/* Familiar character generator */
- { "chargen", SOCK_STREAM, BIF_FORK, -1, chargen_stream },
+ { "chargen", SOCK_STREAM, BIF_CAPENTER, -1, chargen_stream },
{ "chargen", SOCK_DGRAM, 0, 1, chargen_dg },
+ /*
+ * tcpmux cap_enter is effectively ignored; we explicitly check if the
+ * service is tcpmux, then we invoke it manually and proceed as if the
+ * service it resolved to was selected.
+ */
{ "tcpmux", SOCK_STREAM, BIF_FORK, -1, (bi_fn_t *)tcpmux },
{ "auth", SOCK_STREAM, BIF_FORK, -1, ident_stream },
diff --git a/usr.sbin/inetd/inetd.h b/usr.sbin/inetd/inetd.h
--- a/usr.sbin/inetd/inetd.h
+++ b/usr.sbin/inetd/inetd.h
@@ -36,6 +36,7 @@
#include <netinet/in.h>
+#include <stdbool.h>
#include <stdio.h>
#define BUFSIZE 8192
@@ -151,6 +152,11 @@
extern struct biltin biltins[];
#define BIF_FORK 0x0001 /* should fork before call */
+#define BIF_CAPENTER 0x0002 /* enter capability mode upon fork */
#define SERVTAB_FORK(sep) \
- ((sep)->se_bi == NULL || ((sep)->se_bi->bi_flags & BIF_FORK) != 0)
+ ((sep)->se_bi == NULL || \
+ ((sep)->se_bi->bi_flags & (BIF_FORK | BIF_CAPENTER)) != 0)
+
+#define SERVTAB_CAPENTER(sep) \
+ ((sep)->se_bi != NULL && ((sep)->se_bi->bi_flags & BIF_CAPENTER) != 0)
diff --git a/usr.sbin/inetd/inetd.c b/usr.sbin/inetd/inetd.c
--- a/usr.sbin/inetd/inetd.c
+++ b/usr.sbin/inetd/inetd.c
@@ -96,6 +96,7 @@
* #endif
*/
#include <sys/param.h>
+#include <sys/capsicum.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/wait.h>
@@ -110,6 +111,7 @@
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
+#include <capsicum_helpers.h>
#include <ctype.h>
#include <errno.h>
#include <err.h>
@@ -280,6 +282,9 @@
static LIST_HEAD(, procinfo) proctable[PERIPSIZE];
+static cap_rights_t ctrl_rights, dgram_svc_rights, svc_rights;
+static unsigned long *ctrl_ioctls, nctrl_ioctls;
+
static int
getvalue(const char *arg, int *value, const char *whine)
{
@@ -313,6 +318,101 @@
}
#endif
+static void
+setup_sigpipe(void)
+{
+ const unsigned long sigpipe_cmds[] = { FIONREAD };
+ cap_rights_t sigrxpipe_rights, sigtxpipe_rights;
+
+ cap_rights_init(&sigtxpipe_rights, CAP_WRITE);
+ cap_rights_init(&sigrxpipe_rights, CAP_READ, CAP_IOCTL, CAP_EVENT);
+ if (pipe2(signalpipe, O_CLOEXEC) != 0) {
+ syslog(LOG_ERR, "pipe: %m");
+ exit(EX_OSERR);
+ }
+ if (caph_rights_limit(signalpipe[1], &sigtxpipe_rights) == -1) {
+ syslog(LOG_ERR, "failed to limit tx signalpipe: %m");
+ exit(EX_OSERR);
+ }
+ if (caph_rights_limit(signalpipe[0], &sigrxpipe_rights) == -1 ||
+ caph_ioctls_limit(signalpipe[0], sigpipe_cmds,
+ nitems(sigpipe_cmds)) == -1) {
+ syslog(LOG_ERR, "failed to limit rx signalpipe: %m");
+ exit(EX_OSERR);
+ }
+}
+
+static void
+prepare_ctrl_caps(void)
+{
+ const unsigned long std_ctrl_ioctls[] = { FIONBIO, FIONREAD };
+ size_t i, j;
+
+ /*
+ * The rights we're imposing on these sockets will be passed down to
+ * inetd services as stdio, so we need to both be somewhat respectful of
+ * what they may reasonably attempt to do and we need to make sure we
+ * apply a superset of the standard rights we grant to stdio.
+ */
+ caph_stream_rights(&ctrl_rights, CAPH_READ | CAPH_WRITE);
+ caph_stream_rights(&svc_rights, CAPH_READ | CAPH_WRITE);
+
+ /* Control rights need to be a superset of service rights. */
+ cap_rights_set(&ctrl_rights, CAP_ACCEPT, CAP_BIND, CAP_CONNECT,
+ CAP_LISTEN, CAP_SETSOCKOPT, CAP_GETSOCKNAME);
+ cap_rights_set(&svc_rights, CAP_SETSOCKOPT, CAP_GETSOCKNAME);
+#ifdef LIBWRAP
+ cap_rights_set(&ctrl_rights, CAP_GETPEERNAME);
+ cap_rights_set(&svc_rights, CAP_GETPEERNAME);
+#endif
+ /* Now build dgram_svc_rights as based on svc_rights + CAP_CONNECT. */
+ cap_rights_init(&dgram_svc_rights);
+ cap_rights_merge(&dgram_svc_rights, &svc_rights);
+ cap_rights_set(&dgram_svc_rights, CAP_CONNECT);
+
+ nctrl_ioctls = nitems(caph_stream_cmds) + nitems(std_ctrl_ioctls);
+ ctrl_ioctls = calloc(nctrl_ioctls, sizeof(*ctrl_ioctls));
+ if (ctrl_ioctls == NULL) {
+ syslog(LOG_ERR, "calloc: %m");
+ exit(EX_OSERR);
+ }
+
+ for (i = 0, j = 0; i < nitems(caph_stream_cmds); ++i, ++j)
+ ctrl_ioctls[j] = caph_stream_cmds[i];
+ for (i = 0; i < nitems(std_ctrl_ioctls); ++i, ++j)
+ ctrl_ioctls[j] = std_ctrl_ioctls[i];
+}
+
+/*
+ * Service sockets are created via accept(2), thus inheriting the rights of the
+ * control socket. Therefore, control caps must be a superset of those needed
+ * by the services. Apply rights here for both service sockets and control
+ * socket to make it easier to audit what is happening.
+ *
+ * rights(4) are applied based both on whether we're a control or service
+ * socket, and whether we're UDP or TCP for service sockets. ioctl limitations
+ * are applied purely for control sockets.
+ */
+static void
+setup_ctrl_caps(int fd, struct servtab *sep)
+{
+ cap_rights_t *rights;
+
+ if (sep != NULL && sep->se_socktype == SOCK_DGRAM)
+ rights = &dgram_svc_rights;
+ else if (sep != NULL)
+ rights = &svc_rights;
+ else
+ rights = &ctrl_rights;
+
+ if (caph_rights_limit(fd, rights) == -1 || (sep == NULL &&
+ caph_ioctls_limit(fd, ctrl_ioctls, nctrl_ioctls) == -1)) {
+ syslog(LOG_ERR, "failed to limit %s sock: %m",
+ sep != NULL ? "service" : "control");
+ exit(EX_OSERR);
+ }
+}
+
int
main(int argc, char **argv)
{
@@ -320,7 +420,7 @@
struct passwd *pwd;
struct group *grp;
struct sigaction sa, saalrm, sachld, sahup, sapipe;
- int ch, dofork;
+ int ch;
pid_t pid;
char buf[50];
#ifdef LOGIN_CAP
@@ -337,8 +437,17 @@
const char *servname;
int error;
struct conninfo *conn;
+ bool dofork;
openlog("inetd", LOG_PID | LOG_NOWAIT | LOG_PERROR, LOG_DAEMON);
+ /* Relies on syslog(3). */
+ prepare_ctrl_caps();
+
+ caph_cache_catpages();
+ if (caph_limit_stdio() == -1) {
+ syslog(LOG_ERR, "caph_limit_stdio: %m");
+ exit(EX_OSERR);
+ }
while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1)
switch(ch) {
@@ -543,10 +652,7 @@
(void)setenv("inetd_dummy", dummy, 1);
}
- if (pipe2(signalpipe, O_CLOEXEC) != 0) {
- syslog(LOG_ERR, "pipe: %m");
- exit(EX_OSERR);
- }
+ setup_sigpipe();
FD_SET(signalpipe[0], &allsock);
#ifdef SANITY_CHECK
nsock++;
@@ -629,6 +735,13 @@
close(ctrl);
continue;
}
+
+ /*
+ * This will limit the service effectively to
+ * read/write/select for all spawned processes, both
+ * builtin and external.
+ */
+ setup_ctrl_caps(ctrl, sep);
i = 0;
if (ioctl(sep->se_fd, FIONBIO, &i) < 0)
syslog(LOG_ERR, "ioctl1(FIONBIO, 0): %m");
@@ -644,8 +757,18 @@
close(ctrl);
continue;
}
- } else
+ } else {
+ /*
+ * "wait" sockets and datagram sockets won't have
+ * any further limiting applied to them. For the
+ * former, the service needs to be able to accept(2)
+ * other sockets. The latter has already been
+ * limited as far as it can go, since we won't be
+ * needing any further bind(2).
+ */
ctrl = sep->se_fd;
+ }
+
if (dolog && !ISWRAP(sep)) {
char pname[NI_MAXHOST] = "unknown";
socklen_t sl;
@@ -741,6 +864,7 @@
_exit(0);
}
}
+
#ifdef LIBWRAP
if (ISWRAP(sep)) {
inetd_setproctitle("wrapping", ctrl);
@@ -771,7 +895,13 @@
}
}
#endif
+
if (sep->se_bi) {
+ if (SERVTAB_CAPENTER(sep) &&
+ caph_enter() == -1) {
+ syslog(LOG_ERR, "cap_enter: %m");
+ _exit(0);
+ }
(*sep->se_bi->bi_fn)(ctrl, sep);
} else {
if (debug)
@@ -1253,6 +1383,8 @@
sep->se_service, sep->se_proto);
return;
}
+
+ setup_ctrl_caps(sep->se_fd, NULL);
#define turnon(fd, opt) \
setsockopt(fd, SOL_SOCKET, opt, (char *)&on, sizeof (on))
if (strcmp(sep->se_proto, "tcp") == 0 && (options & SO_DEBUG) &&
@@ -1368,6 +1500,8 @@
}
if (sep->se_socktype == SOCK_STREAM)
listen(sep->se_fd, -1);
+ else if (sep->se_socktype == SOCK_DGRAM)
+ setup_ctrl_caps(sep->se_fd, sep);
enable(sep);
if (debug) {
warnx("registered %s on %d",

File Metadata

Mime Type
text/plain
Expires
Sun, Jan 25, 8:59 PM (11 h, 43 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
27968563
Default Alt Text
D23022.id.diff (8 KB)

Event Timeline