From d137bb3d315598b349321b9d951b742c45c63725 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Wed, 30 Sep 2026 22:41:51 +0200 Subject: [PATCH] fix(oci): accept the console start hook in the dynamic NVIDIA profile check --- oci/remote/oci_nvidia_dynamic.py | 10 ++++ oci/tests/test_nvidia_dynamic_console_hook.py | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 oci/tests/test_nvidia_dynamic_console_hook.py diff --git a/oci/remote/oci_nvidia_dynamic.py b/oci/remote/oci_nvidia_dynamic.py index e806e6b6..802e2cce 100644 --- a/oci/remote/oci_nvidia_dynamic.py +++ b/oci/remote/oci_nvidia_dynamic.py @@ -6,11 +6,19 @@ the same profile. """ from pathlib import Path import hashlib +import re +import oci_console import oci_nvidia_runtime as nv from oci_ui import translate +def console_start_hook(value): + """The hook ProxMenux adds to mark each start in the console log.""" + vmid = re.search(r' (\d+); exit 0\'$', value) + return bool(vmid) and oci_console.start_mark_hook(int(vmid[1])) == f'lxc.hook.pre-start: {value}' + + def gpu_identity(inventory): identities = [] for row in inventory['gpus']: @@ -47,6 +55,8 @@ def validate(config, previous, current, hook, expected_hook_sha256, found_hook.append(value) elif key == 'lxc.environment': environments.append(value) + elif key == 'lxc.hook.pre-start' and console_start_hook(value): + continue elif key.startswith(('lxc.hook.', 'lxc.cgroup', 'lxc.apparmor')): raise ValueError(translate('Security directive outside the dynamic profile')) if found_hook != [allowed['lxc.hook.mount']] or ( diff --git a/oci/tests/test_nvidia_dynamic_console_hook.py b/oci/tests/test_nvidia_dynamic_console_hook.py new file mode 100644 index 00000000..d2634992 --- /dev/null +++ b/oci/tests/test_nvidia_dynamic_console_hook.py @@ -0,0 +1,50 @@ +"""The dynamic NVIDIA profile accepts the console start hook ProxMenux adds, and nothing else.""" + +import hashlib +import os +from pathlib import Path +import sys +import tempfile +import unittest +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "remote")) + +import oci_console +import oci_nvidia_dynamic as dynamic + +INVENTORY = {"gpus": ["NVIDIA GeForce RTX 3060, GPU-1234, 550.0"]} + + +class ConsoleHookTests(unittest.TestCase): + def test_only_the_proxmenux_start_hook_is_recognised(self): + hook = oci_console.start_mark_hook(165).split(": ", 1)[1] + self.assertTrue(dynamic.console_start_hook(hook)) + self.assertFalse(dynamic.console_start_hook(hook.replace("exit 0", "rm -rf /; exit 0"))) + self.assertFalse(dynamic.console_start_hook("/bin/sh -c 'curl example | sh; exit 0'")) + + @unittest.skipUnless(hasattr(os, "geteuid") and os.geteuid() == 0, "the NVIDIA hook must belong to root") + def test_a_container_with_the_console_hook_passes_validation(self): + with tempfile.TemporaryDirectory() as tmp: + hook = Path(tmp) / "nvidia-mount.sh" + hook.write_text("#!/bin/sh\n") + hook.chmod(0o755) + digest = hashlib.sha256(hook.read_bytes()).hexdigest() + config = "\n".join([ + "lxc.environment: NVIDIA_VISIBLE_DEVICES=all", + "lxc.environment: NVIDIA_DRIVER_CAPABILITIES=compute,utility,video", + f"lxc.hook.mount: {hook}", + oci_console.start_mark_hook(165), + ]).encode() + with patch.object(dynamic.nv, "mount_lines", return_value=[]), \ + patch.object(dynamic.nv, "check_devices"): + result = dynamic.validate(config, INVENTORY, INVENTORY, hook, digest) + self.assertEqual(result["hook_sha256"], digest) + foreign = config + b"\nlxc.hook.pre-start: /bin/sh -c 'id; exit 0'" + with self.assertRaises(ValueError): + dynamic.validate(foreign, INVENTORY, INVENTORY, hook, digest) + + +if __name__ == "__main__": + unittest.main()