From 01f04c51e9383b3cab8f4da0ceb7f621715808fe Mon Sep 17 00:00:00 2001 From: Rajkaran Date: Mon, 27 Jul 2026 17:47:29 +0000 Subject: [PATCH 1/2] linux: error on invalid capabilities Instead of silently ignoring invalid capability strings in config.json, return an error. This prevents containers from starting without the intended privileges due to typos. Fixes: #2121 Signed-off-by: Rajkaran --- src/libcrun/linux.c | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/libcrun/linux.c b/src/libcrun/linux.c index 09f94227d5..8195a87f25 100644 --- a/src/libcrun/linux.c +++ b/src/libcrun/linux.c @@ -4296,7 +4296,7 @@ set_required_caps (struct all_caps_s *caps, uid_t uid, gid_t gid, int no_new_pri } static int -read_caps (unsigned long caps[2], char **values, size_t len) +read_caps (unsigned long caps[2], char **values, size_t len, libcrun_error_t *err) { #ifdef HAVE_CAP size_t i; @@ -4304,10 +4304,8 @@ read_caps (unsigned long caps[2], char **values, size_t len) { cap_value_t cap; if (cap_from_name (values[i], &cap) < 0) - { - libcrun_warning ("unknown cap: `%s`", values[i]); - continue; - } + return crun_make_error (err, 0, "unknown capability `%s`", values[i]); + if (cap < 32) caps[0] |= CAP_TO_MASK_0 (cap); else @@ -4342,14 +4340,29 @@ libcrun_set_caps (runtime_spec_schema_config_schema_process_capabilities *capabi int no_new_privileges, libcrun_error_t *err) { struct all_caps_s caps = {}; + int ret; if (capabilities) { - read_caps (caps.effective, capabilities->effective, capabilities->effective_len); - read_caps (caps.inheritable, capabilities->inheritable, capabilities->inheritable_len); - read_caps (caps.ambient, capabilities->ambient, capabilities->ambient_len); - read_caps (caps.bounding, capabilities->bounding, capabilities->bounding_len); - read_caps (caps.permitted, capabilities->permitted, capabilities->permitted_len); + ret = read_caps (caps.effective, capabilities->effective, capabilities->effective_len, err); + if (UNLIKELY (ret < 0)) + return ret; + + ret = read_caps (caps.inheritable, capabilities->inheritable, capabilities->inheritable_len, err); + if (UNLIKELY (ret < 0)) + return ret; + + ret = read_caps (caps.ambient, capabilities->ambient, capabilities->ambient_len, err); + if (UNLIKELY (ret < 0)) + return ret; + + ret = read_caps (caps.bounding, capabilities->bounding, capabilities->bounding_len, err); + if (UNLIKELY (ret < 0)) + return ret; + + ret = read_caps (caps.permitted, capabilities->permitted, capabilities->permitted_len, err); + if (UNLIKELY (ret < 0)) + return ret; } return set_required_caps (&caps, uid, gid, no_new_privileges, err); } From 7036c66323ba52f9be34956466b713e31c91d8f8 Mon Sep 17 00:00:00 2001 From: Rajkaran Date: Mon, 27 Jul 2026 18:28:51 +0000 Subject: [PATCH 2/2] tests: expect error on invalid capabilities Update tests to match the new behavior where unknown capability strings in config.json and crun exec are rejected instead of silently ignored. Signed-off-by: Rajkaran --- tests/test_capabilities.py | 17 +++++++---------- tests/test_exec.py | 15 ++++++++------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/tests/test_capabilities.py b/tests/test_capabilities.py index 71df85c7b8..22861e85e1 100755 --- a/tests/test_capabilities.py +++ b/tests/test_capabilities.py @@ -18,6 +18,7 @@ import json import os import shutil +import subprocess import sys from tests_utils import * @@ -60,18 +61,14 @@ def test_unknown_caps(): conf['process']['args'] = ['/init', 'cat', '/proc/self/status'] add_all_namespaces(conf) conf['process']['capabilities'] = {} - # unknown caps must be ignored for i in ['bounding', 'effective', 'inheritable', 'permitted', 'ambient']: conf['process']['capabilities'][i] = ['CAP_UNKNOWN', 'UNKNOWN_CAP'] - out, _ = run_and_get_output(conf, hide_stderr=True) - proc_status = parse_proc_status(out) - - for i in ['CapInh', 'CapPrm', 'CapEff', 'CapBnd', 'CapAmb']: - if proc_status.get(i, '') != "0000000000000000": - actual = proc_status.get(i, 'MISSING') - logger.info("%s capability check failed (unknown caps should be ignored): expected '0000000000000000', got '%s'", i, actual) - return -1 - return 0 + try: + run_and_get_output(conf, hide_stderr=True) + logger.info("unknown caps should be rejected") + return -1 + except subprocess.CalledProcessError: + return 0 def test_new_privs(): conf = base_config() diff --git a/tests/test_exec.py b/tests/test_exec.py index 6247f3a952..c0848d48f0 100755 --- a/tests/test_exec.py +++ b/tests/test_exec.py @@ -255,11 +255,6 @@ def test_exec_add_capability(): add_all_namespaces(conf) conf['process']['capabilities'] = {} cid = None - cap_unknown_dict = {"CapInh":"0000000000000000", \ - "CapPrm":"0000000000000000", \ - "CapEff":"0000000000000000", \ - "CapBnd":"0000000000000000", \ - "CapAmb":"0000000000000000"} cap_kill_dict = {"CapInh":"0000000000000000", \ "CapPrm":"0000000000000020", \ "CapEff":"0000000000000020", \ @@ -270,8 +265,7 @@ def test_exec_add_capability(): "CapEff":"0000000000200000", \ "CapBnd":"0000000000200000", \ "CapAmb":"0000000000000000"} - cap_dict = {"CAP_UNKNOWN": cap_unknown_dict, \ - "CAP_KILL": cap_kill_dict, \ + cap_dict = {"CAP_KILL": cap_kill_dict, \ "CAP_SYS_ADMIN": cap_sys_admin_dict} try: _, cid = run_and_get_output(conf, hide_stderr=True, command='run', detach=True) @@ -284,6 +278,13 @@ def test_exec_add_capability(): for i in ['CapInh', 'CapPrm', 'CapEff', 'CapBnd', 'CapAmb']: if proc_status[i] != value[i]: return -1 + + try: + run_crun_command(["exec", "--cap", "CAP_UNKNOWN", cid, "/init", "cat", "/proc/self/status"]) + logger.info("CAP_UNKNOWN should be rejected") + return -1 + except subprocess.CalledProcessError: + pass finally: if cid is not None: run_crun_command(["delete", "-f", cid])