Line data Source code
1 : /* SPDX-License-Identifier: LGPL-2.1+ */
2 :
3 : #include <sys/prctl.h>
4 :
5 : #include "alloc-util.h"
6 : #include "btrfs-util.h"
7 : #include "capability-util.h"
8 : #include "copy.h"
9 : #include "dirent-util.h"
10 : #include "escape.h"
11 : #include "fd-util.h"
12 : #include "io-util.h"
13 : #include "path-util.h"
14 : #include "process-util.h"
15 : #include "pull-common.h"
16 : #include "pull-job.h"
17 : #include "rlimit-util.h"
18 : #include "rm-rf.h"
19 : #include "signal-util.h"
20 : #include "siphash24.h"
21 : #include "string-util.h"
22 : #include "strv.h"
23 : #include "util.h"
24 : #include "web-util.h"
25 :
26 : #define FILENAME_ESCAPE "/.#\"\'"
27 : #define HASH_URL_THRESHOLD_LENGTH (_POSIX_PATH_MAX - 16)
28 :
29 0 : int pull_find_old_etags(
30 : const char *url,
31 : const char *image_root,
32 : int dt,
33 : const char *prefix,
34 : const char *suffix,
35 : char ***etags) {
36 :
37 0 : _cleanup_free_ char *escaped_url = NULL;
38 0 : _cleanup_closedir_ DIR *d = NULL;
39 0 : _cleanup_strv_free_ char **l = NULL;
40 : struct dirent *de;
41 : int r;
42 :
43 0 : assert(url);
44 0 : assert(etags);
45 :
46 0 : if (!image_root)
47 0 : image_root = "/var/lib/machines";
48 :
49 0 : escaped_url = xescape(url, FILENAME_ESCAPE);
50 0 : if (!escaped_url)
51 0 : return -ENOMEM;
52 :
53 0 : d = opendir(image_root);
54 0 : if (!d) {
55 0 : if (errno == ENOENT) {
56 0 : *etags = NULL;
57 0 : return 0;
58 : }
59 :
60 0 : return -errno;
61 : }
62 :
63 0 : FOREACH_DIRENT_ALL(de, d, return -errno) {
64 0 : _cleanup_free_ char *u = NULL;
65 : const char *a, *b;
66 :
67 0 : if (de->d_type != DT_UNKNOWN &&
68 0 : de->d_type != dt)
69 0 : continue;
70 :
71 0 : if (prefix) {
72 0 : a = startswith(de->d_name, prefix);
73 0 : if (!a)
74 0 : continue;
75 : } else
76 0 : a = de->d_name;
77 :
78 0 : a = startswith(a, escaped_url);
79 0 : if (!a)
80 0 : continue;
81 :
82 0 : a = startswith(a, ".");
83 0 : if (!a)
84 0 : continue;
85 :
86 0 : if (suffix) {
87 0 : b = endswith(de->d_name, suffix);
88 0 : if (!b)
89 0 : continue;
90 : } else
91 0 : b = strchr(de->d_name, 0);
92 :
93 0 : if (a >= b)
94 0 : continue;
95 :
96 0 : r = cunescape_length(a, b - a, 0, &u);
97 0 : if (r < 0)
98 0 : return r;
99 :
100 0 : if (!http_etag_is_valid(u))
101 0 : continue;
102 :
103 0 : r = strv_consume(&l, TAKE_PTR(u));
104 0 : if (r < 0)
105 0 : return r;
106 : }
107 :
108 0 : *etags = TAKE_PTR(l);
109 :
110 0 : return 0;
111 : }
112 :
113 0 : int pull_make_local_copy(const char *final, const char *image_root, const char *local, bool force_local) {
114 : const char *p;
115 : int r;
116 :
117 0 : assert(final);
118 0 : assert(local);
119 :
120 0 : if (!image_root)
121 0 : image_root = "/var/lib/machines";
122 :
123 0 : p = prefix_roota(image_root, local);
124 :
125 0 : if (force_local)
126 0 : (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
127 :
128 0 : r = btrfs_subvol_snapshot(final, p,
129 : BTRFS_SNAPSHOT_QUOTA|
130 : BTRFS_SNAPSHOT_FALLBACK_COPY|
131 : BTRFS_SNAPSHOT_FALLBACK_DIRECTORY|
132 : BTRFS_SNAPSHOT_RECURSIVE);
133 0 : if (r < 0)
134 0 : return log_error_errno(r, "Failed to create local image: %m");
135 :
136 0 : log_info("Created new local image '%s'.", local);
137 :
138 0 : return 0;
139 : }
140 :
141 0 : static int hash_url(const char *url, char **ret) {
142 : uint64_t h;
143 : static const sd_id128_t k = SD_ID128_ARRAY(df,89,16,87,01,cc,42,30,98,ab,4a,19,a6,a5,63,4f);
144 :
145 0 : assert(url);
146 :
147 0 : h = siphash24(url, strlen(url), k.bytes);
148 0 : if (asprintf(ret, "%"PRIx64, h) < 0)
149 0 : return -ENOMEM;
150 :
151 0 : return 0;
152 : }
153 :
154 0 : int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret) {
155 0 : _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
156 : char *path;
157 :
158 0 : assert(url);
159 0 : assert(ret);
160 :
161 0 : if (!image_root)
162 0 : image_root = "/var/lib/machines";
163 :
164 0 : escaped_url = xescape(url, FILENAME_ESCAPE);
165 0 : if (!escaped_url)
166 0 : return -ENOMEM;
167 :
168 0 : if (etag) {
169 0 : escaped_etag = xescape(etag, FILENAME_ESCAPE);
170 0 : if (!escaped_etag)
171 0 : return -ENOMEM;
172 : }
173 :
174 0 : path = strjoin(image_root, "/", strempty(prefix), escaped_url, escaped_etag ? "." : "",
175 : strempty(escaped_etag), strempty(suffix));
176 0 : if (!path)
177 0 : return -ENOMEM;
178 :
179 : /* URLs might make the path longer than the maximum allowed length for a file name.
180 : * When that happens, a URL hash is used instead. Paths returned by this function
181 : * can be later used with tempfn_random() which adds 16 bytes to the resulting name. */
182 0 : if (strlen(path) >= HASH_URL_THRESHOLD_LENGTH) {
183 0 : _cleanup_free_ char *hash = NULL;
184 : int r;
185 :
186 0 : free(path);
187 :
188 0 : r = hash_url(url, &hash);
189 0 : if (r < 0)
190 0 : return r;
191 :
192 0 : path = strjoin(image_root, "/", strempty(prefix), hash, escaped_etag ? "." : "",
193 : strempty(escaped_etag), strempty(suffix));
194 0 : if (!path)
195 0 : return -ENOMEM;
196 : }
197 :
198 0 : *ret = path;
199 0 : return 0;
200 : }
201 :
202 0 : int pull_make_auxiliary_job(
203 : PullJob **ret,
204 : const char *url,
205 : int (*strip_suffixes)(const char *name, char **ret),
206 : const char *suffix,
207 : CurlGlue *glue,
208 : PullJobFinished on_finished,
209 : void *userdata) {
210 :
211 0 : _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL;
212 0 : _cleanup_(pull_job_unrefp) PullJob *job = NULL;
213 : const char *q;
214 : int r;
215 :
216 0 : assert(ret);
217 0 : assert(url);
218 0 : assert(strip_suffixes);
219 0 : assert(glue);
220 :
221 0 : r = import_url_last_component(url, &last_component);
222 0 : if (r < 0)
223 0 : return r;
224 :
225 0 : r = strip_suffixes(last_component, &ll);
226 0 : if (r < 0)
227 0 : return r;
228 :
229 0 : q = strjoina(ll, suffix);
230 :
231 0 : r = import_url_change_last_component(url, q, &auxiliary_url);
232 0 : if (r < 0)
233 0 : return r;
234 :
235 0 : r = pull_job_new(&job, auxiliary_url, glue, userdata);
236 0 : if (r < 0)
237 0 : return r;
238 :
239 0 : job->on_finished = on_finished;
240 0 : job->compressed_max = job->uncompressed_max = 1ULL * 1024ULL * 1024ULL;
241 :
242 0 : *ret = TAKE_PTR(job);
243 :
244 0 : return 0;
245 : }
246 :
247 0 : int pull_make_verification_jobs(
248 : PullJob **ret_checksum_job,
249 : PullJob **ret_signature_job,
250 : ImportVerify verify,
251 : const char *url,
252 : CurlGlue *glue,
253 : PullJobFinished on_finished,
254 : void *userdata) {
255 :
256 0 : _cleanup_(pull_job_unrefp) PullJob *checksum_job = NULL, *signature_job = NULL;
257 : int r;
258 0 : const char *chksums = NULL;
259 :
260 0 : assert(ret_checksum_job);
261 0 : assert(ret_signature_job);
262 0 : assert(verify >= 0);
263 0 : assert(verify < _IMPORT_VERIFY_MAX);
264 0 : assert(url);
265 0 : assert(glue);
266 :
267 0 : if (verify != IMPORT_VERIFY_NO) {
268 0 : _cleanup_free_ char *checksum_url = NULL, *fn = NULL;
269 :
270 : /* Queue jobs for the checksum file for the image. */
271 0 : r = import_url_last_component(url, &fn);
272 0 : if (r < 0)
273 0 : return r;
274 :
275 0 : chksums = strjoina(fn, ".sha256");
276 :
277 0 : r = import_url_change_last_component(url, chksums, &checksum_url);
278 0 : if (r < 0)
279 0 : return r;
280 :
281 0 : r = pull_job_new(&checksum_job, checksum_url, glue, userdata);
282 0 : if (r < 0)
283 0 : return r;
284 :
285 0 : checksum_job->on_finished = on_finished;
286 0 : checksum_job->uncompressed_max = checksum_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
287 : }
288 :
289 0 : if (verify == IMPORT_VERIFY_SIGNATURE) {
290 0 : _cleanup_free_ char *signature_url = NULL;
291 :
292 : /* Queue job for the SHA256SUMS.gpg file for the image. */
293 0 : r = import_url_change_last_component(url, "SHA256SUMS.gpg", &signature_url);
294 0 : if (r < 0)
295 0 : return r;
296 :
297 0 : r = pull_job_new(&signature_job, signature_url, glue, userdata);
298 0 : if (r < 0)
299 0 : return r;
300 :
301 0 : signature_job->on_finished = on_finished;
302 0 : signature_job->uncompressed_max = signature_job->compressed_max = 1ULL * 1024ULL * 1024ULL;
303 : }
304 :
305 0 : *ret_checksum_job = checksum_job;
306 0 : *ret_signature_job = signature_job;
307 :
308 0 : checksum_job = signature_job = NULL;
309 :
310 0 : return 0;
311 : }
312 :
313 0 : static int verify_one(PullJob *checksum_job, PullJob *job) {
314 0 : _cleanup_free_ char *fn = NULL;
315 : const char *line, *p;
316 : int r;
317 :
318 0 : assert(checksum_job);
319 :
320 0 : if (!job)
321 0 : return 0;
322 :
323 0 : assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED));
324 :
325 : /* Don't verify the checksum if we didn't actually successfully download something new */
326 0 : if (job->state != PULL_JOB_DONE)
327 0 : return 0;
328 0 : if (job->error != 0)
329 0 : return 0;
330 0 : if (job->etag_exists)
331 0 : return 0;
332 :
333 0 : assert(job->calc_checksum);
334 0 : assert(job->checksum);
335 :
336 0 : r = import_url_last_component(job->url, &fn);
337 0 : if (r < 0)
338 0 : return log_oom();
339 :
340 0 : if (!filename_is_valid(fn))
341 0 : return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
342 : "Cannot verify checksum, could not determine server-side file name.");
343 :
344 0 : line = strjoina(job->checksum, " *", fn, "\n");
345 :
346 0 : p = memmem(checksum_job->payload,
347 : checksum_job->payload_size,
348 : line,
349 : strlen(line));
350 :
351 0 : if (!p) {
352 0 : line = strjoina(job->checksum, " ", fn, "\n");
353 :
354 0 : p = memmem(checksum_job->payload,
355 : checksum_job->payload_size,
356 : line,
357 : strlen(line));
358 : }
359 :
360 0 : if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n'))
361 0 : return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
362 : "DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn);
363 :
364 0 : log_info("SHA256 checksum of %s is valid.", job->url);
365 0 : return 1;
366 : }
367 :
368 0 : int pull_verify(PullJob *main_job,
369 : PullJob *roothash_job,
370 : PullJob *settings_job,
371 : PullJob *checksum_job,
372 : PullJob *signature_job) {
373 :
374 0 : _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 };
375 0 : _cleanup_close_ int sig_file = -1;
376 0 : char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX";
377 0 : _cleanup_(sigkill_waitp) pid_t pid = 0;
378 0 : bool gpg_home_created = false;
379 : int r;
380 :
381 0 : assert(main_job);
382 0 : assert(main_job->state == PULL_JOB_DONE);
383 :
384 0 : if (!checksum_job)
385 0 : return 0;
386 :
387 0 : assert(main_job->calc_checksum);
388 0 : assert(main_job->checksum);
389 :
390 0 : assert(checksum_job->state == PULL_JOB_DONE);
391 :
392 0 : if (!checksum_job->payload || checksum_job->payload_size <= 0) {
393 0 : log_error("Checksum is empty, cannot verify.");
394 0 : return -EBADMSG;
395 : }
396 :
397 0 : r = verify_one(checksum_job, main_job);
398 0 : if (r < 0)
399 0 : return r;
400 :
401 0 : r = verify_one(checksum_job, roothash_job);
402 0 : if (r < 0)
403 0 : return r;
404 :
405 0 : r = verify_one(checksum_job, settings_job);
406 0 : if (r < 0)
407 0 : return r;
408 :
409 0 : if (!signature_job)
410 0 : return 0;
411 :
412 0 : if (checksum_job->style == VERIFICATION_PER_FILE)
413 0 : signature_job = checksum_job;
414 :
415 0 : assert(signature_job->state == PULL_JOB_DONE);
416 :
417 0 : if (!signature_job->payload || signature_job->payload_size <= 0) {
418 0 : log_error("Signature is empty, cannot verify.");
419 0 : return -EBADMSG;
420 : }
421 :
422 0 : r = pipe2(gpg_pipe, O_CLOEXEC);
423 0 : if (r < 0)
424 0 : return log_error_errno(errno, "Failed to create pipe for gpg: %m");
425 :
426 0 : sig_file = mkostemp(sig_file_path, O_RDWR);
427 0 : if (sig_file < 0)
428 0 : return log_error_errno(errno, "Failed to create temporary file: %m");
429 :
430 0 : r = loop_write(sig_file, signature_job->payload, signature_job->payload_size, false);
431 0 : if (r < 0) {
432 0 : log_error_errno(r, "Failed to write to temporary file: %m");
433 0 : goto finish;
434 : }
435 :
436 0 : if (!mkdtemp(gpg_home)) {
437 0 : r = log_error_errno(errno, "Failed to create temporary home for gpg: %m");
438 0 : goto finish;
439 : }
440 :
441 0 : gpg_home_created = true;
442 :
443 0 : r = safe_fork("(gpg)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid);
444 0 : if (r < 0)
445 0 : return r;
446 0 : if (r == 0) {
447 0 : const char *cmd[] = {
448 : "gpg",
449 : "--no-options",
450 : "--no-default-keyring",
451 : "--no-auto-key-locate",
452 : "--no-auto-check-trustdb",
453 : "--batch",
454 : "--trust-model=always",
455 : NULL, /* --homedir= */
456 : NULL, /* --keyring= */
457 : NULL, /* --verify */
458 : NULL, /* signature file */
459 : NULL, /* dash */
460 : NULL /* trailing NULL */
461 : };
462 0 : unsigned k = ELEMENTSOF(cmd) - 6;
463 :
464 : /* Child */
465 :
466 0 : gpg_pipe[1] = safe_close(gpg_pipe[1]);
467 :
468 0 : r = rearrange_stdio(gpg_pipe[0], -1, STDERR_FILENO);
469 0 : if (r < 0) {
470 0 : log_error_errno(r, "Failed to rearrange stdin/stdout: %m");
471 0 : _exit(EXIT_FAILURE);
472 : }
473 :
474 0 : (void) rlimit_nofile_safe();
475 :
476 0 : cmd[k++] = strjoina("--homedir=", gpg_home);
477 :
478 : /* We add the user keyring only to the command line
479 : * arguments, if it's around since gpg fails
480 : * otherwise. */
481 0 : if (access(USER_KEYRING_PATH, F_OK) >= 0)
482 0 : cmd[k++] = "--keyring=" USER_KEYRING_PATH;
483 : else
484 0 : cmd[k++] = "--keyring=" VENDOR_KEYRING_PATH;
485 :
486 0 : cmd[k++] = "--verify";
487 0 : if (checksum_job->style == VERIFICATION_PER_DIRECTORY) {
488 0 : cmd[k++] = sig_file_path;
489 0 : cmd[k++] = "-";
490 0 : cmd[k++] = NULL;
491 : }
492 :
493 0 : execvp("gpg2", (char * const *) cmd);
494 0 : execvp("gpg", (char * const *) cmd);
495 0 : log_error_errno(errno, "Failed to execute gpg: %m");
496 0 : _exit(EXIT_FAILURE);
497 : }
498 :
499 0 : gpg_pipe[0] = safe_close(gpg_pipe[0]);
500 :
501 0 : r = loop_write(gpg_pipe[1], checksum_job->payload, checksum_job->payload_size, false);
502 0 : if (r < 0) {
503 0 : log_error_errno(r, "Failed to write to pipe: %m");
504 0 : goto finish;
505 : }
506 :
507 0 : gpg_pipe[1] = safe_close(gpg_pipe[1]);
508 :
509 0 : r = wait_for_terminate_and_check("gpg", pid, WAIT_LOG_ABNORMAL);
510 0 : pid = 0;
511 0 : if (r < 0)
512 0 : goto finish;
513 0 : if (r != EXIT_SUCCESS) {
514 0 : log_error("DOWNLOAD INVALID: Signature verification failed.");
515 0 : r = -EBADMSG;
516 : } else {
517 0 : log_info("Signature verification succeeded.");
518 0 : r = 0;
519 : }
520 :
521 0 : finish:
522 0 : (void) unlink(sig_file_path);
523 :
524 0 : if (gpg_home_created)
525 0 : (void) rm_rf(gpg_home, REMOVE_ROOT|REMOVE_PHYSICAL);
526 :
527 0 : return r;
528 : }
|