minstall: drop privileges before running rebuild_all

If the user runs `sudo meson install` this may run ninja to build
everything that gets installed. This naturally happens as root also, by
default, which is bad. Instead, detect root elevation tools and drop the
uid/gid of the child ninja process back to the original invoking user
before doing anything.
pull/11366/head
Eli Schwartz 2 years ago
parent a878c38476
commit ecb32bf457
No known key found for this signature in database
GPG Key ID: CEB167EFB5722BD6
  1. 4
      docs/markdown/Installing.md
  2. 16
      docs/markdown/snippets/meson_install_drop_privs.md
  3. 38
      mesonbuild/minstall.py

@ -110,7 +110,9 @@ targets as root. This results in various bad behaviors due to build outputs and
ninja internal files being owned by root.
Running `meson install` is preferred for several reasons. It can rebuild out of
date targets and then re-invoke itself as root.
date targets and then re-invoke itself as root. *(since 1.1.0)* Additionally,
running `sudo meson install` will drop permissions and rebuild out of date
targets as the original user, not as root.
*(since 1.1.0)* Re-invoking as root will try to guess the user's preferred method for
re-running commands as root. The order of precedence is: sudo, doas, pkexec

@ -0,0 +1,16 @@
## `sudo meson install` now drops privileges when rebuilding targets
It is common to install projects using sudo, which should not affect build
outputs but simply install the results. Unfortunately, since the ninja backend
updates a state file when run, it's not safe to run ninja as root at all.
It has always been possible to carefully build with:
```
ninja && sudo meson install --no-rebuild
```
Meson now tries to be extra safe as a general solution. `sudo meson install`
will attempt to rebuild, but has learned to run `ninja` as the original
(pre-sudo or pre-doas) user, ensuring that build outputs are generated/compiled
as non-root.

@ -42,7 +42,7 @@ if T.TYPE_CHECKING:
ExecutableSerialisation, InstallDataBase, InstallEmptyDir,
InstallSymlinkData, TargetInstallData
)
from .mesonlib import FileMode
from .mesonlib import FileMode, EnvironOrDict
try:
from typing import Protocol
@ -753,7 +753,41 @@ def rebuild_all(wd: str) -> bool:
print("Can't find ninja, can't rebuild test.")
return False
ret = subprocess.run(ninja + ['-C', wd]).returncode
def drop_privileges() -> T.Tuple[T.Optional[EnvironOrDict], T.Optional[T.Callable[[], None]]]:
if not is_windows() and os.geteuid() == 0:
import pwd
env = os.environ.copy()
if os.environ.get('SUDO_USER') is not None:
orig_user = env.pop('SUDO_USER')
orig_uid = env.pop('SUDO_UID', 0)
orig_gid = env.pop('SUDO_GID', 0)
homedir = pwd.getpwuid(int(orig_uid)).pw_dir
elif os.environ.get('DOAS_USER') is not None:
orig_user = env.pop('DOAS_USER')
pwdata = pwd.getpwnam(orig_user)
orig_uid = pwdata.pw_uid
orig_gid = pwdata.pw_gid
homedir = pwdata.pw_dir
else:
return None, None
env['USER'] = orig_user
env['HOME'] = homedir
def wrapped() -> None:
print(f'Dropping privileges to {orig_user!r} before running ninja...')
if orig_gid is not None:
os.setgid(int(orig_gid))
if orig_uid is not None:
os.setuid(int(orig_uid))
return env, wrapped
else:
return None, None
env, preexec_fn = drop_privileges()
ret = subprocess.run(ninja + ['-C', wd], env=env, preexec_fn=preexec_fn).returncode
if ret != 0:
print(f'Could not rebuild {wd}')
return False

Loading…
Cancel
Save