Ticket #13579: sys_path_security.patch

File sys_path_security.patch, 11.1 KB (added by jdemeyer, 9 years ago)

Patch added to the Python sources

  • Python/sysmodule.c

    diff -ru src/Python/sysmodule.c b/Python/sysmodule.c
    src b  
    4646#include <langinfo.h>
    4747#endif
    4848
     49#ifdef HAVE_SYS_TYPES_H
     50#include <sys/types.h>
     51#endif
     52
    4953PyObject *
    5054PySys_GetObject(char *name)
    5155{
     
    15971601    return av;
    15981602}
    15991603
    1600 void
    1601 PySys_SetArgvEx(int argc, char **argv, int updatepath)
     1604/* Prepend the parent directory of filename "arg" to the Python list
     1605 * "path".  Return 0 normally, return -1 in case of error. */
     1606static int
     1607PySys_UpdatePath(PyObject *path, char *arg)
    16021608{
    16031609#if defined(HAVE_REALPATH)
    16041610    char fullpath[MAXPATHLEN];
    16051611#elif defined(MS_WINDOWS) && !defined(MS_WINCE)
    16061612    char fullpath[MAX_PATH];
    16071613#endif
    1608     PyObject *av = makeargvobject(argc, argv);
    1609     PyObject *path = PySys_GetObject("path");
    1610     if (av == NULL)
    1611         Py_FatalError("no mem for sys.argv");
    1612     if (PySys_SetObject("argv", av) != 0)
    1613         Py_FatalError("can't assign sys.argv");
    1614     if (updatepath && path != NULL) {
    1615         char *argv0 = argv[0];
     1614
     1615    /* Store original "arg" */
     1616    char *given_arg = arg;
     1617
     1618    Py_ssize_t n = 0;  /* Length of arg */
     1619    if (arg[0] != '\0')
     1620    {
    16161621        char *p = NULL;
    1617         Py_ssize_t n = 0;
    1618         PyObject *a;
    16191622#ifdef HAVE_READLINK
    16201623        char link[MAXPATHLEN+1];
    1621         char argv0copy[2*MAXPATHLEN+1];
    1622         int nr = 0;
    1623         if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0)
    1624             nr = readlink(argv0, link, MAXPATHLEN);
     1624        char argcopy[2*MAXPATHLEN+1];
     1625        int nr = readlink(arg, link, MAXPATHLEN);
    16251626        if (nr > 0) {
    16261627            /* It's a symlink */
    16271628            link[nr] = '\0';
    16281629            if (link[0] == SEP)
    1629                 argv0 = link; /* Link to absolute path */
     1630                arg = link; /* Link to absolute path */
    16301631            else if (strchr(link, SEP) == NULL)
    16311632                ; /* Link without path */
    16321633            else {
    1633                 /* Must join(dirname(argv0), link) */
    1634                 char *q = strrchr(argv0, SEP);
     1634                /* Must join(dirname(arg), link) */
     1635                char *q = strrchr(arg, SEP);
    16351636                if (q == NULL)
    1636                     argv0 = link; /* argv0 without path */
     1637                    arg = link; /* arg without path */
    16371638                else {
    16381639                    /* Must make a copy */
    1639                     strcpy(argv0copy, argv0);
    1640                     q = strrchr(argv0copy, SEP);
     1640                    strcpy(argcopy, arg);
     1641                    q = strrchr(argcopy, SEP);
    16411642                    strcpy(q+1, link);
    1642                     argv0 = argv0copy;
     1643                    arg = argcopy;
    16431644                }
    16441645            }
    16451646        }
    16461647#endif /* HAVE_READLINK */
    16471648#if SEP == '\\' /* Special case for MS filename syntax */
    1648         if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0) {
    1649             char *q;
     1649        char *q;
    16501650#if defined(MS_WINDOWS) && !defined(MS_WINCE)
    1651             /* This code here replaces the first element in argv with the full
    1652             path that it represents. Under CE, there are no relative paths so
    1653             the argument must be the full path anyway. */
    1654             char *ptemp;
    1655             if (GetFullPathName(argv0,
    1656                                sizeof(fullpath),
    1657                                fullpath,
    1658                                &ptemp)) {
    1659                 argv0 = fullpath;
    1660             }
     1651        /* This code here replaces the first element in argv with the full
     1652        path that it represents. Under CE, there are no relative paths so
     1653        the argument must be the full path anyway. */
     1654        char *ptemp;
     1655        if (GetFullPathName(arg,
     1656                           sizeof(fullpath),
     1657                           fullpath,
     1658                           &ptemp)) {
     1659            arg = fullpath;
     1660        }
    16611661#endif
    1662             p = strrchr(argv0, SEP);
    1663             /* Test for alternate separator */
    1664             q = strrchr(p ? p : argv0, '/');
    1665             if (q != NULL)
    1666                 p = q;
    1667             if (p != NULL) {
    1668                 n = p + 1 - argv0;
    1669                 if (n > 1 && p[-1] != ':')
    1670                     n--; /* Drop trailing separator */
    1671             }
     1662        p = strrchr(arg, SEP);
     1663        /* Test for alternate separator */
     1664        q = strrchr(p ? p : arg, '/');
     1665        if (q != NULL)
     1666            p = q;
     1667        if (p != NULL) {
     1668            n = p + 1 - arg;
     1669            if (n > 1 && p[-1] != ':')
     1670                n--; /* Drop trailing separator */
    16721671        }
    16731672#else /* All other filename syntaxes */
    1674         if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0) {
    16751673#if defined(HAVE_REALPATH)
    1676             if (realpath(argv0, fullpath)) {
    1677                 argv0 = fullpath;
    1678             }
    1679 #endif
    1680             p = strrchr(argv0, SEP);
     1674        if (realpath(arg, fullpath)) {
     1675            arg = fullpath;
    16811676        }
     1677#endif
     1678        p = strrchr(arg, SEP);
    16821679        if (p != NULL) {
    16831680#ifndef RISCOS
    1684             n = p + 1 - argv0;
     1681            n = p + 1 - arg;
    16851682#else /* don't include trailing separator */
    1686             n = p - argv0;
     1683            n = p - arg;
    16871684#endif /* RISCOS */
    16881685#if SEP == '/' /* Special case for Unix filename syntax */
    16891686            if (n > 1)
     
    16911688#endif /* Unix */
    16921689        }
    16931690#endif /* All others */
    1694         a = PyString_FromStringAndSize(argv0, n);
    1695         if (a == NULL)
    1696             Py_FatalError("no mem for sys.path insertion");
    1697         if (PyList_Insert(path, 0, a) < 0)
    1698             Py_FatalError("sys.path.insert(0) failed");
    1699         Py_DECREF(a);
     1691    }
     1692
     1693    /* Copy n bytes of arg to parent (the parent directory
     1694     * to be added to sys.path) */
     1695    char parent[MAXPATHLEN+1];
     1696    memcpy(parent, arg, n);
     1697    parent[n] = '\0';
     1698
     1699    /* Do some security checks before adding "parent" to sys.path */
     1700#ifdef HAVE_STAT
     1701    struct stat parent_stat;
     1702    struct stat arg_stat;
     1703    struct stat program_stat;  /* Python program */
     1704    char warnmsg[MAXPATHLEN + 400];
     1705    const char *lecture = "Untrusted users could put files in this "
     1706        "directory which might then be imported by your Python code. "
     1707        "As a general precaution from similar exploits, "
     1708        "you should not execute Python code from this directory";
     1709    if (stat( (parent[0] != '\0') ? parent : ".", &parent_stat) != 0) {
     1710        snprintf(warnmsg, sizeof(warnmsg), "not adding '%s' to sys.path since its status cannot be determined", parent);
     1711        return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1);
     1712    }
     1713    if (!S_ISDIR(parent_stat.st_mode)) {
     1714        snprintf(warnmsg, sizeof(warnmsg), "not adding '%s' to sys.path since it's not a directory", parent);
     1715        return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1);
     1716    }
     1717
     1718    if (given_arg[0] != '\0' && stat(given_arg, &arg_stat) == 0) {
     1719        /* If parent does *not* have the sticky bit set, "arg" is at
     1720         * least as writable as "parent".  This obviously only applies
     1721         * if "arg" is an existing file/directory inside "parent", which
     1722         * is the case here. */
     1723        if (!(parent_stat.st_mode & S_ISVTX))
     1724            arg_stat.st_mode |= parent_stat.st_mode;
     1725        /* Only keep group bits if the group is the same as the
     1726         * group of "parent" (otherwise the group is considered unsafe). */
     1727        if (arg_stat.st_gid != parent_stat.st_gid)
     1728            arg_stat.st_mode &= 0707;
     1729    } else {
     1730        /* given_arg was "" or stat() failed, manually set relevant
     1731         * stat members to safe values. */
     1732        arg_stat.st_mode = 0644;
     1733        arg_stat.st_uid = 0;
     1734    }
     1735
     1736    if (stat(Py_GetProgramFullPath(), &program_stat) == 0) {
     1737        /* Only keep group bits if the group is the same as the
     1738         * group of "parent" (otherwise the group is considered unsafe). */
     1739        if (program_stat.st_gid != parent_stat.st_gid)
     1740            program_stat.st_mode &= 0707;
     1741    } else {
     1742        /* stat() failed, set relevant stat members to safe values. */
     1743        program_stat.st_mode = 0644;
     1744        program_stat.st_uid = 0;
     1745    }
     1746
     1747    /* Check permissions, check that the "parent" directory is not
     1748     * more permissive than the script "arg" or the Python program
     1749     * "program".  Otherwise adding "parent" to sys.path is a security
     1750     * risk. */
     1751    if (parent_stat.st_mode & 0002) {
     1752        /* (A) "parent" is world-writable */
     1753        if ((arg_stat.st_mode & 0002) == 0 && (program_stat.st_mode & 0002) == 0) {
     1754            snprintf(warnmsg, sizeof(warnmsg),
     1755                "not adding directory '%s' to sys.path since everybody can write to it.\n%s",
     1756                parent, lecture);
     1757            return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1);
     1758        }
     1759    } else if (parent_stat.st_mode & 0020) {
     1760        /* (B) "parent" is group-writable.  Recall that the group
     1761         * permissions of "arg" and "program" refer to the group owning
     1762         * "parent". */
     1763        if ((arg_stat.st_mode & 0022) == 0 && (program_stat.st_mode & 0022) == 0) {
     1764            snprintf(warnmsg, sizeof(warnmsg),
     1765                "not adding directory '%s' to sys.path since it's writable by an untrusted group.\n%s",
     1766                parent, lecture);
     1767            return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1);
     1768        }
     1769    } else {
     1770        /* (C) parent is neither group-, neither world-writable.
     1771         * We are safe if "arg" or "program" is group- or
     1772         * world-writable or if "parent" is owned by a trusted user:
     1773         * either the same owner as "arg" or "program",
     1774         * or root, or the current user. */
     1775        if (
     1776          (arg_stat.st_mode & 0022) == 0 &&
     1777          (program_stat.st_mode & 0022) == 0 &&
     1778          parent_stat.st_uid != arg_stat.st_uid &&
     1779          parent_stat.st_uid != program_stat.st_uid &&
     1780          parent_stat.st_uid != 0 &&
     1781          parent_stat.st_uid != getuid()) {
     1782            snprintf(warnmsg, sizeof(warnmsg),
     1783                "not adding directory '%s' to sys.path since it's not owned by a trusted user.\n%s",
     1784                parent, lecture);
     1785            return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1);
     1786        }
     1787    }
     1788#endif  /* HAVE_STAT */
     1789
     1790    PyObject *a = PyString_FromString(parent);
     1791    if (a == NULL)
     1792        Py_FatalError("no mem for sys.path insertion");
     1793    if (PyList_Insert(path, 0, a) < 0)
     1794        return -1;
     1795    Py_DECREF(a);
     1796
     1797    return 0;
     1798}
     1799
     1800void
     1801PySys_SetArgvEx(int argc, char **argv, int updatepath)
     1802{
     1803    PyObject *av = makeargvobject(argc, argv);
     1804    PyObject *path = PySys_GetObject("path");
     1805    if (av == NULL)
     1806        Py_FatalError("no mem for sys.argv");
     1807    if (PySys_SetObject("argv", av) != 0)
     1808        Py_FatalError("can't assign sys.argv");
     1809
     1810    if (updatepath && path != NULL) {
     1811        char *argv0;
     1812        if (argc <= 0 || argv[0] == NULL || strcmp(argv[0], "-c") == 0) {
     1813            /* If there is no argv[0] or argv[0] equals "-c", add "" to sys.path */
     1814            argv0 = "";
     1815        }
     1816        else {
     1817            argv0 = argv[0];
     1818        }
     1819        if (PySys_UpdatePath(path, argv0)) {
     1820            /* No way to signal failure, so print exception and exit */
     1821            PyErr_PrintEx(0);
     1822            exit(1);
     1823        }
    17001824    }
    17011825    Py_DECREF(av);
    17021826}