1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
commit a9d129964f9221a423b4bda1c7663934971200a8
Author: Giuseppe Scrivano <gscrivan@redhat.com>
Date: Tue Aug 5 11:16:08 2025 +0200
linux: never chown devices
Closes: https://github.com/containers/crun/issues/1845
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Index: crun/src/libcrun/container.c
===================================================================
--- crun.orig/src/libcrun/container.c
+++ crun/src/libcrun/container.c
@@ -1019,8 +1019,7 @@ send_sync_cb (void *data, libcrun_error_
}
static int
-maybe_chown_std_streams (uid_t container_uid, gid_t container_gid,
- libcrun_error_t *err)
+maybe_chown_std_streams (uid_t container_uid, gid_t container_gid, libcrun_error_t *err)
{
int ret, i;
@@ -1028,6 +1027,19 @@ maybe_chown_std_streams (uid_t container
{
if (! isatty (i))
{
+ struct stat statbuf;
+ ret = fstat (i, &statbuf);
+ if (UNLIKELY (ret < 0))
+ {
+ if (errno == EBADF)
+ continue;
+ return crun_make_error (err, errno, "fstat fd `%d`", i);
+ }
+
+ /* Skip chown for device files */
+ if (S_ISCHR (statbuf.st_mode) || S_ISBLK (statbuf.st_mode))
+ continue;
+
ret = fchown (i, container_uid, container_gid);
if (UNLIKELY (ret < 0))
{
Index: crun/tests/test_uid_gid.py
===================================================================
--- crun.orig/tests/test_uid_gid.py
+++ crun/tests/test_uid_gid.py
@@ -1,7 +1,7 @@
#!/bin/env python3
# crun - OCI runtime written in C
#
-# Copyright (C) 2017, 2018, 2019 Giuseppe Scrivano <giuseppe@scrivano.org>
+# Copyright (C) 2017, 2018, 2019, 2025 Giuseppe Scrivano <giuseppe@scrivano.org>
# crun is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
@@ -16,6 +16,7 @@
# along with crun. If not, see <http://www.gnu.org/licenses/>.
import os
+import sys
from tests_utils import *
def test_userns_full_mapping():
@@ -114,12 +115,89 @@ def test_keep_groups():
return -1
return 0
+def test_dev_null_no_chown():
+ """Test that /dev/null file descriptors are not chowned to container user."""
+ if is_rootless():
+ return 77
+
+ # Get current owner of /dev/null and use owner + 1 as container user
+ dev_null_stat = os.stat('/dev/null')
+ container_uid = dev_null_stat.st_uid + 1
+ container_gid = dev_null_stat.st_gid + 1
+
+ conf = base_config()
+ conf['process']['user'] = {"uid": container_uid, "gid": container_gid}
+ add_all_namespaces(conf)
+
+ # Check ownership of stdin fd which should be /dev/null
+ conf['process']['args'] = ['/init', 'owner', '/proc/self/fd/0']
+
+ try:
+ out, container_id = run_and_get_output(conf, stdin_dev_null=True)
+ sys.stderr.write("# Container ran successfully, output: %s\n" % repr(out))
+ if ':' in out:
+ uid_str, gid_str = out.strip().split(':')
+ uid, gid = int(uid_str), int(gid_str)
+ # Should NOT be owned by container user
+ if uid == container_uid or gid == container_gid:
+ sys.stderr.write("# dev-null-no-chown test failed: /dev/null fd owned by container user %d:%d\n" % (uid, gid))
+ sys.stderr.write("# stdout: %s\n" % repr(out))
+ return -1
+ sys.stderr.write("# dev-null-no-chown test passed: /dev/null fd owned by %d:%d (not container user %d:%d)\n" % (uid, gid, container_uid, container_gid))
+ else:
+ sys.stderr.write("# dev-null-no-chown test failed: unexpected owner output format\n")
+ sys.stderr.write("# stdout: %s\n" % repr(out))
+ return -1
+ return 0
+ except Exception as e:
+ sys.stderr.write("# dev-null-no-chown test failed with exception: %s\n" % str(e))
+ if hasattr(e, 'output'):
+ sys.stderr.write("# command output: %s\n" % repr(e.output))
+ return -1
+
+def test_regular_files_chowned():
+ """Test that regular file descriptors are chowned to container user."""
+ if is_rootless():
+ return 77
+
+ # Get current owner of /dev/null and use owner + 1 as container user
+ dev_null_stat = os.stat('/dev/null')
+ container_uid = dev_null_stat.st_uid + 1
+ container_gid = dev_null_stat.st_gid + 1
+
+ conf = base_config()
+ conf['process']['user'] = {"uid": container_uid, "gid": container_gid}
+ add_all_namespaces(conf)
+
+ # Check ownership of regular stdout (not /dev/null)
+ conf['process']['args'] = ['/init', 'owner', '/proc/self/fd/1']
+
+ try:
+ out, _ = run_and_get_output(conf)
+ if ':' in out:
+ uid_str, gid_str = out.strip().split(':')
+ uid, gid = int(uid_str), int(gid_str)
+ # Should be owned by container user
+ if uid != container_uid or gid != container_gid:
+ sys.stderr.write("# regular-files-chowned test failed: regular fd owned by %d:%d (expected %d:%d)\n" % (uid, gid, container_uid, container_gid))
+ return -1
+ sys.stderr.write("# regular-files-chowned test passed: regular fd owned by %d:%d (container user)\n" % (uid, gid))
+ else:
+ sys.stderr.write("# regular-files-chowned test failed: unexpected output format: %s\n" % repr(out))
+ return -1
+ return 0
+ except Exception as e:
+ sys.stderr.write("# regular-files-chowned test failed with exception: %s\n" % str(e))
+ return -1
+
all_tests = {
"uid" : test_uid,
"gid" : test_gid,
"userns-full-mapping" : test_userns_full_mapping,
"no-groups" : test_no_groups,
"keep-groups" : test_keep_groups,
+ "dev-null-no-chown": test_dev_null_no_chown,
+ "regular-files-chowned": test_regular_files_chowned,
}
if __name__ == "__main__":
Index: crun/tests/tests_utils.py
===================================================================
--- crun.orig/tests/tests_utils.py
+++ crun/tests/tests_utils.py
@@ -210,7 +210,7 @@ def get_crun_path():
def run_and_get_output(config, detach=False, preserve_fds=None, pid_file=None,
keep=False,
command='run', env=None, use_popen=False, hide_stderr=False, cgroup_manager='cgroupfs',
- all_dev_null=False, id_container=None, relative_config_path="config.json",
+ all_dev_null=False, stdin_dev_null=False, id_container=None, relative_config_path="config.json",
chown_rootfs_to=None, callback_prepare_rootfs=None):
# Some tests require that the container user, which might not be the
@@ -286,6 +286,9 @@ def run_and_get_output(config, detach=Fa
stdin = subprocess.DEVNULL
stdout = subprocess.DEVNULL
stderr = subprocess.DEVNULL
+ elif stdin_dev_null:
+ stdin = subprocess.DEVNULL
+
if use_popen:
if not stdout:
stdout=subprocess.PIPE
@@ -295,7 +298,7 @@ def run_and_get_output(config, detach=Fa
stderr=stderr, stdin=stdin, env=env,
close_fds=False), id_container
else:
- return subprocess.check_output(args, cwd=temp_dir, stderr=stderr, env=env, close_fds=False, umask=default_umask).decode(), id_container
+ return subprocess.check_output(args, cwd=temp_dir, stdin=stdin, stderr=stderr, env=env, close_fds=False, umask=default_umask).decode(), id_container
def run_crun_command(args):
root = get_tests_root_status()
|