pypa / pip

The Python package installer
https://pip.pypa.io/
MIT License
9.55k stars 3.04k forks source link

`pip` should drop rights when it builds wheels. Also it may make sense to sandbox the building process from i.e. introducing malware into `.bashrc` with the tools like `firejail`, if they are available in the system. #11034

Open KOLANICH opened 2 years ago

KOLANICH commented 2 years ago

What's the problem this feature will solve?

Building a wheel results in execution of a lot of potentially vulnerable or untrustworthy code:

  1. setup.py
  2. build system
  3. setuptools plugins, which can have large dependencies trees
  4. variables imported from modules using attr: in setup.cfg and tool.setuptools.dynamic section (it is planned to be fixed somewhen in sallow only statically parsed stuff by default).
  5. console tools called by setuptools plugins.

There is usually no reason to call all these from a privileged user. But some packages are still available on pypi as sdists only, so users installing them with sudo pip result in them being fetched and built from root. I personally have disabled fetching anything from pypi, BTW.

The most severe issue for me is editable install. It is extremily inconvenient to rebuild wheels and reinstall them after every minorest change when developing python code that must be run from root. I know it introduces local privilege escalation, but TBH if one runs untrustworthy code on own machine, he is already deeply in danger.

The issue got more severe today because of https://github.com/pypa/setuptools_scm/issues/707 . setuptools_scm is a tool to get version from git tags, it calls git for that. But pip runs wheel building from root, while the source dir is usually owned by the user initiating the build. The recent fix in git to eliminate such vulnerabilities checks that the user running git is the same as the user owning the repo. Of course fakeroot is the workaround, but I think a more correct solution is needed.

Describe the solution you'd like

  1. pip should detect the user that has started it via sudo
  2. pip should build a wheel from that user and only install from root
  3. pip should detect if there are any sandboxing solutions present in the system, and should use it to allow wheel building process to write only to the source dir and to the build dir.

Alternative Solutions

No.

Additional context

Code of Conduct

pfmoore commented 2 years ago

Running pip as root is not supported in any case, so you simply shouldn't do this (or if you do, you should be aware of, and deal with, the consequences yourself). From your description, it sounds like you are at least aware of the risks, so that is good. But pip won't address these risks for you, that's something you have to handle yourself (ideally by not running sudo pip in the first place...)

zahlman commented 6 days ago

I think solving the problem should be straightforward now that there are isolated builds running in subprocesses.

A demonstration - in an otherwise empty directory:

$ cat > setup.py
import os

raise ValueError(os.geteuid())
$ python -m venv test_venv
$ sudo test_venv/bin/pip install -e .
Obtaining file:///...
  Preparing metadata (setup.py) ... error
  error: subprocess-exited-with-error

  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [6 lines of output]
      Traceback (most recent call last):
        File "<string>", line 2, in <module>
        File "<pip-setuptools-caller>", line 34, in <module>
        File "/path/to/setup.py", line 3, in <module>
          raise ValueError(os.geteuid())
      ValueError: 0
      [end of output]

(Etc.) All that should be necessary is:

  1. The spawned subprocess should os.seteuid() (or the main process should temporarily do so before spawning) to run as the original user (int(os.environ[SUDO_UID])) or as nobody before setup.py is invoked.
  2. Group membership should be cleared (os.setgroups([]) - ref. https://stackoverflow.com/questions/2699907).

Allowing the build process to run as root increases the attack surface area: malware (whether typo-squatted, supply-chain-attacked etc.) now gets to do its work with admin privileges automatically at install time, before the local source can be inspected. Dropping rights for wheel building is a useful mitigation: even if malware were installed globally by sudo pip, if the install process only has user rights, it can't harm the system until the installed package is actually used. It also represents defense-in-depth in case Setuptools, or another popular build system, gets compromised.

Telling people "running pip as root is not supported" ignores the huge volume of bad authoritative advice already out there as described in https://github.com/pypa/pip/issues/7802#issuecomment-593698570 ). It also ignores the fact that Pipx explicitly supports global installation with the --global flag now (as discussed in #12651), which of course requires sudo rights, which will of course be passed on to Pip (so that it can unpack files in a root-owned directory).

Using --only-binary=:all: is an incomplete workaround; sometimes wheels do need to be built. (Especially as long as "editable wheels" are the primary model for editable installs.)

It's also possible, I suppose, to install in a user venv, manually fix all the paths in the venv according to what they'll need to be, and then sudo mv it to an appropriate location. But this is complex enough to justify automation already. Installation into an existing global venv (like with pipx inject) or (for those who really live dangerously) the system Python would be even more awkward.