1
0
Fork 1
mirror of https://gitlab.postmarketos.org/postmarketOS/pmbootstrap.git synced 2025-07-23 12:35:12 +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:
Casey Connolly 2025-05-03 20:55:14 +02:00
parent c997ce83c2
commit be5e18cf99
No known key found for this signature in database
GPG key ID: 0583312B195F64B6
8 changed files with 60 additions and 76 deletions

View file

@ -1,18 +1,41 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
from pmb.core.arch import Arch from pmb.core.arch import Arch
from pmb.core.chroot import Chroot from pmb.core.chroot import Chroot
from pmb.helpers import logging from pmb.helpers import logging
import pmb.config
import pmb.helpers.run
import pmb.helpers.other
import pmb.parse
import pmb.chroot.apk
def is_registered(arch_qemu: str | Arch) -> bool: 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: def register(arch: Arch) -> None:
@ -28,16 +51,11 @@ def register(arch: Arch) -> None:
pmb.chroot.init(chroot) pmb.chroot.init(chroot)
pmb.chroot.apk.install(["qemu-" + arch_qemu], chroot) pmb.chroot.apk.install(["qemu-" + arch_qemu], chroot)
if is_registered(arch_qemu): # Check if we're already registered
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
if is_registered(arch_qemu): if is_registered(arch_qemu):
return return
info = pmb.parse.binfmt_info(arch_qemu) info = parse_binfmt_info(arch_qemu)
# Build registration string # Build registration string
# https://en.wikipedia.org/wiki/Binfmt_misc # https://en.wikipedia.org/wiki/Binfmt_misc
@ -47,20 +65,27 @@ def register(arch: Arch) -> None:
offset = "" offset = ""
magic = info["magic"] magic = info["magic"]
mask = info["mask"] 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" interpreter = "/usr/bin/qemu-" + arch_qemu + "-static"
flags = "C" flags = "C"
code = ":".join(["", name, type, offset, magic, mask, interpreter, flags]) code = ":".join(["", name, type, offset, magic, mask, interpreter, flags])
# Register in binfmt_misc # Register in binfmt_misc
logging.info("Register qemu binfmt (" + arch_qemu + ")") 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]) pmb.helpers.run.root(["sh", "-c", 'echo "' + code + '" > ' + register])
logging.warning("WARNING: FIXME: binfmt borked because no perms!")
def unregister(arch: Arch) -> None: def unregister(arch: Arch) -> None:
arch_qemu = arch.qemu() 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): if not os.path.exists(binfmt_file):
return return
logging.info("Unregister qemu binfmt (" + arch_qemu + ")") 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!")

View file

@ -23,6 +23,9 @@ from . import workdir as workdir
pmb_src: Path = Path(Path(__file__) / "../../..").resolve() pmb_src: Path = Path(Path(__file__) / "../../..").resolve()
apk_keys_path: Path = pmb_src / "pmb/data/keys" 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 # apk-tools minimum version
# https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge # https://pkgs.alpinelinux.org/packages?name=apk-tools&branch=edge
# Update this frequently to prevent a MITM attack with an outdated version # Update this frequently to prevent a MITM attack with an outdated version

View file

@ -1,5 +1,6 @@
# Copyright 2023 Oliver Smith # Copyright 2023 Oliver Smith
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from pmb.core.arch import Arch
from pmb.core.context import get_context from pmb.core.context import get_context
from pmb.core.chroot import Chroot from pmb.core.chroot import Chroot
from pmb.core.config import SystemdConfig from pmb.core.config import SystemdConfig

View file

@ -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: def migrate_success(work: Path, version: int) -> None:
logging.info("Migration to version " + str(version) + " done") logging.info("Migration to version " + str(version) + " done")
with open(work / "version", "w") as handle: with open(work / "version", "w") as handle:

View file

@ -1,4 +1,6 @@
# SPDX-License-Identifier: LGPL-2.1-or-later # 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 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 CLONE_NEWUSER = 0x10000000
EBADF = 9 EBADF = 9
UNSHARE_EPERM_MSGEPERM = 1 UNSHARE_EPERM_MSGEPERM = 1
EPERM = 1
ENOENT = 2 ENOENT = 2
ENOSYS = 38 ENOSYS = 38
F_DUPFD = 0 F_DUPFD = 0
@ -671,6 +674,15 @@ class ProcOperation(FSOperation):
mount_rbind(joinpath(oldroot, "proc"), dst) 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): class DevOperation(FSOperation):
def __init__(self, ttyname: str, dst: str) -> None: def __init__(self, ttyname: str, dst: str) -> None:
self.ttyname = ttyname self.ttyname = ttyname

View file

@ -8,7 +8,6 @@ from pmb.parse.arguments import (
) )
from pmb.parse._apkbuild import apkbuild as apkbuild from pmb.parse._apkbuild import apkbuild as apkbuild
from pmb.parse._apkbuild import function_body as function_body 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.deviceinfo import deviceinfo as deviceinfo
from pmb.parse.kconfig import check as check from pmb.parse.kconfig import check as check
from pmb.parse.bootimg import bootimg as bootimg from pmb.parse.bootimg import bootimg as bootimg

View file

@ -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

View file

@ -34,7 +34,9 @@ fsops = [
readonly=False, readonly=False,
required=True, required=True,
relative=False, relative=False,
) ),
# Mount binfmt_misc at /tmp/pmb_binfmt_misc
sandbox.BinfmtOperation(pmb.config.binfmt_misc),
] ]
sandbox.setup_mounts(fsops) sandbox.setup_mounts(fsops)