diff -ru src/Python/sysmodule.c b/Python/sysmodule.c --- src/Python/sysmodule.c 2012-04-10 01:07:35.000000000 +0200 +++ b/Python/sysmodule.c 2012-10-29 22:18:46.337514322 +0100 @@ -46,6 +46,10 @@ #include #endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif + PyObject * PySys_GetObject(char *name) { @@ -1597,93 +1601,86 @@ return av; } -void -PySys_SetArgvEx(int argc, char **argv, int updatepath) +/* Prepend the parent directory of filename "arg" to the Python list + * "path". Return 0 normally, return -1 in case of error. */ +static int +PySys_UpdatePath(PyObject *path, char *arg) { #if defined(HAVE_REALPATH) char fullpath[MAXPATHLEN]; #elif defined(MS_WINDOWS) && !defined(MS_WINCE) char fullpath[MAX_PATH]; #endif - PyObject *av = makeargvobject(argc, argv); - PyObject *path = PySys_GetObject("path"); - if (av == NULL) - Py_FatalError("no mem for sys.argv"); - if (PySys_SetObject("argv", av) != 0) - Py_FatalError("can't assign sys.argv"); - if (updatepath && path != NULL) { - char *argv0 = argv[0]; + + /* Store original "arg" */ + char *given_arg = arg; + + Py_ssize_t n = 0; /* Length of arg */ + if (arg[0] != '\0') + { char *p = NULL; - Py_ssize_t n = 0; - PyObject *a; #ifdef HAVE_READLINK char link[MAXPATHLEN+1]; - char argv0copy[2*MAXPATHLEN+1]; - int nr = 0; - if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0) - nr = readlink(argv0, link, MAXPATHLEN); + char argcopy[2*MAXPATHLEN+1]; + int nr = readlink(arg, link, MAXPATHLEN); if (nr > 0) { /* It's a symlink */ link[nr] = '\0'; if (link[0] == SEP) - argv0 = link; /* Link to absolute path */ + arg = link; /* Link to absolute path */ else if (strchr(link, SEP) == NULL) ; /* Link without path */ else { - /* Must join(dirname(argv0), link) */ - char *q = strrchr(argv0, SEP); + /* Must join(dirname(arg), link) */ + char *q = strrchr(arg, SEP); if (q == NULL) - argv0 = link; /* argv0 without path */ + arg = link; /* arg without path */ else { /* Must make a copy */ - strcpy(argv0copy, argv0); - q = strrchr(argv0copy, SEP); + strcpy(argcopy, arg); + q = strrchr(argcopy, SEP); strcpy(q+1, link); - argv0 = argv0copy; + arg = argcopy; } } } #endif /* HAVE_READLINK */ #if SEP == '\\' /* Special case for MS filename syntax */ - if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0) { - char *q; + char *q; #if defined(MS_WINDOWS) && !defined(MS_WINCE) - /* This code here replaces the first element in argv with the full - path that it represents. Under CE, there are no relative paths so - the argument must be the full path anyway. */ - char *ptemp; - if (GetFullPathName(argv0, - sizeof(fullpath), - fullpath, - &ptemp)) { - argv0 = fullpath; - } + /* This code here replaces the first element in argv with the full + path that it represents. Under CE, there are no relative paths so + the argument must be the full path anyway. */ + char *ptemp; + if (GetFullPathName(arg, + sizeof(fullpath), + fullpath, + &ptemp)) { + arg = fullpath; + } #endif - p = strrchr(argv0, SEP); - /* Test for alternate separator */ - q = strrchr(p ? p : argv0, '/'); - if (q != NULL) - p = q; - if (p != NULL) { - n = p + 1 - argv0; - if (n > 1 && p[-1] != ':') - n--; /* Drop trailing separator */ - } + p = strrchr(arg, SEP); + /* Test for alternate separator */ + q = strrchr(p ? p : arg, '/'); + if (q != NULL) + p = q; + if (p != NULL) { + n = p + 1 - arg; + if (n > 1 && p[-1] != ':') + n--; /* Drop trailing separator */ } #else /* All other filename syntaxes */ - if (argc > 0 && argv0 != NULL && strcmp(argv0, "-c") != 0) { #if defined(HAVE_REALPATH) - if (realpath(argv0, fullpath)) { - argv0 = fullpath; - } -#endif - p = strrchr(argv0, SEP); + if (realpath(arg, fullpath)) { + arg = fullpath; } +#endif + p = strrchr(arg, SEP); if (p != NULL) { #ifndef RISCOS - n = p + 1 - argv0; + n = p + 1 - arg; #else /* don't include trailing separator */ - n = p - argv0; + n = p - arg; #endif /* RISCOS */ #if SEP == '/' /* Special case for Unix filename syntax */ if (n > 1) @@ -1691,12 +1688,146 @@ #endif /* Unix */ } #endif /* All others */ - a = PyString_FromStringAndSize(argv0, n); - if (a == NULL) - Py_FatalError("no mem for sys.path insertion"); - if (PyList_Insert(path, 0, a) < 0) - Py_FatalError("sys.path.insert(0) failed"); - Py_DECREF(a); + } + + /* Copy n bytes of arg to parent (the parent directory + * to be added to sys.path) */ + char parent[MAXPATHLEN+1]; + memcpy(parent, arg, n); + parent[n] = '\0'; + + /* Do some security checks before adding "parent" to sys.path */ +#ifdef HAVE_STAT + struct stat parent_stat; + struct stat arg_stat; + struct stat program_stat; /* Python program */ + char warnmsg[MAXPATHLEN + 400]; + const char *lecture = "Untrusted users could put files in this " + "directory which might then be imported by your Python code. " + "As a general precaution from similar exploits, " + "you should not execute Python code from this directory"; + if (stat( (parent[0] != '\0') ? parent : ".", &parent_stat) != 0) { + snprintf(warnmsg, sizeof(warnmsg), "not adding '%s' to sys.path since its status cannot be determined", parent); + return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1); + } + if (!S_ISDIR(parent_stat.st_mode)) { + snprintf(warnmsg, sizeof(warnmsg), "not adding '%s' to sys.path since it's not a directory", parent); + return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1); + } + + if (given_arg[0] != '\0' && stat(given_arg, &arg_stat) == 0) { + /* Only keep group bits if the group is the same as the + * group of "parent" (otherwise the group is considered unsafe). */ + if (arg_stat.st_gid != parent_stat.st_gid) + arg_stat.st_mode &= 0707; + /* If parent does *not* have the sticky bit set, "arg" is at + * least as writable as "parent". This obviously only applies + * if "arg" is an existing file/directory inside "parent", which + * is the case here. */ + if (!(parent_stat.st_mode & S_ISVTX)) + arg_stat.st_mode |= parent_stat.st_mode; + } else { + /* given_arg was "" or stat() failed, manually set relevant + * stat members to sensible values. Set the mode to whatever + * it would be if we would create a new file, keeping in mind + * the current umask. */ + unsigned int mask = umask(0777); umask(mask); + arg_stat.st_mode = 0666 & ~mask; + arg_stat.st_uid = 0; + /* Only keep group bit if the current group ID is the same as + * the group of "parent" */ + if (getgid() != parent_stat.st_gid) + arg_stat.st_mode &= 0707; + } + + if (stat(Py_GetProgramFullPath(), &program_stat) == 0) { + /* Only keep group bits if the group is the same as the + * group of "parent" (otherwise the group is considered unsafe). */ + if (program_stat.st_gid != parent_stat.st_gid) + program_stat.st_mode &= 0707; + } else { + /* stat() failed, set relevant stat members to safe values. */ + program_stat.st_mode = 0644; + program_stat.st_uid = 0; + } + + /* Check permissions, check that the "parent" directory is not + * more permissive than the script "arg" or the Python program + * "program". Otherwise adding "parent" to sys.path is a security + * risk. */ + if (parent_stat.st_mode & 0002) { + /* (A) "parent" is world-writable */ + if ((arg_stat.st_mode & 0002) == 0 && (program_stat.st_mode & 0002) == 0) { + snprintf(warnmsg, sizeof(warnmsg), + "not adding directory '%s' to sys.path since everybody can write to it.\n%s", + parent, lecture); + return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1); + } + } else if (parent_stat.st_mode & 0020) { + /* (B) "parent" is group-writable. Recall that the group + * permissions of "arg" and "program" refer to the group owning + * "parent". */ + if ((arg_stat.st_mode & 0022) == 0 && (program_stat.st_mode & 0022) == 0) { + snprintf(warnmsg, sizeof(warnmsg), + "not adding directory '%s' to sys.path since it's writable by an untrusted group.\n%s", + parent, lecture); + return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1); + } + } else { + /* (C) parent is neither group-, neither world-writable. + * We are safe if "arg" or "program" is group- or + * world-writable or if "parent" is owned by a trusted user: + * either the same owner as "arg" or "program", + * or root, or the current user. */ + if ( + (arg_stat.st_mode & 0022) == 0 && + (program_stat.st_mode & 0022) == 0 && + parent_stat.st_uid != arg_stat.st_uid && + parent_stat.st_uid != program_stat.st_uid && + parent_stat.st_uid != 0 && + parent_stat.st_uid != getuid()) { + snprintf(warnmsg, sizeof(warnmsg), + "not adding directory '%s' to sys.path since it's not owned by a trusted user.\n%s", + parent, lecture); + return PyErr_WarnEx(PyExc_RuntimeWarning, warnmsg, 1); + } + } +#endif /* HAVE_STAT */ + + PyObject *a = PyString_FromString(parent); + if (a == NULL) + Py_FatalError("no mem for sys.path insertion"); + if (PyList_Insert(path, 0, a) < 0) + return -1; + Py_DECREF(a); + + return 0; +} + +void +PySys_SetArgvEx(int argc, char **argv, int updatepath) +{ + PyObject *av = makeargvobject(argc, argv); + PyObject *path = PySys_GetObject("path"); + if (av == NULL) + Py_FatalError("no mem for sys.argv"); + if (PySys_SetObject("argv", av) != 0) + Py_FatalError("can't assign sys.argv"); + + if (updatepath && path != NULL) { + char *argv0; + if (argc <= 0 || argv[0] == NULL || strcmp(argv[0], "-c") == 0) { + /* If there is no argv[0] or argv[0] equals "-c", add "" to sys.path */ + argv0 = ""; + } + else { + argv0 = argv[0]; + } + if (PySys_UpdatePath(path, argv0)) { + /* No way to signal failure, so print exception and exit */ + PyErr_PrintEx(0); + exit(1); + } } Py_DECREF(av); }