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:
parent
c997ce83c2
commit
be5e18cf99
8 changed files with 60 additions and 76 deletions
|
@ -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!")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
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)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue