Index: usr.sbin/inetd/builtins.c =================================================================== --- usr.sbin/inetd/builtins.c +++ usr.sbin/inetd/builtins.c @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -75,30 +76,37 @@ struct biltin biltins[] = { /* Echo received data */ - { "echo", SOCK_STREAM, 1, -1, echo_stream }, - { "echo", SOCK_DGRAM, 0, 1, echo_dg }, + { "echo", SOCK_STREAM, true, -1, echo_stream, true }, + { "echo", SOCK_DGRAM, false, 1, echo_dg, false }, /* Internet /dev/null */ - { "discard", SOCK_STREAM, 1, -1, discard_stream }, - { "discard", SOCK_DGRAM, 0, 1, discard_dg }, + { "discard", SOCK_STREAM, true, -1, discard_stream, true }, + { "discard", SOCK_DGRAM, false, 1, discard_dg, false }, /* Return 32 bit time since 1900 */ - { "time", SOCK_STREAM, 0, -1, machtime_stream }, - { "time", SOCK_DGRAM, 0, 1, machtime_dg }, + { "time", SOCK_STREAM, false, -1, machtime_stream, + false }, + { "time", SOCK_DGRAM, false, 1, machtime_dg, false }, /* Return human-readable time */ - { "daytime", SOCK_STREAM, 0, -1, daytime_stream }, - { "daytime", SOCK_DGRAM, 0, 1, daytime_dg }, + { "daytime", SOCK_STREAM, false, -1, daytime_stream, false }, + { "daytime", SOCK_DGRAM, false, 1, daytime_dg, false }, /* Familiar character generator */ - { "chargen", SOCK_STREAM, 1, -1, chargen_stream }, - { "chargen", SOCK_DGRAM, 0, 1, chargen_dg }, + { "chargen", SOCK_STREAM, true, -1, chargen_stream, true }, + { "chargen", SOCK_DGRAM, false, 1, chargen_dg, false }, - { "tcpmux", SOCK_STREAM, 1, -1, (bi_fn_t *)tcpmux }, + /* + * 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, true, -1, (bi_fn_t *)tcpmux, + false }, - { "auth", SOCK_STREAM, 1, -1, ident_stream }, + { "auth", SOCK_STREAM, true, -1, ident_stream, false }, - { NULL, 0, 0, 0, NULL } + { NULL, 0, 0, 0, NULL, false } }; /* @@ -621,6 +629,8 @@ iderror(lport, fport, s, ID_UNKNOWN); fakeid_fd = open(p, O_RDONLY | O_NONBLOCK); free(p); + if (caph_limit_stream(fakeid_fd, CAPH_READ) == -1) + goto fakeid_fail; if (fakeid_fd == -1 || fstat(fakeid_fd, &sb) == -1 || !S_ISREG(sb.st_mode)) goto fakeid_fail; Index: usr.sbin/inetd/inetd.h =================================================================== --- usr.sbin/inetd/inetd.h +++ usr.sbin/inetd/inetd.h @@ -38,6 +38,7 @@ #include +#include #include #define BUFSIZE 8192 @@ -146,8 +147,9 @@ struct biltin { const char *bi_service; /* internally provided service name */ int bi_socktype; /* type of socket supported */ - short bi_fork; /* 1 if should fork before call */ + bool bi_fork; /* 1 if should fork before call */ int bi_maxchild; /* max number of children, -1=default */ bi_fn_t *bi_fn; /* function which performs it */ + bool bi_capenter; /* enter capability mode upon fork */ }; extern struct biltin biltins[]; Index: usr.sbin/inetd/inetd.c =================================================================== --- usr.sbin/inetd/inetd.c +++ usr.sbin/inetd/inetd.c @@ -111,6 +111,7 @@ * #endif */ #include +#include #include #include #include @@ -125,6 +126,7 @@ #include #include +#include #include #include #include @@ -295,6 +297,9 @@ static LIST_HEAD(, procinfo) proctable[PERIPSIZE]; +static cap_rights_t ctrl_rights, dgram_svc_rights, svc_rights; +static unsigned long *ctrl_cmds, nctrl_cmds; + static int getvalue(const char *arg, int *value, const char *whine) { @@ -328,6 +333,99 @@ } #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_cmds[] = { 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_cmds = nitems(caph_stream_cmds) + nitems(std_ctrl_cmds); + ctrl_cmds = reallocarray(NULL, nctrl_cmds, sizeof(*ctrl_cmds)); + if (ctrl_cmds == NULL) { + syslog(LOG_ERR, "reallocarray: %m"); + exit(EX_OSERR); + } + + for (i = 0, j = 0; i < nitems(caph_stream_cmds); ++i, ++j) + ctrl_cmds[j] = caph_stream_cmds[i]; + for (i = 0; i < nitems(std_ctrl_cmds); ++i, ++j) + ctrl_cmds[j] = std_ctrl_cmds[i]; +} + +/* + * Both service and control capabilities are handled through here, to simplify + * paths needed to read to understand what can be done. 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. + */ +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) { + syslog(LOG_ERR, "failed to limit %s sock: %m", + sep != NULL ? "service" : "control"); + exit(EX_OSERR); + } + if (sep == NULL && caph_ioctls_limit(fd, ctrl_cmds, nctrl_cmds) == -1) { + syslog(LOG_ERR, "failed to limit control ioctls: %m"); + exit(EX_OSERR); + } +} + int main(int argc, char **argv) { @@ -335,7 +433,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 @@ -352,8 +450,16 @@ 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(); + + if (caph_limit_stdio() == -1) { + syslog(LOG_ERR, "caph_limit_stdio: %m"); + exit(1); + } while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1) switch(ch) { @@ -558,10 +664,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++; @@ -644,6 +747,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"); @@ -661,6 +771,8 @@ } } else ctrl = sep->se_fd; + if (sep->se_socktype == SOCK_DGRAM) + setup_ctrl_caps(ctrl, sep); if (dolog && !ISWRAP(sep)) { char pname[NI_MAXHOST] = "unknown"; socklen_t sl; @@ -756,6 +868,7 @@ _exit(0); } } + #ifdef LIBWRAP if (ISWRAP(sep)) { inetd_setproctitle("wrapping", ctrl); @@ -786,6 +899,12 @@ } } #endif + + if (dofork && sep->se_bi != NULL && + sep->se_bi->bi_capenter && caph_enter() == -1) { + syslog(LOG_ERR, "cap_enter: %m"); + _exit(0); + } if (sep->se_bi) { (*sep->se_bi->bi_fn)(ctrl, sep); } else { @@ -1268,6 +1387,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) &&