VYPR
Moderate severityNVD Advisory· Published Nov 24, 2014· Updated May 6, 2026

CVE-2014-8991

CVE-2014-8991

Description

pip 1.3 through 1.5.6 allows local users to cause a denial of service (prevention of package installation) by creating a /tmp/pip-build-* file for another user.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
pipPyPI
>= 1.3, < 6.06.0

Affected products

2

Patches

1
043fe9f57003

Merge pull request #2122 from dstufft/randomized-build-directory

https://github.com/pypa/pipDonald StufftNov 12, 2014via ghsa
7 files changed · +256 186
  • CHANGES.txt+3 0 modified
    @@ -98,6 +98,9 @@
       looking for any file located inside of it instead of relying on the record
       file listing a directory. (:pull:`2076`)
     
    +* Fixed :issue:`1964`, :issue:`1935`, :issue:`676`, Use a randomized and secure
    +  default build directory when possible. (:pull:`2122`)
    +
     
     **1.5.6 (2014-05-16)**
     
    
  • pip/cmdoptions.py+3 7 modified
    @@ -11,9 +11,7 @@
     
     import copy
     from optparse import OptionGroup, SUPPRESS_HELP, Option
    -from pip.locations import (
    -    CA_BUNDLE_PATH, USER_CACHE_DIR, build_prefix, src_prefix,
    -)
    +from pip.locations import CA_BUNDLE_PATH, USER_CACHE_DIR, src_prefix
     
     
     def make_option_group(group, parser):
    @@ -360,10 +358,8 @@ def make(self):
         '-b', '--build', '--build-dir', '--build-directory',
         dest='build_dir',
         metavar='dir',
    -    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_<username>".')
    +    help='Directory to unpack packages into and build in.'
    +)
     
     install_options = OptionMaker(
         '--install-option',
    
  • pip/commands/install.py+131 109 modified
    @@ -7,13 +7,14 @@
     import warnings
     
     from pip.req import InstallRequirement, RequirementSet, parse_requirements
    -from pip.locations import virtualenv_no_global, distutils_scheme
    +from pip.locations import build_prefix, virtualenv_no_global, distutils_scheme
     from pip.basecommand import Command
     from pip.index import PackageFinder
     from pip.exceptions import (
         InstallationError, CommandError, PreviousBuildDirError,
     )
     from pip import cmdoptions
    +from pip.utils.build import BuildDirectory
     from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning
     
     
    @@ -209,7 +210,16 @@ def run(self, options, args):
             if options.download_dir:
                 options.no_install = True
                 options.ignore_installed = True
    -        options.build_dir = os.path.abspath(options.build_dir)
    +
    +        # If we have --no-install or --no-download and no --build we use the
    +        # legacy static build dir
    +        if (options.build_dir is None
    +                and (options.no_install or options.no_download)):
    +            options.build_dir = build_prefix
    +
    +        if options.build_dir:
    +            options.build_dir = os.path.abspath(options.build_dir)
    +
             options.src_dir = os.path.abspath(options.src_dir)
             install_options = options.install_options or []
             if options.use_user_site:
    @@ -268,113 +278,125 @@ def run(self, options, args):
     
                 finder = self._build_package_finder(options, index_urls, session)
     
    -            requirement_set = RequirementSet(
    -                build_dir=options.build_dir,
    -                src_dir=options.src_dir,
    -                download_dir=options.download_dir,
    -                upgrade=options.upgrade,
    -                as_egg=options.as_egg,
    -                ignore_installed=options.ignore_installed,
    -                ignore_dependencies=options.ignore_dependencies,
    -                force_reinstall=options.force_reinstall,
    -                use_user_site=options.use_user_site,
    -                target_dir=temp_target_dir,
    -                session=session,
    -                pycompile=options.compile,
    -            )
    -            for name in args:
    -                requirement_set.add_requirement(
    -                    InstallRequirement.from_line(name, None))
    -            for name in options.editables:
    -                requirement_set.add_requirement(
    -                    InstallRequirement.from_editable(
    -                        name,
    -                        default_vcs=options.default_vcs
    -                    )
    +            build_delete = (not (options.no_clean or options.build_dir))
    +            with BuildDirectory(options.build_dir,
    +                                delete=build_delete) as build_dir:
    +                requirement_set = RequirementSet(
    +                    build_dir=build_dir,
    +                    src_dir=options.src_dir,
    +                    download_dir=options.download_dir,
    +                    upgrade=options.upgrade,
    +                    as_egg=options.as_egg,
    +                    ignore_installed=options.ignore_installed,
    +                    ignore_dependencies=options.ignore_dependencies,
    +                    force_reinstall=options.force_reinstall,
    +                    use_user_site=options.use_user_site,
    +                    target_dir=temp_target_dir,
    +                    session=session,
    +                    pycompile=options.compile,
                     )
    -            for filename in options.requirements:
    -                for req in parse_requirements(
    -                        filename,
    -                        finder=finder, options=options, session=session):
    -                    requirement_set.add_requirement(req)
    -            if not requirement_set.has_requirements:
    -                opts = {'name': self.name}
    -                if options.find_links:
    -                    msg = ('You must give at least one requirement to %(name)s'
    -                           ' (maybe you meant "pip %(name)s %(links)s"?)' %
    -                           dict(opts, links=' '.join(options.find_links)))
    -                else:
    -                    msg = ('You must give at least one requirement '
    -                           'to %(name)s (see "pip help %(name)s")' % opts)
    -                logger.warning(msg)
    -                return
    -
    -            try:
    -                if not options.no_download:
    -                    requirement_set.prepare_files(finder)
    -                else:
    -                    requirement_set.locate_files()
    -
    -                if not options.no_install:
    -                    requirement_set.install(
    -                        install_options,
    -                        global_options,
    -                        root=options.root_path,
    +
    +                for name in args:
    +                    requirement_set.add_requirement(
    +                        InstallRequirement.from_line(name, None))
    +
    +                for name in options.editables:
    +                    requirement_set.add_requirement(
    +                        InstallRequirement.from_editable(
    +                            name,
    +                            default_vcs=options.default_vcs
    +                        )
                         )
    -                    installed = ' '.join([
    -                        req.name for req in
    -                        requirement_set.successfully_installed
    -                    ])
    -                    if installed:
    -                        logger.info('Successfully installed %s', installed)
    -                else:
    -                    downloaded = ' '.join([
    -                        req.name
    -                        for req in requirement_set.successfully_downloaded
    -                    ])
    -                    if downloaded:
    -                        logger.info('Successfully downloaded %s', downloaded)
    -            except PreviousBuildDirError:
    -                options.no_clean = True
    -                raise
    -            finally:
    -                # Clean up
    -                if ((not options.no_clean)
    -                        and ((not options.no_install)
    -                             or options.download_dir)):
    -                    requirement_set.cleanup_files()
    -
    -            if options.target_dir:
    -                if not os.path.exists(options.target_dir):
    -                    os.makedirs(options.target_dir)
    -                lib_dir = distutils_scheme('', home=temp_target_dir)['purelib']
    -                for item in os.listdir(lib_dir):
    -                    target_item_dir = os.path.join(options.target_dir, item)
    -                    if os.path.exists(target_item_dir):
    -                        if not options.upgrade:
    -                            logger.warning(
    -                                'Target directory %s already exists. Specify '
    -                                '--upgrade to force replacement.',
    -                                target_item_dir
    -                            )
    -                            continue
    -                        if os.path.islink(target_item_dir):
    -                            logger.warning(
    -                                'Target directory %s already exists and is '
    -                                'a link. Pip will not automatically replace '
    -                                'links, please remove if replacement is '
    -                                'desired.',
    -                                target_item_dir
    +
    +                for filename in options.requirements:
    +                    for req in parse_requirements(
    +                            filename,
    +                            finder=finder, options=options, session=session):
    +                        requirement_set.add_requirement(req)
    +
    +                if not requirement_set.has_requirements:
    +                    opts = {'name': self.name}
    +                    if options.find_links:
    +                        msg = ('You must give at least one requirement to '
    +                               '%(name)s (maybe you meant "pip %(name)s '
    +                               '%(links)s"?)' %
    +                               dict(opts, links=' '.join(options.find_links)))
    +                    else:
    +                        msg = ('You must give at least one requirement '
    +                               'to %(name)s (see "pip help %(name)s")' % opts)
    +                    logger.warning(msg)
    +                    return
    +
    +                try:
    +                    if not options.no_download:
    +                        requirement_set.prepare_files(finder)
    +                    else:
    +                        requirement_set.locate_files()
    +
    +                    if not options.no_install:
    +                        requirement_set.install(
    +                            install_options,
    +                            global_options,
    +                            root=options.root_path,
    +                        )
    +                        installed = ' '.join([
    +                            req.name for req in
    +                            requirement_set.successfully_installed
    +                        ])
    +                        if installed:
    +                            logger.info('Successfully installed %s', installed)
    +                    else:
    +                        downloaded = ' '.join([
    +                            req.name
    +                            for req in requirement_set.successfully_downloaded
    +                        ])
    +                        if downloaded:
    +                            logger.info(
    +                                'Successfully downloaded %s', downloaded
                                 )
    -                            continue
    -                        if os.path.isdir(target_item_dir):
    -                            shutil.rmtree(target_item_dir)
    -                        else:
    -                            os.remove(target_item_dir)
    -
    -                    shutil.move(
    -                        os.path.join(lib_dir, item),
    -                        target_item_dir
    -                    )
    -                shutil.rmtree(temp_target_dir)
    -            return requirement_set
    +                except PreviousBuildDirError:
    +                    options.no_clean = True
    +                    raise
    +                finally:
    +                    # Clean up
    +                    if ((not options.no_clean)
    +                            and ((not options.no_install)
    +                                 or options.download_dir)):
    +                        requirement_set.cleanup_files()
    +
    +        if options.target_dir:
    +            if not os.path.exists(options.target_dir):
    +                os.makedirs(options.target_dir)
    +
    +            lib_dir = distutils_scheme('', home=temp_target_dir)['purelib']
    +
    +            for item in os.listdir(lib_dir):
    +                target_item_dir = os.path.join(options.target_dir, item)
    +                if os.path.exists(target_item_dir):
    +                    if not options.upgrade:
    +                        logger.warning(
    +                            'Target directory %s already exists. Specify '
    +                            '--upgrade to force replacement.',
    +                            target_item_dir
    +                        )
    +                        continue
    +                    if os.path.islink(target_item_dir):
    +                        logger.warning(
    +                            'Target directory %s already exists and is '
    +                            'a link. Pip will not automatically replace '
    +                            'links, please remove if replacement is '
    +                            'desired.',
    +                            target_item_dir
    +                        )
    +                        continue
    +                    if os.path.isdir(target_item_dir):
    +                        shutil.rmtree(target_item_dir)
    +                    else:
    +                        os.remove(target_item_dir)
    +
    +                shutil.move(
    +                    os.path.join(lib_dir, item),
    +                    target_item_dir
    +                )
    +            shutil.rmtree(temp_target_dir)
    +        return requirement_set
    
  • pip/commands/wheel.py+66 58 modified
    @@ -10,6 +10,7 @@
     from pip.exceptions import CommandError, PreviousBuildDirError
     from pip.req import InstallRequirement, RequirementSet, parse_requirements
     from pip.utils import normalize_path
    +from pip.utils.build import BuildDirectory
     from pip.utils.deprecation import RemovedInPip7Warning, RemovedInPip8Warning
     from pip.wheel import WheelBuilder
     from pip import cmdoptions
    @@ -157,6 +158,9 @@ def run(self, options, args):
                     RemovedInPip8Warning,
                 )
     
    +        if options.build_dir:
    +            options.build_dir = os.path.abspath(options.build_dir)
    +
             with self._build_session(options) as session:
     
                 finder = PackageFinder(
    @@ -171,63 +175,67 @@ def run(self, options, args):
                     session=session,
                 )
     
    -            options.build_dir = os.path.abspath(options.build_dir)
    -            requirement_set = RequirementSet(
    -                build_dir=options.build_dir,
    -                src_dir=options.src_dir,
    -                download_dir=None,
    -                ignore_dependencies=options.ignore_dependencies,
    -                ignore_installed=True,
    -                session=session,
    -                wheel_download_dir=options.wheel_dir
    -            )
    +            build_delete = (not (options.no_clean or options.build_dir))
    +            with BuildDirectory(options.build_dir,
    +                                delete=build_delete) as build_dir:
    +                requirement_set = RequirementSet(
    +                    build_dir=build_dir,
    +                    src_dir=options.src_dir,
    +                    download_dir=None,
    +                    ignore_dependencies=options.ignore_dependencies,
    +                    ignore_installed=True,
    +                    session=session,
    +                    wheel_download_dir=options.wheel_dir
    +                )
     
    -            # make the wheelhouse
    -            if not os.path.exists(options.wheel_dir):
    -                os.makedirs(options.wheel_dir)
    -
    -            # parse args and/or requirements files
    -            for name in args:
    -                requirement_set.add_requirement(
    -                    InstallRequirement.from_line(name, None))
    -            for name in options.editables:
    -                requirement_set.add_requirement(
    -                    InstallRequirement.from_editable(
    -                        name,
    -                        default_vcs=options.default_vcs
    +                # make the wheelhouse
    +                if not os.path.exists(options.wheel_dir):
    +                    os.makedirs(options.wheel_dir)
    +
    +                # parse args and/or requirements files
    +                for name in args:
    +                    requirement_set.add_requirement(
    +                        InstallRequirement.from_line(name, None))
    +                for name in options.editables:
    +                    requirement_set.add_requirement(
    +                        InstallRequirement.from_editable(
    +                            name,
    +                            default_vcs=options.default_vcs
    +                        )
                         )
    -                )
    -            for filename in options.requirements:
    -                for req in parse_requirements(
    -                        filename,
    -                        finder=finder,
    -                        options=options,
    -                        session=session):
    -                    requirement_set.add_requirement(req)
    -
    -            # fail if no requirements
    -            if not requirement_set.has_requirements:
    -                logger.error(
    -                    "You must give at least one requirement to %s "
    -                    "(see \"pip help %s\")",
    -                    self.name,
    -                )
    -                return
    -
    -            try:
    -                # build wheels
    -                wb = WheelBuilder(
    -                    requirement_set,
    -                    finder,
    -                    options.wheel_dir,
    -                    build_options=options.build_options or [],
    -                    global_options=options.global_options or [],
    -                )
    -                if not wb.build():
    -                    raise CommandError("Failed to build one or more wheels")
    -            except PreviousBuildDirError:
    -                options.no_clean = True
    -                raise
    -            finally:
    -                if not options.no_clean:
    -                    requirement_set.cleanup_files()
    +                for filename in options.requirements:
    +                    for req in parse_requirements(
    +                            filename,
    +                            finder=finder,
    +                            options=options,
    +                            session=session):
    +                        requirement_set.add_requirement(req)
    +
    +                # fail if no requirements
    +                if not requirement_set.has_requirements:
    +                    logger.error(
    +                        "You must give at least one requirement to %s "
    +                        "(see \"pip help %s\")",
    +                        self.name,
    +                    )
    +                    return
    +
    +                try:
    +                    # build wheels
    +                    wb = WheelBuilder(
    +                        requirement_set,
    +                        finder,
    +                        options.wheel_dir,
    +                        build_options=options.build_options or [],
    +                        global_options=options.global_options or [],
    +                    )
    +                    if not wb.build():
    +                        raise CommandError(
    +                            "Failed to build one or more wheels"
    +                        )
    +                except PreviousBuildDirError:
    +                    options.no_clean = True
    +                    raise
    +                finally:
    +                    if not options.no_clean:
    +                        requirement_set.cleanup_files()
    
  • pip/utils/build.py+37 0 added
    @@ -0,0 +1,37 @@
    +from __future__ import absolute_import
    +
    +import tempfile
    +
    +from pip.utils import rmtree
    +
    +
    +class BuildDirectory(object):
    +
    +    def __init__(self, name=None, delete=None):
    +        # If we were not given an explicit directory, and we were not given an
    +        # explicit delete option, then we'll default to deleting.
    +        if name is None and delete is None:
    +            delete = True
    +
    +        if name is None:
    +            name = tempfile.mkdtemp(prefix="pip-build-")
    +            # If we were not given an explicit directory, and we were not given
    +            # an explicit delete option, then we'll default to deleting.
    +            if delete is None:
    +                delete = True
    +
    +        self.name = name
    +        self.delete = delete
    +
    +    def __repr__(self):
    +        return "<{} {!r}>".format(self.__class__.__name__, self.name)
    +
    +    def __enter__(self):
    +        return self.name
    +
    +    def __exit__(self, exc, value, tb):
    +        self.cleanup()
    +
    +    def cleanup(self):
    +        if self.delete:
    +            rmtree(self.name)
    
  • tests/functional/test_install_cleanup.py+12 10 modified
    @@ -26,12 +26,12 @@ def test_no_clean_option_blocks_cleaning_after_install(script, data):
         """
         Test --no-clean option blocks cleaning after install
         """
    -    result = script.pip(
    -        'install', '--no-clean', '--no-index',
    -        '--find-links=%s' % data.find_links, 'simple'
    +    build = script.base_path / 'pip-build'
    +    script.pip(
    +        'install', '--no-clean', '--no-index', '--build', build,
    +        '--find-links=%s' % data.find_links, 'simple',
         )
    -    build = script.venv_path / 'build' / 'simple'
    -    assert exists(build), "build/simple should still exist %s" % str(result)
    +    assert exists(build)
     
     
     def test_cleanup_after_install_editable_from_hg(script, tmpdir):
    @@ -157,15 +157,17 @@ def test_cleanup_prevented_upon_build_dir_exception(script, data):
         """
         Test no cleanup occurs after a PreviousBuildDirError
         """
    -    build = script.venv_path / 'build' / 'simple'
    -    os.makedirs(build)
    -    write_delete_marker_file(script.venv_path / 'build')
    -    build.join("setup.py").write("#")
    +    build = script.venv_path / 'build'
    +    build_simple = build / 'simple'
    +    os.makedirs(build_simple)
    +    write_delete_marker_file(build)
    +    build_simple.join("setup.py").write("#")
         result = script.pip(
             'install', '-f', data.find_links, '--no-index', 'simple',
    +        '--build', build,
             expect_error=True,
         )
     
         assert result.returncode == PREVIOUS_BUILD_DIR_ERROR
         assert "pip can't proceed" in result.stdout, result.stdout
    -    assert exists(build)
    +    assert exists(build_simple)
    
  • tests/functional/test_wheel.py+4 2 modified
    @@ -86,11 +86,12 @@ def test_no_clean_option_blocks_cleaning_after_wheel(script, data):
         Test --no-clean option blocks cleaning after wheel build
         """
         script.pip('install', 'wheel')
    +    build = script.venv_path / 'build'
         result = script.pip(
    -        'wheel', '--no-clean', '--no-index',
    +        'wheel', '--no-clean', '--no-index', '--build', build,
             '--find-links=%s' % data.find_links, 'simple',
         )
    -    build = script.venv_path / 'build' / 'simple'
    +    build = build / 'simple'
         assert exists(build), "build/simple should still exist %s" % str(result)
     
     
    @@ -128,6 +129,7 @@ def test_pip_wheel_fail_cause_of_previous_build_dir(script, data):
         # When I call pip trying to install things again
         result = script.pip(
             'wheel', '--no-index', '--find-links=%s' % data.find_links,
    +        '--build', script.venv_path / 'build',
             'simple==3.0', expect_error=True,
         )
     
    

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

News mentions

0

No linked articles in our index yet.