=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/ssh/misc.c,v retrieving revision 1.156 retrieving revision 1.157 diff -u -r1.156 -r1.157 --- src/usr.bin/ssh/misc.c 2020/11/27 00:49:58 1.156 +++ src/usr.bin/ssh/misc.c 2020/12/22 00:12:22 1.157 @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.156 2020/11/27 00:49:58 djm Exp $ */ +/* $OpenBSD: misc.c,v 1.157 2020/12/22 00:12:22 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005-2020 Damien Miller. All rights reserved. @@ -2377,4 +2377,173 @@ if (devnull > STDERR_FILENO) close(devnull); return ret; +} + +/* + * Runs command in a subprocess with a minimal environment. + * Returns pid on success, 0 on failure. + * The child stdout and stderr maybe captured, left attached or sent to + * /dev/null depending on the contents of flags. + * "tag" is prepended to log messages. + * NB. "command" is only used for logging; the actual command executed is + * av[0]. + */ +pid_t +subprocess(const char *tag, const char *command, + int ac, char **av, FILE **child, u_int flags, + struct passwd *pw, privdrop_fn *drop_privs, privrestore_fn *restore_privs) +{ + FILE *f = NULL; + struct stat st; + int fd, devnull, p[2], i; + pid_t pid; + char *cp, errmsg[512]; + u_int nenv = 0; + char **env = NULL; + + /* If dropping privs, then must specify user and restore function */ + if (drop_privs != NULL && (pw == NULL || restore_privs == NULL)) { + error("%s: inconsistent arguments", tag); /* XXX fatal? */ + return 0; + } + if (pw == NULL && (pw = getpwuid(getuid())) == NULL) { + error("%s: no user for current uid", tag); + return 0; + } + if (child != NULL) + *child = NULL; + + debug3_f("%s command \"%s\" running as %s (flags 0x%x)", + tag, command, pw->pw_name, flags); + + /* Check consistency */ + if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && + (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { + error_f("inconsistent flags"); + return 0; + } + if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { + error_f("inconsistent flags/output"); + return 0; + } + + /* + * If executing an explicit binary, then verify the it exists + * and appears safe-ish to execute + */ + if (!path_absolute(av[0])) { + error("%s path is not absolute", tag); + return 0; + } + if (drop_privs != NULL) + drop_privs(pw); + if (stat(av[0], &st) == -1) { + error("Could not stat %s \"%s\": %s", tag, + av[0], strerror(errno)); + goto restore_return; + } + if ((flags & SSH_SUBPROCESS_UNSAFE_PATH) == 0 && + safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { + error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); + goto restore_return; + } + /* Prepare to keep the child's stdout if requested */ + if (pipe(p) == -1) { + error("%s: pipe: %s", tag, strerror(errno)); + restore_return: + if (restore_privs != NULL) + restore_privs(); + return 0; + } + if (restore_privs != NULL) + restore_privs(); + + switch ((pid = fork())) { + case -1: /* error */ + error("%s: fork: %s", tag, strerror(errno)); + close(p[0]); + close(p[1]); + return 0; + case 0: /* child */ + /* Prepare a minimal environment for the child. */ + if ((flags & SSH_SUBPROCESS_PRESERVE_ENV) == 0) { + nenv = 5; + env = xcalloc(sizeof(*env), nenv); + child_set_env(&env, &nenv, "PATH", _PATH_STDPATH); + child_set_env(&env, &nenv, "USER", pw->pw_name); + child_set_env(&env, &nenv, "LOGNAME", pw->pw_name); + child_set_env(&env, &nenv, "HOME", pw->pw_dir); + if ((cp = getenv("LANG")) != NULL) + child_set_env(&env, &nenv, "LANG", cp); + } + + for (i = 0; i < NSIG; i++) + ssh_signal(i, SIG_DFL); + + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { + error("%s: open %s: %s", tag, _PATH_DEVNULL, + strerror(errno)); + _exit(1); + } + if (dup2(devnull, STDIN_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + + /* Set up stdout as requested; leave stderr in place for now. */ + fd = -1; + if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) + fd = p[1]; + else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) + fd = devnull; + if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + closefrom(STDERR_FILENO + 1); + + if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) { + error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, + strerror(errno)); + _exit(1); + } + if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) { + error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, + strerror(errno)); + _exit(1); + } + /* stdin is pointed to /dev/null at this point */ + if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && + dup2(STDIN_FILENO, STDERR_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + if (env != NULL) + execve(av[0], av, env); + else + execv(av[0], av); + error("%s %s \"%s\": %s", tag, env == NULL ? "execv" : "execve", + command, strerror(errno)); + _exit(127); + default: /* parent */ + break; + } + + close(p[1]); + if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) + close(p[0]); + else if ((f = fdopen(p[0], "r")) == NULL) { + error("%s: fdopen: %s", tag, strerror(errno)); + close(p[0]); + /* Don't leave zombie child */ + kill(pid, SIGTERM); + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) + ; + return 0; + } + /* Success */ + debug3_f("%s pid %ld", tag, (long)pid); + if (child != NULL) + *child = f; + return pid; }