summaryrefslogtreecommitdiff
path: root/misc.c
diff options
context:
space:
mode:
authordjm@openbsd.org <djm@openbsd.org>2017-08-18 05:36:45 +0000
committerDamien Miller <djm@mindrot.org>2017-08-23 19:47:06 +1000
commitde4ae07f12dabf8815ecede54235fce5d22e3f63 (patch)
treee62b3e975ed519f37c7cda4b5229bcffca73023f /misc.c
parent643c2ad82910691b2240551ea8b14472f60b5078 (diff)
upstream commit
Move several subprocess-related functions from various locations to misc.c. Extend subprocess() to offer a little more control over stdio disposition. feedback & ok dtucker@ Upstream-ID: 3573dd7109d13ef9bd3bed93a3deb170fbfce049
Diffstat (limited to 'misc.c')
-rw-r--r--misc.c468
1 files changed, 467 insertions, 1 deletions
diff --git a/misc.c b/misc.c
index 313c44109..8398f0b36 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: misc.c,v 1.111 2017/07/23 23:37:02 djm Exp $ */ 1/* $OpenBSD: misc.c,v 1.112 2017/08/18 05:36:45 djm Exp $ */
2/* 2/*
3 * Copyright (c) 2000 Markus Friedl. All rights reserved. 3 * Copyright (c) 2000 Markus Friedl. All rights reserved.
4 * Copyright (c) 2005,2006 Damien Miller. All rights reserved. 4 * Copyright (c) 2005,2006 Damien Miller. All rights reserved.
@@ -29,10 +29,16 @@
29#include <sys/types.h> 29#include <sys/types.h>
30#include <sys/ioctl.h> 30#include <sys/ioctl.h>
31#include <sys/socket.h> 31#include <sys/socket.h>
32#include <sys/stat.h>
32#include <sys/time.h> 33#include <sys/time.h>
34#include <sys/wait.h>
33#include <sys/un.h> 35#include <sys/un.h>
34 36
35#include <limits.h> 37#include <limits.h>
38#ifdef HAVE_LIBGEN_H
39# include <libgen.h>
40#endif
41#include <signal.h>
36#include <stdarg.h> 42#include <stdarg.h>
37#include <stdio.h> 43#include <stdio.h>
38#include <stdlib.h> 44#include <stdlib.h>
@@ -61,6 +67,9 @@
61#include "misc.h" 67#include "misc.h"
62#include "log.h" 68#include "log.h"
63#include "ssh.h" 69#include "ssh.h"
70#include "sshbuf.h"
71#include "ssherr.h"
72#include "uidswap.h"
64 73
65/* remove newline at end of string */ 74/* remove newline at end of string */
66char * 75char *
@@ -1275,3 +1284,460 @@ daemonized(void)
1275 debug3("already daemonized"); 1284 debug3("already daemonized");
1276 return 1; 1285 return 1;
1277} 1286}
1287
1288
1289/*
1290 * Splits 's' into an argument vector. Handles quoted string and basic
1291 * escape characters (\\, \", \'). Caller must free the argument vector
1292 * and its members.
1293 */
1294int
1295argv_split(const char *s, int *argcp, char ***argvp)
1296{
1297 int r = SSH_ERR_INTERNAL_ERROR;
1298 int argc = 0, quote, i, j;
1299 char *arg, **argv = xcalloc(1, sizeof(*argv));
1300
1301 *argvp = NULL;
1302 *argcp = 0;
1303
1304 for (i = 0; s[i] != '\0'; i++) {
1305 /* Skip leading whitespace */
1306 if (s[i] == ' ' || s[i] == '\t')
1307 continue;
1308
1309 /* Start of a token */
1310 quote = 0;
1311 if (s[i] == '\\' &&
1312 (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\'))
1313 i++;
1314 else if (s[i] == '\'' || s[i] == '"')
1315 quote = s[i++];
1316
1317 argv = xreallocarray(argv, (argc + 2), sizeof(*argv));
1318 arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1);
1319 argv[argc] = NULL;
1320
1321 /* Copy the token in, removing escapes */
1322 for (j = 0; s[i] != '\0'; i++) {
1323 if (s[i] == '\\') {
1324 if (s[i + 1] == '\'' ||
1325 s[i + 1] == '\"' ||
1326 s[i + 1] == '\\') {
1327 i++; /* Skip '\' */
1328 arg[j++] = s[i];
1329 } else {
1330 /* Unrecognised escape */
1331 arg[j++] = s[i];
1332 }
1333 } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t'))
1334 break; /* done */
1335 else if (quote != 0 && s[i] == quote)
1336 break; /* done */
1337 else
1338 arg[j++] = s[i];
1339 }
1340 if (s[i] == '\0') {
1341 if (quote != 0) {
1342 /* Ran out of string looking for close quote */
1343 r = SSH_ERR_INVALID_FORMAT;
1344 goto out;
1345 }
1346 break;
1347 }
1348 }
1349 /* Success */
1350 *argcp = argc;
1351 *argvp = argv;
1352 argc = 0;
1353 argv = NULL;
1354 r = 0;
1355 out:
1356 if (argc != 0 && argv != NULL) {
1357 for (i = 0; i < argc; i++)
1358 free(argv[i]);
1359 free(argv);
1360 }
1361 return r;
1362}
1363
1364/*
1365 * Reassemble an argument vector into a string, quoting and escaping as
1366 * necessary. Caller must free returned string.
1367 */
1368char *
1369argv_assemble(int argc, char **argv)
1370{
1371 int i, j, ws, r;
1372 char c, *ret;
1373 struct sshbuf *buf, *arg;
1374
1375 if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL)
1376 fatal("%s: sshbuf_new failed", __func__);
1377
1378 for (i = 0; i < argc; i++) {
1379 ws = 0;
1380 sshbuf_reset(arg);
1381 for (j = 0; argv[i][j] != '\0'; j++) {
1382 r = 0;
1383 c = argv[i][j];
1384 switch (c) {
1385 case ' ':
1386 case '\t':
1387 ws = 1;
1388 r = sshbuf_put_u8(arg, c);
1389 break;
1390 case '\\':
1391 case '\'':
1392 case '"':
1393 if ((r = sshbuf_put_u8(arg, '\\')) != 0)
1394 break;
1395 /* FALLTHROUGH */
1396 default:
1397 r = sshbuf_put_u8(arg, c);
1398 break;
1399 }
1400 if (r != 0)
1401 fatal("%s: sshbuf_put_u8: %s",
1402 __func__, ssh_err(r));
1403 }
1404 if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) ||
1405 (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) ||
1406 (r = sshbuf_putb(buf, arg)) != 0 ||
1407 (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0))
1408 fatal("%s: buffer error: %s", __func__, ssh_err(r));
1409 }
1410 if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL)
1411 fatal("%s: malloc failed", __func__);
1412 memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf));
1413 ret[sshbuf_len(buf)] = '\0';
1414 sshbuf_free(buf);
1415 sshbuf_free(arg);
1416 return ret;
1417}
1418
1419/*
1420 * Runs command in a subprocess wuth a minimal environment.
1421 * Returns pid on success, 0 on failure.
1422 * The child stdout and stderr maybe captured, left attached or sent to
1423 * /dev/null depending on the contents of flags.
1424 * "tag" is prepended to log messages.
1425 * NB. "command" is only used for logging; the actual command executed is
1426 * av[0].
1427 */
1428pid_t
1429subprocess(const char *tag, struct passwd *pw, const char *command,
1430 int ac, char **av, FILE **child, u_int flags)
1431{
1432 FILE *f = NULL;
1433 struct stat st;
1434 int fd, devnull, p[2], i;
1435 pid_t pid;
1436 char *cp, errmsg[512];
1437 u_int envsize;
1438 char **child_env;
1439
1440 if (child != NULL)
1441 *child = NULL;
1442
1443 debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__,
1444 tag, command, pw->pw_name, flags);
1445
1446 /* Check consistency */
1447 if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
1448 (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) {
1449 error("%s: inconsistent flags", __func__);
1450 return 0;
1451 }
1452 if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) {
1453 error("%s: inconsistent flags/output", __func__);
1454 return 0;
1455 }
1456
1457 /*
1458 * If executing an explicit binary, then verify the it exists
1459 * and appears safe-ish to execute
1460 */
1461 if (*av[0] != '/') {
1462 error("%s path is not absolute", tag);
1463 return 0;
1464 }
1465 temporarily_use_uid(pw);
1466 if (stat(av[0], &st) < 0) {
1467 error("Could not stat %s \"%s\": %s", tag,
1468 av[0], strerror(errno));
1469 restore_uid();
1470 return 0;
1471 }
1472 if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
1473 error("Unsafe %s \"%s\": %s", tag, av[0], errmsg);
1474 restore_uid();
1475 return 0;
1476 }
1477 /* Prepare to keep the child's stdout if requested */
1478 if (pipe(p) != 0) {
1479 error("%s: pipe: %s", tag, strerror(errno));
1480 restore_uid();
1481 return 0;
1482 }
1483 restore_uid();
1484
1485 switch ((pid = fork())) {
1486 case -1: /* error */
1487 error("%s: fork: %s", tag, strerror(errno));
1488 close(p[0]);
1489 close(p[1]);
1490 return 0;
1491 case 0: /* child */
1492 /* Prepare a minimal environment for the child. */
1493 envsize = 5;
1494 child_env = xcalloc(sizeof(*child_env), envsize);
1495 child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH);
1496 child_set_env(&child_env, &envsize, "USER", pw->pw_name);
1497 child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name);
1498 child_set_env(&child_env, &envsize, "HOME", pw->pw_dir);
1499 if ((cp = getenv("LANG")) != NULL)
1500 child_set_env(&child_env, &envsize, "LANG", cp);
1501
1502 for (i = 0; i < NSIG; i++)
1503 signal(i, SIG_DFL);
1504
1505 if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
1506 error("%s: open %s: %s", tag, _PATH_DEVNULL,
1507 strerror(errno));
1508 _exit(1);
1509 }
1510 if (dup2(devnull, STDIN_FILENO) == -1) {
1511 error("%s: dup2: %s", tag, strerror(errno));
1512 _exit(1);
1513 }
1514
1515 /* Set up stdout as requested; leave stderr in place for now. */
1516 fd = -1;
1517 if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0)
1518 fd = p[1];
1519 else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0)
1520 fd = devnull;
1521 if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) {
1522 error("%s: dup2: %s", tag, strerror(errno));
1523 _exit(1);
1524 }
1525 closefrom(STDERR_FILENO + 1);
1526
1527 /* Don't use permanently_set_uid() here to avoid fatal() */
1528 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
1529 error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
1530 strerror(errno));
1531 _exit(1);
1532 }
1533 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
1534 error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid,
1535 strerror(errno));
1536 _exit(1);
1537 }
1538 /* stdin is pointed to /dev/null at this point */
1539 if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
1540 dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
1541 error("%s: dup2: %s", tag, strerror(errno));
1542 _exit(1);
1543 }
1544
1545 execve(av[0], av, child_env);
1546 error("%s exec \"%s\": %s", tag, command, strerror(errno));
1547 _exit(127);
1548 default: /* parent */
1549 break;
1550 }
1551
1552 close(p[1]);
1553 if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0)
1554 close(p[0]);
1555 else if ((f = fdopen(p[0], "r")) == NULL) {
1556 error("%s: fdopen: %s", tag, strerror(errno));
1557 close(p[0]);
1558 /* Don't leave zombie child */
1559 kill(pid, SIGTERM);
1560 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
1561 ;
1562 return 0;
1563 }
1564 /* Success */
1565 debug3("%s: %s pid %ld", __func__, tag, (long)pid);
1566 if (child != NULL)
1567 *child = f;
1568 return pid;
1569}
1570
1571/* Returns 0 if pid exited cleanly, non-zero otherwise */
1572int
1573exited_cleanly(pid_t pid, const char *tag, const char *cmd)
1574{
1575 int status;
1576
1577 while (waitpid(pid, &status, 0) == -1) {
1578 if (errno != EINTR) {
1579 error("%s: waitpid: %s", tag, strerror(errno));
1580 return -1;
1581 }
1582 }
1583 if (WIFSIGNALED(status)) {
1584 error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status));
1585 return -1;
1586 } else if (WEXITSTATUS(status) != 0) {
1587 error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status));
1588 return -1;
1589 }
1590 return 0;
1591}
1592
1593/*
1594 * Check a given path for security. This is defined as all components
1595 * of the path to the file must be owned by either the owner of
1596 * of the file or root and no directories must be group or world writable.
1597 *
1598 * XXX Should any specific check be done for sym links ?
1599 *
1600 * Takes a file name, its stat information (preferably from fstat() to
1601 * avoid races), the uid of the expected owner, their home directory and an
1602 * error buffer plus max size as arguments.
1603 *
1604 * Returns 0 on success and -1 on failure
1605 */
1606int
1607safe_path(const char *name, struct stat *stp, const char *pw_dir,
1608 uid_t uid, char *err, size_t errlen)
1609{
1610 char buf[PATH_MAX], homedir[PATH_MAX];
1611 char *cp;
1612 int comparehome = 0;
1613 struct stat st;
1614
1615 if (realpath(name, buf) == NULL) {
1616 snprintf(err, errlen, "realpath %s failed: %s", name,
1617 strerror(errno));
1618 return -1;
1619 }
1620 if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL)
1621 comparehome = 1;
1622
1623 if (!S_ISREG(stp->st_mode)) {
1624 snprintf(err, errlen, "%s is not a regular file", buf);
1625 return -1;
1626 }
1627 if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) ||
1628 (stp->st_mode & 022) != 0) {
1629 snprintf(err, errlen, "bad ownership or modes for file %s",
1630 buf);
1631 return -1;
1632 }
1633
1634 /* for each component of the canonical path, walking upwards */
1635 for (;;) {
1636 if ((cp = dirname(buf)) == NULL) {
1637 snprintf(err, errlen, "dirname() failed");
1638 return -1;
1639 }
1640 strlcpy(buf, cp, sizeof(buf));
1641
1642 if (stat(buf, &st) < 0 ||
1643 (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) ||
1644 (st.st_mode & 022) != 0) {
1645 snprintf(err, errlen,
1646 "bad ownership or modes for directory %s", buf);
1647 return -1;
1648 }
1649
1650 /* If are past the homedir then we can stop */
1651 if (comparehome && strcmp(homedir, buf) == 0)
1652 break;
1653
1654 /*
1655 * dirname should always complete with a "/" path,
1656 * but we can be paranoid and check for "." too
1657 */
1658 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
1659 break;
1660 }
1661 return 0;
1662}
1663
1664/*
1665 * Version of safe_path() that accepts an open file descriptor to
1666 * avoid races.
1667 *
1668 * Returns 0 on success and -1 on failure
1669 */
1670int
1671safe_path_fd(int fd, const char *file, struct passwd *pw,
1672 char *err, size_t errlen)
1673{
1674 struct stat st;
1675
1676 /* check the open file to avoid races */
1677 if (fstat(fd, &st) < 0) {
1678 snprintf(err, errlen, "cannot stat file %s: %s",
1679 file, strerror(errno));
1680 return -1;
1681 }
1682 return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen);
1683}
1684
1685/*
1686 * Sets the value of the given variable in the environment. If the variable
1687 * already exists, its value is overridden.
1688 */
1689void
1690child_set_env(char ***envp, u_int *envsizep, const char *name,
1691 const char *value)
1692{
1693 char **env;
1694 u_int envsize;
1695 u_int i, namelen;
1696
1697 if (strchr(name, '=') != NULL) {
1698 error("Invalid environment variable \"%.100s\"", name);
1699 return;
1700 }
1701
1702 /*
1703 * If we're passed an uninitialized list, allocate a single null
1704 * entry before continuing.
1705 */
1706 if (*envp == NULL && *envsizep == 0) {
1707 *envp = xmalloc(sizeof(char *));
1708 *envp[0] = NULL;
1709 *envsizep = 1;
1710 }
1711
1712 /*
1713 * Find the slot where the value should be stored. If the variable
1714 * already exists, we reuse the slot; otherwise we append a new slot
1715 * at the end of the array, expanding if necessary.
1716 */
1717 env = *envp;
1718 namelen = strlen(name);
1719 for (i = 0; env[i]; i++)
1720 if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=')
1721 break;
1722 if (env[i]) {
1723 /* Reuse the slot. */
1724 free(env[i]);
1725 } else {
1726 /* New variable. Expand if necessary. */
1727 envsize = *envsizep;
1728 if (i >= envsize - 1) {
1729 if (envsize >= 1000)
1730 fatal("child_set_env: too many env vars");
1731 envsize += 50;
1732 env = (*envp) = xreallocarray(env, envsize, sizeof(char *));
1733 *envsizep = envsize;
1734 }
1735 /* Need to set the NULL pointer at end of array beyond the new slot. */
1736 env[i + 1] = NULL;
1737 }
1738
1739 /* Allocate space and format the variable in the appropriate slot. */
1740 env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1);
1741 snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value);
1742}
1743