mirror of
https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git
synced 2025-07-12 19:09:56 +03:00
Since we are root in the namespace, ssh will attempt to use the root users config, additionally the ssh_config.d directory will have invalid permissions (since it's owned by root on the host which is not mapped into the userns). Hack around all of these with some fancy mounts in the sandbox. The "proper" way to do this would be to stop using the host rootfs alltogether and unshare ourselves into an Alpine chroot, with the users .ssh directory mounted in. But that will require refactoring pmbootstrap's init code to be able to parse args and do actions before entering the namespace. Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
122 lines
4.3 KiB
Python
Executable file
122 lines
4.3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- encoding: UTF-8 -*-
|
|
# Copyright 2023 Oliver Smith
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
# PYTHON_ARGCOMPLETE_OK
|
|
import sys
|
|
import pmb
|
|
import os
|
|
from pmb.init import sandbox
|
|
|
|
# Sanitise environment a bit
|
|
os.environ["SHELL"] = "/bin/sh" if os.path.exists("/bin/sh") else "/bin/bash"
|
|
|
|
if os.geteuid() == 0:
|
|
raise RuntimeError("pmbootstrap can't be run as root, please run it as a regular user.")
|
|
|
|
# FIXME: parse args before unshare(), this is really hacky
|
|
if "install" in sys.argv and "--disk" in sys.argv:
|
|
from pmb.init.run import run_root
|
|
disk = sys.argv[sys.argv.index("--disk")+1]
|
|
if not os.access(disk, os.W_OK):
|
|
cmd = ["sudo", "chown", str(os.getlogin()), disk]
|
|
if "-y" in sys.argv:
|
|
print(f"Using sudo to chown the target disk {disk}")
|
|
print(f" $ {' '.join(cmd)}")
|
|
run_root(cmd)
|
|
else:
|
|
print(f"The target disk '{disk}' is not writable by your user. Please run the following"
|
|
" command manually or press 'y' and enter your sudo password when prompted.")
|
|
print(f" $ {' '.join(cmd)}")
|
|
ans = input("> ")
|
|
if ans.lower() == "y":
|
|
run_root(cmd)
|
|
|
|
sandbox.acquire_privileges(become_root=False)
|
|
# Unshare mount and PID namespaces. We create a new PID namespace so
|
|
# that any log-running daemons (e.g. adbd, sccache) will be killed when
|
|
# pmbootstrap exits
|
|
sandbox.unshare(sandbox.CLONE_NEWNS | sandbox.CLONE_NEWPID)
|
|
|
|
# We are now PID 1 in a new PID namespace. We don't want to run all our
|
|
# logic as PID 1 since subprocess.Popen() seemingly causes our PID to
|
|
# change. So we fork now, the child process continues and we just wait
|
|
# around and propagate the exit code.
|
|
# This is all kinda hacky, we should integrate this with the acquire_privileges()
|
|
# implementation since it's already doing similar fork shenanigans, we could
|
|
# save a call to fork() this way. But for now it's fine.
|
|
pid = os.fork()
|
|
if pid > 0:
|
|
# We are PID 1! let's hang out
|
|
pid, wstatus = os.waitpid(pid, 0)
|
|
exitcode = os.waitstatus_to_exitcode(wstatus)
|
|
os._exit(exitcode)
|
|
|
|
# print("Caps: ")
|
|
# with open("/proc/self/status", "rb") as f:
|
|
# for line in f.readlines():
|
|
# if line.startswith(b"CapEff:"):
|
|
# print(line)
|
|
|
|
# print(f"cap_sys_admin: {sandbox.have_effective_cap(sandbox.CAP_SYS_ADMIN)}")
|
|
# print(f"single user: {sandbox.userns_has_single_user()}")
|
|
|
|
def create_stub_passwd() -> str:
|
|
"""
|
|
Create a version of passwd where the root user home dir is replaced with our user
|
|
home dir, so that commands like ssh with read the right configs.
|
|
"""
|
|
return "".join(map(
|
|
lambda x: x.replace(":/root:", f":{os.environ['HOME']}:")
|
|
if x.startswith("root:") else x,
|
|
open("/etc/passwd").readlines()))
|
|
|
|
|
|
# We set up a very basic mount environment, where we just bind mount the host
|
|
# rootfs in. We can extend this in the future to isolate the pmb workdir but
|
|
# for now this is enough.
|
|
fsops = [
|
|
sandbox.BindOperation(
|
|
"/",
|
|
"/",
|
|
readonly=False,
|
|
required=True,
|
|
relative=False,
|
|
),
|
|
# Mount binfmt_misc at /tmp/pmb_binfmt_misc
|
|
sandbox.BinfmtOperation(pmb.config.binfmt_misc),
|
|
# /tmp/pmb is a tmpfs we can use for scratch data that will be removed
|
|
# on exit.
|
|
sandbox.TmpfsOperation("/tmp/pmb"),
|
|
# We need to make /etc/ssh/ssh_config.d appear empty because permissions
|
|
# are all messed up. Probably there's a fancy way to mount with adjusted
|
|
# perms but for now this will do...
|
|
sandbox.DirOperation("/tmp/pmb/empty"),
|
|
sandbox.BindOperation(
|
|
"/tmp/pmb/empty",
|
|
"/etc/ssh/ssh_config.d",
|
|
readonly=True,
|
|
required=True,
|
|
relative=True
|
|
),
|
|
sandbox.WriteOperation(
|
|
create_stub_passwd(),
|
|
"/tmp/pmb/etc_passwd",
|
|
),
|
|
sandbox.BindOperation(
|
|
"/tmp/pmb/etc_passwd",
|
|
"/etc/passwd",
|
|
readonly=True,
|
|
required=True,
|
|
relative=True
|
|
),
|
|
]
|
|
sandbox.setup_mounts(fsops)
|
|
|
|
# Reset our CWD now that we're inside the mount namespace
|
|
os.chdir(os.environ["PWD"])
|
|
|
|
# A convenience wrapper for running pmbootstrap from the git repository. This
|
|
# script is not part of the python packaging, so don't add more logic here!
|
|
if __name__ == "__main__":
|
|
sys.exit(pmb.main())
|