Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/boot_animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,46 @@ def build_runtime_payload(path: str | os.PathLike[str]) -> bytes:
return output.getvalue()


def _install_partition_context_aliases(
fs: Any,
partition: str,
raw_targets: Iterable[str],
) -> None:
"""Map partition-relative image paths to their runtime SELinux labels."""

contexts = list(fs.contexts)
aliases: list[tuple[re.Pattern[str], str]] = []
image_paths: set[PurePosixPath] = set()
for raw_target in raw_targets:
target = PurePosixPath(raw_target)
image_paths.add(target.parent)
image_paths.add(target)

for image_path in sorted(image_paths, key=str):
image_path_str = str(image_path)
if any(pattern.fullmatch(image_path_str) for pattern, _ in contexts + aliases):
continue

runtime_path = PurePosixPath("/") / partition / image_path.relative_to("/")
runtime_path_str = str(runtime_path)
try:
label = next(
label
for pattern, label in contexts
if pattern.fullmatch(runtime_path_str)
)
except StopIteration as exc:
raise RuntimeError(
"no SELinux context maps boot animation runtime path: "
f"{runtime_path_str}"
) from exc

aliases.append((re.compile(re.escape(image_path_str)), label))

if aliases:
fs.contexts = aliases + contexts


def install_runtime_payload(
ext_fs: dict[str, Any],
light_payload: bytes,
Expand All @@ -247,6 +287,17 @@ def install_runtime_payload(
"/media/bootanimation-dark.zip": dark_payload,
}

targets_by_partition: dict[str, list[str]] = {}
for partition, raw_target in BOOT_ANIMATION_TARGETS:
targets_by_partition.setdefault(partition, []).append(raw_target)
for partition, raw_targets in targets_by_partition.items():
fs = ext_fs.get(partition)
if fs is None:
raise RuntimeError(
f"boot animation target partition is missing: {partition}"
)
_install_partition_context_aliases(fs, partition, raw_targets)

for partition, raw_target in BOOT_ANIMATION_TARGETS:
fs = ext_fs.get(partition)
if fs is None:
Expand Down
2 changes: 1 addition & 1 deletion src/ci/selection_variant.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# this helper owns the byte-for-byte representation that is hashed by both the
# build and release preflight paths.

BOOT_ANIMATION_SELECTION_CONTRACT="product-image-root-theme-fallback-stored-v5"
BOOT_ANIMATION_SELECTION_CONTRACT="product-image-root-theme-context-stored-v6"

function selection_variant_manifest() {
local field value
Expand Down
28 changes: 28 additions & 0 deletions tests/boot_animation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import hashlib
import io
import re
import stat
import sys
import tempfile
Expand Down Expand Up @@ -43,6 +44,9 @@ def __init__(self, root: Path) -> None:
self.root = root
self.mkdir_calls: list[tuple[str, int, bool, bool]] = []
self.open_calls: list[tuple[str, str, int]] = []
self.contexts = [
(re.compile(r"/product(/.*)?"), "u:object_r:system_file:s0"),
]

@property
def tree(self) -> Path:
Expand Down Expand Up @@ -94,6 +98,21 @@ def test_runtime_payload_installation() -> None:
for target in expected_targets:
assert (product.root / target.lstrip("/")).read_bytes() == runtime

expected_alias_paths = {
"/media",
"/media/bootanimation.zip",
"/media/bootanimation-dark.zip",
}
alias_labels = {
path: next(
label
for pattern, label in product.contexts
if pattern.fullmatch(path)
)
for path in expected_alias_paths
}
assert set(alias_labels.values()) == {"u:object_r:system_file:s0"}

# A standalone product.img is mounted at /product. Its filesystem root
# therefore contains /media, not another nested /product directory.
assert not (product.root / "product").exists()
Expand Down Expand Up @@ -142,6 +161,15 @@ def test_runtime_payload_installation() -> None:
else:
raise AssertionError("missing light and dark payloads were accepted")

missing_contexts = FakeExtFs(root / "missing-contexts")
missing_contexts.contexts = []
try:
install_runtime_payload({"product": missing_contexts}, runtime)
except RuntimeError:
pass
else:
raise AssertionError("missing runtime SELinux context was accepted")

try:
install_runtime_payload({}, runtime)
except RuntimeError:
Expand Down
Loading