Permalink
Browse files

Merge pull request #780 from qwcode/pip_build_directory

/tmp/pip-build fixes
  • Loading branch information...
2 parents c96548f + 1af3f2b commit 6961e33fd4f9bc28f3e6f9e42f62db599ccb3d27 @qwcode qwcode committed Jan 26, 2013
Showing with 137 additions and 4 deletions.
  1. +1 −0 AUTHORS.txt
  2. +3 −0 CHANGES.txt
  3. +1 −1 docs/cookbook.txt
  4. +1 −1 pip/commands/install.py
  5. +28 −1 pip/locations.py
  6. +1 −1 setup.py
  7. +102 −0 tests/test_locations.py
View
@@ -14,6 +14,7 @@ Clay McClure
Cody Soyland
Daniel Holth
Dave Abrahams
+David (d1b)
Dmitry Gladkov
Donald Stufft
Francesco
View
@@ -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
View
@@ -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::
@@ -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',
View
@@ -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:
View
@@ -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)
@@ -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()

0 comments on commit 6961e33

Please sign in to comment.