Harden isolated release builds in Flatpak
Some checks failed
Dependency Audit / dependency-audit (push) Failing after 16s
Security Audit / security-audit (push) Failing after 15s

This commit is contained in:
2026-07-22 21:48:48 +02:00
parent 76f19dc602
commit b00d4e55ee
2 changed files with 242 additions and 15 deletions

View File

@@ -73,6 +73,9 @@ MAX_CATALOG_SOURCE_REPOSITORIES = 128
MAX_TRUSTED_RUNTIME_ENTRIES = 10_000
MAX_TRUSTED_GIT_ENTRIES = 500_000
DURABLE_REMOTE = "origin"
FLATPAK_BUILDER_PROXY = Path("/usr/bin/flatpak-spawn")
HOST_BUBBLEWRAP = Path("/usr/bin/bwrap")
FLATPAK_HOST_BUS = Path("/run/flatpak/bus")
class ReleaseExecutionError(RuntimeError):
@@ -1165,16 +1168,34 @@ def _build_selected_wheels_from_sources(
tempfile.mkdtemp(prefix=f".{repo}-build-", dir=artifacts)
)
os.chmod(staging, 0o700, follow_symlinks=False)
source_staging = staging / "source"
wheel_staging = staging / "out"
source_staging.mkdir(mode=0o700)
staged = git(
frozen_path,
"checkout-index",
"--all",
"--force",
f"--prefix={source_staging.absolute()}/",
timeout=120,
)
if staged.returncode != 0:
shutil.rmtree(staging)
raise ReleaseExecutionBlocked(
f"Could not stage the verified tracked source tree for {repo}."
)
wheel_staging.mkdir(mode=0o700)
command = isolated_wheel_builder_command(
source=frozen_path,
output=staging,
source=source_staging,
output=wheel_staging,
)
if _run_isolated_wheel_builder(command) != 0:
shutil.rmtree(staging)
raise ReleaseExecutionBlocked(
f"Server-owned wheel staging failed for {repo}: "
"the isolated no-network worker did not complete successfully"
)
created = _single_isolated_wheel(staging)
created = _single_isolated_wheel(wheel_staging)
destination = artifacts / created.name
if destination.exists() or destination.is_symlink():
raise ReleaseExecutionBlocked(
@@ -1191,8 +1212,7 @@ def _build_selected_wheels_from_sources(
f"Isolated wheel identity does not match selected source {repo}."
)
os.chmod(destination, 0o600, follow_symlinks=False)
created.unlink()
staging.rmdir()
shutil.rmtree(staging)
result[repo] = destination
return result
@@ -1228,12 +1248,12 @@ def isolated_wheel_builder_command(*, source: Path, output: Path) -> tuple[str,
"/tmp",
"--dir",
"/tmp/home",
"--ro-bind",
str(source.absolute()),
"/src",
"--bind",
str(output.absolute()),
"/out",
"--bind",
str(source.absolute()),
"/src",
"--chdir",
"/tmp",
"--clearenv",
@@ -1288,12 +1308,10 @@ def isolated_wheel_builder_command(*, source: Path, output: Path) -> tuple[str,
def isolated_builder_launcher() -> tuple[str, ...]:
flatpak_spawn = Path("/usr/bin/flatpak-spawn")
direct_bwrap = Path("/usr/bin/bwrap")
if _trusted_system_executable(flatpak_spawn):
return (str(flatpak_spawn), "--host", "/usr/bin/bwrap")
if _trusted_system_executable(direct_bwrap):
return (str(direct_bwrap),)
if _trusted_flatpak_builder_proxy():
return (str(FLATPAK_BUILDER_PROXY), "--host", str(HOST_BUBBLEWRAP))
if _trusted_system_executable(HOST_BUBBLEWRAP):
return (str(HOST_BUBBLEWRAP),)
raise ReleaseExecutionBlocked(
"Catalog generation requires a trusted bubblewrap worker; no supported "
"isolated builder is available."
@@ -1313,10 +1331,109 @@ def _trusted_system_executable(path: Path) -> bool:
)
def _trusted_flatpak_builder_proxy() -> bool:
"""Trust only Flatpak's immutable SDK proxy and a root-owned host bwrap."""
if os.environ.get("container") != "flatpak":
return False
try:
flatpak_info = Path("/.flatpak-info").lstat()
flatpak_runtime = FLATPAK_HOST_BUS.parent.lstat()
host_bus = FLATPAK_HOST_BUS.lstat()
proxy = FLATPAK_BUILDER_PROXY.lstat()
ancestors = (Path("/usr"), Path("/usr/bin"))
ancestor_metadata = tuple(path.lstat() for path in ancestors)
read_only = all(
os.statvfs(path).f_flag & os.ST_RDONLY
for path in (*ancestors, FLATPAK_BUILDER_PROXY)
)
except (AttributeError, OSError):
return False
if not (
stat.S_ISREG(flatpak_info.st_mode)
and flatpak_info.st_uid == os.getuid()
and flatpak_info.st_mode & 0o077 == 0
and stat.S_ISDIR(flatpak_runtime.st_mode)
and flatpak_runtime.st_uid == os.getuid()
and flatpak_runtime.st_mode & 0o077 == 0
and stat.S_ISSOCK(host_bus.st_mode)
and host_bus.st_uid == os.getuid()
and host_bus.st_mode & 0o022 == 0
and stat.S_ISREG(proxy.st_mode)
and proxy.st_mode & 0o022 == 0
and proxy.st_mode & 0o111 != 0
and all(
stat.S_ISDIR(metadata.st_mode)
and metadata.st_mode & 0o022 == 0
for metadata in ancestor_metadata
)
and read_only
):
return False
return _trusted_host_bubblewrap()
def _trusted_host_bubblewrap() -> bool:
try:
check = subprocess.run( # noqa: S603 - immutable allowlisted proxy.
(
str(FLATPAK_BUILDER_PROXY),
"--host",
"/usr/bin/stat",
"-c",
"%f|%u",
str(HOST_BUBBLEWRAP),
),
cwd=Path("/"),
env=_flatpak_proxy_environment(),
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
timeout=5,
check=False,
close_fds=True,
)
except (OSError, subprocess.SubprocessError):
return False
output = check.stdout.strip()
if check.returncode != 0 or len(output) > 64:
return False
try:
mode_text, uid_text = output.split("|", maxsplit=1)
mode = int(mode_text, 16)
uid = int(uid_text, 10)
except ValueError:
return False
return (
stat.S_ISREG(mode)
and uid == 0
and mode & 0o022 == 0
and mode & 0o111 != 0
)
def _flatpak_proxy_environment() -> dict[str, str]:
return {
"PATH": "/usr/bin:/bin",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"DBUS_SESSION_BUS_ADDRESS": f"unix:path={FLATPAK_HOST_BUS}",
}
def _run_isolated_wheel_builder(command: tuple[str, ...]) -> int:
environment = {
"PATH": "/usr/bin:/bin",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
}
if command[:1] == (str(FLATPAK_BUILDER_PROXY),):
environment = _flatpak_proxy_environment()
process = subprocess.Popen( # noqa: S603 - absolute trusted launcher only.
command,
cwd=Path("/"),
env=environment,
stdin=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,