Moderate severityNVD Advisory· Published Aug 17, 2013· Updated Apr 29, 2026
CVE-2013-1888
CVE-2013-1888
Description
pip before 1.3 allows local users to overwrite arbitrary files via a symlink attack on a file in the /tmp/pip-build temporary directory.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
pipPyPI | < 1.3 | 1.3 |
Affected products
4cpe:2.3:o:fedoraproject:fedora:17:*:*:*:*:*:*:*+ 2 more
- cpe:2.3:o:fedoraproject:fedora:17:*:*:*:*:*:*:*
- cpe:2.3:o:fedoraproject:fedora:18:*:*:*:*:*:*:*
- cpe:2.3:o:fedoraproject:fedora:19:*:*:*:*:*:*:*
Patches
2c039e47df471Merge 1af3f2b7140dd240d0c5836f59b5d589e94c7bb6 into c96548f57909b0acc33321ce9021f9a86ece61ee
7 files changed · +137 −4
AUTHORS.txt+1 −0 modified@@ -14,6 +14,7 @@ Clay McClure Cody Soyland Daniel Holth Dave Abrahams +David (d1b) Dmitry Gladkov Donald Stufft Francesco
CHANGES.txt+3 −0 modified@@ -7,6 +7,9 @@ develop (unreleased) * Added "pip list" for listing installed packages and the latest version available. Thanks Rafael Caricio, Miguel Araujo, Dmitry Gladkov (Pull #752) +* Fixed security issues with pip's use of temp build directories. + Thanks David (d1b) and Thomas Güttler. (Pull #780) + * Improvements to sphinx docs and cli help. (Pull #773) * Fixed issue #707, dealing with OS X temp dir handling, which was causing
docs/cookbook.txt+1 −1 modified@@ -72,7 +72,7 @@ pip allows you to *just* unpack archives to a build directory without installing $ pip install --no-install SomePackage -If you're in a virtualenv, the build dir is ``<virtualenv path>/build``. Otherwise, it's ``<OS temp dir>/pip-build`` +If you're in a virtualenv, the build dir is ``<virtualenv path>/build``. Otherwise, it's ``<OS temp dir>/pip-build-<username>`` Afterwards, to finish the job of installing unpacked archives, run::
pip/commands/install.py+1 −1 modified@@ -70,7 +70,7 @@ def __init__(self, *args, **kw): default=build_prefix, help='Directory to unpack packages into and build in. ' 'The default in a virtualenv is "<venv path>/build". ' - 'The default for global installs is "<OS temp dir>/pip-build".') + 'The default for global installs is "<OS temp dir>/pip-build-<username>".') cmd_opts.add_option( '-t', '--target',
pip/locations.py+28 −1 modified@@ -4,7 +4,9 @@ import site import os import tempfile +import getpass from pip.backwardcompat import get_python_lib +import pip.exceptions def running_under_virtualenv(): @@ -25,6 +27,31 @@ def virtualenv_no_global(): if running_under_virtualenv() and os.path.isfile(no_global_file): return True +def _get_build_prefix(): + """ Returns a safe build_prefix """ + path = os.path.join(tempfile.gettempdir(), 'pip-build-%s' % \ + getpass.getuser()) + if sys.platform == 'win32': + """ on windows(tested on 7) temp dirs are isolated """ + return path + try: + os.mkdir(path) + except OSError: + file_uid = None + try: + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) + file_uid = os.fstat(fd).st_uid + os.close(fd) + except OSError: + file_uid = None + if file_uid != os.getuid(): + msg = "The temporary folder for building (%s) is not owned by your user!" \ + % path + print (msg) + print("pip will not work until the temporary folder is " + \ + "either deleted or owned by your user account.") + raise pip.exceptions.InstallationError(msg) + return path if running_under_virtualenv(): build_prefix = os.path.join(sys.prefix, 'build') @@ -33,7 +60,7 @@ def virtualenv_no_global(): # Use tempfile to create a temporary folder for build # Note: we are NOT using mkdtemp so we can have a consistent build dir # Note: using realpath due to tmp dirs on OSX being symlinks - build_prefix = os.path.realpath(os.path.join(tempfile.gettempdir(), 'pip-build')) + build_prefix = os.path.realpath(_get_build_prefix()) ## FIXME: keep src in cwd for now (it is not a temporary folder) try:
setup.py+1 −1 modified@@ -8,7 +8,7 @@ here = os.path.abspath(os.path.dirname(__file__)) def read(*parts): - return codecs.open(os.path.join(here, *parts), 'r', 'utf8').read() + return codecs.open(os.path.join(here, *parts), 'r').read() def find_version(*file_paths): version_file = read(*file_paths)
tests/test_locations.py+102 −0 added@@ -0,0 +1,102 @@ +""" +locations.py tests + +""" +import os +import sys +import shutil +import tempfile +import getpass +from mock import Mock +from nose import SkipTest +from nose.tools import assert_raises +import pip + +class TestLocations: + def setup(self): + self.tempdir = tempfile.mkdtemp() + self.st_uid = 9999 + self.username = "example" + self.patch() + + def tearDown(self): + self.revert_patch() + shutil.rmtree(self.tempdir, ignore_errors=True) + + def patch(self): + """ first store and then patch python methods pythons """ + self.tempfile_gettempdir = tempfile.gettempdir + self.old_os_fstat = os.fstat + if sys.platform != 'win32': + # os.getuid not implemented on windows + self.old_os_getuid = os.getuid + self.old_getpass_getuser = getpass.getuser + + # now patch + tempfile.gettempdir = lambda : self.tempdir + getpass.getuser = lambda : self.username + os.getuid = lambda : self.st_uid + os.fstat = lambda fd : self.get_mock_fstat(fd) + + def revert_patch(self): + """ revert the patches to python methods """ + tempfile.gettempdir = self.tempfile_gettempdir + getpass.getuser = self.old_getpass_getuser + if sys.platform != 'win32': + # os.getuid not implemented on windows + os.getuid = self.old_os_getuid + os.fstat = self.old_os_fstat + + def get_mock_fstat(self, fd): + """ returns a basic mock fstat call result. + Currently only the st_uid attribute has been set. + """ + result = Mock() + result.st_uid = self.st_uid + return result + + def get_build_dir_location(self): + """ returns a string pointing to the + current build_prefix. + """ + return os.path.join(self.tempdir, 'pip-build-%s' % self.username) + + def test_dir_path(self): + """ test the path name for the build_prefix + """ + from pip import locations + assert locations._get_build_prefix() == self.get_build_dir_location() + + def test_dir_created(self): + """ test that the build_prefix directory is generated when + _get_build_prefix is called. + """ + #skip on windows, build dir is not created + if sys.platform == 'win32': + raise SkipTest() + assert not os.path.exists(self.get_build_dir_location() ), \ + "the build_prefix directory should not exist yet!" + from pip import locations + locations._get_build_prefix() + assert os.path.exists(self.get_build_dir_location() ), \ + "the build_prefix directory should now exist!" + + def test_error_raised_when_owned_by_another(self): + """ test calling _get_build_prefix when there is a temporary + directory owned by another user raises an InstallationError. + """ + #skip on windows; this exception logic only runs on linux + if sys.platform == 'win32': + raise SkipTest() + from pip import locations + os.getuid = lambda : 1111 + os.mkdir(self.get_build_dir_location() ) + assert_raises(pip.exceptions.InstallationError, locations._get_build_prefix) + + def test_no_error_raised_when_owned_by_you(self): + """ test calling _get_build_prefix when there is a temporary + directory owned by you raise no InstallationError. + """ + from pip import locations + os.mkdir(self.get_build_dir_location()) + locations._get_build_prefix()
dd8b41e9e730Merge 5c10fc5bbd7088311375b592c7874523836ac037 into c96548f57909b0acc33321ce9021f9a86ece61ee
2 files changed · +109 −1
pip/locations.py+28 −1 modified@@ -4,7 +4,9 @@ import site import os import tempfile +import getpass from pip.backwardcompat import get_python_lib +import pip.exceptions def running_under_virtualenv(): @@ -25,6 +27,31 @@ def virtualenv_no_global(): if running_under_virtualenv() and os.path.isfile(no_global_file): return True +def _get_build_prefix(): + """ Returns a safe build_prefix """ + path = os.path.join(tempfile.gettempdir(), 'pip-build-%s' % \ + getpass.getuser()) + if sys.platform == 'win32': + """ on windows(tested on 7) temp dirs are isolated """ + return path + try: + os.mkdir(path) + except OSError: + file_uid = None + try: + fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW) + file_uid = os.fstat(fd).st_uid + os.close(fd) + except OSError: + file_uid = None + if file_uid != os.getuid(): + msg = "The temporary folder for building (%s) is not owned by your user!" \ + % path + print (msg) + print("pip will not work until the temporary folder is " + \ + "either deleted or owned by your user account.") + raise pip.exceptions.InstallationError(msg) + return path if running_under_virtualenv(): build_prefix = os.path.join(sys.prefix, 'build') @@ -33,7 +60,7 @@ def virtualenv_no_global(): # Use tempfile to create a temporary folder for build # Note: we are NOT using mkdtemp so we can have a consistent build dir # Note: using realpath due to tmp dirs on OSX being symlinks - build_prefix = os.path.realpath(os.path.join(tempfile.gettempdir(), 'pip-build')) + build_prefix = os.path.realpath(_get_build_prefix()) ## FIXME: keep src in cwd for now (it is not a temporary folder) try:
tests/test_locations.py+81 −0 added@@ -0,0 +1,81 @@ +""" +locations.py tests + +""" +import os +import sys +import shutil +import tempfile +import getpass +from mock import Mock +import pip + +class TestLocations: + def setup(self): + self.tempdir = tempfile.mkdtemp() + self.st_uid = 9999 + self.username = "example" + self.patch() + + def tearDown(self): + self.revert_patch() + shutil.rmtree(self.tempdir, ignore_errors=True) + + def patch(self): + """ first store and then patch python methods pythons """ + self.tempfile_gettempdir = tempfile.gettempdir + self.old_os_fstat = os.fstat + self.old_os_getuid = os.getuid + self.old_getpass_getuser = getpass.getuser + + # now patch + tempfile.gettempdir = lambda : self.tempdir + getpass.getuser = lambda : self.username + os.getuid = lambda : self.st_uid + os.fstat = lambda fd : self.get_mock_fstat(fd) + + def revert_patch(self): + """ revert the patches to python methods """ + tempfile.gettempdir = self.tempfile_gettempdir + getpass.getuser = self.old_getpass_getuser + os.getuid = self.old_os_getuid + os.fstat = self.old_os_fstat + + def get_mock_fstat(self, fd): + """ returns a basic mock fstat call result. + Currently only the st_uid attribute has been set. + """ + result = Mock() + result.st_uid = self.st_uid + return result + + def get_build_dir_location(self): + """ returns a string pointing to the + current build_prefix. + """ + return os.path.join(self.tempdir, 'pip-build-%s' % self.username) + + def test_dir_created(self): + """ test that the build_prefix directory is generated when + _get_build_prefix is called. + """ + + assert not os.path.exists(self.get_build_dir_location() ), \ + "the build_prefix directory should not exist yet!" + from pip import locations + locations._get_build_prefix() + assert os.path.exists(self.get_build_dir_location() ), \ + "the build_prefix directory should now exist!" + + def test_error_raised_when_owned_by_another(self): + """ test calling _get_build_prefix when there is a temporary + directory owned by another user raises an InstallationError. + """ + from pip import locations + os.getuid = lambda : 1111 + os.mkdir(self.get_build_dir_location() ) + try: + locations._get_build_prefix() + raise AssertionError("An InstallationError should have been raised!") + except pip.exceptions.InstallationError: + pass
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- github.com/pypa/pip/pull/734/filesnvdPatchThird Party AdvisoryWEB
- github.com/pypa/pip/pull/780/filesnvdPatchThird Party AdvisoryWEB
- lists.fedoraproject.org/pipermail/package-announce/2013-May/105952.htmlnvdThird Party AdvisoryWEB
- lists.fedoraproject.org/pipermail/package-announce/2013-May/105989.htmlnvdThird Party AdvisoryWEB
- lists.fedoraproject.org/pipermail/package-announce/2013-May/106311.htmlnvdThird Party AdvisoryWEB
- www.openwall.com/lists/oss-security/2013/03/22/10nvdMailing ListThird Party AdvisoryWEB
- github.com/advisories/GHSA-4gv5-qhvr-36vvghsaADVISORY
- github.com/pypa/pip/issues/725nvdThird Party AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2013-1888ghsaADVISORY
- github.com/pypa/advisory-database/tree/main/vulns/pip/PYSEC-2013-9.yamlghsaWEB
News mentions
0No linked articles in our index yet.