Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <linux/oom.h>
4 : : #if HAVE_SECCOMP
5 : : #include <seccomp.h>
6 : : #endif
7 : :
8 : : #include "bus-util.h"
9 : : #include "cap-list.h"
10 : : #include "cpu-set-util.h"
11 : : #include "env-util.h"
12 : : #include "format-util.h"
13 : : #include "fs-util.h"
14 : : #include "hostname-util.h"
15 : : #include "json.h"
16 : : #include "missing_sched.h"
17 : : #include "nspawn-oci.h"
18 : : #include "path-util.h"
19 : : #include "rlimit-util.h"
20 : : #if HAVE_SECCOMP
21 : : #include "seccomp-util.h"
22 : : #endif
23 : : #include "stat-util.h"
24 : : #include "stdio-util.h"
25 : : #include "string-util.h"
26 : : #include "strv.h"
27 : : #include "user-util.h"
28 : :
29 : : /* TODO:
30 : : * OCI runtime tool implementation
31 : : * hooks
32 : : *
33 : : * Spec issues:
34 : : *
35 : : * How is RLIM_INFINITY supposed to be encoded?
36 : : * configured effective caps is bullshit, as execv() corrupts it anyway
37 : : * pipes bind mounted is *very* different from pipes newly created, comments regarding bind mount or not are bogus
38 : : * annotation values structured? or string?
39 : : * configurable file system namespace path, but then also root path? wtf?
40 : : * apply sysctl inside of the container? or outside?
41 : : * how is unlimited pids tasks limit to be encoded?
42 : : * what are the defaults for caps if not specified?
43 : : * what are the default uid/gid mappings if one is missing but the other set, or when user ns is on but no namespace configured
44 : : * the source field of "mounts" is really weird, as it cannot realistically be relative to the bundle, since we never know if that's what the fs wants
45 : : * spec contradicts itself on the mount "type" field, as the example uses "bind" as type, but it's not listed in /proc/filesystem, and is something made up by /bin/mount
46 : : * if type of mount is left out, what shall be assumed? "bind"?
47 : : * readonly mounts is entirely redundant?
48 : : * should escaping be applied when joining mount options with ","?
49 : : * devices cgroup support is bogus, "allow" and "deny" on the kernel level is about adding/removing entries, not about access
50 : : * spec needs to say that "rwm" devices cgroup combination can't be the empty string
51 : : * cgrouspv1 crap: kernel, kernelTCP, swapiness, disableOOMKiller, swap, devices, leafWeight
52 : : * general: it shouldn't leak lower level abstractions this obviously
53 : : * unmanagable cgroups stuff: realtimeRuntime/realtimePeriod
54 : : * needs to say what happense when some option is not specified, i.e. which defautls apply
55 : : * no architecture? no personality?
56 : : * seccomp example and logic is simply broken: there's no constant "SCMP_ACT_ERRNO".
57 : : * spec should say what to do with unknown props
58 : : * /bin/mount regarding NFS and FUSE required?
59 : : * what does terminal=false mean?
60 : : * sysctl inside or outside? whitelisting?
61 : : * swapiness typo -> swappiness
62 : : *
63 : : * Unsupported:
64 : : *
65 : : * apparmorProfile
66 : : * selinuxLabel + mountLabel
67 : : * hugepageLimits
68 : : * network
69 : : * rdma
70 : : * intelRdt
71 : : * swappiness, disableOOMKiller, kernel, kernelTCP, leafWeight (because it's dead, cgroupsv2 can't do it and hence systemd neither)
72 : : *
73 : : * Non-slice cgroup paths
74 : : * Propagation that is not slave + shared
75 : : * more than one uid/gid mapping, mappings with a container base != 0, or non-matching uid/gid mappings
76 : : * device cgroups access = false items that are not catchall
77 : : * device cgroups matches where minor is specified, but major isn't. similar where major is specified but char/block is not. also, any match that only has a type set that has less than "rwm" set. also, any entry that has none of rwm set.
78 : : *
79 : : */
80 : :
81 : 0 : static int oci_unexpected(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
82 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
83 : : "Unexpected OCI element '%s' of type '%s'.", name, json_variant_type_to_string(json_variant_type(v)));
84 : : }
85 : :
86 : 0 : static int oci_unsupported(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
87 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
88 : : "Unsupported OCI element '%s' of type '%s'.", name, json_variant_type_to_string(json_variant_type(v)));
89 : : }
90 : :
91 : 0 : static int oci_terminal(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
92 : 0 : Settings *s = userdata;
93 : :
94 : : /* If not specified, or set to true, we'll default to either an interactive or a read-only
95 : : * console. If specified as false, we'll forcibly move to "pipe" mode though. */
96 [ # # ]: 0 : s->console_mode = json_variant_boolean(v) ? _CONSOLE_MODE_INVALID : CONSOLE_PIPE;
97 : 0 : return 0;
98 : : }
99 : :
100 : 0 : static int oci_console_dimension(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
101 : 0 : unsigned *u = userdata;
102 : : uintmax_t k;
103 : :
104 [ # # ]: 0 : assert(u);
105 : :
106 : 0 : k = json_variant_unsigned(variant);
107 [ # # ]: 0 : if (k == 0)
108 [ # # ]: 0 : return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
109 : : "Console size field '%s' is too small.", strna(name));
110 [ # # ]: 0 : if (k > USHRT_MAX) /* TIOCSWINSZ's struct winsize uses "unsigned short" for width and height */
111 [ # # ]: 0 : return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE),
112 : : "Console size field '%s' is too large.", strna(name));
113 : :
114 : 0 : *u = (unsigned) k;
115 : 0 : return 0;
116 : : }
117 : :
118 : 0 : static int oci_console_size(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
119 : :
120 : : static const JsonDispatch table[] = {
121 : : { "height", JSON_VARIANT_UNSIGNED, oci_console_dimension, offsetof(Settings, console_height), JSON_MANDATORY },
122 : : { "width", JSON_VARIANT_UNSIGNED, oci_console_dimension, offsetof(Settings, console_width), JSON_MANDATORY },
123 : : {}
124 : : };
125 : :
126 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
127 : : }
128 : :
129 : 0 : static int oci_absolute_path(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
130 : 0 : char **p = userdata;
131 : : const char *n;
132 : :
133 [ # # ]: 0 : assert(p);
134 : :
135 : 0 : n = json_variant_string(v);
136 : :
137 [ # # ]: 0 : if (!path_is_absolute(n))
138 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
139 : : "Path in JSON field '%s' is not absolute: %s", strna(name), n);
140 : :
141 : 0 : return free_and_strdup_warn(p, n);
142 : : }
143 : :
144 : 0 : static int oci_env(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
145 : 0 : char ***l = userdata;
146 : : JsonVariant *e;
147 : : int r;
148 : :
149 [ # # ]: 0 : assert(l);
150 : :
151 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
152 : : const char *n;
153 : :
154 [ # # ]: 0 : if (!json_variant_is_string(e))
155 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
156 : : "Environment array contains non-string.");
157 : :
158 [ # # ]: 0 : assert_se(n = json_variant_string(e));
159 : :
160 [ # # ]: 0 : if (!env_assignment_is_valid(n))
161 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
162 : : "Environment assignment not valid: %s", n);
163 : :
164 : 0 : r = strv_extend(l, n);
165 [ # # ]: 0 : if (r < 0)
166 : 0 : return log_oom();
167 : : }
168 : :
169 : 0 : return 0;
170 : : }
171 : :
172 : 0 : static int oci_args(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
173 : 0 : _cleanup_strv_free_ char **l = NULL;
174 : 0 : char ***value = userdata;
175 : : JsonVariant *e;
176 : : int r;
177 : :
178 [ # # ]: 0 : assert(value);
179 : :
180 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
181 : : const char *n;
182 : :
183 [ # # ]: 0 : if (!json_variant_is_string(e))
184 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
185 : : "Argument is not a string.");
186 : :
187 [ # # ]: 0 : assert_se(n = json_variant_string(e));
188 : :
189 : 0 : r = strv_extend(&l, n);
190 [ # # ]: 0 : if (r < 0)
191 : 0 : return log_oom();
192 : : }
193 : :
194 [ # # ]: 0 : if (strv_isempty(l))
195 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
196 : : "Argument list empty, refusing.");
197 : :
198 [ # # ]: 0 : if (isempty(l[0]))
199 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
200 : : "Executable name is empty, refusing.");
201 : :
202 : 0 : return strv_free_and_replace(*value, l);
203 : : }
204 : :
205 : 0 : static int oci_rlimit_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
206 : : const char *z;
207 : 0 : int t, *type = userdata;
208 : :
209 [ # # ]: 0 : assert_se(type);
210 : :
211 : 0 : z = startswith(json_variant_string(v), "RLIMIT_");
212 [ # # ]: 0 : if (!z)
213 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
214 : : "rlimit entry's name does not begin with 'RLIMIT_', refusing: %s",
215 : : json_variant_string(v));
216 : :
217 : 0 : t = rlimit_from_string(z);
218 [ # # ]: 0 : if (t < 0)
219 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
220 : : "rlimit name unknown: %s", json_variant_string(v));
221 : :
222 : 0 : *type = t;
223 : 0 : return 0;
224 : : }
225 : :
226 : 0 : static int oci_rlimit_value(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
227 : 0 : rlim_t z, *value = userdata;
228 : :
229 [ # # ]: 0 : assert(value);
230 : :
231 [ # # ]: 0 : if (json_variant_is_negative(v))
232 : 0 : z = RLIM_INFINITY;
233 : : else {
234 [ # # ]: 0 : if (!json_variant_is_unsigned(v))
235 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
236 : : "rlimits limit not unsigned, refusing.");
237 : :
238 : 0 : z = (rlim_t) json_variant_unsigned(v);
239 : :
240 [ # # ]: 0 : if ((uintmax_t) z != json_variant_unsigned(v))
241 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
242 : : "rlimits limit out of range, refusing.");
243 : : }
244 : :
245 : 0 : *value = z;
246 : 0 : return 0;
247 : : }
248 : :
249 : 0 : static int oci_rlimits(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
250 : :
251 : 0 : Settings *s = userdata;
252 : : JsonVariant *e;
253 : : int r;
254 : :
255 [ # # ]: 0 : assert(s);
256 : :
257 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
258 : :
259 : : struct rlimit_data {
260 : : int type;
261 : : rlim_t soft;
262 : : rlim_t hard;
263 : 0 : } data = {
264 : : .type = -1,
265 : : .soft = RLIM_INFINITY,
266 : : .hard = RLIM_INFINITY,
267 : : };
268 : :
269 : : static const JsonDispatch table[] = {
270 : : { "soft", JSON_VARIANT_NUMBER, oci_rlimit_value, offsetof(struct rlimit_data, soft), JSON_MANDATORY },
271 : : { "hard", JSON_VARIANT_NUMBER, oci_rlimit_value, offsetof(struct rlimit_data, hard), JSON_MANDATORY },
272 : : { "type", JSON_VARIANT_STRING, oci_rlimit_type, offsetof(struct rlimit_data, type), JSON_MANDATORY },
273 : : {}
274 : : };
275 : :
276 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
277 [ # # ]: 0 : if (r < 0)
278 : 0 : return r;
279 : :
280 [ # # ]: 0 : assert(data.type >= 0);
281 [ # # ]: 0 : assert(data.type < _RLIMIT_MAX);
282 : :
283 [ # # ]: 0 : if (s->rlimit[data.type])
284 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
285 : : "rlimits array contains duplicate entry, refusing.");
286 : :
287 : 0 : s->rlimit[data.type] = new(struct rlimit, 1);
288 [ # # ]: 0 : if (!s->rlimit[data.type])
289 : 0 : return log_oom();
290 : :
291 : 0 : *s->rlimit[data.type] = (struct rlimit) {
292 : 0 : .rlim_cur = data.soft,
293 : 0 : .rlim_max = data.hard,
294 : : };
295 : :
296 : : }
297 : 0 : return 0;
298 : : }
299 : :
300 : 0 : static int oci_capability_array(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
301 : 0 : uint64_t *mask = userdata, m = 0;
302 : : JsonVariant *e;
303 : :
304 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
305 : : const char *n;
306 : : int cap;
307 : :
308 [ # # ]: 0 : if (!json_variant_is_string(e))
309 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
310 : : "Entry in capabilities array is not a string.");
311 : :
312 [ # # ]: 0 : assert_se(n = json_variant_string(e));
313 : :
314 : 0 : cap = capability_from_name(n);
315 [ # # ]: 0 : if (cap < 0)
316 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
317 : : "Unknown capability: %s", n);
318 : :
319 : 0 : m |= UINT64_C(1) << cap;
320 : : }
321 : :
322 [ # # ]: 0 : if (*mask == (uint64_t) -1)
323 : 0 : *mask = m;
324 : : else
325 : 0 : *mask |= m;
326 : :
327 : 0 : return 0;
328 : : }
329 : :
330 : 0 : static int oci_capabilities(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
331 : :
332 : : static const JsonDispatch table[] = {
333 : : { "effective", JSON_VARIANT_ARRAY, oci_capability_array, offsetof(CapabilityQuintet, effective) },
334 : : { "bounding", JSON_VARIANT_ARRAY, oci_capability_array, offsetof(CapabilityQuintet, bounding) },
335 : : { "inheritable", JSON_VARIANT_ARRAY, oci_capability_array, offsetof(CapabilityQuintet, inheritable) },
336 : : { "permitted", JSON_VARIANT_ARRAY, oci_capability_array, offsetof(CapabilityQuintet, permitted) },
337 : : { "ambient", JSON_VARIANT_ARRAY, oci_capability_array, offsetof(CapabilityQuintet, ambient) },
338 : : {}
339 : : };
340 : :
341 : 0 : Settings *s = userdata;
342 : : int r;
343 : :
344 [ # # ]: 0 : assert(s);
345 : :
346 : 0 : r = json_dispatch(v, table, oci_unexpected, flags, &s->full_capabilities);
347 [ # # ]: 0 : if (r < 0)
348 : 0 : return r;
349 : :
350 [ # # ]: 0 : if (s->full_capabilities.bounding != (uint64_t) -1) {
351 : 0 : s->capability = s->full_capabilities.bounding;
352 : 0 : s->drop_capability = ~s->full_capabilities.bounding;
353 : : }
354 : :
355 : 0 : return 0;
356 : : }
357 : :
358 : 0 : static int oci_oom_score_adj(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
359 : 0 : Settings *s = userdata;
360 : : intmax_t k;
361 : :
362 [ # # ]: 0 : assert(s);
363 : :
364 : 0 : k = json_variant_integer(v);
365 [ # # # # ]: 0 : if (k < OOM_SCORE_ADJ_MIN || k > OOM_SCORE_ADJ_MAX)
366 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
367 : : "oomScoreAdj value out of range: %ji", k);
368 : :
369 : 0 : s->oom_score_adjust = (int) k;
370 : 0 : s->oom_score_adjust_set = true;
371 : :
372 : 0 : return 0;
373 : : }
374 : :
375 : 0 : static int oci_uid_gid(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
376 : 0 : uid_t *uid = userdata, u;
377 : : uintmax_t k;
378 : :
379 [ # # ]: 0 : assert(uid);
380 : : assert_cc(sizeof(uid_t) == sizeof(gid_t));
381 : :
382 : 0 : k = json_variant_unsigned(v);
383 : 0 : u = (uid_t) k;
384 [ # # ]: 0 : if ((uintmax_t) u != k)
385 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
386 : : "UID/GID out of range: %ji", k);
387 : :
388 [ # # ]: 0 : if (!uid_is_valid(u))
389 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
390 : : "UID/GID is not valid: " UID_FMT, u);
391 : :
392 : 0 : *uid = u;
393 : 0 : return 0;
394 : : }
395 : :
396 : 0 : static int oci_supplementary_gids(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
397 : 0 : Settings *s = userdata;
398 : : JsonVariant *e;
399 : : int r;
400 : :
401 [ # # ]: 0 : assert(s);
402 : :
403 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
404 : : gid_t gid, *a;
405 : :
406 [ # # ]: 0 : if (!json_variant_is_unsigned(e))
407 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
408 : : "Supplementary GID entry is not a UID.");
409 : :
410 : 0 : r = oci_uid_gid(name, e, flags, &gid);
411 [ # # ]: 0 : if (r < 0)
412 : 0 : return r;
413 : :
414 : 0 : a = reallocarray(s->supplementary_gids, s->n_supplementary_gids + 1, sizeof(gid_t));
415 [ # # ]: 0 : if (!a)
416 : 0 : return log_oom();
417 : :
418 : 0 : s->supplementary_gids = a;
419 : 0 : s->supplementary_gids[s->n_supplementary_gids++] = gid;
420 : : }
421 : :
422 : 0 : return 0;
423 : : }
424 : :
425 : 0 : static int oci_user(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
426 : : static const JsonDispatch table[] = {
427 : : { "uid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(Settings, uid), JSON_MANDATORY },
428 : : { "gid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(Settings, gid), JSON_MANDATORY },
429 : : { "additionalGids", JSON_VARIANT_ARRAY, oci_supplementary_gids, 0, 0 },
430 : : {}
431 : : };
432 : :
433 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
434 : : }
435 : :
436 : 0 : static int oci_process(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
437 : :
438 : : static const JsonDispatch table[] = {
439 : : { "terminal", JSON_VARIANT_BOOLEAN, oci_terminal, 0, 0 },
440 : : { "consoleSize", JSON_VARIANT_OBJECT, oci_console_size, 0, 0 },
441 : : { "cwd", JSON_VARIANT_STRING, oci_absolute_path, offsetof(Settings, working_directory), 0 },
442 : : { "env", JSON_VARIANT_ARRAY, oci_env, offsetof(Settings, environment), 0 },
443 : : { "args", JSON_VARIANT_ARRAY, oci_args, offsetof(Settings, parameters), 0 },
444 : : { "rlimits", JSON_VARIANT_ARRAY, oci_rlimits, 0, 0 },
445 : : { "apparmorProfile", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE },
446 : : { "capabilities", JSON_VARIANT_OBJECT, oci_capabilities, 0, 0 },
447 : : { "noNewPrivileges", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(Settings, no_new_privileges), 0 },
448 : : { "oomScoreAdj", JSON_VARIANT_INTEGER, oci_oom_score_adj, 0, 0 },
449 : : { "selinuxLabel", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE },
450 : : { "user", JSON_VARIANT_OBJECT, oci_user, 0, 0 },
451 : : {}
452 : : };
453 : :
454 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
455 : : }
456 : :
457 : 0 : static int oci_root(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
458 : :
459 : : static const JsonDispatch table[] = {
460 : : { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Settings, root) },
461 : : { "readonly", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(Settings, read_only) },
462 : : {}
463 : : };
464 : :
465 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
466 : : }
467 : :
468 : 0 : static int oci_hostname(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
469 : 0 : Settings *s = userdata;
470 : : const char *n;
471 : :
472 [ # # ]: 0 : assert(s);
473 : :
474 [ # # ]: 0 : assert_se(n = json_variant_string(v));
475 : :
476 [ # # ]: 0 : if (!hostname_is_valid(n, false))
477 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
478 : : "Hostname string is not a valid hostname: %s", n);
479 : :
480 : 0 : return free_and_strdup_warn(&s->hostname, n);
481 : : }
482 : :
483 : 0 : static bool oci_exclude_mount(const char *path) {
484 : :
485 : : /* Returns "true" for all mounts we insist to mount on our own, and hence ignore the OCI data. */
486 : :
487 [ # # # # : 0 : if (PATH_IN_SET(path,
# # # # ]
488 : : "/dev",
489 : : "/dev/mqueue",
490 : : "/dev/pts",
491 : : "/dev/shm",
492 : : "/proc",
493 : : "/proc/acpi",
494 : : "/proc/apm",
495 : : "/proc/asound",
496 : : "/proc/bus",
497 : : "/proc/fs",
498 : : "/proc/irq",
499 : : "/proc/kallsyms",
500 : : "/proc/kcore",
501 : : "/proc/keys",
502 : : "/proc/scsi",
503 : : "/proc/sys",
504 : : "/proc/sys/net",
505 : : "/proc/sysrq-trigger",
506 : : "/proc/timer_list",
507 : : "/run",
508 : : "/sys",
509 : : "/sys",
510 : : "/sys/fs/selinux",
511 : : "/tmp"))
512 : 0 : return true;
513 : :
514 : : /* Similar, skip the whole /sys/fs/cgroups subtree */
515 [ # # ]: 0 : if (path_startswith(path, "/sys/fs/cgroup"))
516 : 0 : return true;
517 : :
518 : 0 : return false;
519 : : }
520 : :
521 : : typedef struct oci_mount_data {
522 : : char *destination;
523 : : char *source;
524 : : char *type;
525 : : char **options;
526 : : } oci_mount_data;
527 : :
528 : 0 : static void cleanup_oci_mount_data(oci_mount_data *data) {
529 : 0 : free(data->destination);
530 : 0 : free(data->source);
531 : 0 : strv_free(data->options);
532 : 0 : free(data->type);
533 : 0 : }
534 : :
535 : 0 : static int oci_mounts(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
536 : 0 : Settings *s = userdata;
537 : : JsonVariant *e;
538 : : int r;
539 : :
540 [ # # ]: 0 : assert(s);
541 : :
542 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
543 : : static const JsonDispatch table[] = {
544 : : { "destination", JSON_VARIANT_STRING, oci_absolute_path, offsetof(oci_mount_data, destination), JSON_MANDATORY },
545 : : { "source", JSON_VARIANT_STRING, json_dispatch_string, offsetof(oci_mount_data, source), 0 },
546 : : { "options", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(oci_mount_data, options), 0, },
547 : : { "type", JSON_VARIANT_STRING, json_dispatch_string, offsetof(oci_mount_data, type), 0 },
548 : : {}
549 : : };
550 : :
551 [ # # # ]: 0 : _cleanup_free_ char *joined_options = NULL;
552 : : CustomMount *m;
553 [ # # # ]: 0 : _cleanup_(cleanup_oci_mount_data) oci_mount_data data = {};
554 : :
555 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
556 [ # # ]: 0 : if (r < 0)
557 : 0 : return r;
558 : :
559 [ # # ]: 0 : if (!path_is_absolute(data.destination))
560 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
561 : : "Mount destination not an absolute path: %s", data.destination);
562 : :
563 [ # # ]: 0 : if (oci_exclude_mount(data.destination))
564 : 0 : continue;
565 : :
566 [ # # ]: 0 : if (data.options) {
567 : 0 : joined_options = strv_join(data.options, ",");
568 [ # # ]: 0 : if (!joined_options)
569 : 0 : return log_oom();
570 : : }
571 : :
572 [ # # # # ]: 0 : if (!data.type || streq(data.type, "bind")) {
573 [ # # # # ]: 0 : if (data.source && !path_is_absolute(data.source)) {
574 : : char *joined;
575 : :
576 : 0 : joined = path_join(s->bundle, data.source);
577 [ # # ]: 0 : if (!joined)
578 : 0 : return log_oom();
579 : :
580 : 0 : free_and_replace(data.source, joined);
581 : : }
582 : :
583 : 0 : data.type = mfree(data.type);
584 : :
585 : 0 : m = custom_mount_add(&s->custom_mounts, &s->n_custom_mounts, CUSTOM_MOUNT_BIND);
586 : : } else
587 : 0 : m = custom_mount_add(&s->custom_mounts, &s->n_custom_mounts, CUSTOM_MOUNT_ARBITRARY);
588 [ # # ]: 0 : if (!m)
589 : 0 : return log_oom();
590 : :
591 : 0 : m->destination = TAKE_PTR(data.destination);
592 : 0 : m->source = TAKE_PTR(data.source);
593 : 0 : m->options = TAKE_PTR(joined_options);
594 : 0 : m->type_argument = TAKE_PTR(data.type);
595 : : }
596 : :
597 : 0 : return 0;
598 : : }
599 : :
600 : 0 : static int oci_namespace_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
601 : 0 : unsigned long *nsflags = userdata;
602 : : const char *n;
603 : :
604 [ # # ]: 0 : assert(nsflags);
605 [ # # ]: 0 : assert_se(n = json_variant_string(v));
606 : :
607 : : /* We don't use namespace_flags_from_string() here, as the OCI spec uses slightly different names than the
608 : : * kernel here. */
609 [ # # ]: 0 : if (streq(n, "pid"))
610 : 0 : *nsflags = CLONE_NEWPID;
611 [ # # ]: 0 : else if (streq(n, "network"))
612 : 0 : *nsflags = CLONE_NEWNET;
613 [ # # ]: 0 : else if (streq(n, "mount"))
614 : 0 : *nsflags = CLONE_NEWNS;
615 [ # # ]: 0 : else if (streq(n, "ipc"))
616 : 0 : *nsflags = CLONE_NEWIPC;
617 [ # # ]: 0 : else if (streq(n, "uts"))
618 : 0 : *nsflags = CLONE_NEWUTS;
619 [ # # ]: 0 : else if (streq(n, "user"))
620 : 0 : *nsflags = CLONE_NEWUSER;
621 [ # # ]: 0 : else if (streq(n, "cgroup"))
622 : 0 : *nsflags = CLONE_NEWCGROUP;
623 : : else
624 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
625 : : "Unknown cgroup type, refusing: %s", n);
626 : :
627 : 0 : return 0;
628 : : }
629 : :
630 : 0 : static int oci_namespaces(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
631 : 0 : Settings *s = userdata;
632 : 0 : unsigned long n = 0;
633 : : JsonVariant *e;
634 : : int r;
635 : :
636 [ # # ]: 0 : assert_se(s);
637 : :
638 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
639 : :
640 : : struct namespace_data {
641 : : unsigned long type;
642 : : char *path;
643 : 0 : } data = {};
644 : :
645 : : static const JsonDispatch table[] = {
646 : : { "type", JSON_VARIANT_STRING, oci_namespace_type, offsetof(struct namespace_data, type), JSON_MANDATORY },
647 : : { "path", JSON_VARIANT_STRING, oci_absolute_path, offsetof(struct namespace_data, path), 0 },
648 : : {}
649 : : };
650 : :
651 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
652 [ # # ]: 0 : if (r < 0) {
653 : 0 : free(data.path);
654 : 0 : return r;
655 : : }
656 : :
657 [ # # ]: 0 : if (data.path) {
658 [ # # ]: 0 : if (data.type != CLONE_NEWNET) {
659 : 0 : free(data.path);
660 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
661 : : "Specifying namespace path for non-network namespace is not supported.");
662 : : }
663 : :
664 [ # # ]: 0 : if (s->network_namespace_path) {
665 : 0 : free(data.path);
666 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
667 : : "Network namespace path specified more than once, refusing.");
668 : : }
669 : :
670 : 0 : free(s->network_namespace_path);
671 : 0 : s->network_namespace_path = data.path;
672 : : }
673 : :
674 [ # # ]: 0 : if (FLAGS_SET(n, data.type)) {
675 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
676 : : "Duplicate namespace specification, refusing.");
677 : : }
678 : :
679 : 0 : n |= data.type;
680 : : }
681 : :
682 [ # # ]: 0 : if (!FLAGS_SET(n, CLONE_NEWNS))
683 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
684 : : "Containers without file system namespace aren't supported.");
685 : :
686 : 0 : s->private_network = FLAGS_SET(n, CLONE_NEWNET);
687 : 0 : s->userns_mode = FLAGS_SET(n, CLONE_NEWUSER) ? USER_NAMESPACE_FIXED : USER_NAMESPACE_NO;
688 : 0 : s->use_cgns = FLAGS_SET(n, CLONE_NEWCGROUP);
689 : :
690 : 0 : s->clone_ns_flags = n & (CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS);
691 : :
692 : 0 : return 0;
693 : : }
694 : :
695 : 0 : static int oci_uid_gid_range(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
696 : 0 : uid_t *uid = userdata, u;
697 : : uintmax_t k;
698 : :
699 [ # # ]: 0 : assert(uid);
700 : : assert_cc(sizeof(uid_t) == sizeof(gid_t));
701 : :
702 : : /* This is very much like oci_uid_gid(), except the checks are a bit different, as this is a UID range rather
703 : : * than a specific UID, and hence (uid_t) -1 has no special significance. OTOH a range of zero makes no
704 : : * sense. */
705 : :
706 : 0 : k = json_variant_unsigned(v);
707 : 0 : u = (uid_t) k;
708 [ # # ]: 0 : if ((uintmax_t) u != k)
709 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
710 : : "UID/GID out of range: %ji", k);
711 [ # # ]: 0 : if (u == 0)
712 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
713 : : "UID/GID range can't be zero.");
714 : :
715 : 0 : *uid = u;
716 : 0 : return 0;
717 : : }
718 : :
719 : 0 : static int oci_uid_gid_mappings(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
720 : : struct mapping_data {
721 : : uid_t host_id;
722 : : uid_t container_id;
723 : : uid_t range;
724 : 0 : } data = {
725 : : .host_id = UID_INVALID,
726 : : .container_id = UID_INVALID,
727 : : .range = 0,
728 : : };
729 : :
730 : : static const JsonDispatch table[] = {
731 : : { "containerID", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(struct mapping_data, container_id), JSON_MANDATORY },
732 : : { "hostID", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(struct mapping_data, host_id), JSON_MANDATORY },
733 : : { "size", JSON_VARIANT_UNSIGNED, oci_uid_gid_range, offsetof(struct mapping_data, range), JSON_MANDATORY },
734 : : {}
735 : : };
736 : :
737 : 0 : Settings *s = userdata;
738 : : JsonVariant *e;
739 : : int r;
740 : :
741 [ # # ]: 0 : assert(s);
742 : :
743 [ # # ]: 0 : if (json_variant_elements(v) == 0)
744 : 0 : return 0;
745 : :
746 [ # # ]: 0 : if (json_variant_elements(v) > 1)
747 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
748 : : "UID/GID mappings with more than one entry are not supported.");
749 : :
750 [ # # ]: 0 : assert_se(e = json_variant_by_index(v, 0));
751 : :
752 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
753 [ # # ]: 0 : if (r < 0)
754 : 0 : return r;
755 : :
756 [ # # ]: 0 : if (data.host_id + data.range < data.host_id ||
757 [ # # ]: 0 : data.container_id + data.range < data.container_id)
758 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
759 : : "UID/GID range goes beyond UID/GID validity range, refusing.");
760 : :
761 [ # # ]: 0 : if (data.container_id != 0)
762 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
763 : : "UID/GID mappings with a non-zero container base are not supported.");
764 : :
765 [ # # ]: 0 : if (data.range < 0x10000)
766 [ # # ]: 0 : json_log(v, flags|JSON_WARNING, 0,
767 : : "UID/GID mapping with less than 65536 UID/GIDS set up, you are looking for trouble.");
768 : :
769 [ # # ]: 0 : if (s->uid_range != UID_INVALID &&
770 [ # # # # ]: 0 : (s->uid_shift != data.host_id || s->uid_range != data.range))
771 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
772 : : "Non-matching UID and GID mappings are not supported.");
773 : :
774 : 0 : s->uid_shift = data.host_id;
775 : 0 : s->uid_range = data.range;
776 : :
777 : 0 : return 0;
778 : : }
779 : :
780 : 0 : static int oci_device_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
781 : 0 : mode_t *mode = userdata;
782 : : const char *t;
783 : :
784 [ # # ]: 0 : assert(mode);
785 [ # # ]: 0 : assert_se(t = json_variant_string(v));
786 : :
787 [ # # ]: 0 : if (STR_IN_SET(t, "c", "u"))
788 : 0 : *mode = (*mode & ~S_IFMT) | S_IFCHR;
789 [ # # ]: 0 : else if (streq(t, "b"))
790 : 0 : *mode = (*mode & ~S_IFMT) | S_IFBLK;
791 [ # # ]: 0 : else if (streq(t, "p"))
792 : 0 : *mode = (*mode & ~S_IFMT) | S_IFIFO;
793 : : else
794 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
795 : : "Unknown device type: %s", t);
796 : :
797 : 0 : return 0;
798 : : }
799 : :
800 : 0 : static int oci_device_major(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
801 : 0 : unsigned *u = userdata;
802 : : uintmax_t k;
803 : :
804 [ # # ]: 0 : assert_se(u);
805 : :
806 : 0 : k = json_variant_unsigned(v);
807 [ # # # # : 0 : if (!DEVICE_MAJOR_VALID(k))
# # ]
808 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
809 : : "Device major %ji out of range.", k);
810 : :
811 : 0 : *u = (unsigned) k;
812 : 0 : return 0;
813 : : }
814 : :
815 : 0 : static int oci_device_minor(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
816 : 0 : unsigned *u = userdata;
817 : : uintmax_t k;
818 : :
819 [ # # ]: 0 : assert_se(u);
820 : :
821 : 0 : k = json_variant_unsigned(v);
822 [ # # # # : 0 : if (!DEVICE_MINOR_VALID(k))
# # ]
823 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
824 : : "Device minor %ji out of range.", k);
825 : :
826 : 0 : *u = (unsigned) k;
827 : 0 : return 0;
828 : : }
829 : :
830 : 0 : static int oci_device_file_mode(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
831 : 0 : mode_t *mode = userdata, m;
832 : : uintmax_t k;
833 : :
834 [ # # ]: 0 : assert(mode);
835 : :
836 : 0 : k = json_variant_unsigned(v);
837 : 0 : m = (mode_t) k;
838 : :
839 [ # # # # ]: 0 : if ((m & ~07777) != 0 || (uintmax_t) m != k)
840 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
841 : : "fileMode out of range, refusing.");
842 : :
843 : 0 : *mode = m;
844 : 0 : return 0;
845 : : }
846 : :
847 : 0 : static int oci_devices(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
848 : 0 : Settings *s = userdata;
849 : : JsonVariant *e;
850 : : int r;
851 : :
852 [ # # ]: 0 : assert(s);
853 : :
854 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
855 : :
856 : : static const JsonDispatch table[] = {
857 : : { "type", JSON_VARIANT_STRING, oci_device_type, offsetof(DeviceNode, mode), JSON_MANDATORY },
858 : : { "path", JSON_VARIANT_STRING, oci_absolute_path, offsetof(DeviceNode, path), JSON_MANDATORY },
859 : : { "major", JSON_VARIANT_UNSIGNED, oci_device_major, offsetof(DeviceNode, major), 0 },
860 : : { "minor", JSON_VARIANT_UNSIGNED, oci_device_minor, offsetof(DeviceNode, minor), 0 },
861 : : { "fileMode", JSON_VARIANT_UNSIGNED, oci_device_file_mode, offsetof(DeviceNode, mode), 0 },
862 : : { "uid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(DeviceNode, uid), 0 },
863 : : { "gid", JSON_VARIANT_UNSIGNED, oci_uid_gid, offsetof(DeviceNode, gid), 0 },
864 : : {}
865 : : };
866 : :
867 : : DeviceNode *node, *nodes;
868 : :
869 : 0 : nodes = reallocarray(s->extra_nodes, s->n_extra_nodes + 1, sizeof(DeviceNode));
870 [ # # ]: 0 : if (!nodes)
871 : 0 : return log_oom();
872 : :
873 : 0 : s->extra_nodes = nodes;
874 : :
875 : 0 : node = nodes + s->n_extra_nodes;
876 : 0 : *node = (DeviceNode) {
877 : : .uid = UID_INVALID,
878 : : .gid = GID_INVALID,
879 : : .major = (unsigned) -1,
880 : : .minor = (unsigned) -1,
881 : : .mode = 0644,
882 : : };
883 : :
884 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, node);
885 [ # # ]: 0 : if (r < 0)
886 : 0 : goto fail_element;
887 : :
888 [ # # # # ]: 0 : if (S_ISCHR(node->mode) || S_ISBLK(node->mode)) {
889 [ # # # ]: 0 : _cleanup_free_ char *path = NULL;
890 : :
891 [ # # # # ]: 0 : if (node->major == (unsigned) -1 || node->minor == (unsigned) -1) {
892 [ # # ]: 0 : r = json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
893 : : "Major/minor required when device node is device node");
894 : 0 : goto fail_element;
895 : : }
896 : :
897 : : /* Suppress a couple of implicit device nodes */
898 : 0 : r = device_path_make_canonical(node->mode, makedev(node->major, node->minor), &path);
899 [ # # ]: 0 : if (r < 0)
900 [ # # ]: 0 : json_log(e, flags|JSON_DEBUG, 0, "Failed to resolve device node %u:%u, ignoring: %m", node->major, node->minor);
901 : : else {
902 [ # # # # : 0 : if (PATH_IN_SET(path,
# # # # ]
903 : : "/dev/null",
904 : : "/dev/zero",
905 : : "/dev/full",
906 : : "/dev/random",
907 : : "/dev/urandom",
908 : : "/dev/tty",
909 : : "/dev/net/tun",
910 : : "/dev/ptmx",
911 : : "/dev/pts/ptmx",
912 : : "/dev/console")) {
913 : :
914 [ # # ]: 0 : json_log(e, flags|JSON_DEBUG, 0, "Ignoring devices item for device '%s', as it is implicitly created anyway.", path);
915 : 0 : free(node->path);
916 : 0 : continue;
917 : : }
918 : : }
919 : : }
920 : :
921 : 0 : s->n_extra_nodes++;
922 : 0 : continue;
923 : :
924 : 0 : fail_element:
925 : 0 : free(node->path);
926 : 0 : return r;
927 : : }
928 : :
929 : 0 : return 0;
930 : : }
931 : :
932 : 0 : static int oci_cgroups_path(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
933 : 0 : _cleanup_free_ char *slice = NULL, *backwards = NULL;
934 : 0 : Settings *s = userdata;
935 : : const char *p;
936 : : int r;
937 : :
938 [ # # ]: 0 : assert(s);
939 : :
940 [ # # ]: 0 : assert_se(p = json_variant_string(v));
941 : :
942 : 0 : r = cg_path_get_slice(p, &slice);
943 [ # # ]: 0 : if (r < 0)
944 [ # # ]: 0 : return json_log(v, flags, r, "Couldn't derive slice unit name from path '%s': %m", p);
945 : :
946 : 0 : r = cg_slice_to_path(slice, &backwards);
947 [ # # ]: 0 : if (r < 0)
948 [ # # ]: 0 : return json_log(v, flags, r, "Couldn't convert slice unit name '%s' back to path: %m", slice);
949 : :
950 [ # # ]: 0 : if (!path_equal(backwards, p))
951 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
952 : : "Control group path '%s' does not refer to slice unit, refusing.", p);
953 : :
954 : 0 : free_and_replace(s->slice, slice);
955 : 0 : return 0;
956 : : }
957 : :
958 : 0 : static int oci_cgroup_device_type(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
959 : 0 : mode_t *mode = userdata;
960 : : const char *n;
961 : :
962 [ # # ]: 0 : assert_se(n = json_variant_string(v));
963 : :
964 [ # # ]: 0 : if (streq(n, "c"))
965 : 0 : *mode = S_IFCHR;
966 [ # # ]: 0 : else if (streq(n, "b"))
967 : 0 : *mode = S_IFBLK;
968 : : else
969 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
970 : : "Control group device type unknown: %s", n);
971 : :
972 : 0 : return 0;
973 : : }
974 : :
975 : : struct device_data {
976 : : bool allow;
977 : : bool r;
978 : : bool w;
979 : : bool m;
980 : : mode_t type;
981 : : unsigned major;
982 : : unsigned minor;
983 : : };
984 : :
985 : 0 : static int oci_cgroup_device_access(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
986 : 0 : struct device_data *d = userdata;
987 : 0 : bool r = false, w = false, m = false;
988 : : const char *s;
989 : : size_t i;
990 : :
991 [ # # ]: 0 : assert_se(s = json_variant_string(v));
992 : :
993 [ # # ]: 0 : for (i = 0; s[i]; i++)
994 [ # # ]: 0 : if (s[i] == 'r')
995 : 0 : r = true;
996 [ # # ]: 0 : else if (s[i] == 'w')
997 : 0 : w = true;
998 [ # # ]: 0 : else if (s[i] == 'm')
999 : 0 : m = true;
1000 : : else
1001 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1002 : : "Unknown device access character '%c'.", s[i]);
1003 : :
1004 : 0 : d->r = r;
1005 : 0 : d->w = w;
1006 : 0 : d->m = m;
1007 : :
1008 : 0 : return 0;
1009 : : }
1010 : :
1011 : 0 : static int oci_cgroup_devices(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1012 : :
1013 : 0 : _cleanup_free_ struct device_data *list = NULL;
1014 : 0 : Settings *s = userdata;
1015 : 0 : size_t n_list = 0, i;
1016 : 0 : bool noop = false;
1017 : : JsonVariant *e;
1018 : : int r;
1019 : :
1020 [ # # ]: 0 : assert(s);
1021 : :
1022 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1023 : :
1024 : 0 : struct device_data data = {
1025 : : .major = (unsigned) -1,
1026 : : .minor = (unsigned) -1,
1027 : : }, *a;
1028 : :
1029 : : static const JsonDispatch table[] = {
1030 : : { "allow", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct device_data, allow), JSON_MANDATORY },
1031 : : { "type", JSON_VARIANT_STRING, oci_cgroup_device_type, offsetof(struct device_data, type), 0 },
1032 : : { "major", JSON_VARIANT_UNSIGNED, oci_device_major, offsetof(struct device_data, major), 0 },
1033 : : { "minor", JSON_VARIANT_UNSIGNED, oci_device_minor, offsetof(struct device_data, minor), 0 },
1034 : : { "access", JSON_VARIANT_STRING, oci_cgroup_device_access, 0, 0 },
1035 : : {}
1036 : : };
1037 : :
1038 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
1039 [ # # ]: 0 : if (r < 0)
1040 : 0 : return r;
1041 : :
1042 [ # # ]: 0 : if (!data.allow) {
1043 : : /* The fact that OCI allows 'deny' entries makes really no sense, as 'allow' vs. 'deny' for the
1044 : : * devices cgroup controller is really not about whitelisting and blacklisting but about adding
1045 : : * and removing entries from the whitelist. Since we always start out with an empty whitelist
1046 : : * we hence ignore the whole thing, as removing entries which don't exist make no sense. We'll
1047 : : * log about this, since this is really borked in the spec, with one exception: the entry
1048 : : * that's supposed to drop the kernel's default we ignore silently */
1049 : :
1050 [ # # # # : 0 : if (!data.r || !data.w || !data.m || data.type != 0 || data.major != (unsigned) -1 || data.minor != (unsigned) -1)
# # # # #
# # # ]
1051 [ # # ]: 0 : json_log(v, flags|JSON_WARNING, 0, "Devices cgroup whitelist with arbitrary 'allow' entries not supported, ignoring.");
1052 : :
1053 : : /* We ignore the 'deny' entry as for us that's implied */
1054 : 0 : continue;
1055 : : }
1056 : :
1057 [ # # # # : 0 : if (!data.r && !data.w && !data.m) {
# # ]
1058 [ # # ]: 0 : json_log(v, flags|LOG_WARNING, 0, "Device cgroup whitelist entry with no effect found, ignoring.");
1059 : 0 : continue;
1060 : : }
1061 : :
1062 [ # # # # ]: 0 : if (data.minor != (unsigned) -1 && data.major == (unsigned) -1)
1063 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
1064 : : "Device cgroup whitelist entries with minors but no majors not supported.");
1065 : :
1066 [ # # # # ]: 0 : if (data.major != (unsigned) -1 && data.type == 0)
1067 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
1068 : : "Device cgroup whitelist entries with majors but no device node type not supported.");
1069 : :
1070 [ # # ]: 0 : if (data.type == 0) {
1071 [ # # # # : 0 : if (data.r && data.w && data.m) /* a catchall whitelist entry means we are looking at a noop */
# # ]
1072 : 0 : noop = true;
1073 : : else
1074 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP),
1075 : : "Device cgroup whitelist entries with no type not supported.");
1076 : : }
1077 : :
1078 : 0 : a = reallocarray(list, n_list + 1, sizeof(struct device_data));
1079 [ # # ]: 0 : if (!a)
1080 : 0 : return log_oom();
1081 : :
1082 : 0 : list = a;
1083 : 0 : list[n_list++] = data;
1084 : : }
1085 : :
1086 [ # # ]: 0 : if (noop)
1087 : 0 : return 0;
1088 : :
1089 : 0 : r = settings_allocate_properties(s);
1090 [ # # ]: 0 : if (r < 0)
1091 : 0 : return r;
1092 : :
1093 : 0 : r = sd_bus_message_open_container(s->properties, 'r', "sv");
1094 [ # # ]: 0 : if (r < 0)
1095 [ # # ]: 0 : return bus_log_create_error(r);
1096 : :
1097 : 0 : r = sd_bus_message_append(s->properties, "s", "DeviceAllow");
1098 [ # # ]: 0 : if (r < 0)
1099 [ # # ]: 0 : return bus_log_create_error(r);
1100 : :
1101 : 0 : r = sd_bus_message_open_container(s->properties, 'v', "a(ss)");
1102 [ # # ]: 0 : if (r < 0)
1103 [ # # ]: 0 : return bus_log_create_error(r);
1104 : :
1105 : 0 : r = sd_bus_message_open_container(s->properties, 'a', "(ss)");
1106 [ # # ]: 0 : if (r < 0)
1107 [ # # ]: 0 : return bus_log_create_error(r);
1108 : :
1109 [ # # ]: 0 : for (i = 0; i < n_list; i++) {
1110 [ # # ]: 0 : _cleanup_free_ char *pattern = NULL;
1111 : : char access[4];
1112 : 0 : size_t n = 0;
1113 : :
1114 [ # # ]: 0 : if (list[i].minor == (unsigned) -1) {
1115 : : const char *t;
1116 : :
1117 [ # # ]: 0 : if (list[i].type == S_IFBLK)
1118 : 0 : t = "block";
1119 : : else {
1120 [ # # ]: 0 : assert(list[i].type == S_IFCHR);
1121 : 0 : t = "char";
1122 : : }
1123 : :
1124 [ # # ]: 0 : if (list[i].major == (unsigned) -1) {
1125 : 0 : pattern = strjoin(t, "-*");
1126 [ # # ]: 0 : if (!pattern)
1127 : 0 : return log_oom();
1128 : : } else {
1129 [ # # ]: 0 : if (asprintf(&pattern, "%s-%u", t, list[i].major) < 0)
1130 : 0 : return log_oom();
1131 : : }
1132 : :
1133 : : } else {
1134 [ # # ]: 0 : assert(list[i].major != (unsigned) -1); /* If a minor is specified, then a major also needs to be specified */
1135 : :
1136 : 0 : r = device_path_make_major_minor(list[i].type, makedev(list[i].major, list[i].minor), &pattern);
1137 [ # # ]: 0 : if (r < 0)
1138 : 0 : return log_oom();
1139 : : }
1140 : :
1141 [ # # ]: 0 : if (list[i].r)
1142 : 0 : access[n++] = 'r';
1143 [ # # ]: 0 : if (list[i].w)
1144 : 0 : access[n++] = 'w';
1145 [ # # ]: 0 : if (list[i].m)
1146 : 0 : access[n++] = 'm';
1147 : 0 : access[n] = 0;
1148 : :
1149 [ # # ]: 0 : assert(n > 0);
1150 : :
1151 : 0 : r = sd_bus_message_append(s->properties, "(ss)", pattern, access);
1152 [ # # ]: 0 : if (r < 0)
1153 [ # # ]: 0 : return bus_log_create_error(r);
1154 : : }
1155 : :
1156 : 0 : r = sd_bus_message_close_container(s->properties);
1157 [ # # ]: 0 : if (r < 0)
1158 [ # # ]: 0 : return bus_log_create_error(r);
1159 : :
1160 : 0 : r = sd_bus_message_close_container(s->properties);
1161 [ # # ]: 0 : if (r < 0)
1162 [ # # ]: 0 : return bus_log_create_error(r);
1163 : :
1164 : 0 : r = sd_bus_message_close_container(s->properties);
1165 [ # # ]: 0 : if (r < 0)
1166 [ # # ]: 0 : return bus_log_create_error(r);
1167 : :
1168 : 0 : return 0;
1169 : : }
1170 : :
1171 : 0 : static int oci_cgroup_memory_limit(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1172 : 0 : uint64_t *m = userdata;
1173 : : uintmax_t k;
1174 : :
1175 [ # # ]: 0 : assert(m);
1176 : :
1177 [ # # ]: 0 : if (json_variant_is_negative(v)) {
1178 : 0 : *m = UINT64_MAX;
1179 : 0 : return 0;
1180 : : }
1181 : :
1182 [ # # ]: 0 : if (!json_variant_is_unsigned(v))
1183 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1184 : : "Memory limit is not an unsigned integer");
1185 : :
1186 : 0 : k = json_variant_unsigned(v);
1187 [ # # ]: 0 : if (k >= UINT64_MAX)
1188 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1189 : : "Memory limit too large: %ji", k);
1190 : :
1191 : 0 : *m = (uint64_t) k;
1192 : 0 : return 0;
1193 : : }
1194 : :
1195 : 0 : static int oci_cgroup_memory(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1196 : :
1197 : : struct memory_data {
1198 : : uint64_t limit;
1199 : : uint64_t reservation;
1200 : : uint64_t swap;
1201 : 0 : } data = {
1202 : : .limit = UINT64_MAX,
1203 : : .reservation = UINT64_MAX,
1204 : : .swap = UINT64_MAX,
1205 : : };
1206 : :
1207 : : static const JsonDispatch table[] = {
1208 : : { "limit", JSON_VARIANT_NUMBER, oci_cgroup_memory_limit, offsetof(struct memory_data, limit), 0 },
1209 : : { "reservation", JSON_VARIANT_NUMBER, oci_cgroup_memory_limit, offsetof(struct memory_data, reservation), 0 },
1210 : : { "swap", JSON_VARIANT_NUMBER, oci_cgroup_memory_limit, offsetof(struct memory_data, swap), 0 },
1211 : : { "kernel", JSON_VARIANT_NUMBER, oci_unsupported, 0, JSON_PERMISSIVE },
1212 : : { "kernelTCP", JSON_VARIANT_NUMBER, oci_unsupported, 0, JSON_PERMISSIVE },
1213 : : { "swapiness", JSON_VARIANT_NUMBER, oci_unsupported, 0, JSON_PERMISSIVE },
1214 : : { "disableOOMKiller", JSON_VARIANT_NUMBER, oci_unsupported, 0, JSON_PERMISSIVE },
1215 : : {}
1216 : : };
1217 : :
1218 : 0 : Settings *s = userdata;
1219 : : int r;
1220 : :
1221 : 0 : r = json_dispatch(v, table, oci_unexpected, flags, &data);
1222 [ # # ]: 0 : if (r < 0)
1223 : 0 : return r;
1224 : :
1225 [ # # ]: 0 : if (data.swap != UINT64_MAX) {
1226 [ # # ]: 0 : if (data.limit == UINT64_MAX)
1227 [ # # ]: 0 : json_log(v, flags|LOG_WARNING, 0, "swap limit without memory limit is not supported, ignoring.");
1228 [ # # ]: 0 : else if (data.swap < data.limit)
1229 [ # # ]: 0 : json_log(v, flags|LOG_WARNING, 0, "swap limit is below memory limit, ignoring.");
1230 : : else {
1231 : 0 : r = settings_allocate_properties(s);
1232 [ # # ]: 0 : if (r < 0)
1233 : 0 : return r;
1234 : :
1235 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "MemorySwapMax", "t", data.swap - data.limit);
1236 [ # # ]: 0 : if (r < 0)
1237 [ # # ]: 0 : return bus_log_create_error(r);
1238 : : }
1239 : : }
1240 : :
1241 [ # # ]: 0 : if (data.limit != UINT64_MAX) {
1242 : 0 : r = settings_allocate_properties(s);
1243 [ # # ]: 0 : if (r < 0)
1244 : 0 : return r;
1245 : :
1246 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "MemoryMax", "t", data.limit);
1247 [ # # ]: 0 : if (r < 0)
1248 [ # # ]: 0 : return bus_log_create_error(r);
1249 : : }
1250 : :
1251 [ # # ]: 0 : if (data.reservation != UINT64_MAX) {
1252 : 0 : r = settings_allocate_properties(s);
1253 [ # # ]: 0 : if (r < 0)
1254 : 0 : return r;
1255 : :
1256 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "MemoryLow", "t", data.reservation);
1257 [ # # ]: 0 : if (r < 0)
1258 [ # # ]: 0 : return bus_log_create_error(r);
1259 : : }
1260 : :
1261 : 0 : return 0;
1262 : : }
1263 : :
1264 : : struct cpu_data {
1265 : : uint64_t shares;
1266 : : uint64_t quota;
1267 : : uint64_t period;
1268 : : CPUSet cpu_set;
1269 : : };
1270 : :
1271 : 0 : static int oci_cgroup_cpu_shares(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1272 : 0 : uint64_t *u = userdata;
1273 : : uintmax_t k;
1274 : :
1275 [ # # ]: 0 : assert(u);
1276 : :
1277 : 0 : k = json_variant_unsigned(v);
1278 [ # # # # ]: 0 : if (k < CGROUP_CPU_SHARES_MIN || k > CGROUP_CPU_SHARES_MAX)
1279 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1280 : : "shares value out of range.");
1281 : :
1282 : 0 : *u = (uint64_t) k;
1283 : 0 : return 0;
1284 : : }
1285 : :
1286 : 0 : static int oci_cgroup_cpu_quota(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1287 : 0 : uint64_t *u = userdata;
1288 : : uintmax_t k;
1289 : :
1290 [ # # ]: 0 : assert(u);
1291 : :
1292 : 0 : k = json_variant_unsigned(v);
1293 [ # # # # ]: 0 : if (k <= 0 || k >= UINT64_MAX)
1294 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1295 : : "period/quota value out of range.");
1296 : :
1297 : 0 : *u = (uint64_t) k;
1298 : 0 : return 0;
1299 : : }
1300 : :
1301 : 0 : static int oci_cgroup_cpu_cpus(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1302 : 0 : struct cpu_data *data = userdata;
1303 : : CPUSet set;
1304 : : const char *n;
1305 : : int r;
1306 : :
1307 [ # # ]: 0 : assert(data);
1308 : :
1309 [ # # ]: 0 : assert_se(n = json_variant_string(v));
1310 : :
1311 : 0 : r = parse_cpu_set(n, &set);
1312 [ # # ]: 0 : if (r < 0)
1313 [ # # ]: 0 : return json_log(v, flags, r, "Failed to parse CPU set specification: %s", n);
1314 : :
1315 : 0 : cpu_set_reset(&data->cpu_set);
1316 : 0 : data->cpu_set = set;
1317 : :
1318 : 0 : return 0;
1319 : : }
1320 : :
1321 : 0 : static int oci_cgroup_cpu(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1322 : :
1323 : : static const JsonDispatch table[] = {
1324 : : { "shares", JSON_VARIANT_UNSIGNED, oci_cgroup_cpu_shares, offsetof(struct cpu_data, shares), 0 },
1325 : : { "quota", JSON_VARIANT_UNSIGNED, oci_cgroup_cpu_quota, offsetof(struct cpu_data, quota), 0 },
1326 : : { "period", JSON_VARIANT_UNSIGNED, oci_cgroup_cpu_quota, offsetof(struct cpu_data, period), 0 },
1327 : : { "realtimeRuntime", JSON_VARIANT_UNSIGNED, oci_unsupported, 0, 0 },
1328 : : { "realtimePeriod", JSON_VARIANT_UNSIGNED, oci_unsupported, 0, 0 },
1329 : : { "cpus", JSON_VARIANT_STRING, oci_cgroup_cpu_cpus, 0, 0 },
1330 : : { "mems", JSON_VARIANT_STRING, oci_unsupported, 0, 0 },
1331 : : {}
1332 : : };
1333 : :
1334 : 0 : struct cpu_data data = {
1335 : : .shares = UINT64_MAX,
1336 : : .quota = UINT64_MAX,
1337 : : .period = UINT64_MAX,
1338 : : };
1339 : :
1340 : 0 : Settings *s = userdata;
1341 : : int r;
1342 : :
1343 : 0 : r = json_dispatch(v, table, oci_unexpected, flags, &data);
1344 [ # # ]: 0 : if (r < 0) {
1345 : 0 : cpu_set_reset(&data.cpu_set);
1346 : 0 : return r;
1347 : : }
1348 : :
1349 : 0 : cpu_set_reset(&s->cpu_set);
1350 : 0 : s->cpu_set = data.cpu_set;
1351 : :
1352 [ # # ]: 0 : if (data.shares != UINT64_MAX) {
1353 : 0 : r = settings_allocate_properties(s);
1354 [ # # ]: 0 : if (r < 0)
1355 : 0 : return r;
1356 : :
1357 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "CPUShares", "t", data.shares);
1358 [ # # ]: 0 : if (r < 0)
1359 [ # # ]: 0 : return bus_log_create_error(r);
1360 : : }
1361 : :
1362 [ # # # # ]: 0 : if (data.quota != UINT64_MAX && data.period != UINT64_MAX) {
1363 : 0 : r = settings_allocate_properties(s);
1364 [ # # ]: 0 : if (r < 0)
1365 : 0 : return r;
1366 : :
1367 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "CPUQuotaPerSecUSec", "t", (uint64_t) (data.quota * USEC_PER_SEC / data.period));
1368 [ # # ]: 0 : if (r < 0)
1369 [ # # ]: 0 : return bus_log_create_error(r);
1370 : :
1371 [ # # ]: 0 : } else if ((data.quota != UINT64_MAX) != (data.period != UINT64_MAX))
1372 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1373 : : "CPU quota and period not used together.");
1374 : :
1375 : 0 : return 0;
1376 : : }
1377 : :
1378 : 0 : static int oci_cgroup_block_io_weight(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1379 : 0 : Settings *s = userdata;
1380 : : uintmax_t k;
1381 : : int r;
1382 : :
1383 [ # # ]: 0 : assert(s);
1384 : :
1385 : 0 : k = json_variant_unsigned(v);
1386 [ # # # # ]: 0 : if (k < CGROUP_BLKIO_WEIGHT_MIN || k > CGROUP_BLKIO_WEIGHT_MAX)
1387 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1388 : : "Block I/O weight out of range.");
1389 : :
1390 : 0 : r = settings_allocate_properties(s);
1391 [ # # ]: 0 : if (r < 0)
1392 : 0 : return r;
1393 : :
1394 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "BlockIOWeight", "t", (uint64_t) k);
1395 [ # # ]: 0 : if (r < 0)
1396 [ # # ]: 0 : return bus_log_create_error(r);
1397 : :
1398 : 0 : return 0;
1399 : : }
1400 : :
1401 : 0 : static int oci_cgroup_block_io_weight_device(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1402 : 0 : Settings *s = userdata;
1403 : : JsonVariant *e;
1404 : : int r;
1405 : :
1406 [ # # ]: 0 : assert(s);
1407 : :
1408 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1409 : : struct device_data {
1410 : : unsigned major;
1411 : : unsigned minor;
1412 : : uintmax_t weight;
1413 : 0 : } data = {
1414 : : .major = (unsigned) -1,
1415 : : .minor = (unsigned) -1,
1416 : : .weight = UINTMAX_MAX,
1417 : : };
1418 : :
1419 : : static const JsonDispatch table[] = {
1420 : : { "major", JSON_VARIANT_UNSIGNED, oci_device_major, offsetof(struct device_data, major), JSON_MANDATORY },
1421 : : { "minor", JSON_VARIANT_UNSIGNED, oci_device_minor, offsetof(struct device_data, minor), JSON_MANDATORY },
1422 : : { "weight", JSON_VARIANT_UNSIGNED, json_dispatch_unsigned, offsetof(struct device_data, weight), 0 },
1423 : : { "leafWeight", JSON_VARIANT_INTEGER, oci_unsupported, 0, JSON_PERMISSIVE },
1424 : : {}
1425 : : };
1426 : :
1427 [ # # # ]: 0 : _cleanup_free_ char *path = NULL;
1428 : :
1429 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
1430 [ # # ]: 0 : if (r < 0)
1431 : 0 : return r;
1432 : :
1433 [ # # ]: 0 : if (data.weight == UINTMAX_MAX)
1434 : 0 : continue;
1435 : :
1436 [ # # # # ]: 0 : if (data.weight < CGROUP_BLKIO_WEIGHT_MIN || data.weight > CGROUP_BLKIO_WEIGHT_MAX)
1437 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1438 : : "Block I/O device weight out of range.");
1439 : :
1440 : 0 : r = device_path_make_major_minor(S_IFBLK, makedev(data.major, data.minor), &path);
1441 [ # # ]: 0 : if (r < 0)
1442 [ # # ]: 0 : return json_log(v, flags, r, "Failed to build device path: %m");
1443 : :
1444 : 0 : r = settings_allocate_properties(s);
1445 [ # # ]: 0 : if (r < 0)
1446 : 0 : return r;
1447 : :
1448 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "BlockIODeviceWeight", "a(st)", 1, path, (uint64_t) data.weight);
1449 [ # # ]: 0 : if (r < 0)
1450 [ # # ]: 0 : return bus_log_create_error(r);
1451 : : }
1452 : :
1453 : 0 : return 0;
1454 : : }
1455 : :
1456 : 0 : static int oci_cgroup_block_io_throttle(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1457 : 0 : Settings *s = userdata;
1458 : : const char *pname;
1459 : : JsonVariant *e;
1460 : : int r;
1461 : :
1462 [ # # ]: 0 : assert(s);
1463 : :
1464 [ # # ]: 0 : pname = streq(name, "throttleReadBpsDevice") ? "IOReadBandwidthMax" :
1465 [ # # ]: 0 : streq(name, "throttleWriteBpsDevice") ? "IOWriteBandwidthMax" :
1466 [ # # ]: 0 : streq(name, "throttleReadIOPSDevice") ? "IOReadIOPSMax" :
1467 : : "IOWriteIOPSMax";
1468 : :
1469 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1470 : : struct device_data {
1471 : : unsigned major;
1472 : : unsigned minor;
1473 : : uintmax_t rate;
1474 : 0 : } data = {
1475 : : .major = (unsigned) -1,
1476 : : .minor = (unsigned) -1,
1477 : : };
1478 : :
1479 : : static const JsonDispatch table[] = {
1480 : : { "major", JSON_VARIANT_UNSIGNED, oci_device_major, offsetof(struct device_data, major), JSON_MANDATORY },
1481 : : { "minor", JSON_VARIANT_UNSIGNED, oci_device_minor, offsetof(struct device_data, minor), JSON_MANDATORY },
1482 : : { "rate", JSON_VARIANT_UNSIGNED, json_dispatch_unsigned, offsetof(struct device_data, rate), JSON_MANDATORY },
1483 : : {}
1484 : : };
1485 : :
1486 [ # # ]: 0 : _cleanup_free_ char *path = NULL;
1487 : :
1488 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &data);
1489 [ # # ]: 0 : if (r < 0)
1490 : 0 : return r;
1491 : :
1492 [ # # ]: 0 : if (data.rate >= UINT64_MAX)
1493 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
1494 : : "Block I/O device rate out of range.");
1495 : :
1496 : 0 : r = device_path_make_major_minor(S_IFBLK, makedev(data.major, data.minor), &path);
1497 [ # # ]: 0 : if (r < 0)
1498 [ # # ]: 0 : return json_log(v, flags, r, "Failed to build device path: %m");
1499 : :
1500 : 0 : r = settings_allocate_properties(s);
1501 [ # # ]: 0 : if (r < 0)
1502 : 0 : return r;
1503 : :
1504 : 0 : r = sd_bus_message_append(s->properties, "(sv)", pname, "a(st)", 1, path, (uint64_t) data.rate);
1505 [ # # ]: 0 : if (r < 0)
1506 [ # # ]: 0 : return bus_log_create_error(r);
1507 : : }
1508 : :
1509 : 0 : return 0;
1510 : : }
1511 : :
1512 : 0 : static int oci_cgroup_block_io(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1513 : :
1514 : : static const JsonDispatch table[] = {
1515 : : { "weight", JSON_VARIANT_UNSIGNED, oci_cgroup_block_io_weight, 0, 0 },
1516 : : { "leafWeight", JSON_VARIANT_UNSIGNED, oci_unsupported, 0, JSON_PERMISSIVE },
1517 : : { "weightDevice", JSON_VARIANT_ARRAY, oci_cgroup_block_io_weight_device, 0, 0 },
1518 : : { "throttleReadBpsDevice", JSON_VARIANT_ARRAY, oci_cgroup_block_io_throttle, 0, 0 },
1519 : : { "throttleWriteBpsDevice", JSON_VARIANT_ARRAY, oci_cgroup_block_io_throttle, 0, 0 },
1520 : : { "throttleReadIOPSDevice", JSON_VARIANT_ARRAY, oci_cgroup_block_io_throttle, 0, 0 },
1521 : : { "throttleWriteIOPSDevice", JSON_VARIANT_ARRAY, oci_cgroup_block_io_throttle, 0, 0 },
1522 : : {}
1523 : : };
1524 : :
1525 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
1526 : : }
1527 : :
1528 : 0 : static int oci_cgroup_pids(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1529 : :
1530 : : static const JsonDispatch table[] = {
1531 : : { "limit", JSON_VARIANT_NUMBER, json_dispatch_variant, 0, JSON_MANDATORY },
1532 : : {}
1533 : : };
1534 : :
1535 : 0 : _cleanup_(json_variant_unrefp) JsonVariant *k = NULL;
1536 : 0 : Settings *s = userdata;
1537 : : uint64_t m;
1538 : : int r;
1539 : :
1540 [ # # ]: 0 : assert(s);
1541 : :
1542 : 0 : r = json_dispatch(v, table, oci_unexpected, flags, &k);
1543 [ # # ]: 0 : if (r < 0)
1544 : 0 : return r;
1545 : :
1546 [ # # ]: 0 : if (json_variant_is_negative(k))
1547 : 0 : m = UINT64_MAX;
1548 : : else {
1549 [ # # ]: 0 : if (!json_variant_is_unsigned(k))
1550 [ # # ]: 0 : return json_log(k, flags, SYNTHETIC_ERRNO(EINVAL),
1551 : : "pids limit not unsigned integer, refusing.");
1552 : :
1553 : 0 : m = (uint64_t) json_variant_unsigned(k);
1554 : :
1555 [ # # ]: 0 : if ((uintmax_t) m != json_variant_unsigned(k))
1556 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1557 : : "pids limit out of range, refusing.");
1558 : : }
1559 : :
1560 : 0 : r = settings_allocate_properties(s);
1561 [ # # ]: 0 : if (r < 0)
1562 : 0 : return r;
1563 : :
1564 : 0 : r = sd_bus_message_append(s->properties, "(sv)", "TasksMax", "t", m);
1565 [ # # ]: 0 : if (r < 0)
1566 [ # # ]: 0 : return bus_log_create_error(r);
1567 : :
1568 : 0 : return 0;
1569 : : }
1570 : :
1571 : 0 : static int oci_resources(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1572 : :
1573 : : static const JsonDispatch table[] = {
1574 : : { "devices", JSON_VARIANT_ARRAY, oci_cgroup_devices, 0, 0 },
1575 : : { "memory", JSON_VARIANT_OBJECT, oci_cgroup_memory, 0, 0 },
1576 : : { "cpu", JSON_VARIANT_OBJECT, oci_cgroup_cpu, 0, 0 },
1577 : : { "blockIO", JSON_VARIANT_OBJECT, oci_cgroup_block_io, 0, 0 },
1578 : : { "hugepageLimits", JSON_VARIANT_ARRAY, oci_unsupported, 0, 0 },
1579 : : { "network", JSON_VARIANT_OBJECT, oci_unsupported, 0, 0 },
1580 : : { "pids", JSON_VARIANT_OBJECT, oci_cgroup_pids, 0, 0 },
1581 : : { "rdma", JSON_VARIANT_OBJECT, oci_unsupported, 0, 0 },
1582 : : {}
1583 : : };
1584 : :
1585 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
1586 : : }
1587 : :
1588 : 0 : static bool sysctl_key_valid(const char *s) {
1589 : 0 : bool dot = true;
1590 : :
1591 : : /* Note that we are a bit stricter here than in systemd-sysctl, as that inherited semantics from the old sysctl
1592 : : * tool, which were really weird (as it swaps / and . in both ways) */
1593 : :
1594 [ # # ]: 0 : if (isempty(s))
1595 : 0 : return false;
1596 : :
1597 [ # # ]: 0 : for (; *s; s++) {
1598 : :
1599 [ # # # # ]: 0 : if (*s <= ' ' || *s >= 127)
1600 : 0 : return false;
1601 [ # # ]: 0 : if (*s == '/')
1602 : 0 : return false;
1603 [ # # ]: 0 : if (*s == '.') {
1604 : :
1605 [ # # ]: 0 : if (dot) /* Don't allow two dots next to each other (or at the beginning) */
1606 : 0 : return false;
1607 : :
1608 : 0 : dot = true;
1609 : : } else
1610 : 0 : dot = false;
1611 : : }
1612 : :
1613 [ # # ]: 0 : if (dot) /* don't allow a dot at the end */
1614 : 0 : return false;
1615 : :
1616 : 0 : return true;
1617 : : }
1618 : :
1619 : 0 : static int oci_sysctl(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1620 : 0 : Settings *s = userdata;
1621 : : JsonVariant *w;
1622 : : const char *k;
1623 : : int r;
1624 : :
1625 [ # # ]: 0 : assert(s);
1626 : :
1627 [ # # # # : 0 : JSON_VARIANT_OBJECT_FOREACH(k, w, v) {
# # ]
1628 : : const char *m;
1629 : :
1630 [ # # ]: 0 : if (!json_variant_is_string(w))
1631 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1632 : : "sysctl parameter is not a string, refusing.");
1633 : :
1634 [ # # ]: 0 : assert_se(m = json_variant_string(w));
1635 : :
1636 [ # # ]: 0 : if (sysctl_key_valid(k))
1637 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1638 : : "sysctl key invalid, refusing: %s", k);
1639 : :
1640 : 0 : r = strv_extend_strv(&s->sysctl, STRV_MAKE(k, m), false);
1641 [ # # ]: 0 : if (r < 0)
1642 : 0 : return log_oom();
1643 : : }
1644 : :
1645 : 0 : return 0;
1646 : : }
1647 : :
1648 : : #if HAVE_SECCOMP
1649 : 0 : static int oci_seccomp_action_from_string(const char *name, uint32_t *ret) {
1650 : :
1651 : : static const struct {
1652 : : const char *name;
1653 : : uint32_t action;
1654 : : } table[] = {
1655 : : { "SCMP_ACT_ALLOW", SCMP_ACT_ALLOW },
1656 : : { "SCMP_ACT_ERRNO", SCMP_ACT_ERRNO(EPERM) }, /* the OCI spec doesn't document the error, but it appears EPERM is supposed to be used */
1657 : : { "SCMP_ACT_KILL", SCMP_ACT_KILL },
1658 : : #ifdef SCMP_ACT_KILL_PROCESS
1659 : : { "SCMP_ACT_KILL_PROCESS", SCMP_ACT_KILL_PROCESS },
1660 : : #endif
1661 : : #ifdef SCMP_ACT_KILL_THREAD
1662 : : { "SCMP_ACT_KILL_THREAD", SCMP_ACT_KILL_THREAD },
1663 : : #endif
1664 : : #ifdef SCMP_ACT_LOG
1665 : : { "SCMP_ACT_LOG", SCMP_ACT_LOG },
1666 : : #endif
1667 : : { "SCMP_ACT_TRAP", SCMP_ACT_TRAP },
1668 : :
1669 : : /* We don't support SCMP_ACT_TRACE because that requires a tracer, and that doesn't really make sense
1670 : : * here */
1671 : : };
1672 : :
1673 : : size_t i;
1674 : :
1675 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(table); i++)
1676 [ # # ]: 0 : if (streq_ptr(name, table[i].name)) {
1677 : 0 : *ret = table[i].action;
1678 : 0 : return 0;
1679 : : }
1680 : :
1681 : 0 : return -EINVAL;
1682 : : }
1683 : :
1684 : 0 : static int oci_seccomp_arch_from_string(const char *name, uint32_t *ret) {
1685 : :
1686 : : static const struct {
1687 : : const char *name;
1688 : : uint32_t arch;
1689 : : } table[] = {
1690 : : { "SCMP_ARCH_AARCH64", SCMP_ARCH_AARCH64 },
1691 : : { "SCMP_ARCH_ARM", SCMP_ARCH_ARM },
1692 : : { "SCMP_ARCH_MIPS", SCMP_ARCH_MIPS },
1693 : : { "SCMP_ARCH_MIPS64", SCMP_ARCH_MIPS64 },
1694 : : { "SCMP_ARCH_MIPS64N32", SCMP_ARCH_MIPS64N32 },
1695 : : { "SCMP_ARCH_MIPSEL", SCMP_ARCH_MIPSEL },
1696 : : { "SCMP_ARCH_MIPSEL64", SCMP_ARCH_MIPSEL64 },
1697 : : { "SCMP_ARCH_MIPSEL64N32", SCMP_ARCH_MIPSEL64N32 },
1698 : : { "SCMP_ARCH_NATIVE", SCMP_ARCH_NATIVE },
1699 : : #ifdef SCMP_ARCH_PARISC
1700 : : { "SCMP_ARCH_PARISC", SCMP_ARCH_PARISC },
1701 : : #endif
1702 : : #ifdef SCMP_ARCH_PARISC64
1703 : : { "SCMP_ARCH_PARISC64", SCMP_ARCH_PARISC64 },
1704 : : #endif
1705 : : { "SCMP_ARCH_PPC", SCMP_ARCH_PPC },
1706 : : { "SCMP_ARCH_PPC64", SCMP_ARCH_PPC64 },
1707 : : { "SCMP_ARCH_PPC64LE", SCMP_ARCH_PPC64LE },
1708 : : { "SCMP_ARCH_S390", SCMP_ARCH_S390 },
1709 : : { "SCMP_ARCH_S390X", SCMP_ARCH_S390X },
1710 : : { "SCMP_ARCH_X32", SCMP_ARCH_X32 },
1711 : : { "SCMP_ARCH_X86", SCMP_ARCH_X86 },
1712 : : { "SCMP_ARCH_X86_64", SCMP_ARCH_X86_64 },
1713 : : };
1714 : :
1715 : : size_t i;
1716 : :
1717 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(table); i++)
1718 [ # # ]: 0 : if (streq_ptr(table[i].name, name)) {
1719 : 0 : *ret = table[i].arch;
1720 : 0 : return 0;
1721 : : }
1722 : :
1723 : 0 : return -EINVAL;
1724 : : }
1725 : :
1726 : 0 : static int oci_seccomp_compare_from_string(const char *name, enum scmp_compare *ret) {
1727 : :
1728 : : static const struct {
1729 : : const char *name;
1730 : : enum scmp_compare op;
1731 : : } table[] = {
1732 : : { "SCMP_CMP_NE", SCMP_CMP_NE },
1733 : : { "SCMP_CMP_LT", SCMP_CMP_LT },
1734 : : { "SCMP_CMP_LE", SCMP_CMP_LE },
1735 : : { "SCMP_CMP_EQ", SCMP_CMP_EQ },
1736 : : { "SCMP_CMP_GE", SCMP_CMP_GE },
1737 : : { "SCMP_CMP_GT", SCMP_CMP_GT },
1738 : : { "SCMP_CMP_MASKED_EQ", SCMP_CMP_MASKED_EQ },
1739 : : };
1740 : :
1741 : : size_t i;
1742 : :
1743 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(table); i++)
1744 [ # # ]: 0 : if (streq_ptr(table[i].name, name)) {
1745 : 0 : *ret = table[i].op;
1746 : 0 : return 0;
1747 : : }
1748 : :
1749 : 0 : return -EINVAL;
1750 : : }
1751 : :
1752 : 0 : static int oci_seccomp_archs(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1753 : 0 : scmp_filter_ctx *sc = userdata;
1754 : : JsonVariant *e;
1755 : : int r;
1756 : :
1757 [ # # ]: 0 : assert(sc);
1758 : :
1759 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1760 : : uint32_t a;
1761 : :
1762 [ # # ]: 0 : if (!json_variant_is_string(e))
1763 [ # # ]: 0 : return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL),
1764 : : "Architecture entry is not a string");
1765 : :
1766 : 0 : r = oci_seccomp_arch_from_string(json_variant_string(e), &a);
1767 [ # # ]: 0 : if (r < 0)
1768 [ # # ]: 0 : return json_log(e, flags, r, "Unknown architecture: %s", json_variant_string(e));
1769 : :
1770 : 0 : r = seccomp_arch_add(sc, a);
1771 [ # # ]: 0 : if (r == -EEXIST)
1772 : 0 : continue;
1773 [ # # ]: 0 : if (r < 0)
1774 [ # # ]: 0 : return json_log(e, flags, r, "Failed to add architecture to seccomp filter: %m");
1775 : : }
1776 : :
1777 : 0 : return 0;
1778 : : }
1779 : :
1780 : : struct syscall_rule {
1781 : : char **names;
1782 : : uint32_t action;
1783 : : struct scmp_arg_cmp *arguments;
1784 : : size_t n_arguments;
1785 : : };
1786 : :
1787 : 0 : static void syscall_rule_free(struct syscall_rule *rule) {
1788 [ # # ]: 0 : assert(rule);
1789 : :
1790 : 0 : strv_free(rule->names);
1791 : 0 : free(rule->arguments);
1792 : 0 : };
1793 : :
1794 : 0 : static int oci_seccomp_action(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1795 : 0 : uint32_t *action = userdata;
1796 : : int r;
1797 : :
1798 [ # # ]: 0 : assert(action);
1799 : :
1800 : 0 : r = oci_seccomp_action_from_string(json_variant_string(v), action);
1801 [ # # ]: 0 : if (r < 0)
1802 [ # # ]: 0 : return json_log(v, flags, r, "Unknown system call action '%s': %m", json_variant_string(v));
1803 : :
1804 : 0 : return 0;
1805 : : }
1806 : :
1807 : 0 : static int oci_seccomp_op(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1808 : 0 : enum scmp_compare *op = userdata;
1809 : : int r;
1810 : :
1811 [ # # ]: 0 : assert(op);
1812 : :
1813 : 0 : r = oci_seccomp_compare_from_string(json_variant_string(v), op);
1814 [ # # ]: 0 : if (r < 0)
1815 [ # # ]: 0 : return json_log(v, flags, r, "Unknown seccomp operator '%s': %m", json_variant_string(v));
1816 : :
1817 : 0 : return 0;
1818 : : }
1819 : :
1820 : 0 : static int oci_seccomp_args(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1821 : 0 : struct syscall_rule *rule = userdata;
1822 : : JsonVariant *e;
1823 : : int r;
1824 : :
1825 [ # # ]: 0 : assert(rule);
1826 : :
1827 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1828 : : static const struct JsonDispatch table[] = {
1829 : : { "index", JSON_VARIANT_UNSIGNED, json_dispatch_uint32, offsetof(struct scmp_arg_cmp, arg), JSON_MANDATORY },
1830 : : { "value", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(struct scmp_arg_cmp, datum_a), JSON_MANDATORY },
1831 : : { "valueTwo", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(struct scmp_arg_cmp, datum_b), 0 },
1832 : : { "op", JSON_VARIANT_STRING, oci_seccomp_op, offsetof(struct scmp_arg_cmp, op), JSON_MANDATORY },
1833 : : {},
1834 : : };
1835 : :
1836 : : struct scmp_arg_cmp *a, *p;
1837 : : int expected;
1838 : :
1839 : 0 : a = reallocarray(rule->arguments, rule->n_arguments + 1, sizeof(struct syscall_rule));
1840 [ # # ]: 0 : if (!a)
1841 : 0 : return log_oom();
1842 : :
1843 : 0 : rule->arguments = a;
1844 : 0 : p = rule->arguments + rule->n_arguments;
1845 : :
1846 : 0 : *p = (struct scmp_arg_cmp) {
1847 : : .arg = 0,
1848 : : .datum_a = 0,
1849 : : .datum_b = 0,
1850 : : .op = 0,
1851 : : };
1852 : :
1853 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, p);
1854 [ # # ]: 0 : if (r < 0)
1855 : 0 : return r;
1856 : :
1857 [ # # ]: 0 : expected = p->op == SCMP_CMP_MASKED_EQ ? 4 : 3;
1858 [ # # ]: 0 : if (r != expected)
1859 [ # # ]: 0 : json_log(e, flags|JSON_WARNING, 0, "Wrong number of system call arguments for JSON data data, ignoring.");
1860 : :
1861 : : /* Note that we are a bit sloppy here and do not insist that SCMP_CMP_MASKED_EQ gets two datum values,
1862 : : * and the other only one. That's because buildah for example by default calls things with
1863 : : * SCMP_CMP_MASKED_EQ but only one argument. We use 0 when the value is not specified. */
1864 : :
1865 : 0 : rule->n_arguments++;
1866 : : }
1867 : :
1868 : 0 : return 0;
1869 : : }
1870 : :
1871 : 0 : static int oci_seccomp_syscalls(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1872 : 0 : scmp_filter_ctx *sc = userdata;
1873 : : JsonVariant *e;
1874 : : int r;
1875 : :
1876 [ # # ]: 0 : assert(sc);
1877 : :
1878 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1879 : : static const JsonDispatch table[] = {
1880 : : { "names", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(struct syscall_rule, names), JSON_MANDATORY },
1881 : : { "action", JSON_VARIANT_STRING, oci_seccomp_action, offsetof(struct syscall_rule, action), JSON_MANDATORY },
1882 : : { "args", JSON_VARIANT_ARRAY, oci_seccomp_args, 0, 0 },
1883 : : };
1884 : 0 : struct syscall_rule rule = {
1885 : : .action = (uint32_t) -1,
1886 : : };
1887 : : char **i;
1888 : :
1889 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, &rule);
1890 [ # # ]: 0 : if (r < 0)
1891 : 0 : goto fail_rule;
1892 : :
1893 [ # # ]: 0 : if (strv_isempty(rule.names)) {
1894 [ # # ]: 0 : json_log(e, flags, 0, "System call name list is empty.");
1895 : 0 : r = -EINVAL;
1896 : 0 : goto fail_rule;
1897 : : }
1898 : :
1899 [ # # # # ]: 0 : STRV_FOREACH(i, rule.names) {
1900 : : int nr;
1901 : :
1902 : 0 : nr = seccomp_syscall_resolve_name(*i);
1903 [ # # ]: 0 : if (nr == __NR_SCMP_ERROR) {
1904 [ # # ]: 0 : log_debug("Unknown syscall %s, skipping.", *i);
1905 : 0 : continue;
1906 : : }
1907 : :
1908 : 0 : r = seccomp_rule_add_array(sc, rule.action, nr, rule.n_arguments, rule.arguments);
1909 [ # # ]: 0 : if (r < 0)
1910 : 0 : goto fail_rule;
1911 : : }
1912 : :
1913 : 0 : syscall_rule_free(&rule);
1914 : 0 : continue;
1915 : :
1916 : 0 : fail_rule:
1917 : 0 : syscall_rule_free(&rule);
1918 : 0 : return r;
1919 : : }
1920 : :
1921 : 0 : return 0;
1922 : : }
1923 : : #endif
1924 : :
1925 : 0 : static int oci_seccomp(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1926 : :
1927 : : #if HAVE_SECCOMP
1928 : : static const JsonDispatch table[] = {
1929 : : { "defaultAction", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
1930 : : { "architectures", JSON_VARIANT_ARRAY, oci_seccomp_archs, 0, 0 },
1931 : : { "syscalls", JSON_VARIANT_ARRAY, oci_seccomp_syscalls, 0, 0 },
1932 : : {}
1933 : : };
1934 : :
1935 : 0 : _cleanup_(seccomp_releasep) scmp_filter_ctx sc = NULL;
1936 : 0 : Settings *s = userdata;
1937 : : JsonVariant *def;
1938 : : uint32_t d;
1939 : : int r;
1940 : :
1941 [ # # ]: 0 : assert(s);
1942 : :
1943 : 0 : def = json_variant_by_key(v, "defaultAction");
1944 [ # # ]: 0 : if (!def)
1945 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "defaultAction element missing.");
1946 : :
1947 [ # # ]: 0 : if (!json_variant_is_string(def))
1948 [ # # ]: 0 : return json_log(def, flags, SYNTHETIC_ERRNO(EINVAL), "defaultAction is not a string.");
1949 : :
1950 : 0 : r = oci_seccomp_action_from_string(json_variant_string(def), &d);
1951 [ # # ]: 0 : if (r < 0)
1952 [ # # ]: 0 : return json_log(def, flags, r, "Unknown default action: %s", json_variant_string(def));
1953 : :
1954 : 0 : sc = seccomp_init(d);
1955 [ # # ]: 0 : if (!sc)
1956 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ENOMEM), "Couldn't allocate seccomp object.");
1957 : :
1958 : 0 : r = json_dispatch(v, table, oci_unexpected, flags, sc);
1959 [ # # ]: 0 : if (r < 0)
1960 : 0 : return r;
1961 : :
1962 : 0 : seccomp_release(s->seccomp);
1963 : 0 : s->seccomp = TAKE_PTR(sc);
1964 : 0 : return 0;
1965 : : #else
1966 : : return json_log(v, flags, SYNTHETIC_ERRNO(EOPNOTSUPP), "libseccomp support not enabled, can't parse seccomp object.");
1967 : : #endif
1968 : : }
1969 : :
1970 : 0 : static int oci_rootfs_propagation(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1971 : : const char *s;
1972 : :
1973 : 0 : s = json_variant_string(v);
1974 : :
1975 [ # # ]: 0 : if (streq(s, "shared"))
1976 : 0 : return 0;
1977 : :
1978 [ # # ]: 0 : json_log(v, flags|JSON_DEBUG, 0, "Ignoring rootfsPropagation setting '%s'.", s);
1979 : 0 : return 0;
1980 : : }
1981 : :
1982 : 0 : static int oci_masked_paths(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
1983 : 0 : Settings *s = userdata;
1984 : : JsonVariant *e;
1985 : :
1986 [ # # ]: 0 : assert(s);
1987 : :
1988 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
1989 [ # # # ]: 0 : _cleanup_free_ char *destination = NULL;
1990 : : CustomMount *m;
1991 : : const char *p;
1992 : :
1993 [ # # ]: 0 : if (!json_variant_is_string(e))
1994 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
1995 : : "Path is not a string, refusing.");
1996 : :
1997 [ # # ]: 0 : assert_se(p = json_variant_string(e));
1998 : :
1999 [ # # ]: 0 : if (!path_is_absolute(p))
2000 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
2001 : : "Path is not not absolute, refusing: %s", p);
2002 : :
2003 [ # # ]: 0 : if (oci_exclude_mount(p))
2004 : 0 : continue;
2005 : :
2006 : 0 : destination = strdup(p);
2007 [ # # ]: 0 : if (!destination)
2008 : 0 : return log_oom();
2009 : :
2010 : 0 : m = custom_mount_add(&s->custom_mounts, &s->n_custom_mounts, CUSTOM_MOUNT_INACCESSIBLE);
2011 [ # # ]: 0 : if (!m)
2012 : 0 : return log_oom();
2013 : :
2014 : 0 : m->destination = TAKE_PTR(destination);
2015 : :
2016 : : /* The spec doesn't say this, but apparently pre-existing implementations are lenient towards
2017 : : * non-existing paths to mask. Let's hence be too. */
2018 : 0 : m->graceful = true;
2019 : : }
2020 : :
2021 : 0 : return 0;
2022 : : }
2023 : :
2024 : 0 : static int oci_readonly_paths(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2025 : 0 : Settings *s = userdata;
2026 : : JsonVariant *e;
2027 : :
2028 [ # # ]: 0 : assert(s);
2029 : :
2030 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
2031 [ # # # # : 0 : _cleanup_free_ char *source = NULL, *destination = NULL;
# # ]
2032 : : CustomMount *m;
2033 : : const char *p;
2034 : :
2035 [ # # ]: 0 : if (!json_variant_is_string(e))
2036 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
2037 : : "Path is not a string, refusing.");
2038 : :
2039 [ # # ]: 0 : assert_se(p = json_variant_string(e));
2040 : :
2041 [ # # ]: 0 : if (!path_is_absolute(p))
2042 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
2043 : : "Path is not not absolute, refusing: %s", p);
2044 : :
2045 [ # # ]: 0 : if (oci_exclude_mount(p))
2046 : 0 : continue;
2047 : :
2048 : 0 : source = strjoin("+", p);
2049 [ # # ]: 0 : if (!source)
2050 : 0 : return log_oom();
2051 : :
2052 : 0 : destination = strdup(p);
2053 [ # # ]: 0 : if (!destination)
2054 : 0 : return log_oom();
2055 : :
2056 : 0 : m = custom_mount_add(&s->custom_mounts, &s->n_custom_mounts, CUSTOM_MOUNT_BIND);
2057 [ # # ]: 0 : if (!m)
2058 : 0 : return log_oom();
2059 : :
2060 : 0 : m->source = TAKE_PTR(source);
2061 : 0 : m->destination = TAKE_PTR(destination);
2062 : 0 : m->read_only = true;
2063 : : }
2064 : :
2065 : 0 : return 0;
2066 : : }
2067 : :
2068 : 0 : static int oci_linux(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2069 : :
2070 : : static const JsonDispatch table[] = {
2071 : : { "namespaces", JSON_VARIANT_ARRAY, oci_namespaces, 0, 0 },
2072 : : { "uidMappings", JSON_VARIANT_ARRAY, oci_uid_gid_mappings, 0, 0 },
2073 : : { "gidMappings", JSON_VARIANT_ARRAY, oci_uid_gid_mappings, 0, 0 },
2074 : : { "devices", JSON_VARIANT_ARRAY, oci_devices, 0, 0 },
2075 : : { "cgroupsPath", JSON_VARIANT_STRING, oci_cgroups_path, 0, 0 },
2076 : : { "resources", JSON_VARIANT_OBJECT, oci_resources, 0, 0 },
2077 : : { "intelRdt", JSON_VARIANT_OBJECT, oci_unsupported, 0, JSON_PERMISSIVE },
2078 : : { "sysctl", JSON_VARIANT_OBJECT, oci_sysctl, 0, 0 },
2079 : : { "seccomp", JSON_VARIANT_OBJECT, oci_seccomp, 0, 0 },
2080 : : { "rootfsPropagation", JSON_VARIANT_STRING, oci_rootfs_propagation, 0, 0 },
2081 : : { "maskedPaths", JSON_VARIANT_ARRAY, oci_masked_paths, 0, 0 },
2082 : : { "readonlyPaths", JSON_VARIANT_ARRAY, oci_readonly_paths, 0, 0 },
2083 : : { "mountLabel", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE },
2084 : : {}
2085 : : };
2086 : :
2087 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
2088 : : }
2089 : :
2090 : 0 : static int oci_hook_timeout(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2091 : 0 : usec_t *u = userdata;
2092 : : uintmax_t k;
2093 : :
2094 : 0 : k = json_variant_unsigned(v);
2095 [ # # # # ]: 0 : if (k == 0 || k > (UINT64_MAX-1)/USEC_PER_SEC)
2096 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(ERANGE),
2097 : : "Hook timeout value out of range.");
2098 : :
2099 : 0 : *u = k * USEC_PER_SEC;
2100 : 0 : return 0;
2101 : : }
2102 : :
2103 : 0 : static int oci_hooks_array(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2104 : 0 : Settings *s = userdata;
2105 : : JsonVariant *e;
2106 : : int r;
2107 : :
2108 [ # # ]: 0 : assert(s);
2109 : :
2110 [ # # # # : 0 : JSON_VARIANT_ARRAY_FOREACH(e, v) {
# # ]
2111 : :
2112 : : static const JsonDispatch table[] = {
2113 : : { "path", JSON_VARIANT_STRING, oci_absolute_path, offsetof(OciHook, path), JSON_MANDATORY },
2114 : : { "args", JSON_VARIANT_ARRAY, oci_args, offsetof(OciHook, args), 0 },
2115 : : { "env", JSON_VARIANT_ARRAY, oci_env, offsetof(OciHook, env), 0 },
2116 : : { "timeout", JSON_VARIANT_UNSIGNED, oci_hook_timeout, offsetof(OciHook, timeout), 0 },
2117 : : {}
2118 : : };
2119 : :
2120 : : OciHook *a, **array, *new_item;
2121 : : size_t *n_array;
2122 : :
2123 [ # # ]: 0 : if (streq(name, "prestart")) {
2124 : 0 : array = &s->oci_hooks_prestart;
2125 : 0 : n_array = &s->n_oci_hooks_prestart;
2126 [ # # ]: 0 : } else if (streq(name, "poststart")) {
2127 : 0 : array = &s->oci_hooks_poststart;
2128 : 0 : n_array = &s->n_oci_hooks_poststart;
2129 : : } else {
2130 [ # # ]: 0 : assert(streq(name, "poststop"));
2131 : 0 : array = &s->oci_hooks_poststop;
2132 : 0 : n_array = &s->n_oci_hooks_poststop;
2133 : : }
2134 : :
2135 : 0 : a = reallocarray(*array, *n_array + 1, sizeof(OciHook));
2136 [ # # ]: 0 : if (!a)
2137 : 0 : return log_oom();
2138 : :
2139 : 0 : *array = a;
2140 : 0 : new_item = a + *n_array;
2141 : :
2142 : 0 : *new_item = (OciHook) {
2143 : : .timeout = USEC_INFINITY,
2144 : : };
2145 : :
2146 : 0 : r = json_dispatch(e, table, oci_unexpected, flags, userdata);
2147 [ # # ]: 0 : if (r < 0) {
2148 : 0 : free(new_item->path);
2149 : 0 : strv_free(new_item->args);
2150 : 0 : strv_free(new_item->env);
2151 : 0 : return r;
2152 : : }
2153 : :
2154 : 0 : (*n_array) ++;
2155 : : }
2156 : :
2157 : 0 : return 0;
2158 : : }
2159 : :
2160 : 0 : static int oci_hooks(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2161 : :
2162 : : static const JsonDispatch table[] = {
2163 : : { "prestart", JSON_VARIANT_OBJECT, oci_hooks_array, 0, 0 },
2164 : : { "poststart", JSON_VARIANT_OBJECT, oci_hooks_array, 0, 0 },
2165 : : { "poststop", JSON_VARIANT_OBJECT, oci_hooks_array, 0, 0 },
2166 : : {}
2167 : : };
2168 : :
2169 : 0 : return json_dispatch(v, table, oci_unexpected, flags, userdata);
2170 : : }
2171 : :
2172 : 0 : static int oci_annotations(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) {
2173 : : JsonVariant *w;
2174 : : const char *k;
2175 : :
2176 [ # # # # : 0 : JSON_VARIANT_OBJECT_FOREACH(k, w, v) {
# # ]
2177 : :
2178 [ # # ]: 0 : if (isempty(k))
2179 [ # # ]: 0 : return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL),
2180 : : "Annotation with empty key, refusing.");
2181 : :
2182 [ # # ]: 0 : if (!json_variant_is_string(w))
2183 [ # # ]: 0 : return json_log(w, flags, SYNTHETIC_ERRNO(EINVAL),
2184 : : "Annotation has non-string value, refusing.");
2185 : :
2186 [ # # ]: 0 : json_log(w, flags|JSON_DEBUG, 0, "Ignoring annotation '%s' with value '%s'.", k, json_variant_string(w));
2187 : : }
2188 : :
2189 : 0 : return 0;
2190 : : }
2191 : :
2192 : 0 : int oci_load(FILE *f, const char *bundle, Settings **ret) {
2193 : :
2194 : : static const JsonDispatch table[] = {
2195 : : { "ociVersion", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY },
2196 : : { "process", JSON_VARIANT_OBJECT, oci_process, 0, 0 },
2197 : : { "root", JSON_VARIANT_OBJECT, oci_root, 0, 0 },
2198 : : { "hostname", JSON_VARIANT_STRING, oci_hostname, 0, 0 },
2199 : : { "mounts", JSON_VARIANT_ARRAY, oci_mounts, 0, 0 },
2200 : : { "linux", JSON_VARIANT_OBJECT, oci_linux, 0, 0 },
2201 : : { "hooks", JSON_VARIANT_OBJECT, oci_hooks, 0, 0 },
2202 : : { "annotations", JSON_VARIANT_OBJECT, oci_annotations, 0, 0 },
2203 : : {}
2204 : : };
2205 : :
2206 : 0 : _cleanup_(json_variant_unrefp) JsonVariant *oci = NULL;
2207 : 0 : _cleanup_(settings_freep) Settings *s = NULL;
2208 : 0 : unsigned line = 0, column = 0;
2209 : : JsonVariant *v;
2210 : : const char *path;
2211 : : int r;
2212 : :
2213 [ # # ]: 0 : assert_se(bundle);
2214 : :
2215 [ # # # # : 0 : path = strjoina(bundle, "/config.json");
# # # # #
# # # ]
2216 : :
2217 : 0 : r = json_parse_file(f, path, &oci, &line, &column);
2218 [ # # ]: 0 : if (r < 0) {
2219 [ # # # # ]: 0 : if (line != 0 && column != 0)
2220 [ # # ]: 0 : return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", path, line, column);
2221 : : else
2222 [ # # ]: 0 : return log_error_errno(r, "Failed to parse '%s': %m", path);
2223 : : }
2224 : :
2225 : 0 : v = json_variant_by_key(oci, "ociVersion");
2226 [ # # ]: 0 : if (!v) {
2227 [ # # ]: 0 : log_error("JSON file '%s' is not an OCI bundle configuration file. Refusing.", path);
2228 : 0 : return -EINVAL;
2229 : : }
2230 [ # # ]: 0 : if (!streq_ptr(json_variant_string(v), "1.0.0")) {
2231 [ # # ]: 0 : log_error("OCI bundle version not supported: %s", strna(json_variant_string(v)));
2232 : 0 : return -EINVAL;
2233 : : }
2234 : :
2235 : : // {
2236 : : // _cleanup_free_ char *formatted = NULL;
2237 : : // assert_se(json_variant_format(oci, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR, &formatted) >= 0);
2238 : : // fputs(formatted, stdout);
2239 : : // }
2240 : :
2241 : 0 : s = settings_new();
2242 [ # # ]: 0 : if (!s)
2243 : 0 : return log_oom();
2244 : :
2245 : 0 : s->start_mode = START_PID1;
2246 : 0 : s->resolv_conf = RESOLV_CONF_OFF;
2247 : 0 : s->link_journal = LINK_NO;
2248 : 0 : s->timezone = TIMEZONE_OFF;
2249 : :
2250 : 0 : s->bundle = strdup(bundle);
2251 [ # # ]: 0 : if (!s->bundle)
2252 : 0 : return log_oom();
2253 : :
2254 : 0 : r = json_dispatch(oci, table, oci_unexpected, 0, s);
2255 [ # # ]: 0 : if (r < 0)
2256 : 0 : return r;
2257 : :
2258 [ # # ]: 0 : if (s->properties) {
2259 : 0 : r = sd_bus_message_seal(s->properties, 0, 0);
2260 [ # # ]: 0 : if (r < 0)
2261 [ # # ]: 0 : return log_error_errno(r, "Cannot seal properties bus message: %m");
2262 : : }
2263 : :
2264 : 0 : *ret = TAKE_PTR(s);
2265 : 0 : return 0;
2266 : : }
|