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