/src/systemd/src/shared/extension-util.c
Line | Count | Source |
1 | | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | | |
3 | | #include "alloc-util.h" |
4 | | #include "architecture.h" |
5 | | #include "chase.h" |
6 | | #include "env-util.h" |
7 | | #include "extension-util.h" |
8 | | #include "log.h" |
9 | | #include "os-util.h" |
10 | | #include "string-util.h" |
11 | | #include "strv.h" |
12 | | |
13 | | int extension_release_validate( |
14 | | const char *name, |
15 | | const char *host_os_release_id, |
16 | | const char *host_os_release_id_like, |
17 | | const char *host_os_release_version_id, |
18 | | const char *host_os_extension_release_level, |
19 | | const char *host_extension_scope, |
20 | | char **extension_release, |
21 | 0 | ImageClass image_class) { |
22 | |
|
23 | 0 | const char *extension_release_id = NULL, *extension_release_level = NULL, *extension_architecture = NULL; |
24 | 0 | const char *extension_level = image_class == IMAGE_CONFEXT ? "CONFEXT_LEVEL" : "SYSEXT_LEVEL"; |
25 | 0 | const char *extension_scope = image_class == IMAGE_CONFEXT ? "CONFEXT_SCOPE" : "SYSEXT_SCOPE"; |
26 | 0 | _cleanup_strv_free_ char **id_like_l = NULL; |
27 | |
|
28 | 0 | assert(name); |
29 | 0 | assert(!isempty(host_os_release_id)); |
30 | | |
31 | | /* Now that we can look into the extension/confext image, let's see if the OS version is compatible */ |
32 | 0 | if (strv_isempty(extension_release)) { |
33 | 0 | log_debug("Extension '%s' carries no release data, ignoring.", name); |
34 | 0 | return 0; |
35 | 0 | } |
36 | | |
37 | 0 | if (host_extension_scope) { |
38 | 0 | _cleanup_strv_free_ char **scope_list = NULL; |
39 | 0 | const char *scope; |
40 | 0 | bool valid; |
41 | |
|
42 | 0 | scope = strv_env_pairs_get(extension_release, extension_scope); |
43 | 0 | if (scope) { |
44 | 0 | scope_list = strv_split(scope, WHITESPACE); |
45 | 0 | if (!scope_list) |
46 | 0 | return -ENOMEM; |
47 | 0 | } |
48 | | |
49 | | /* By default extension are good for attachment in portable service and on the system */ |
50 | 0 | valid = strv_contains( |
51 | 0 | scope_list ?: STRV_MAKE("system", "portable"), |
52 | 0 | host_extension_scope); |
53 | 0 | if (!valid) { |
54 | 0 | log_debug("Extension '%s' is not suitable for scope %s, ignoring.", name, host_extension_scope); |
55 | 0 | return 0; |
56 | 0 | } |
57 | 0 | } |
58 | | |
59 | | /* When the architecture field is present and not '_any' it must match the host - for now just look at uname but in |
60 | | * the future we could check if the kernel also supports 32 bit or binfmt has a translator set up for the architecture */ |
61 | 0 | extension_architecture = strv_env_pairs_get(extension_release, "ARCHITECTURE"); |
62 | 0 | if (!isempty(extension_architecture) && !streq(extension_architecture, "_any") && |
63 | 0 | !streq(architecture_to_string(uname_architecture()), extension_architecture)) { |
64 | 0 | log_debug("Extension '%s' is for architecture '%s', but deployed on top of '%s'.", |
65 | 0 | name, extension_architecture, architecture_to_string(uname_architecture())); |
66 | 0 | return 0; |
67 | 0 | } |
68 | | |
69 | 0 | extension_release_id = strv_env_pairs_get(extension_release, "ID"); |
70 | 0 | if (isempty(extension_release_id)) { |
71 | 0 | log_debug("Extension '%s' does not contain ID in release file but requested to match '%s' or be '_any'", |
72 | 0 | name, host_os_release_id); |
73 | 0 | return 0; |
74 | 0 | } |
75 | | |
76 | | /* A sysext(or confext) with no host OS dependency (static binaries or scripts) can match |
77 | | * '_any' host OS, and VERSION_ID or SYSEXT_LEVEL(or CONFEXT_LEVEL) are not required anywhere */ |
78 | 0 | if (streq(extension_release_id, "_any")) { |
79 | 0 | log_debug("Extension '%s' matches '_any' OS.", name); |
80 | 0 | return 1; |
81 | 0 | } |
82 | | |
83 | | /* Match extension OS ID against host OS ID or ID_LIKE */ |
84 | 0 | if (host_os_release_id_like) { |
85 | 0 | id_like_l = strv_split(host_os_release_id_like, WHITESPACE); |
86 | 0 | if (!id_like_l) |
87 | 0 | return log_oom(); |
88 | 0 | } |
89 | | |
90 | 0 | if (!streq(host_os_release_id, extension_release_id) && !strv_contains(id_like_l, extension_release_id)) { |
91 | 0 | log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'%s%s%s.", |
92 | 0 | name, extension_release_id, host_os_release_id, |
93 | 0 | host_os_release_id_like ? " (like '" : "", |
94 | 0 | strempty(host_os_release_id_like), |
95 | 0 | host_os_release_id_like ? "')" : ""); |
96 | 0 | return 0; |
97 | 0 | } |
98 | | |
99 | | /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */ |
100 | 0 | if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) { |
101 | 0 | log_debug("No version info on the host (rolling release?), but ID in %s matched.", name); |
102 | 0 | return 1; |
103 | 0 | } |
104 | | |
105 | | /* If the extension has a sysext API level declared, then it must match the host API |
106 | | * level. Otherwise, compare OS version as a whole */ |
107 | 0 | extension_release_level = strv_env_pairs_get(extension_release, extension_level); |
108 | 0 | if (!isempty(host_os_extension_release_level) && !isempty(extension_release_level)) { |
109 | 0 | if (!streq_ptr(host_os_extension_release_level, extension_release_level)) { |
110 | 0 | log_debug("Extension '%s' is for API level '%s', but running on API level '%s'", |
111 | 0 | name, strna(extension_release_level), strna(host_os_extension_release_level)); |
112 | 0 | return 0; |
113 | 0 | } |
114 | 0 | } else if (!isempty(host_os_release_version_id)) { |
115 | 0 | const char *extension_release_version_id; |
116 | |
|
117 | 0 | extension_release_version_id = strv_env_pairs_get(extension_release, "VERSION_ID"); |
118 | 0 | if (isempty(extension_release_version_id)) { |
119 | 0 | log_debug("Extension '%s' does not contain VERSION_ID in release file but requested to match '%s'", |
120 | 0 | name, strna(host_os_release_version_id)); |
121 | 0 | return 0; |
122 | 0 | } |
123 | | |
124 | 0 | if (!streq_ptr(host_os_release_version_id, extension_release_version_id)) { |
125 | 0 | log_debug("Extension '%s' is for OS '%s', but deployed on top of '%s'.", |
126 | 0 | name, strna(extension_release_version_id), strna(host_os_release_version_id)); |
127 | 0 | return 0; |
128 | 0 | } |
129 | 0 | } else if (isempty(host_os_release_version_id) && isempty(host_os_extension_release_level)) { |
130 | | /* Rolling releases do not typically set VERSION_ID (eg: ArchLinux) */ |
131 | 0 | log_debug("No version info on the host (rolling release?), but ID in %s matched.", name); |
132 | 0 | return 1; |
133 | 0 | } |
134 | | |
135 | 0 | log_debug("Version info of extension '%s' matches host.", name); |
136 | 0 | return 1; |
137 | 0 | } |
138 | | |
139 | 0 | int parse_env_extension_hierarchies(char ***ret_hierarchies, const char *hierarchy_env) { |
140 | 0 | _cleanup_free_ char **l = NULL; |
141 | 0 | int r; |
142 | |
|
143 | 0 | assert(ret_hierarchies); |
144 | 0 | assert(hierarchy_env); |
145 | 0 | r = getenv_path_list(hierarchy_env, &l); |
146 | 0 | if (r == -ENXIO) { |
147 | 0 | if (streq(hierarchy_env, "SYSTEMD_CONFEXT_HIERARCHIES")) |
148 | | /* Default for confext when unset */ |
149 | 0 | l = strv_new("/etc"); |
150 | 0 | else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_HIERARCHIES")) |
151 | | /* Default for sysext when unset */ |
152 | 0 | l = strv_new("/usr", "/opt"); |
153 | 0 | else if (streq(hierarchy_env, "SYSTEMD_SYSEXT_AND_CONFEXT_HIERARCHIES")) |
154 | | /* Combined sysext and confext directories */ |
155 | 0 | l = strv_new("/usr", "/opt", "/etc"); |
156 | 0 | else |
157 | 0 | return -ENXIO; |
158 | 0 | } else if (r < 0) |
159 | 0 | return r; |
160 | | |
161 | 0 | *ret_hierarchies = TAKE_PTR(l); |
162 | 0 | return 0; |
163 | 0 | } |
164 | | |
165 | 0 | int extension_has_forbidden_content(const char *root) { |
166 | 0 | int r; |
167 | | |
168 | | /* Insist that extension images do not overwrite the underlying OS release file (it's fine if |
169 | | * they place one in /etc/os-release, i.e. where things don't matter, as they aren't |
170 | | * merged.) */ |
171 | 0 | r = chase("/usr/lib/os-release", root, CHASE_PREFIX_ROOT, NULL, NULL); |
172 | 0 | if (r > 0) { |
173 | 0 | log_debug("Extension contains '/usr/lib/os-release', which is not allowed, refusing."); |
174 | 0 | return 1; |
175 | 0 | } |
176 | 0 | if (r < 0 && r != -ENOENT) |
177 | 0 | return log_debug_errno(r, "Failed to determine whether '/usr/lib/os-release' exists in the extension: %m"); |
178 | | |
179 | 0 | return 0; |
180 | 0 | } |