File: never-chown-devices.patch

package info (click to toggle)
crun 1.21-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 9,464 kB
  • sloc: ansic: 62,804; python: 5,867; sh: 4,873; makefile: 769
file content (187 lines) | stat: -rw-r--r-- 7,737 bytes parent folder | download
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()