]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/sysupdate/sysupdate-pattern.c
hexdecoct: make unbase64mem and unhexmem always use SIZE_MAX
[thirdparty/systemd.git] / src / sysupdate / sysupdate-pattern.c
CommitLineData
43cc7a3e
LP
1/* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3#include "alloc-util.h"
4#include "hexdecoct.h"
5#include "list.h"
6#include "parse-util.h"
7#include "path-util.h"
8#include "stdio-util.h"
9#include "string-util.h"
10#include "sysupdate-pattern.h"
43cc7a3e
LP
11
12typedef enum PatternElementType {
13 PATTERN_LITERAL,
14 PATTERN_VERSION,
15 PATTERN_PARTITION_UUID,
16 PATTERN_PARTITION_FLAGS,
17 PATTERN_MTIME,
18 PATTERN_MODE,
19 PATTERN_SIZE,
20 PATTERN_TRIES_DONE,
21 PATTERN_TRIES_LEFT,
22 PATTERN_NO_AUTO,
23 PATTERN_READ_ONLY,
24 PATTERN_GROWFS,
25 PATTERN_SHA256SUM,
8b051623 26 PATTERN_SLASH,
43cc7a3e
LP
27 _PATTERN_ELEMENT_TYPE_MAX,
28 _PATTERN_ELEMENT_TYPE_INVALID = -EINVAL,
29} PatternElementType;
30
31typedef struct PatternElement PatternElement;
32
33struct PatternElement {
34 PatternElementType type;
35 LIST_FIELDS(PatternElement, elements);
36 char literal[];
37};
38
39static PatternElement *pattern_element_free_all(PatternElement *e) {
9aad490e 40 LIST_CLEAR(elements, e, free);
43cc7a3e
LP
41
42 return NULL;
43}
44
45DEFINE_TRIVIAL_CLEANUP_FUNC(PatternElement*, pattern_element_free_all);
46
47static PatternElementType pattern_element_type_from_char(char c) {
48 switch (c) {
49 case 'v':
50 return PATTERN_VERSION;
51 case 'u':
52 return PATTERN_PARTITION_UUID;
53 case 'f':
54 return PATTERN_PARTITION_FLAGS;
55 case 't':
56 return PATTERN_MTIME;
57 case 'm':
58 return PATTERN_MODE;
59 case 's':
60 return PATTERN_SIZE;
61 case 'd':
62 return PATTERN_TRIES_DONE;
63 case 'l':
64 return PATTERN_TRIES_LEFT;
65 case 'a':
66 return PATTERN_NO_AUTO;
67 case 'r':
68 return PATTERN_READ_ONLY;
69 case 'g':
70 return PATTERN_GROWFS;
71 case 'h':
72 return PATTERN_SHA256SUM;
73 default:
74 return _PATTERN_ELEMENT_TYPE_INVALID;
75 }
76}
77
78static bool valid_char(char x) {
79
80 /* Let's refuse control characters here, and let's reserve some characters typically used in pattern
81 * languages so that we can use them later, possibly. */
82
83 if ((unsigned) x < ' ' || x >= 127)
84 return false;
85
8b051623 86 return !IN_SET(x, '$', '*', '?', '[', ']', '!', '\\', '|');
43cc7a3e
LP
87}
88
89static int pattern_split(
90 const char *pattern,
91 PatternElement **ret) {
92
93 _cleanup_(pattern_element_free_allp) PatternElement *first = NULL;
8b051623 94 bool at = false, last_literal = true, last_slash = false;
43cc7a3e
LP
95 PatternElement *last = NULL;
96 uint64_t mask_found = 0;
97 size_t l, k = 0;
98
99 assert(pattern);
100
101 l = strlen(pattern);
102
103 for (const char *e = pattern; *e != 0; e++) {
104 if (*e == '@') {
105 if (!at) {
106 at = true;
107 continue;
108 }
109
110 /* Two at signs in a sequence, write out one */
111 at = false;
112
113 } else if (at) {
114 PatternElementType t;
115 uint64_t bit;
116
117 t = pattern_element_type_from_char(*e);
118 if (t < 0)
119 return log_debug_errno(t, "Unknown pattern field marker '@%c'.", *e);
120
121 bit = UINT64_C(1) << t;
122 if (mask_found & bit)
123 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Pattern field marker '@%c' appears twice in pattern.", *e);
124
125 /* We insist that two pattern field markers are separated by some literal string that
126 * we can use to separate the fields when parsing. */
8b051623 127 if (!last_literal && !last_slash)
43cc7a3e
LP
128 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Found two pattern field markers without separating literal.");
129
130 if (ret) {
131 PatternElement *z;
132
133 z = malloc(offsetof(PatternElement, literal));
134 if (!z)
135 return -ENOMEM;
136
137 z->type = t;
138 LIST_INSERT_AFTER(elements, first, last, z);
139 last = z;
140 }
141
142 mask_found |= bit;
8b051623 143 last_slash = last_literal = at = false;
43cc7a3e
LP
144 continue;
145 }
146
8b051623
VD
147 if (*e == '/') {
148 if (ret) {
149 PatternElement *z;
150
151 z = malloc(offsetof(PatternElement, literal));
152 if (!z)
153 return -ENOMEM;
154
155 z->type = PATTERN_SLASH;
156 LIST_INSERT_AFTER(elements, first, last, z);
157 last = z;
158 }
159
160 last_literal = false;
161 last_slash = true;
162 continue ;
163 }
164
43cc7a3e 165 if (!valid_char(*e))
5570a097
JJ
166 return log_debug_errno(
167 SYNTHETIC_ERRNO(EBADRQC),
168 "Invalid character 0x%0x in pattern, refusing.",
169 (unsigned) *e);
43cc7a3e
LP
170
171 last_literal = true;
8b051623 172 last_slash = false;
43cc7a3e
LP
173
174 if (!ret)
175 continue;
176
177 if (!last || last->type != PATTERN_LITERAL) {
178 PatternElement *z;
179
180 z = malloc0(offsetof(PatternElement, literal) + l + 1); /* l is an upper bound to all literal elements */
181 if (!z)
182 return -ENOMEM;
183
184 z->type = PATTERN_LITERAL;
185 k = 0;
186
187 LIST_INSERT_AFTER(elements, first, last, z);
188 last = z;
189 }
190
191 assert(last);
192 assert(last->type == PATTERN_LITERAL);
193
194 last->literal[k++] = *e;
195 }
196
197 if (at)
198 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Trailing @ character found, refusing.");
199 if (!(mask_found & (UINT64_C(1) << PATTERN_VERSION)))
200 return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Version field marker '@v' not specified in pattern, refusing.");
201
202 if (ret)
203 *ret = TAKE_PTR(first);
204
205 return 0;
206}
207
208int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) {
209 _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
210 _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
43cc7a3e
LP
211 const char *p;
212 int r;
213
214 assert(pattern);
215 assert(s);
216
217 r = pattern_split(pattern, &elements);
218 if (r < 0)
219 return r;
220
221 p = s;
222 LIST_FOREACH(elements, e, elements) {
223 _cleanup_free_ char *t = NULL;
224 const char *n;
225
8b051623
VD
226 if (e->type == PATTERN_SLASH) {
227 if (*p == '/') {
228 ++p;
229 continue;
230 } else if (*p == '\0')
231 goto retry;
232 else
233 goto nope;
234 }
235
43cc7a3e
LP
236 if (e->type == PATTERN_LITERAL) {
237 const char *k;
238
239 /* Skip literal fields */
240 k = startswith(p, e->literal);
241 if (!k)
242 goto nope;
243
244 p = k;
245 continue;
246 }
247
248 if (e->elements_next) {
249 /* The next element must be literal, as we use it to determine where to split */
250 assert(e->elements_next->type == PATTERN_LITERAL);
251
252 n = strstr(p, e->elements_next->literal);
253 if (!n)
254 goto nope;
255
256 } else
257 /* End of the string */
258 assert_se(n = strchr(p, 0));
259 t = strndup(p, n - p);
260 if (!t)
261 return -ENOMEM;
262
263 switch (e->type) {
264
265 case PATTERN_VERSION:
266 if (!version_is_valid(t)) {
267 log_debug("Version string is not valid, refusing: %s", t);
268 goto nope;
269 }
270
271 assert(!found.version);
272 found.version = TAKE_PTR(t);
273 break;
274
275 case PATTERN_PARTITION_UUID: {
276 sd_id128_t id;
277
278 if (sd_id128_from_string(t, &id) < 0)
279 goto nope;
280
281 assert(!found.partition_uuid_set);
282 found.partition_uuid = id;
283 found.partition_uuid_set = true;
284 break;
285 }
286
287 case PATTERN_PARTITION_FLAGS: {
288 uint64_t f;
289
290 if (safe_atoux64(t, &f) < 0)
291 goto nope;
292
293 if (found.partition_flags_set && found.partition_flags != f)
294 goto nope;
295
296 assert(!found.partition_flags_set);
297 found.partition_flags = f;
298 found.partition_flags_set = true;
299 break;
300 }
301
302 case PATTERN_MTIME: {
303 uint64_t v;
304
305 if (safe_atou64(t, &v) < 0)
306 goto nope;
307 if (v == USEC_INFINITY) /* Don't permit our internal special infinity value */
308 goto nope;
309 if (v / 1000000U > TIME_T_MAX) /* Make sure this fits in a timespec structure */
310 goto nope;
311
312 assert(found.mtime == USEC_INFINITY);
313 found.mtime = v;
314 break;
315 }
316
317 case PATTERN_MODE: {
318 mode_t m;
319
320 r = parse_mode(t, &m);
321 if (r < 0)
322 goto nope;
323 if (m & ~0775) /* Don't allow world-writable files or suid files to be generated this way */
324 goto nope;
325
326 assert(found.mode == MODE_INVALID);
327 found.mode = m;
328 break;
329 }
330
331 case PATTERN_SIZE: {
332 uint64_t u;
333
334 r = safe_atou64(t, &u);
335 if (r < 0)
336 goto nope;
337 if (u == UINT64_MAX)
338 goto nope;
339
340 assert(found.size == UINT64_MAX);
341 found.size = u;
342 break;
343 }
344
345 case PATTERN_TRIES_DONE: {
346 uint64_t u;
347
348 r = safe_atou64(t, &u);
349 if (r < 0)
350 goto nope;
351 if (u == UINT64_MAX)
352 goto nope;
353
354 assert(found.tries_done == UINT64_MAX);
355 found.tries_done = u;
356 break;
357 }
358
359 case PATTERN_TRIES_LEFT: {
360 uint64_t u;
361
362 r = safe_atou64(t, &u);
363 if (r < 0)
364 goto nope;
365 if (u == UINT64_MAX)
366 goto nope;
367
368 assert(found.tries_left == UINT64_MAX);
369 found.tries_left = u;
370 break;
371 }
372
373 case PATTERN_NO_AUTO:
374 r = parse_boolean(t);
375 if (r < 0)
376 goto nope;
377
378 assert(found.no_auto < 0);
379 found.no_auto = r;
380 break;
381
382 case PATTERN_READ_ONLY:
383 r = parse_boolean(t);
384 if (r < 0)
385 goto nope;
386
387 assert(found.read_only < 0);
388 found.read_only = r;
389 break;
390
391 case PATTERN_GROWFS:
392 r = parse_boolean(t);
393 if (r < 0)
394 goto nope;
395
396 assert(found.growfs < 0);
397 found.growfs = r;
398 break;
399
400 case PATTERN_SHA256SUM: {
401 _cleanup_free_ void *d = NULL;
402 size_t l;
403
404 if (strlen(t) != sizeof(found.sha256sum) * 2)
405 goto nope;
406
bdd2036e 407 r = unhexmem_full(t, sizeof(found.sha256sum) * 2, /* secure = */ false, &d, &l);
43cc7a3e
LP
408 if (r == -ENOMEM)
409 return r;
410 if (r < 0)
411 goto nope;
412
413 assert(!found.sha256sum_set);
414 assert(l == sizeof(found.sha256sum));
415 memcpy(found.sha256sum, d, l);
416 found.sha256sum_set = true;
417 break;
418 }
419
420 default:
421 assert_se("unexpected pattern element");
422 }
423
424 p = n;
425 }
426
427 if (ret) {
428 *ret = found;
429 found = (InstanceMetadata) INSTANCE_METADATA_NULL;
430 }
431
8b051623 432 return PATTERN_MATCH_YES;
43cc7a3e
LP
433
434nope:
435 if (ret)
436 *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
437
8b051623
VD
438 return PATTERN_MATCH_NO;
439
440retry:
441 if (ret)
442 *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
443
444 return PATTERN_MATCH_RETRY;
43cc7a3e
LP
445}
446
447int pattern_match_many(char **patterns, const char *s, InstanceMetadata *ret) {
448 _cleanup_(instance_metadata_destroy) InstanceMetadata found = INSTANCE_METADATA_NULL;
43cc7a3e
LP
449 int r;
450
451 STRV_FOREACH(p, patterns) {
452 r = pattern_match(*p, s, &found);
453 if (r < 0)
454 return r;
455 if (r > 0) {
456 if (ret) {
457 *ret = found;
458 found = (InstanceMetadata) INSTANCE_METADATA_NULL;
459 }
460
8b051623 461 return r;
43cc7a3e
LP
462 }
463 }
464
465 if (ret)
466 *ret = (InstanceMetadata) INSTANCE_METADATA_NULL;
467
8b051623 468 return PATTERN_MATCH_NO;
43cc7a3e
LP
469}
470
471int pattern_valid(const char *pattern) {
472 int r;
473
474 r = pattern_split(pattern, NULL);
475 if (r == -EINVAL)
476 return false;
477 if (r < 0)
478 return r;
479
480 return true;
481}
482
483int pattern_format(
484 const char *pattern,
485 const InstanceMetadata *fields,
486 char **ret) {
487
488 _cleanup_(pattern_element_free_allp) PatternElement *elements = NULL;
489 _cleanup_free_ char *j = NULL;
43cc7a3e
LP
490 int r;
491
492 assert(pattern);
493 assert(fields);
494 assert(ret);
495
496 r = pattern_split(pattern, &elements);
497 if (r < 0)
498 return r;
499
500 LIST_FOREACH(elements, e, elements) {
501
502 switch (e->type) {
503
8b051623
VD
504 case PATTERN_SLASH:
505 if (!strextend(&j, "/"))
506 return -ENOMEM;
507
508 break;
509
43cc7a3e
LP
510 case PATTERN_LITERAL:
511 if (!strextend(&j, e->literal))
512 return -ENOMEM;
513
514 break;
515
516 case PATTERN_VERSION:
517 if (!fields->version)
518 return -ENXIO;
519
520 if (!strextend(&j, fields->version))
521 return -ENOMEM;
522 break;
523
524 case PATTERN_PARTITION_UUID: {
525 char formatted[SD_ID128_STRING_MAX];
526
527 if (!fields->partition_uuid_set)
528 return -ENXIO;
529
530 if (!strextend(&j, sd_id128_to_string(fields->partition_uuid, formatted)))
531 return -ENOMEM;
532
533 break;
534 }
535
536 case PATTERN_PARTITION_FLAGS:
537 if (!fields->partition_flags_set)
538 return -ENXIO;
539
540 r = strextendf(&j, "%" PRIx64, fields->partition_flags);
541 if (r < 0)
542 return r;
543
544 break;
545
546 case PATTERN_MTIME:
547 if (fields->mtime == USEC_INFINITY)
548 return -ENXIO;
549
550 r = strextendf(&j, "%" PRIu64, fields->mtime);
551 if (r < 0)
552 return r;
553
554 break;
555
556 case PATTERN_MODE:
557 if (fields->mode == MODE_INVALID)
558 return -ENXIO;
559
560 r = strextendf(&j, "%03o", fields->mode);
561 if (r < 0)
562 return r;
563
564 break;
565
566 case PATTERN_SIZE:
567 if (fields->size == UINT64_MAX)
568 return -ENXIO;
569
570 r = strextendf(&j, "%" PRIu64, fields->size);
571 if (r < 0)
572 return r;
573 break;
574
575 case PATTERN_TRIES_DONE:
576 if (fields->tries_done == UINT64_MAX)
577 return -ENXIO;
578
579 r = strextendf(&j, "%" PRIu64, fields->tries_done);
580 if (r < 0)
581 return r;
582 break;
583
584 case PATTERN_TRIES_LEFT:
585 if (fields->tries_left == UINT64_MAX)
586 return -ENXIO;
587
588 r = strextendf(&j, "%" PRIu64, fields->tries_left);
589 if (r < 0)
590 return r;
591 break;
592
593 case PATTERN_NO_AUTO:
594 if (fields->no_auto < 0)
595 return -ENXIO;
596
597 if (!strextend(&j, one_zero(fields->no_auto)))
598 return -ENOMEM;
599
600 break;
601
602 case PATTERN_READ_ONLY:
603 if (fields->read_only < 0)
604 return -ENXIO;
605
606 if (!strextend(&j, one_zero(fields->read_only)))
607 return -ENOMEM;
608
609 break;
610
611 case PATTERN_GROWFS:
612 if (fields->growfs < 0)
613 return -ENXIO;
614
615 if (!strextend(&j, one_zero(fields->growfs)))
616 return -ENOMEM;
617
618 break;
619
620 case PATTERN_SHA256SUM: {
621 _cleanup_free_ char *h = NULL;
622
623 if (!fields->sha256sum_set)
624 return -ENXIO;
625
626 h = hexmem(fields->sha256sum, sizeof(fields->sha256sum));
627 if (!h)
628 return -ENOMEM;
629
630 if (!strextend(&j, h))
631 return -ENOMEM;
632
633 break;
634 }
635
636 default:
637 assert_not_reached();
638 }
639 }
640
641 *ret = TAKE_PTR(j);
642 return 0;
643}