summaryrefslogtreecommitdiff
path: root/misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'misc.c')
-rw-r--r--misc.c486
1 files changed, 468 insertions, 18 deletions
diff --git a/misc.c b/misc.c
index 6e972f563..40aeeef36 100644
--- a/misc.c
+++ b/misc.c
@@ -1,4 +1,4 @@
1/* $OpenBSD: misc.c,v 1.109 2017/03/14 00:55:37 dtucker Exp $ */ 1/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 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>
@@ -62,6 +68,9 @@
62#include "misc.h" 68#include "misc.h"
63#include "log.h" 69#include "log.h"
64#include "ssh.h" 70#include "ssh.h"
71#include "sshbuf.h"
72#include "ssherr.h"
73#include "uidswap.h"
65#include "platform.h" 74#include "platform.h"
66 75
67/* remove newline at end of string */ 76/* remove newline at end of string */
@@ -541,7 +550,7 @@ addargs(arglist *args, char *fmt, ...)
541 } else if (args->num+2 >= nalloc) 550 } else if (args->num+2 >= nalloc)
542 nalloc *= 2; 551 nalloc *= 2;
543 552
544 args->list = xreallocarray(args->list, nalloc, sizeof(char *)); 553 args->list = xrecallocarray(args->list, args->nalloc, nalloc, sizeof(char *));
545 args->nalloc = nalloc; 554 args->nalloc = nalloc;
546 args->list[args->num++] = cp; 555 args->list[args->num++] = cp;
547 args->list[args->num] = NULL; 556 args->list[args->num] = NULL;
@@ -715,22 +724,6 @@ read_keyfile_line(FILE *f, const char *filename, char *buf, size_t bufsz,
715 return -1; 724 return -1;
716} 725}
717 726
718/*
719 * return 1 if the specified uid is a uid that may own a system directory
720 * otherwise 0.
721 */
722int
723platform_sys_dir_uid(uid_t uid)
724{
725 if (uid == 0)
726 return 1;
727#ifdef PLATFORM_SYS_DIR_UID
728 if (uid == PLATFORM_SYS_DIR_UID)
729 return 1;
730#endif
731 return 0;
732}
733
734int 727int
735secure_permissions(struct stat *st, uid_t uid) 728secure_permissions(struct stat *st, uid_t uid)
736{ 729{
@@ -1152,6 +1145,7 @@ static const struct {
1152 const char *name; 1145 const char *name;
1153 int value; 1146 int value;
1154} ipqos[] = { 1147} ipqos[] = {
1148 { "none", INT_MAX }, /* can't use 0 here; that's CS0 */
1155 { "af11", IPTOS_DSCP_AF11 }, 1149 { "af11", IPTOS_DSCP_AF11 },
1156 { "af12", IPTOS_DSCP_AF12 }, 1150 { "af12", IPTOS_DSCP_AF12 },
1157 { "af13", IPTOS_DSCP_AF13 }, 1151 { "af13", IPTOS_DSCP_AF13 },
@@ -1341,3 +1335,459 @@ daemonized(void)
1341 debug3("already daemonized"); 1335 debug3("already daemonized");
1342 return 1; 1336 return 1;
1343} 1337}
1338
1339
1340/*
1341 * Splits 's' into an argument vector. Handles quoted string and basic
1342 * escape characters (\\, \", \'). Caller must free the argument vector
1343 * and its members.
1344 */
1345int
1346argv_split(const char *s, int *argcp, char ***argvp)
1347{
1348 int r = SSH_ERR_INTERNAL_ERROR;
1349 int argc = 0, quote, i, j;
1350 char *arg, **argv = xcalloc(1, sizeof(*argv));
1351
1352 *argvp = NULL;
1353 *argcp = 0;
1354
1355 for (i = 0; s[i] != '\0'; i++) {
1356 /* Skip leading whitespace */
1357 if (s[i] == ' ' || s[i] == '\t')
1358 continue;
1359
1360 /* Start of a token */
1361 quote = 0;
1362 if (s[i] == '\\' &&
1363 (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\'))
1364 i++;
1365 else if (s[i] == '\'' || s[i] == '"')
1366 quote = s[i++];
1367
1368 argv = xreallocarray(argv, (argc + 2), sizeof(*argv));
1369 arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1);
1370 argv[argc] = NULL;
1371
1372 /* Copy the token in, removing escapes */
1373 for (j = 0; s[i] != '\0'; i++) {
1374 if (s[i] == '\\') {
1375 if (s[i + 1] == '\'' ||
1376 s[i + 1] == '\"' ||
1377 s[i + 1] == '\\') {
1378 i++; /* Skip '\' */
1379 arg[j++] = s[i];
1380 } else {
1381 /* Unrecognised escape */
1382 arg[j++] = s[i];
1383 }
1384 } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t'))
1385 break; /* done */
1386 else if (quote != 0 && s[i] == quote)
1387 break; /* done */
1388 else
1389 arg[j++] = s[i];
1390 }
1391 if (s[i] == '\0') {
1392 if (quote != 0) {
1393 /* Ran out of string looking for close quote */
1394 r = SSH_ERR_INVALID_FORMAT;
1395 goto out;
1396 }
1397 break;
1398 }
1399 }
1400 /* Success */
1401 *argcp = argc;
1402 *argvp = argv;
1403 argc = 0;
1404 argv = NULL;
1405 r = 0;
1406 out:
1407 if (argc != 0 && argv != NULL) {
1408 for (i = 0; i < argc; i++)
1409 free(argv[i]);
1410 free(argv);
1411 }
1412 return r;
1413}
1414
1415/*
1416 * Reassemble an argument vector into a string, quoting and escaping as
1417 * necessary. Caller must free returned string.
1418 */
1419char *
1420argv_assemble(int argc, char **argv)
1421{
1422 int i, j, ws, r;
1423 char c, *ret;
1424 struct sshbuf *buf, *arg;
1425
1426 if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL)
1427 fatal("%s: sshbuf_new failed", __func__);
1428
1429 for (i = 0; i < argc; i++) {
1430 ws = 0;
1431 sshbuf_reset(arg);
1432 for (j = 0; argv[i][j] != '\0'; j++) {
1433 r = 0;
1434 c = argv[i][j];
1435 switch (c) {
1436 case ' ':
1437 case '\t':
1438 ws = 1;
1439 r = sshbuf_put_u8(arg, c);
1440 break;
1441 case '\\':
1442 case '\'':
1443 case '"':
1444 if ((r = sshbuf_put_u8(arg, '\\')) != 0)
1445 break;
1446 /* FALLTHROUGH */
1447 default:
1448 r = sshbuf_put_u8(arg, c);
1449 break;
1450 }
1451 if (r != 0)
1452 fatal("%s: sshbuf_put_u8: %s",
1453 __func__, ssh_err(r));
1454 }
1455 if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) ||
1456 (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) ||
1457 (r = sshbuf_putb(buf, arg)) != 0 ||
1458 (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0))
1459 fatal("%s: buffer error: %s", __func__, ssh_err(r));
1460 }
1461 if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL)
1462 fatal("%s: malloc failed", __func__);
1463 memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf));
1464 ret[sshbuf_len(buf)] = '\0';
1465 sshbuf_free(buf);
1466 sshbuf_free(arg);
1467 return ret;
1468}
1469
1470/*
1471 * Runs command in a subprocess wuth a minimal environment.
1472 * Returns pid on success, 0 on failure.
1473 * The child stdout and stderr maybe captured, left attached or sent to
1474 * /dev/null depending on the contents of flags.
1475 * "tag" is prepended to log messages.
1476 * NB. "command" is only used for logging; the actual command executed is
1477 * av[0].
1478 */
1479pid_t
1480subprocess(const char *tag, struct passwd *pw, const char *command,
1481 int ac, char **av, FILE **child, u_int flags)
1482{
1483 FILE *f = NULL;
1484 struct stat st;
1485 int fd, devnull, p[2], i;
1486 pid_t pid;
1487 char *cp, errmsg[512];
1488 u_int envsize;
1489 char **child_env;
1490
1491 if (child != NULL)
1492 *child = NULL;
1493
1494 debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__,
1495 tag, command, pw->pw_name, flags);
1496
1497 /* Check consistency */
1498 if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
1499 (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) {
1500 error("%s: inconsistent flags", __func__);
1501 return 0;
1502 }
1503 if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) {
1504 error("%s: inconsistent flags/output", __func__);
1505 return 0;
1506 }
1507
1508 /*
1509 * If executing an explicit binary, then verify the it exists
1510 * and appears safe-ish to execute
1511 */
1512 if (*av[0] != '/') {
1513 error("%s path is not absolute", tag);
1514 return 0;
1515 }
1516 temporarily_use_uid(pw);
1517 if (stat(av[0], &st) < 0) {
1518 error("Could not stat %s \"%s\": %s", tag,
1519 av[0], strerror(errno));
1520 restore_uid();
1521 return 0;
1522 }
1523 if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) {
1524 error("Unsafe %s \"%s\": %s", tag, av[0], errmsg);
1525 restore_uid();
1526 return 0;
1527 }
1528 /* Prepare to keep the child's stdout if requested */
1529 if (pipe(p) != 0) {
1530 error("%s: pipe: %s", tag, strerror(errno));
1531 restore_uid();
1532 return 0;
1533 }
1534 restore_uid();
1535
1536 switch ((pid = fork())) {
1537 case -1: /* error */
1538 error("%s: fork: %s", tag, strerror(errno));
1539 close(p[0]);
1540 close(p[1]);
1541 return 0;
1542 case 0: /* child */
1543 /* Prepare a minimal environment for the child. */
1544 envsize = 5;
1545 child_env = xcalloc(sizeof(*child_env), envsize);
1546 child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH);
1547 child_set_env(&child_env, &envsize, "USER", pw->pw_name);
1548 child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name);
1549 child_set_env(&child_env, &envsize, "HOME", pw->pw_dir);
1550 if ((cp = getenv("LANG")) != NULL)
1551 child_set_env(&child_env, &envsize, "LANG", cp);
1552
1553 for (i = 0; i < NSIG; i++)
1554 signal(i, SIG_DFL);
1555
1556 if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
1557 error("%s: open %s: %s", tag, _PATH_DEVNULL,
1558 strerror(errno));
1559 _exit(1);
1560 }
1561 if (dup2(devnull, STDIN_FILENO) == -1) {
1562 error("%s: dup2: %s", tag, strerror(errno));
1563 _exit(1);
1564 }
1565
1566 /* Set up stdout as requested; leave stderr in place for now. */
1567 fd = -1;
1568 if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0)
1569 fd = p[1];
1570 else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0)
1571 fd = devnull;
1572 if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) {
1573 error("%s: dup2: %s", tag, strerror(errno));
1574 _exit(1);
1575 }
1576 closefrom(STDERR_FILENO + 1);
1577
1578 /* Don't use permanently_set_uid() here to avoid fatal() */
1579 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) {
1580 error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid,
1581 strerror(errno));
1582 _exit(1);
1583 }
1584 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) {
1585 error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid,
1586 strerror(errno));
1587 _exit(1);
1588 }
1589 /* stdin is pointed to /dev/null at this point */
1590 if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 &&
1591 dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
1592 error("%s: dup2: %s", tag, strerror(errno));
1593 _exit(1);
1594 }
1595
1596 execve(av[0], av, child_env);
1597 error("%s exec \"%s\": %s", tag, command, strerror(errno));
1598 _exit(127);
1599 default: /* parent */
1600 break;
1601 }
1602
1603 close(p[1]);
1604 if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0)
1605 close(p[0]);
1606 else if ((f = fdopen(p[0], "r")) == NULL) {
1607 error("%s: fdopen: %s", tag, strerror(errno));
1608 close(p[0]);
1609 /* Don't leave zombie child */
1610 kill(pid, SIGTERM);
1611 while (waitpid(pid, NULL, 0) == -1 && errno == EINTR)
1612 ;
1613 return 0;
1614 }
1615 /* Success */
1616 debug3("%s: %s pid %ld", __func__, tag, (long)pid);
1617 if (child != NULL)
1618 *child = f;
1619 return pid;
1620}
1621
1622/* Returns 0 if pid exited cleanly, non-zero otherwise */
1623int
1624exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet)
1625{
1626 int status;
1627
1628 while (waitpid(pid, &status, 0) == -1) {
1629 if (errno != EINTR) {
1630 error("%s: waitpid: %s", tag, strerror(errno));
1631 return -1;
1632 }
1633 }
1634 if (WIFSIGNALED(status)) {
1635 error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status));
1636 return -1;
1637 } else if (WEXITSTATUS(status) != 0) {
1638 do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO,
1639 "%s %s failed, status %d", tag, cmd, WEXITSTATUS(status));
1640 return -1;
1641 }
1642 return 0;
1643}
1644
1645/*
1646 * Check a given path for security. This is defined as all components
1647 * of the path to the file must be owned by either the owner of
1648 * of the file or root and no directories must be group or world writable.
1649 *
1650 * XXX Should any specific check be done for sym links ?
1651 *
1652 * Takes a file name, its stat information (preferably from fstat() to
1653 * avoid races), the uid of the expected owner, their home directory and an
1654 * error buffer plus max size as arguments.
1655 *
1656 * Returns 0 on success and -1 on failure
1657 */
1658int
1659safe_path(const char *name, struct stat *stp, const char *pw_dir,
1660 uid_t uid, char *err, size_t errlen)
1661{
1662 char buf[PATH_MAX], homedir[PATH_MAX];
1663 char *cp;
1664 int comparehome = 0;
1665 struct stat st;
1666
1667 if (realpath(name, buf) == NULL) {
1668 snprintf(err, errlen, "realpath %s failed: %s", name,
1669 strerror(errno));
1670 return -1;
1671 }
1672 if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL)
1673 comparehome = 1;
1674
1675 if (!S_ISREG(stp->st_mode)) {
1676 snprintf(err, errlen, "%s is not a regular file", buf);
1677 return -1;
1678 }
1679 if (!secure_permissions(stp, uid)) {
1680 snprintf(err, errlen, "bad ownership or modes for file %s",
1681 buf);
1682 return -1;
1683 }
1684
1685 /* for each component of the canonical path, walking upwards */
1686 for (;;) {
1687 if ((cp = dirname(buf)) == NULL) {
1688 snprintf(err, errlen, "dirname() failed");
1689 return -1;
1690 }
1691 strlcpy(buf, cp, sizeof(buf));
1692
1693 if (stat(buf, &st) < 0 ||
1694 !secure_permissions(&st, uid)) {
1695 snprintf(err, errlen,
1696 "bad ownership or modes for directory %s", buf);
1697 return -1;
1698 }
1699
1700 /* If are past the homedir then we can stop */
1701 if (comparehome && strcmp(homedir, buf) == 0)
1702 break;
1703
1704 /*
1705 * dirname should always complete with a "/" path,
1706 * but we can be paranoid and check for "." too
1707 */
1708 if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0))
1709 break;
1710 }
1711 return 0;
1712}
1713
1714/*
1715 * Version of safe_path() that accepts an open file descriptor to
1716 * avoid races.
1717 *
1718 * Returns 0 on success and -1 on failure
1719 */
1720int
1721safe_path_fd(int fd, const char *file, struct passwd *pw,
1722 char *err, size_t errlen)
1723{
1724 struct stat st;
1725
1726 /* check the open file to avoid races */
1727 if (fstat(fd, &st) < 0) {
1728 snprintf(err, errlen, "cannot stat file %s: %s",
1729 file, strerror(errno));
1730 return -1;
1731 }
1732 return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen);
1733}
1734
1735/*
1736 * Sets the value of the given variable in the environment. If the variable
1737 * already exists, its value is overridden.
1738 */
1739void
1740child_set_env(char ***envp, u_int *envsizep, const char *name,
1741 const char *value)
1742{
1743 char **env;
1744 u_int envsize;
1745 u_int i, namelen;
1746
1747 if (strchr(name, '=') != NULL) {
1748 error("Invalid environment variable \"%.100s\"", name);
1749 return;
1750 }
1751
1752 /*
1753 * If we're passed an uninitialized list, allocate a single null
1754 * entry before continuing.
1755 */
1756 if (*envp == NULL && *envsizep == 0) {
1757 *envp = xmalloc(sizeof(char *));
1758 *envp[0] = NULL;
1759 *envsizep = 1;
1760 }
1761
1762 /*
1763 * Find the slot where the value should be stored. If the variable
1764 * already exists, we reuse the slot; otherwise we append a new slot
1765 * at the end of the array, expanding if necessary.
1766 */
1767 env = *envp;
1768 namelen = strlen(name);
1769 for (i = 0; env[i]; i++)
1770 if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=')
1771 break;
1772 if (env[i]) {
1773 /* Reuse the slot. */
1774 free(env[i]);
1775 } else {
1776 /* New variable. Expand if necessary. */
1777 envsize = *envsizep;
1778 if (i >= envsize - 1) {
1779 if (envsize >= 1000)
1780 fatal("child_set_env: too many env vars");
1781 envsize += 50;
1782 env = (*envp) = xreallocarray(env, envsize, sizeof(char *));
1783 *envsizep = envsize;
1784 }
1785 /* Need to set the NULL pointer at end of array beyond the new slot. */
1786 env[i + 1] = NULL;
1787 }
1788
1789 /* Allocate space and format the variable in the appropriate slot. */
1790 env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1);
1791 snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value);
1792}
1793