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