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,11 +76,11 @@ 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_STREAM, BIF_CAPENTER, -1, discard_stream }, { "discard", SOCK_DGRAM, 0, 1, discard_dg }, /* Return 32 bit time since 1900 */ @@ -91,9 +92,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 }, @@ -622,7 +628,8 @@ fakeid_fd = open(p, O_RDONLY | O_NONBLOCK); free(p); if (fakeid_fd == -1 || fstat(fakeid_fd, &sb) == -1 || - !S_ISREG(sb.st_mode)) + !S_ISREG(sb.st_mode) || caph_limit_stream(fakeid_fd, + CAPH_READ) == -1) goto fakeid_fail; if ((ssize = read(fakeid_fd, buf, sizeof(buf) - 1)) < 0) 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 @@ -153,6 +154,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) Index: usr.sbin/inetd/inetd.c =================================================================== --- usr.sbin/inetd/inetd.c +++ usr.sbin/inetd/inetd.c @@ -110,6 +110,7 @@ * #endif */ #include +#include #include #include #include @@ -124,6 +125,7 @@ #include #include +#include #include #include #include @@ -294,6 +296,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) { @@ -327,6 +332,96 @@ } #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 || (sep == NULL && + caph_ioctls_limit(fd, ctrl_cmds, nctrl_cmds) == -1)) { + syslog(LOG_ERR, "failed to limit %s sock: %m", + sep != NULL ? "service" : "control"); + exit(EX_OSERR); + } +} + int main(int argc, char **argv) { @@ -334,7 +429,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 @@ -351,8 +446,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) { @@ -557,10 +660,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++; @@ -643,6 +743,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"); @@ -660,6 +767,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; @@ -755,6 +864,7 @@ _exit(0); } } + #ifdef LIBWRAP if (ISWRAP(sep)) { inetd_setproctitle("wrapping", ctrl); @@ -785,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) @@ -1267,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) &&