mirror of
https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git
synced 2025-07-23 04:25:10 +03:00
unshare binfmt
Rework how we handle binfmt_misc so it will work inside a user namespace. * Use a custom mountpoint (only accessible inside the mount namespace), this is the crux of the change, allowing us to mount it as non-root and avoid messing with any host configs too! * No longer explicitly modprobe binfmt_misc, any modern system should probe it automatically when we try to mount it... I think so anyways heh Signed-off-by: Casey Connolly <kcxt@postmarketos.org>
This commit is contained in:
parent
c997ce83c2
commit
be5e18cf99
8 changed files with 60 additions and 76 deletions
|
@ -1,18 +1,41 @@
|
|||
# Copyright 2023 Oliver Smith
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
from pmb.core.arch import Arch
|
||||
from pmb.core.chroot import Chroot
|
||||
from pmb.helpers import logging
|
||||
|
||||
import pmb.helpers.run
|
||||
import pmb.helpers.other
|
||||
import pmb.parse
|
||||
import pmb.chroot.apk
|
||||
import pmb.config
|
||||
|
||||
|
||||
def is_registered(arch_qemu: str | Arch) -> bool:
|
||||
return os.path.exists(f"/proc/sys/fs/binfmt_misc/qemu-{arch_qemu}")
|
||||
return os.path.exists(f"{pmb.config.binfmt_misc}/qemu-{arch_qemu}")
|
||||
|
||||
|
||||
# FIXME: Maybe this should use Arch instead of str.
|
||||
def parse_binfmt_info(arch_qemu: str) -> dict[str, str]:
|
||||
# Parse the info file
|
||||
full = {}
|
||||
info = pmb.config.pmb_src / "pmb/data/qemu-user-binfmt.txt"
|
||||
logging.verbose(f"parsing: {info}")
|
||||
with open(info) as handle:
|
||||
for line in handle:
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
split = line.split("=")
|
||||
key = split[0].strip()
|
||||
value = split[1]
|
||||
full[key] = value[1:-2]
|
||||
|
||||
ret = {}
|
||||
logging.verbose("filtering by architecture: " + arch_qemu)
|
||||
for type in ["mask", "magic"]:
|
||||
key = arch_qemu + "_" + type
|
||||
if key not in full:
|
||||
raise RuntimeError(f"Could not find key {key} in binfmt info file: {info}")
|
||||
ret[type] = full[key]
|
||||
logging.verbose("=> " + str(ret))
|
||||
return ret
|
||||
|
||||
|
||||
def register(arch: Arch) -> None:
|
||||
|
@ -28,16 +51,11 @@ def register(arch: Arch) -> None:
|
|||
pmb.chroot.init(chroot)
|
||||
pmb.chroot.apk.install(["qemu-" + arch_qemu], chroot)
|
||||
|
||||
if is_registered(arch_qemu):
|
||||
return
|
||||
pmb.helpers.other.check_binfmt_misc()
|
||||
|
||||
# Don't continue if the actions from check_binfmt_misc caused the OS to
|
||||
# automatically register the target arch
|
||||
# Check if we're already registered
|
||||
if is_registered(arch_qemu):
|
||||
return
|
||||
|
||||
info = pmb.parse.binfmt_info(arch_qemu)
|
||||
info = parse_binfmt_info(arch_qemu)
|
||||
|
||||
# Build registration string
|
||||
# https://en.wikipedia.org/wiki/Binfmt_misc
|
||||
|
@ -47,20 +65,27 @@ def register(arch: Arch) -> None:
|
|||
offset = ""
|
||||
magic = info["magic"]
|
||||
mask = info["mask"]
|
||||
|
||||
# FIXME: this relies on a hack where we bind-mount the qemu interpreter into the foreign
|
||||
# chroot. This really shouldn't be needed, instead we should unshare pmbootstrap into
|
||||
# an Alpine chroot that would have the interpreter installed, then pass the 'F' flag which
|
||||
# allows the interpreter to always be run even when we're later in a chroot.
|
||||
interpreter = "/usr/bin/qemu-" + arch_qemu + "-static"
|
||||
flags = "C"
|
||||
code = ":".join(["", name, type, offset, magic, mask, interpreter, flags])
|
||||
|
||||
# Register in binfmt_misc
|
||||
logging.info("Register qemu binfmt (" + arch_qemu + ")")
|
||||
register = "/proc/sys/fs/binfmt_misc/register"
|
||||
register = f"{pmb.config.binfmt_misc}/register"
|
||||
pmb.helpers.run.root(["sh", "-c", 'echo "' + code + '" > ' + register])
|
||||
logging.warning("WARNING: FIXME: binfmt borked because no perms!")
|
||||
|
||||
|
||||
def unregister(arch: Arch) -> None:
|
||||
arch_qemu = arch.qemu()
|
||||
binfmt_file = "/proc/sys/fs/binfmt_misc/qemu-" + arch_qemu
|
||||
binfmt_file = f"{pmb.config.binfmt_misc}/qemu-" + arch_qemu
|
||||
if not os.path.exists(binfmt_file):
|
||||
return
|
||||
logging.info("Unregister qemu binfmt (" + arch_qemu + ")")
|
||||
pmb.helpers.run.root(["sh", "-c", "echo -1 > " + binfmt_file])
|
||||
# pmb.helpers.run.root(["sh", "-c", "echo -1 > " + binfmt_file])
|
||||
logging.warning("WARNING: FIXME: binfmt borked because no perms!")
|
||||
|
|
|
@ -23,6 +23,9 @@ from . import workdir as workdir
|
|||
pmb_src: Path = Path(Path(__file__) / "../../..").resolve()
|
||||
apk_keys_path: Path = pmb_src / "pmb/data/keys"
|
||||
|
||||
# In the mount namespace this is where we mount our own binfmt_misc dir
|
||||
binfmt_misc = "/tmp/pmb_binfmt_misc"
|
||||
|
||||
# apk-tools minimum version
|
||||
# https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge
|
||||
# Update this frequently to prevent a MITM attack with an outdated version
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# Copyright 2023 Oliver Smith
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from pmb.core.arch import Arch
|
||||
from pmb.core.context import get_context
|
||||
from pmb.core.chroot import Chroot
|
||||
from pmb.core.config import SystemdConfig
|
||||
|
|
|
@ -47,31 +47,6 @@ def check_grsec() -> None:
|
|||
)
|
||||
|
||||
|
||||
def check_binfmt_misc() -> None:
|
||||
"""Check if the 'binfmt_misc' module is loaded.
|
||||
|
||||
This is done by checking, if /proc/sys/fs/binfmt_misc/ exists.
|
||||
If it exists, then do nothing.
|
||||
Otherwise, load the module and mount binfmt_misc.
|
||||
If that fails as well, raise an exception pointing the user to the wiki.
|
||||
"""
|
||||
path = "/proc/sys/fs/binfmt_misc/status"
|
||||
if os.path.exists(path):
|
||||
return
|
||||
|
||||
# check=False: this might be built-in instead of being a module
|
||||
pmb.helpers.run.root(["modprobe", "binfmt_misc"], check=False)
|
||||
|
||||
# check=False: we check it below and print a more helpful message on error
|
||||
pmb.helpers.run.root(
|
||||
["mount", "-t", "binfmt_misc", "none", "/proc/sys/fs/binfmt_misc"], check=False
|
||||
)
|
||||
|
||||
if not os.path.exists(path):
|
||||
link = "https://postmarketos.org/binfmt_misc"
|
||||
raise RuntimeError(f"Failed to set up binfmt_misc, see: {link}")
|
||||
|
||||
|
||||
def migrate_success(work: Path, version: int) -> None:
|
||||
logging.info("Migration to version " + str(version) + " done")
|
||||
with open(work / "version", "w") as handle:
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
# FIXME: this file is wayyy off lol
|
||||
# ruff: noqa
|
||||
|
||||
"""
|
||||
This is a standalone implementation of sandboxing which is used by mkosi. Note that this is
|
||||
|
@ -30,6 +32,7 @@ CLONE_NEWNS = 0x00020000
|
|||
CLONE_NEWUSER = 0x10000000
|
||||
EBADF = 9
|
||||
UNSHARE_EPERM_MSGEPERM = 1
|
||||
EPERM = 1
|
||||
ENOENT = 2
|
||||
ENOSYS = 38
|
||||
F_DUPFD = 0
|
||||
|
@ -671,6 +674,15 @@ class ProcOperation(FSOperation):
|
|||
mount_rbind(joinpath(oldroot, "proc"), dst)
|
||||
|
||||
|
||||
class BinfmtOperation(FSOperation):
|
||||
def execute(self, oldroot: str, newroot: str) -> None:
|
||||
dst = chase(newroot, self.dst)
|
||||
with umask(~0o755):
|
||||
os.makedirs(dst, exist_ok=True)
|
||||
|
||||
mount("binfmt_misc", dst, "binfmt_misc", 0, "")
|
||||
|
||||
|
||||
class DevOperation(FSOperation):
|
||||
def __init__(self, ttyname: str, dst: str) -> None:
|
||||
self.ttyname = ttyname
|
||||
|
|
|
@ -8,7 +8,6 @@ from pmb.parse.arguments import (
|
|||
)
|
||||
from pmb.parse._apkbuild import apkbuild as apkbuild
|
||||
from pmb.parse._apkbuild import function_body as function_body
|
||||
from pmb.parse.binfmt_info import binfmt_info as binfmt_info
|
||||
from pmb.parse.deviceinfo import deviceinfo as deviceinfo
|
||||
from pmb.parse.kconfig import check as check
|
||||
from pmb.parse.bootimg import bootimg as bootimg
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
# Copyright 2023 Oliver Smith
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from pmb.helpers import logging
|
||||
import pmb.config
|
||||
|
||||
# Get magic and mask from binfmt info file
|
||||
# Return: {magic: ..., mask: ...}
|
||||
|
||||
|
||||
# FIXME: Maybe this should use Arch instead of str.
|
||||
def binfmt_info(arch_qemu: str) -> dict[str, str]:
|
||||
# Parse the info file
|
||||
full = {}
|
||||
info = pmb.config.pmb_src / "pmb/data/qemu-user-binfmt.txt"
|
||||
logging.verbose(f"parsing: {info}")
|
||||
with open(info) as handle:
|
||||
for line in handle:
|
||||
if line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
split = line.split("=")
|
||||
key = split[0].strip()
|
||||
value = split[1]
|
||||
full[key] = value[1:-2]
|
||||
|
||||
ret = {}
|
||||
logging.verbose("filtering by architecture: " + arch_qemu)
|
||||
for type in ["mask", "magic"]:
|
||||
key = arch_qemu + "_" + type
|
||||
if key not in full:
|
||||
raise RuntimeError(f"Could not find key {key} in binfmt info file: {info}")
|
||||
ret[type] = full[key]
|
||||
logging.verbose("=> " + str(ret))
|
||||
return ret
|
|
@ -34,7 +34,9 @@ fsops = [
|
|||
readonly=False,
|
||||
required=True,
|
||||
relative=False,
|
||||
)
|
||||
),
|
||||
# Mount binfmt_misc at /tmp/pmb_binfmt_misc
|
||||
sandbox.BinfmtOperation(pmb.config.binfmt_misc),
|
||||
]
|
||||
sandbox.setup_mounts(fsops)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue