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 : }
|