Branch data Line data Source code
1 : : /* Retrieve ELF / DWARF / source files from the debuginfod.
2 : : Copyright (C) 2019-2024 Red Hat, Inc.
3 : : Copyright (C) 2021, 2022 Mark J. Wielaard <mark@klomp.org>
4 : : This file is part of elfutils.
5 : :
6 : : This file is free software; you can redistribute it and/or modify
7 : : it under the terms of either
8 : :
9 : : * the GNU Lesser General Public License as published by the Free
10 : : Software Foundation; either version 3 of the License, or (at
11 : : your option) any later version
12 : :
13 : : or
14 : :
15 : : * the GNU General Public License as published by the Free
16 : : Software Foundation; either version 2 of the License, or (at
17 : : your option) any later version
18 : :
19 : : or both in parallel, as here.
20 : :
21 : : elfutils is distributed in the hope that it will be useful, but
22 : : WITHOUT ANY WARRANTY; without even the implied warranty of
23 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 : : General Public License for more details.
25 : :
26 : : You should have received copies of the GNU General Public License and
27 : : the GNU Lesser General Public License along with this program. If
28 : : not, see <http://www.gnu.org/licenses/>. */
29 : :
30 : :
31 : : /* cargo-cult from libdwfl linux-kernel-modules.c */
32 : : /* In case we have a bad fts we include this before config.h because it
33 : : can't handle _FILE_OFFSET_BITS.
34 : : Everything we need here is fine if its declarations just come first.
35 : : Also, include sys/types.h before fts. On some systems fts.h is not self
36 : : contained. */
37 : : #ifdef BAD_FTS
38 : : #include <sys/types.h>
39 : : #include <fts.h>
40 : : #endif
41 : :
42 : : #include "config.h"
43 : : #include "debuginfod.h"
44 : : #include "system.h"
45 : : #include <ctype.h>
46 : : #include <errno.h>
47 : : #include <stdlib.h>
48 : : #include <gelf.h>
49 : :
50 : : #ifdef ENABLE_IMA_VERIFICATION
51 : : #include <openssl/sha.h>
52 : : #include <openssl/pem.h>
53 : : #include <openssl/evp.h>
54 : : #include <openssl/x509v3.h>
55 : : #include <arpa/inet.h>
56 : : #include <imaevm.h>
57 : : #endif
58 : : typedef enum {ignore, enforcing, undefined} ima_policy_t;
59 : :
60 : :
61 : : /* We might be building a bootstrap dummy library, which is really simple. */
62 : : #ifdef DUMMY_LIBDEBUGINFOD
63 : :
64 : : debuginfod_client *debuginfod_begin (void) { errno = ENOSYS; return NULL; }
65 : : int debuginfod_find_debuginfo (debuginfod_client *c, const unsigned char *b,
66 : : int s, char **p) { return -ENOSYS; }
67 : : int debuginfod_find_executable (debuginfod_client *c, const unsigned char *b,
68 : : int s, char **p) { return -ENOSYS; }
69 : : int debuginfod_find_source (debuginfod_client *c, const unsigned char *b,
70 : : int s, const char *f, char **p) { return -ENOSYS; }
71 : : int debuginfod_find_section (debuginfod_client *c, const unsigned char *b,
72 : : int s, const char *scn, char **p)
73 : : { return -ENOSYS; }
74 : : void debuginfod_set_progressfn(debuginfod_client *c,
75 : : debuginfod_progressfn_t fn) { }
76 : : void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
77 : : void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
78 : : void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
79 : : const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
80 : : int debuginfod_add_http_header (debuginfod_client *c,
81 : : const char *h) { return -ENOSYS; }
82 : : const char* debuginfod_get_headers (debuginfod_client *c) { return NULL; }
83 : :
84 : : void debuginfod_end (debuginfod_client *c) { }
85 : :
86 : : #else /* DUMMY_LIBDEBUGINFOD */
87 : :
88 : : #include <assert.h>
89 : : #include <dirent.h>
90 : : #include <stdio.h>
91 : : #include <errno.h>
92 : : #include <unistd.h>
93 : : #include <fcntl.h>
94 : : #include <fts.h>
95 : : #include <regex.h>
96 : : #include <string.h>
97 : : #include <stdbool.h>
98 : : #include <linux/limits.h>
99 : : #include <time.h>
100 : : #include <utime.h>
101 : : #include <sys/syscall.h>
102 : : #include <sys/types.h>
103 : : #include <sys/stat.h>
104 : : #include <sys/utsname.h>
105 : : #include <curl/curl.h>
106 : : #include <fnmatch.h>
107 : :
108 : : /* If fts.h is included before config.h, its indirect inclusions may not
109 : : give us the right LFS aliases of these functions, so map them manually. */
110 : : #ifdef BAD_FTS
111 : : #ifdef _FILE_OFFSET_BITS
112 : : #define open open64
113 : : #define fopen fopen64
114 : : #endif
115 : : #else
116 : : #include <sys/types.h>
117 : : #include <fts.h>
118 : : #endif
119 : :
120 : : /* Older curl.h don't define CURL_AT_LEAST_VERSION. */
121 : : #ifndef CURL_AT_LEAST_VERSION
122 : : #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z))
123 : : #define CURL_AT_LEAST_VERSION(x,y,z) \
124 : : (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
125 : : #endif
126 : :
127 : : #include <pthread.h>
128 : :
129 : : static pthread_once_t init_control = PTHREAD_ONCE_INIT;
130 : : static bool curl_has_https; // = false
131 : :
132 : : static void
133 : 386 : libcurl_init(void)
134 : : {
135 : 386 : curl_global_init(CURL_GLOBAL_DEFAULT);
136 : :
137 : 386 : for (const char *const *protocol = curl_version_info(CURLVERSION_NOW)->protocols;
138 [ + + ]: 11966 : *protocol != NULL; ++protocol)
139 : : {
140 [ + + ]: 11580 : if(strcmp("https", *protocol) == 0)
141 : 386 : curl_has_https = true;
142 : : }
143 : 386 : }
144 : :
145 : :
146 : : #ifdef ENABLE_IMA_VERIFICATION
147 : : struct public_key_entry
148 : : {
149 : : struct public_key_entry *next; /* singly-linked list */
150 : : uint32_t keyid; /* last 4 bytes of sha1 of public key */
151 : : EVP_PKEY *key; /* openssl */
152 : : };
153 : : #endif
154 : :
155 : :
156 : : struct debuginfod_client
157 : : {
158 : : /* Progress/interrupt callback function. */
159 : : debuginfod_progressfn_t progressfn;
160 : :
161 : : /* Stores user data. */
162 : : void* user_data;
163 : :
164 : : /* Stores current/last url, if any. */
165 : : char* url;
166 : :
167 : : /* Accumulates outgoing http header names/values. */
168 : : int user_agent_set_p; /* affects add_default_headers */
169 : : struct curl_slist *headers;
170 : :
171 : : /* Flags the default_progressfn having printed something that
172 : : debuginfod_end needs to terminate. */
173 : : int default_progressfn_printed_p;
174 : :
175 : : /* Indicates whether the last query was cancelled by progressfn. */
176 : : bool progressfn_cancel;
177 : :
178 : : /* File descriptor to output any verbose messages if > 0. */
179 : : int verbose_fd;
180 : :
181 : : /* Maintain a long-lived curl multi-handle, which keeps a
182 : : connection/tls/dns cache to recently seen servers. */
183 : : CURLM *server_mhandle;
184 : :
185 : : /* Can contain all other context, like cache_path, server_urls,
186 : : timeout or other info gotten from environment variables, the
187 : : handle data, etc. So those don't have to be reparsed and
188 : : recreated on each request. */
189 : : char * winning_headers;
190 : :
191 : : #ifdef ENABLE_IMA_VERIFICATION
192 : : /* IMA public keys */
193 : : struct public_key_entry *ima_public_keys;
194 : : #endif
195 : : };
196 : :
197 : :
198 : : /* The cache_clean_interval_s file within the debuginfod cache specifies
199 : : how frequently the cache should be cleaned. The file's st_mtime represents
200 : : the time of last cleaning. */
201 : : static const char *cache_clean_interval_filename = "cache_clean_interval_s";
202 : : static const long cache_clean_default_interval_s = 86400; /* 1 day */
203 : :
204 : : /* The cache_miss_default_s within the debuginfod cache specifies how
205 : : frequently the empty file should be released.*/
206 : : static const long cache_miss_default_s = 600; /* 10 min */
207 : : static const char *cache_miss_filename = "cache_miss_s";
208 : :
209 : : /* The cache_max_unused_age_s file within the debuginfod cache specifies the
210 : : the maximum time since last access that a file will remain in the cache. */
211 : : static const char *cache_max_unused_age_filename = "max_unused_age_s";
212 : : static const long cache_default_max_unused_age_s = 604800; /* 1 week */
213 : :
214 : : /* Location of the cache of files downloaded from debuginfods.
215 : : The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
216 : : static const char *cache_default_name = ".debuginfod_client_cache";
217 : : static const char *cache_xdg_name = "debuginfod_client";
218 : :
219 : : /* URLs of debuginfods, separated by url_delim. */
220 : : static const char *url_delim = " ";
221 : :
222 : : /* Timeout for debuginfods, in seconds (to get at least 100K). */
223 : : static const long default_timeout = 90;
224 : :
225 : : /* Default retry count for download error. */
226 : : static const long default_retry_limit = 2;
227 : :
228 : : /* Data associated with a particular CURL easy handle. Passed to
229 : : the write callback. */
230 : : struct handle_data
231 : : {
232 : : /* Cache file to be written to in case query is successful. */
233 : : int fd;
234 : :
235 : : /* URL queried by this handle. */
236 : : char url[PATH_MAX];
237 : :
238 : : /* error buffer for this handle. */
239 : : char errbuf[CURL_ERROR_SIZE];
240 : :
241 : : /* This handle. */
242 : : CURL *handle;
243 : :
244 : : /* The client object whom we're serving. */
245 : : debuginfod_client *client;
246 : :
247 : : /* Pointer to handle that should write to fd. Initially points to NULL,
248 : : then points to the first handle that begins writing the target file
249 : : to the cache. Used to ensure that a file is not downloaded from
250 : : multiple servers unnecessarily. */
251 : : CURL **target_handle;
252 : : /* Response http headers for this client handle, sent from the server */
253 : : char *response_data;
254 : : size_t response_data_size;
255 : : };
256 : :
257 : :
258 : :
259 : : #ifdef ENABLE_IMA_VERIFICATION
260 : : static inline unsigned char hex2dec(char c)
261 : : {
262 : : if (c >= '0' && c <= '9') return (c - '0');
263 : : if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
264 : : if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
265 : : return 0;
266 : : }
267 : :
268 : : static inline ima_policy_t ima_policy_str2enum(const char* ima_pol)
269 : : {
270 : : if (NULL == ima_pol) return undefined;
271 : : if (0 == strcmp(ima_pol, "ignore")) return ignore;
272 : : if (0 == strcmp(ima_pol, "enforcing")) return enforcing;
273 : : return undefined;
274 : : }
275 : :
276 : : static inline const char* ima_policy_enum2str(ima_policy_t ima_pol)
277 : : {
278 : : switch (ima_pol)
279 : : {
280 : : case ignore:
281 : : return "ignore";
282 : : case enforcing:
283 : : return "enforcing";
284 : : case undefined:
285 : : return "undefined";
286 : : }
287 : : return "";
288 : : }
289 : :
290 : :
291 : : static uint32_t extract_skid_pk(EVP_PKEY *pkey) // compute keyid by public key hashing
292 : : {
293 : : if (!pkey) return 0;
294 : : uint32_t keyid = 0;
295 : : X509_PUBKEY *pk = NULL;
296 : : const unsigned char *public_key = NULL;
297 : : int len;
298 : : if (X509_PUBKEY_set(&pk, pkey) &&
299 : : X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk))
300 : : {
301 : : uint8_t sha1[SHA_DIGEST_LENGTH];
302 : : SHA1(public_key, len, sha1);
303 : : memcpy(&keyid, sha1 + 16, 4);
304 : : }
305 : : X509_PUBKEY_free(pk);
306 : : return ntohl(keyid);
307 : : }
308 : :
309 : :
310 : : static uint32_t extract_skid(X509* x509) // compute keyid from cert or its public key
311 : : {
312 : : if (!x509) return 0;
313 : : uint32_t keyid = 0;
314 : : // Attempt to get the skid from the certificate
315 : : const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509);
316 : : if (skid_asn1_str)
317 : : {
318 : : int skid_len = ASN1_STRING_length(skid_asn1_str);
319 : : memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid));
320 : : }
321 : : else // compute keyid ourselves by hashing public key
322 : : {
323 : : EVP_PKEY *pkey = X509_get0_pubkey(x509);
324 : : keyid = htonl(extract_skid_pk(pkey));
325 : : }
326 : : return ntohl(keyid);
327 : : }
328 : :
329 : :
330 : : static void load_ima_public_keys (debuginfod_client *c)
331 : : {
332 : : /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. */
333 : : char *cert_paths = getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR);
334 : : if (cert_paths == NULL || cert_paths[0] == '\0')
335 : : return;
336 : : cert_paths = strdup(cert_paths); // Modified during tokenization
337 : : if (cert_paths == NULL)
338 : : return;
339 : :
340 : : char* cert_dir_path;
341 : : DIR *dp;
342 : : struct dirent *entry;
343 : : int vfd = c->verbose_fd;
344 : :
345 : : char *strtok_context = NULL;
346 : : for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context);
347 : : cert_dir_path != NULL;
348 : : cert_dir_path = strtok_r(NULL, ":", &strtok_context))
349 : : {
350 : : dp = opendir(cert_dir_path);
351 : : if(!dp) continue;
352 : : while((entry = readdir(dp)))
353 : : {
354 : : // Only consider regular files with common x509 cert extensions
355 : : if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer|cert)", entry->d_name, FNM_EXTMATCH)) continue;
356 : : char certfile[PATH_MAX];
357 : : strncpy(certfile, cert_dir_path, PATH_MAX - 1);
358 : : if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/';
359 : : strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1);
360 : : certfile[strlen(certfile)] = '\0';
361 : :
362 : : FILE *cert_fp = fopen(certfile, "r");
363 : : if(!cert_fp) continue;
364 : :
365 : : X509 *x509 = NULL;
366 : : EVP_PKEY *pkey = NULL;
367 : : char *fmt = "";
368 : : // Attempt to read the fp as DER
369 : : if(d2i_X509_fp(cert_fp, &x509))
370 : : fmt = "der ";
371 : : // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used
372 : : // Note we fseek since this is the second time we read from the fp
373 : : else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL))
374 : : fmt = "pem "; // PEM with full certificate
375 : : else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKEY(cert_fp, &pkey, NULL, NULL))
376 : : fmt = "pem "; // some PEM files have just a PUBLIC KEY in them
377 : : fclose(cert_fp);
378 : :
379 : : if (x509)
380 : : {
381 : : struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
382 : : if (ne)
383 : : {
384 : : ne->key = X509_extract_key(x509);
385 : : ne->keyid = extract_skid(x509);
386 : : ne->next = c->ima_public_keys;
387 : : c->ima_public_keys = ne;
388 : : if (vfd >= 0)
389 : : dprintf(vfd, "Loaded %scertificate %s, keyid = %04x\n", fmt, certfile, ne->keyid);
390 : : }
391 : : X509_free (x509);
392 : : }
393 : : else if (pkey)
394 : : {
395 : : struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
396 : : if (ne)
397 : : {
398 : : ne->key = pkey; // preserve refcount
399 : : ne->keyid = extract_skid_pk(pkey);
400 : : ne->next = c->ima_public_keys;
401 : : c->ima_public_keys = ne;
402 : : if (vfd >= 0)
403 : : dprintf(vfd, "Loaded %spubkey %s, keyid %04x\n", fmt, certfile, ne->keyid);
404 : : }
405 : : }
406 : : else
407 : : {
408 : : if (vfd >= 0)
409 : : dprintf(vfd, "Cannot load certificate %s\n", certfile);
410 : : }
411 : : } /* for each file in directory */
412 : : closedir(dp);
413 : : } /* for each directory */
414 : :
415 : : free(cert_paths);
416 : : }
417 : :
418 : :
419 : : static void free_ima_public_keys (debuginfod_client *c)
420 : : {
421 : : while (c->ima_public_keys)
422 : : {
423 : : EVP_PKEY_free (c->ima_public_keys->key);
424 : : struct public_key_entry *oen = c->ima_public_keys->next;
425 : : free (c->ima_public_keys);
426 : : c->ima_public_keys = oen;
427 : : }
428 : : }
429 : : #endif
430 : :
431 : :
432 : :
433 : : static size_t
434 : 7831 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
435 : : {
436 : 7831 : ssize_t count = size * nmemb;
437 : :
438 : 7831 : struct handle_data *d = (struct handle_data*)data;
439 : :
440 : : /* Indicate to other handles that they can abort their transfer. */
441 [ + + ]: 7831 : if (*d->target_handle == NULL)
442 : : {
443 : 763 : *d->target_handle = d->handle;
444 : : /* update the client object */
445 : 763 : const char *url = NULL;
446 : 763 : CURLcode curl_res = curl_easy_getinfo (d->handle,
447 : : CURLINFO_EFFECTIVE_URL, &url);
448 [ + - + - ]: 763 : if (curl_res == CURLE_OK && url)
449 : : {
450 : 763 : free (d->client->url);
451 : 763 : d->client->url = strdup(url); /* ok if fails */
452 : : }
453 : : }
454 : :
455 : : /* If this handle isn't the target handle, abort transfer. */
456 [ + - ]: 7831 : if (*d->target_handle != d->handle)
457 : : return -1;
458 : :
459 : 7831 : return (size_t) write(d->fd, (void*)ptr, count);
460 : : }
461 : :
462 : : /* handle config file read and write */
463 : : static int
464 : 1946 : debuginfod_config_cache(debuginfod_client *c, char *config_path,
465 : : long cache_config_default_s,
466 : : struct stat *st)
467 : : {
468 : 1946 : int fd = open(config_path, O_CREAT | O_RDWR, DEFFILEMODE);
469 [ - + ]: 1946 : if (fd < 0)
470 : 0 : return -errno;
471 : :
472 [ - + ]: 1946 : if (fstat (fd, st) < 0)
473 : : {
474 : 0 : int ret = -errno;
475 : 0 : close (fd);
476 : 0 : return ret;
477 : : }
478 : :
479 [ + + ]: 1946 : if (st->st_size == 0)
480 : : {
481 [ - + ]: 77 : if (dprintf(fd, "%ld", cache_config_default_s) < 0)
482 : : {
483 : 0 : int ret = -errno;
484 : 0 : close (fd);
485 : 0 : return ret;
486 : : }
487 : :
488 : 77 : close (fd);
489 : 77 : return cache_config_default_s;
490 : : }
491 : :
492 : 1869 : long cache_config;
493 : : /* PR29696 - NB: When using fdopen, the file descriptor is NOT
494 : : dup'ed and will be closed when the stream is closed. Manually
495 : : closing fd after fclose is called will lead to a race condition
496 : : where, if reused, the file descriptor will compete for its
497 : : regular use before being incorrectly closed here. */
498 : 1869 : FILE *config_file = fdopen(fd, "r");
499 [ + - ]: 1869 : if (config_file)
500 : : {
501 [ - + ]: 1869 : if (fscanf(config_file, "%ld", &cache_config) != 1)
502 : 0 : cache_config = cache_config_default_s;
503 [ - + - - ]: 1869 : if (0 != fclose (config_file) && c->verbose_fd >= 0)
504 : 0 : dprintf (c->verbose_fd, "fclose failed with %s (err=%d)\n",
505 : 0 : strerror (errno), errno);
506 : : }
507 : : else
508 : : {
509 : 0 : cache_config = cache_config_default_s;
510 [ # # # # ]: 0 : if (0 != close (fd) && c->verbose_fd >= 0)
511 : 0 : dprintf (c->verbose_fd, "close failed with %s (err=%d)\n",
512 : 0 : strerror (errno), errno);
513 : : }
514 : 1869 : return cache_config;
515 : : }
516 : :
517 : : /* Delete any files that have been unmodied for a period
518 : : longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
519 : : static int
520 : 1940 : debuginfod_clean_cache(debuginfod_client *c,
521 : : char *cache_path, char *interval_path,
522 : : char *max_unused_path)
523 : : {
524 : 1940 : time_t clean_interval, max_unused_age;
525 : 1940 : int rc = -1;
526 : 1940 : struct stat st;
527 : :
528 : : /* Create new interval file. */
529 : 1940 : rc = debuginfod_config_cache(c, interval_path,
530 : : cache_clean_default_interval_s, &st);
531 [ + - ]: 1940 : if (rc < 0)
532 : : return rc;
533 : 1940 : clean_interval = (time_t)rc;
534 : :
535 : : /* Check timestamp of interval file to see whether cleaning is necessary. */
536 [ + + ]: 1940 : if (time(NULL) - st.st_mtime < clean_interval)
537 : : /* Interval has not passed, skip cleaning. */
538 : : return 0;
539 : :
540 : : /* Update timestamp representing when the cache was last cleaned.
541 : : Do it at the start to reduce the number of threads trying to do a
542 : : cleanup simultaneously. */
543 : 2 : utime (interval_path, NULL);
544 : :
545 : : /* Read max unused age value from config file. */
546 : 2 : rc = debuginfod_config_cache(c, max_unused_path,
547 : : cache_default_max_unused_age_s, &st);
548 [ + - ]: 2 : if (rc < 0)
549 : : return rc;
550 : 2 : max_unused_age = (time_t)rc;
551 : :
552 : 2 : char * const dirs[] = { cache_path, NULL, };
553 : :
554 : 2 : FTS *fts = fts_open(dirs, 0, NULL);
555 [ - + ]: 2 : if (fts == NULL)
556 : 0 : return -errno;
557 : :
558 : 2 : regex_t re;
559 : 2 : const char * pattern = ".*/[a-f0-9]+(/debuginfo|/executable|/source.*|)$"; /* include dirs */
560 [ + - ]: 2 : if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
561 : : return -ENOMEM;
562 : :
563 : 2 : FTSENT *f;
564 : 2 : long files = 0;
565 : 2 : time_t now = time(NULL);
566 [ + + ]: 24 : while ((f = fts_read(fts)) != NULL)
567 : : {
568 : : /* ignore any files that do not match the pattern. */
569 [ + + ]: 20 : if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
570 : 16 : continue;
571 : :
572 : 4 : files++;
573 [ - + ]: 4 : if (c->progressfn) /* inform/check progress callback */
574 [ # # ]: 0 : if ((c->progressfn) (c, files, 0))
575 : : break;
576 : :
577 [ + - + ]: 4 : switch (f->fts_info)
578 : : {
579 : 0 : case FTS_F:
580 : : /* delete file if max_unused_age has been met or exceeded w.r.t. atime. */
581 [ # # ]: 0 : if (now - f->fts_statp->st_atime >= max_unused_age)
582 : 0 : (void) unlink (f->fts_path);
583 : : break;
584 : :
585 : 2 : case FTS_DP:
586 : : /* Remove if old & empty. Weaken race against concurrent creation by
587 : : checking mtime. */
588 [ - + ]: 2 : if (now - f->fts_statp->st_mtime >= max_unused_age)
589 : 2 : (void) rmdir (f->fts_path);
590 : : break;
591 : :
592 : : default:
593 : 22 : ;
594 : : }
595 : : }
596 : 2 : fts_close (fts);
597 : 2 : regfree (&re);
598 : :
599 : 2 : return 0;
600 : : }
601 : :
602 : :
603 : : #define MAX_BUILD_ID_BYTES 64
604 : :
605 : :
606 : : static void
607 : 1940 : add_default_headers(debuginfod_client *client)
608 : : {
609 [ + + ]: 1940 : if (client->user_agent_set_p)
610 : 548 : return;
611 : :
612 : : /* Compute a User-Agent: string to send. The more accurately this
613 : : describes this host, the likelier that the debuginfod servers
614 : : might be able to locate debuginfo for us. */
615 : :
616 : 1392 : char* utspart = NULL;
617 : 1392 : struct utsname uts;
618 : 1392 : int rc = 0;
619 : 1392 : rc = uname (&uts);
620 [ + - ]: 1392 : if (rc == 0)
621 : 1392 : rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
622 [ - + ]: 1392 : if (rc < 0)
623 : 0 : utspart = NULL;
624 : :
625 : 1392 : FILE *f = fopen ("/etc/os-release", "r");
626 [ - + ]: 1392 : if (f == NULL)
627 : 0 : f = fopen ("/usr/lib/os-release", "r");
628 : 1392 : char *id = NULL;
629 : 1392 : char *version = NULL;
630 [ + - ]: 1392 : if (f != NULL)
631 : : {
632 [ + + ]: 9744 : while (id == NULL || version == NULL)
633 : : {
634 : 8352 : char buf[128];
635 : 8352 : char *s = &buf[0];
636 [ + - ]: 8352 : if (fgets (s, sizeof(buf), f) == NULL)
637 : : break;
638 : :
639 : 8352 : int len = strlen (s);
640 [ - + ]: 8352 : if (len < 3)
641 : 0 : continue;
642 [ + - ]: 8352 : if (s[len - 1] == '\n')
643 : : {
644 : 8352 : s[len - 1] = '\0';
645 : 8352 : len--;
646 : : }
647 : :
648 : 8352 : char *v = strchr (s, '=');
649 [ + - - + ]: 8352 : if (v == NULL || strlen (v) < 2)
650 : 0 : continue;
651 : :
652 : : /* Split var and value. */
653 : 8352 : *v = '\0';
654 : 8352 : v++;
655 : :
656 : : /* Remove optional quotes around value string. */
657 [ + + ]: 8352 : if (*v == '"' || *v == '\'')
658 : : {
659 : 5568 : v++;
660 : 5568 : s[len - 1] = '\0';
661 : : }
662 [ + + ]: 8352 : if (strcmp (s, "ID") == 0)
663 : 1392 : id = strdup (v);
664 [ + + ]: 8352 : if (strcmp (s, "VERSION_ID") == 0)
665 : 1392 : version = strdup (v);
666 : : }
667 : 1392 : fclose (f);
668 : : }
669 : :
670 : 1392 : char *ua = NULL;
671 [ + - + - ]: 2784 : rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
672 : : PACKAGE_NAME, PACKAGE_VERSION,
673 [ - + ]: 1392 : utspart ?: "",
674 : : id ?: "",
675 : : version ?: "");
676 [ - + ]: 1392 : if (rc < 0)
677 : 0 : ua = NULL;
678 : :
679 [ + - ]: 1392 : if (ua)
680 : 1392 : (void) debuginfod_add_http_header (client, ua);
681 : :
682 : 1392 : free (ua);
683 : 1392 : free (id);
684 : 1392 : free (version);
685 : 1392 : free (utspart);
686 : : }
687 : :
688 : : /* Add HTTP headers found in the given file, one per line. Blank lines or invalid
689 : : * headers are ignored.
690 : : */
691 : : static void
692 : 0 : add_headers_from_file(debuginfod_client *client, const char* filename)
693 : : {
694 : 0 : int vds = client->verbose_fd;
695 : 0 : FILE *f = fopen (filename, "r");
696 [ # # ]: 0 : if (f == NULL)
697 : : {
698 [ # # ]: 0 : if (vds >= 0)
699 : 0 : dprintf(vds, "header file %s: %s\n", filename, strerror(errno));
700 : 0 : return;
701 : : }
702 : :
703 : 0 : while (1)
704 : 0 : {
705 : 0 : char buf[8192];
706 : 0 : char *s = &buf[0];
707 [ # # ]: 0 : if (feof(f))
708 : : break;
709 [ # # ]: 0 : if (fgets (s, sizeof(buf), f) == NULL)
710 : : break;
711 [ # # ]: 0 : for (char *c = s; *c != '\0'; ++c)
712 [ # # ]: 0 : if (!isspace(*c))
713 : 0 : goto nonempty;
714 : 0 : continue;
715 : 0 : nonempty:
716 : 0 : ;
717 : 0 : size_t last = strlen(s)-1;
718 [ # # ]: 0 : if (s[last] == '\n')
719 : 0 : s[last] = '\0';
720 : 0 : int rc = debuginfod_add_http_header(client, s);
721 [ # # ]: 0 : if (rc < 0 && vds >= 0)
722 : 0 : dprintf(vds, "skipping bad header: %s\n", strerror(-rc));
723 : : }
724 : 0 : fclose (f);
725 : : }
726 : :
727 : :
728 : : #define xalloc_str(p, fmt, args...) \
729 : : do \
730 : : { \
731 : : if (asprintf (&p, fmt, args) < 0) \
732 : : { \
733 : : p = NULL; \
734 : : rc = -ENOMEM; \
735 : : goto out; \
736 : : } \
737 : : } while (0)
738 : :
739 : :
740 : : /* Offer a basic form of progress tracing */
741 : : static int
742 : 4 : default_progressfn (debuginfod_client *c, long a, long b)
743 : : {
744 : 4 : const char* url = debuginfod_get_url (c);
745 : 4 : int len = 0;
746 : :
747 : : /* We prefer to print the host part of the URL to keep the
748 : : message short. */
749 : 4 : if (url != NULL)
750 : : {
751 : 2 : const char* buildid = strstr(url, "buildid/");
752 [ + - ]: 2 : if (buildid != NULL)
753 : 2 : len = (buildid - url);
754 : : else
755 : 0 : len = strlen(url);
756 : : }
757 : :
758 [ + + ]: 4 : if (b == 0 || url==NULL) /* early stage */
759 : 6 : dprintf(STDERR_FILENO,
760 : 2 : "\rDownloading %c", "-/|\\"[a % 4]);
761 [ - + ]: 2 : else if (b < 0) /* download in progress but unknown total length */
762 : 0 : dprintf(STDERR_FILENO,
763 : : "\rDownloading from %.*s %ld",
764 : : len, url, a);
765 : : else /* download in progress, and known total length */
766 : 2 : dprintf(STDERR_FILENO,
767 : : "\rDownloading from %.*s %ld/%ld",
768 : : len, url, a, b);
769 : 4 : c->default_progressfn_printed_p = 1;
770 : :
771 : 4 : return 0;
772 : : }
773 : :
774 : : /* This is a callback function that receives http response headers in buffer for use
775 : : * in this program. https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html is the
776 : : * online documentation.
777 : : */
778 : : static size_t
779 : 7285 : header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
780 : : {
781 : 7285 : struct handle_data *data = (struct handle_data *) userdata;
782 [ - + ]: 7285 : if (size != 1)
783 : : return 0;
784 [ + - ]: 7285 : if (data->client
785 [ + + ]: 7285 : && data->client->verbose_fd >= 0
786 [ + + ]: 4911 : && numitems > 2)
787 : 4362 : dprintf (data->client->verbose_fd, "header %.*s", (int)numitems, buffer);
788 : : // Some basic checks to ensure the headers received are of the expected format
789 [ + + ]: 7285 : if (strncasecmp(buffer, "X-DEBUGINFOD", 11)
790 [ + + ]: 1690 : || buffer[numitems-2] != '\r'
791 [ + - ]: 1688 : || buffer[numitems-1] != '\n'
792 [ - + ]: 1688 : || (buffer == strstr(buffer, ":")) ){
793 : : return numitems;
794 : : }
795 : : /* Temporary buffer for realloc */
796 : 1688 : char *temp = NULL;
797 [ + + ]: 1688 : if (data->response_data == NULL)
798 : : {
799 : 761 : temp = malloc(numitems);
800 [ - + ]: 761 : if (temp == NULL)
801 : : return 0;
802 : : }
803 : : else
804 : : {
805 : 927 : temp = realloc(data->response_data, data->response_data_size + numitems);
806 [ - + ]: 927 : if (temp == NULL)
807 : : return 0;
808 : : }
809 : :
810 : 1688 : memcpy(temp + data->response_data_size, buffer, numitems-1);
811 : 1688 : data->response_data = temp;
812 : 1688 : data->response_data_size += numitems-1;
813 : 1688 : data->response_data[data->response_data_size-1] = '\n';
814 : 1688 : data->response_data[data->response_data_size] = '\0';
815 : 1688 : return numitems;
816 : : }
817 : :
818 : : /* Copy SRC to DEST, s,/,#,g */
819 : :
820 : : static void
821 : 1126 : path_escape (const char *src, char *dest)
822 : : {
823 : 1126 : unsigned q = 0;
824 : :
825 [ + - ]: 84516 : for (unsigned fi=0; q < PATH_MAX-2; fi++) /* -2, escape is 2 chars. */
826 [ + + + + ]: 84516 : switch (src[fi])
827 : : {
828 : 1126 : case '\0':
829 : 1126 : dest[q] = '\0';
830 : 1126 : return;
831 : 8562 : case '/': /* escape / to prevent dir escape */
832 : 8562 : dest[q++]='#';
833 : 8562 : dest[q++]='#';
834 : 8562 : break;
835 : 2 : case '#': /* escape # to prevent /# vs #/ collisions */
836 : 2 : dest[q++]='#';
837 : 2 : dest[q++]='_';
838 : 2 : break;
839 : 74826 : default:
840 : 74826 : dest[q++]=src[fi];
841 : : }
842 : :
843 : 0 : dest[q] = '\0';
844 : : }
845 : :
846 : : /* Attempt to update the atime */
847 : : static void
848 : 599 : update_atime (int fd)
849 : : {
850 : 599 : struct timespec tvs[2];
851 : :
852 : 599 : tvs[0].tv_sec = tvs[1].tv_sec = 0;
853 : 599 : tvs[0].tv_nsec = UTIME_NOW;
854 : 599 : tvs[1].tv_nsec = UTIME_OMIT;
855 : :
856 : 599 : (void) futimens (fd, tvs); /* best effort */
857 : 599 : }
858 : :
859 : : /* Attempt to read an ELF/DWARF section with name SECTION from FD and write
860 : : it to a separate file in the debuginfod cache. If successful the absolute
861 : : path of the separate file containing SECTION will be stored in USR_PATH.
862 : : FD_PATH is the absolute path for FD.
863 : :
864 : : If the section cannot be extracted, then return a negative error code.
865 : : -ENOENT indicates that the parent file was able to be read but the
866 : : section name was not found. -EEXIST indicates that the section was
867 : : found but had type SHT_NOBITS. */
868 : :
869 : : static int
870 : 12 : extract_section (int fd, const char *section, char *fd_path, char **usr_path)
871 : : {
872 : 12 : elf_version (EV_CURRENT);
873 : :
874 : 12 : Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
875 [ + - ]: 12 : if (elf == NULL)
876 : : return -EIO;
877 : :
878 : 12 : size_t shstrndx;
879 : 12 : int rc = elf_getshdrstrndx (elf, &shstrndx);
880 [ - + ]: 12 : if (rc < 0)
881 : : {
882 : 0 : rc = -EIO;
883 : 0 : goto out;
884 : : }
885 : :
886 : 12 : int sec_fd = -1;
887 : 12 : char *escaped_name = NULL;
888 : 12 : char *sec_path_tmp = NULL;
889 : 12 : Elf_Scn *scn = NULL;
890 : :
891 : : /* Try to find the target section and copy the contents into a
892 : : separate file. */
893 : 432 : while (true)
894 : 210 : {
895 : 222 : scn = elf_nextscn (elf, scn);
896 [ - + ]: 222 : if (scn == NULL)
897 : : {
898 : 0 : rc = -ENOENT;
899 : 4 : goto out;
900 : : }
901 : 222 : GElf_Shdr shdr_storage;
902 : 222 : GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
903 [ - + ]: 222 : if (shdr == NULL)
904 : : {
905 : 0 : rc = -EIO;
906 : 0 : goto out;
907 : : }
908 : :
909 : 222 : const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
910 [ - + ]: 222 : if (scn_name == NULL)
911 : : {
912 : 0 : rc = -EIO;
913 : 0 : goto out;
914 : : }
915 [ + + ]: 222 : if (strcmp (scn_name, section) == 0)
916 : : {
917 : : /* We found the desired section. */
918 [ + + ]: 12 : if (shdr->sh_type == SHT_NOBITS)
919 : : {
920 : 4 : rc = -EEXIST;
921 : 4 : goto out;
922 : : }
923 : :
924 : 8 : Elf_Data *data = NULL;
925 : 8 : data = elf_rawdata (scn, NULL);
926 [ - + ]: 8 : if (data == NULL)
927 : : {
928 : 0 : rc = -EIO;
929 : 0 : goto out;
930 : : }
931 : :
932 [ - + ]: 8 : if (data->d_buf == NULL)
933 : : {
934 : 0 : rc = -EIO;
935 : 0 : goto out;
936 : : }
937 : :
938 : : /* Compute the absolute filename we'll write the section to.
939 : : Replace the last component of FD_PATH with the path-escaped
940 : : section filename. */
941 : 8 : int i = strlen (fd_path);
942 [ + - ]: 92 : while (i >= 0)
943 : : {
944 [ + + ]: 92 : if (fd_path[i] == '/')
945 : : {
946 : 8 : fd_path[i] = '\0';
947 : 8 : break;
948 : : }
949 : 84 : --i;
950 : : }
951 : :
952 : 8 : escaped_name = malloc (strlen (section) * 2 + 1);
953 [ - + ]: 8 : if (escaped_name == NULL)
954 : : {
955 : 0 : rc = -ENOMEM;
956 : 0 : goto out;
957 : : }
958 : 8 : path_escape (section, escaped_name);
959 : :
960 : 8 : rc = asprintf (&sec_path_tmp, "%s/section-%s.XXXXXX",
961 : : fd_path, escaped_name);
962 [ - + ]: 8 : if (rc == -1)
963 : : {
964 : 0 : rc = -ENOMEM;
965 : 0 : goto out1;
966 : : }
967 : :
968 : 8 : sec_fd = mkstemp (sec_path_tmp);
969 [ - + ]: 8 : if (sec_fd < 0)
970 : : {
971 : 0 : rc = -EIO;
972 : 8 : goto out2;
973 : : }
974 : :
975 : 8 : ssize_t res = write_retry (sec_fd, data->d_buf, data->d_size);
976 [ + - - + ]: 8 : if (res < 0 || (size_t) res != data->d_size)
977 : : {
978 : 0 : rc = -EIO;
979 : 0 : goto out3;
980 : : }
981 : :
982 : : /* Success. Rename tmp file and update USR_PATH. */
983 : 8 : char *sec_path;
984 [ - + ]: 8 : if (asprintf (&sec_path, "%s/section-%s", fd_path, section) == -1)
985 : : {
986 : 0 : rc = -ENOMEM;
987 : 0 : goto out3;
988 : : }
989 : :
990 : 8 : rc = rename (sec_path_tmp, sec_path);
991 [ - + ]: 8 : if (rc < 0)
992 : : {
993 : 0 : free (sec_path);
994 : 0 : rc = -EIO;
995 : 0 : goto out3;
996 : : }
997 : :
998 [ + - ]: 8 : if (usr_path != NULL)
999 : 8 : *usr_path = sec_path;
1000 : : else
1001 : 0 : free (sec_path);
1002 : 8 : update_atime(fd);
1003 : 8 : rc = sec_fd;
1004 : 8 : goto out2;
1005 : : }
1006 : : }
1007 : :
1008 : 0 : out3:
1009 : 0 : close (sec_fd);
1010 : 0 : unlink (sec_path_tmp);
1011 : :
1012 : 8 : out2:
1013 : 8 : free (sec_path_tmp);
1014 : :
1015 : 8 : out1:
1016 : 8 : free (escaped_name);
1017 : :
1018 : 12 : out:
1019 : 12 : elf_end (elf);
1020 : 12 : return rc;
1021 : : }
1022 : :
1023 : : /* Search TARGET_CACHE_DIR for a debuginfo or executable file containing
1024 : : an ELF/DWARF section with name SCN_NAME. If found, extract the section
1025 : : to a separate file in TARGET_CACHE_DIR and return a file descriptor
1026 : : for the section file. The path for this file will be stored in USR_PATH.
1027 : : Return a negative errno if unsuccessful. -ENOENT indicates that SCN_NAME
1028 : : is confirmed to not exist. */
1029 : :
1030 : : static int
1031 : 16 : cache_find_section (const char *scn_name, const char *target_cache_dir,
1032 : : char **usr_path)
1033 : : {
1034 : 16 : int debug_fd;
1035 : 16 : int rc = -EEXIST;
1036 : 16 : char parent_path[PATH_MAX];
1037 : :
1038 : : /* Check the debuginfo first. */
1039 : 16 : snprintf (parent_path, PATH_MAX, "%s/debuginfo", target_cache_dir);
1040 : 16 : debug_fd = open (parent_path, O_RDONLY);
1041 [ + + ]: 16 : if (debug_fd >= 0)
1042 : : {
1043 : 8 : rc = extract_section (debug_fd, scn_name, parent_path, usr_path);
1044 : 8 : close (debug_fd);
1045 : : }
1046 : :
1047 : : /* If the debuginfo file couldn't be found or the section type was
1048 : : SHT_NOBITS, check the executable. */
1049 [ + + ]: 8 : if (rc == -EEXIST)
1050 : : {
1051 : 12 : snprintf (parent_path, PATH_MAX, "%s/executable", target_cache_dir);
1052 : 12 : int exec_fd = open (parent_path, O_RDONLY);
1053 : :
1054 [ + + ]: 12 : if (exec_fd >= 0)
1055 : : {
1056 : 4 : rc = extract_section (exec_fd, scn_name, parent_path, usr_path);
1057 : 4 : close (exec_fd);
1058 : :
1059 : : /* Don't return -ENOENT if the debuginfo wasn't opened. The
1060 : : section may exist in the debuginfo but not the executable. */
1061 [ - + ]: 4 : if (debug_fd < 0 && rc == -ENOENT)
1062 : 0 : rc = -EREMOTE;
1063 : : }
1064 : : }
1065 : :
1066 : 16 : return rc;
1067 : : }
1068 : :
1069 : :
1070 : : #ifdef ENABLE_IMA_VERIFICATION
1071 : : /* Extract the hash algorithm name from the signature header, of which
1072 : : there are several types. The name will be used for openssl hashing
1073 : : of the file content. The header doesn't need to be super carefully
1074 : : parsed, because if any part of it is wrong, be it the hash
1075 : : algorithm number or hash value or whatever, it will fail
1076 : : computation or verification. Return NULL in case of error. */
1077 : : static const char*
1078 : : get_signature_params(debuginfod_client *c, unsigned char *bin_sig)
1079 : : {
1080 : : int hashalgo = 0;
1081 : :
1082 : : switch (bin_sig[0])
1083 : : {
1084 : : case EVM_IMA_XATTR_DIGSIG:
1085 : : #ifdef IMA_VERITY_DIGSIG /* missing on debian-i386 trybot */
1086 : : case IMA_VERITY_DIGSIG:
1087 : : #endif
1088 : : break;
1089 : : default:
1090 : : if (c->verbose_fd >= 0)
1091 : : dprintf (c->verbose_fd, "Unknown ima digsig %d\n", (int)bin_sig[0]);
1092 : : return NULL;
1093 : : }
1094 : :
1095 : : switch (bin_sig[1])
1096 : : {
1097 : : case DIGSIG_VERSION_2:
1098 : : struct signature_v2_hdr hdr_v2;
1099 : : memcpy(& hdr_v2, & bin_sig[1], sizeof(struct signature_v2_hdr));
1100 : : hashalgo = hdr_v2.hash_algo;
1101 : : break;
1102 : : default:
1103 : : if (c->verbose_fd >= 0)
1104 : : dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (int)bin_sig[1]);
1105 : : return NULL;
1106 : : }
1107 : :
1108 : : switch (hashalgo)
1109 : : {
1110 : : case PKEY_HASH_SHA1: return "sha1";
1111 : : case PKEY_HASH_SHA256: return "sha256";
1112 : : // (could add many others from enum pkey_hash_algo)
1113 : : default:
1114 : : if (c->verbose_fd >= 0)
1115 : : dprintf (c->verbose_fd, "Unknown ima pkey hash %d\n", hashalgo);
1116 : : return NULL;
1117 : : }
1118 : : }
1119 : :
1120 : :
1121 : : /* Verify given hash against given signature blob. Return 0 on ok, -errno otherwise. */
1122 : : static int
1123 : : debuginfod_verify_hash(debuginfod_client *c, const unsigned char *hash, int size,
1124 : : const char *hash_algo, unsigned char *sig, int siglen)
1125 : : {
1126 : : int ret = -EBADMSG;
1127 : : struct public_key_entry *pkey;
1128 : : struct signature_v2_hdr hdr;
1129 : : EVP_PKEY_CTX *ctx;
1130 : : const EVP_MD *md;
1131 : :
1132 : : memcpy(&hdr, sig, sizeof(struct signature_v2_hdr)); /* avoid just aliasing */
1133 : :
1134 : : if (c->verbose_fd >= 0)
1135 : : dprintf (c->verbose_fd, "Searching for ima keyid %04x\n", ntohl(hdr.keyid));
1136 : :
1137 : : /* Find the matching public key. */
1138 : : for (pkey = c->ima_public_keys; pkey != NULL; pkey = pkey->next)
1139 : : if (pkey->keyid == ntohl(hdr.keyid)) break;
1140 : : if (!pkey)
1141 : : return -ENOKEY;
1142 : :
1143 : : if (!(ctx = EVP_PKEY_CTX_new(pkey->key, NULL)))
1144 : : goto err;
1145 : : if (!EVP_PKEY_verify_init(ctx))
1146 : : goto err;
1147 : : if (!(md = EVP_get_digestbyname(hash_algo)))
1148 : : goto err;
1149 : : if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
1150 : : goto err;
1151 : : ret = EVP_PKEY_verify(ctx, sig + sizeof(hdr),
1152 : : siglen - sizeof(hdr), hash, size);
1153 : : if (ret == 1)
1154 : : ret = 0; // success!
1155 : : else if (ret == 0)
1156 : : ret = -EBADMSG;
1157 : : err:
1158 : : EVP_PKEY_CTX_free(ctx);
1159 : : return ret;
1160 : : }
1161 : :
1162 : :
1163 : :
1164 : : /* Validate an IMA file signature.
1165 : : * Returns 0 on signature validity, -EINVAL on signature invalidity, -ENOSYS on undefined imaevm machinery,
1166 : : * -ENOKEY on key issues, or other -errno.
1167 : : */
1168 : :
1169 : : static int
1170 : : debuginfod_validate_imasig (debuginfod_client *c, int fd)
1171 : : {
1172 : : int rc = ENOSYS;
1173 : :
1174 : : EVP_MD_CTX *ctx = NULL;
1175 : : if (!c || !c->winning_headers)
1176 : : {
1177 : : rc = -ENODATA;
1178 : : goto exit_validate;
1179 : : }
1180 : : // Extract the HEX IMA-signature from the header
1181 : : char* sig_buf = NULL;
1182 : : char* hdr_ima_sig = strcasestr(c->winning_headers, "x-debuginfod-imasignature");
1183 : : if (!hdr_ima_sig || 1 != sscanf(hdr_ima_sig + strlen("x-debuginfod-imasignature:"), "%ms", &sig_buf))
1184 : : {
1185 : : rc = -ENODATA;
1186 : : goto exit_validate;
1187 : : }
1188 : : if (strlen(sig_buf) > MAX_SIGNATURE_SIZE) // reject if too long
1189 : : {
1190 : : rc = -EBADMSG;
1191 : : goto exit_validate;
1192 : : }
1193 : : // Convert the hex signature to bin
1194 : : size_t bin_sig_len = strlen(sig_buf)/2;
1195 : : unsigned char bin_sig[MAX_SIGNATURE_SIZE/2];
1196 : : for (size_t b = 0; b < bin_sig_len; b++)
1197 : : bin_sig[b] = (hex2dec(sig_buf[2*b]) << 4) | hex2dec(sig_buf[2*b+1]);
1198 : :
1199 : : // Compute the binary digest of the cached file (with file descriptor fd)
1200 : : ctx = EVP_MD_CTX_new();
1201 : : const char* sighash_name = get_signature_params(c, bin_sig) ?: "";
1202 : : const EVP_MD *md = EVP_get_digestbyname(sighash_name);
1203 : : if (!ctx || !md || !EVP_DigestInit(ctx, md))
1204 : : {
1205 : : rc = -EBADMSG;
1206 : : goto exit_validate;
1207 : : }
1208 : :
1209 : : long data_len;
1210 : : char* hdr_data_len = strcasestr(c->winning_headers, "x-debuginfod-size");
1211 : : if (!hdr_data_len || 1 != sscanf(hdr_data_len + strlen("x-debuginfod-size:") , "%ld", &data_len))
1212 : : {
1213 : : rc = -ENODATA;
1214 : : goto exit_validate;
1215 : : }
1216 : :
1217 : : char file_data[DATA_SIZE]; // imaevm.h data chunk hash size
1218 : : ssize_t n;
1219 : : for(off_t k = 0; k < data_len; k += n)
1220 : : {
1221 : : if (-1 == (n = pread(fd, file_data, DATA_SIZE, k)))
1222 : : {
1223 : : rc = -errno;
1224 : : goto exit_validate;
1225 : : }
1226 : :
1227 : : if (!EVP_DigestUpdate(ctx, file_data, n))
1228 : : {
1229 : : rc = -EBADMSG;
1230 : : goto exit_validate;
1231 : : }
1232 : : }
1233 : :
1234 : : uint8_t bin_dig[MAX_DIGEST_SIZE];
1235 : : unsigned int bin_dig_len;
1236 : : if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len))
1237 : : {
1238 : : rc = -EBADMSG;
1239 : : goto exit_validate;
1240 : : }
1241 : :
1242 : : // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo dawg
1243 : :
1244 : : int res = debuginfod_verify_hash(c,
1245 : : bin_dig, bin_dig_len,
1246 : : sighash_name,
1247 : : & bin_sig[1], bin_sig_len-1); // skip over first byte of signature
1248 : : if (c->verbose_fd >= 0)
1249 : : dprintf (c->verbose_fd, "Computed ima signature verification res=%d\n", res);
1250 : : rc = res;
1251 : :
1252 : : exit_validate:
1253 : : free (sig_buf);
1254 : : EVP_MD_CTX_free(ctx);
1255 : : return rc;
1256 : : }
1257 : : #endif /* ENABLE_IMA_VERIFICATION */
1258 : :
1259 : :
1260 : :
1261 : : /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
1262 : : with the specified build-id and type (debuginfo, executable, source or
1263 : : section). If type is source, then type_arg should be a filename. If
1264 : : type is section, then type_arg should be the name of an ELF/DWARF
1265 : : section. Otherwise type_arg may be NULL. Return a file descriptor
1266 : : for the target if successful, otherwise return an error code.
1267 : : */
1268 : : static int
1269 : 6178 : debuginfod_query_server (debuginfod_client *c,
1270 : : const unsigned char *build_id,
1271 : : int build_id_len,
1272 : : const char *type,
1273 : : const char *type_arg,
1274 : : char **path)
1275 : : {
1276 : 6178 : char *server_urls;
1277 : 6178 : char *urls_envvar;
1278 : 6178 : const char *section = NULL;
1279 : 6178 : const char *filename = NULL;
1280 : 6178 : char *cache_path = NULL;
1281 : 6178 : char *maxage_path = NULL;
1282 : 6178 : char *interval_path = NULL;
1283 : 6178 : char *cache_miss_path = NULL;
1284 : 6178 : char *target_cache_dir = NULL;
1285 : 6178 : char *target_cache_path = NULL;
1286 : 6178 : char *target_cache_tmppath = NULL;
1287 : 6178 : char suffix[PATH_MAX + 1]; /* +1 for zero terminator. */
1288 : 6178 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
1289 : 6178 : int vfd = c->verbose_fd;
1290 : 6178 : int rc;
1291 : :
1292 : 6178 : c->progressfn_cancel = false;
1293 : :
1294 [ + + ]: 6178 : if (strcmp (type, "source") == 0)
1295 : : filename = type_arg;
1296 [ + + ]: 904 : else if (strcmp (type, "section") == 0)
1297 : : {
1298 : 16 : section = type_arg;
1299 [ + - ]: 16 : if (section == NULL)
1300 : : return -EINVAL;
1301 : : }
1302 : :
1303 [ + + ]: 6178 : if (vfd >= 0)
1304 : : {
1305 : 1078 : dprintf (vfd, "debuginfod_find_%s ", type);
1306 [ + + ]: 1078 : if (build_id_len == 0) /* expect clean hexadecimal */
1307 : 26 : dprintf (vfd, "%s", (const char *) build_id);
1308 : : else
1309 [ + + ]: 22092 : for (int i = 0; i < build_id_len; i++)
1310 : 21040 : dprintf (vfd, "%02x", build_id[i]);
1311 [ + + ]: 1078 : if (filename != NULL)
1312 : 1044 : dprintf (vfd, " %s\n", filename);
1313 : 1078 : dprintf (vfd, "\n");
1314 : : }
1315 : :
1316 : : /* Is there any server we can query? If not, don't do any work,
1317 : : just return with ENOSYS. Don't even access the cache. */
1318 : 6178 : urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR);
1319 [ + + ]: 6178 : if (vfd >= 0)
1320 [ - + ]: 1078 : dprintf (vfd, "server urls \"%s\"\n",
1321 : : urls_envvar != NULL ? urls_envvar : "");
1322 [ + + + + ]: 6178 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
1323 : : {
1324 : 4238 : rc = -ENOSYS;
1325 : 4238 : goto out;
1326 : : }
1327 : :
1328 : : /* Clear the obsolete data from a previous _find operation. */
1329 : 1940 : free (c->url);
1330 : 1940 : c->url = NULL;
1331 : 1940 : free (c->winning_headers);
1332 : 1940 : c->winning_headers = NULL;
1333 : :
1334 : : /* PR 27982: Add max size if DEBUGINFOD_MAXSIZE is set. */
1335 : 1940 : long maxsize = 0;
1336 : 1940 : const char *maxsize_envvar;
1337 : 1940 : maxsize_envvar = getenv(DEBUGINFOD_MAXSIZE_ENV_VAR);
1338 [ + + ]: 1940 : if (maxsize_envvar != NULL)
1339 : 2 : maxsize = atol (maxsize_envvar);
1340 : :
1341 : : /* PR 27982: Add max time if DEBUGINFOD_MAXTIME is set. */
1342 : 1940 : long maxtime = 0;
1343 : 1940 : const char *maxtime_envvar;
1344 : 1940 : maxtime_envvar = getenv(DEBUGINFOD_MAXTIME_ENV_VAR);
1345 [ + + ]: 1940 : if (maxtime_envvar != NULL)
1346 : 2 : maxtime = atol (maxtime_envvar);
1347 [ + + ]: 1940 : if (maxtime && vfd >= 0)
1348 : 2 : dprintf(vfd, "using max time %lds\n", maxtime);
1349 : :
1350 : 1940 : const char *headers_file_envvar;
1351 : 1940 : headers_file_envvar = getenv(DEBUGINFOD_HEADERS_FILE_ENV_VAR);
1352 [ - + ]: 1940 : if (headers_file_envvar != NULL)
1353 : 0 : add_headers_from_file(c, headers_file_envvar);
1354 : :
1355 : : /* Maxsize is valid*/
1356 [ + + ]: 1940 : if (maxsize > 0)
1357 : : {
1358 [ + - ]: 2 : if (vfd)
1359 : 2 : dprintf (vfd, "using max size %ldB\n", maxsize);
1360 : 2 : char *size_header = NULL;
1361 : 2 : rc = asprintf (&size_header, "X-DEBUGINFOD-MAXSIZE: %ld", maxsize);
1362 [ - + ]: 2 : if (rc < 0)
1363 : : {
1364 : 0 : rc = -ENOMEM;
1365 : 0 : goto out;
1366 : : }
1367 : 2 : rc = debuginfod_add_http_header(c, size_header);
1368 : 2 : free(size_header);
1369 [ - + ]: 2 : if (rc < 0)
1370 : 0 : goto out;
1371 : : }
1372 : 1940 : add_default_headers(c);
1373 : :
1374 : : /* Copy lowercase hex representation of build_id into buf. */
1375 [ + + ]: 1940 : if (vfd >= 0)
1376 : 1078 : dprintf (vfd, "checking build-id\n");
1377 [ + - + + ]: 1940 : if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
1378 : 886 : (build_id_len == 0 &&
1379 [ - + ]: 886 : strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
1380 : : {
1381 : 0 : rc = -EINVAL;
1382 : 0 : goto out;
1383 : : }
1384 : :
1385 [ + + ]: 1940 : if (build_id_len == 0) /* expect clean hexadecimal */
1386 : 886 : strcpy (build_id_bytes, (const char *) build_id);
1387 : : else
1388 [ + + ]: 22134 : for (int i = 0; i < build_id_len; i++)
1389 : 21080 : sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
1390 : :
1391 [ + + ]: 1940 : if (filename != NULL)
1392 : : {
1393 [ + + ]: 1102 : if (vfd >= 0)
1394 : 1044 : dprintf (vfd, "checking filename\n");
1395 [ - + ]: 1102 : if (filename[0] != '/') // must start with /
1396 : : {
1397 : 0 : rc = -EINVAL;
1398 : 0 : goto out;
1399 : : }
1400 : :
1401 : 1102 : path_escape (filename, suffix);
1402 : : /* If the DWARF filenames are super long, this could exceed
1403 : : PATH_MAX and truncate/collide. Oh well, that'll teach
1404 : : them! */
1405 : : }
1406 [ + + ]: 838 : else if (section != NULL)
1407 : 16 : path_escape (section, suffix);
1408 : : else
1409 : 822 : suffix[0] = '\0';
1410 : :
1411 [ + + + + ]: 1940 : if (suffix[0] != '\0' && vfd >= 0)
1412 : 1060 : dprintf (vfd, "suffix %s\n", suffix);
1413 : :
1414 : : /* set paths needed to perform the query
1415 : :
1416 : : example format
1417 : : cache_path: $HOME/.cache
1418 : : target_cache_dir: $HOME/.cache/0123abcd
1419 : : target_cache_path: $HOME/.cache/0123abcd/debuginfo
1420 : : target_cache_path: $HOME/.cache/0123abcd/source#PATH#TO#SOURCE ?
1421 : :
1422 : : $XDG_CACHE_HOME takes priority over $HOME/.cache.
1423 : : $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
1424 : : */
1425 : :
1426 : : /* Determine location of the cache. The path specified by the debuginfod
1427 : : cache environment variable takes priority. */
1428 : 1940 : char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
1429 [ + + + + ]: 1940 : if (cache_var != NULL && strlen (cache_var) > 0)
1430 [ - + ]: 890 : xalloc_str (cache_path, "%s", cache_var);
1431 : : else
1432 : : {
1433 : : /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
1434 : : that. Otherwise use the XDG cache directory naming format. */
1435 [ - + - + ]: 2100 : xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
1436 : :
1437 : 1050 : struct stat st;
1438 [ + + ]: 1050 : if (stat (cache_path, &st) < 0)
1439 : : {
1440 : 1048 : char cachedir[PATH_MAX];
1441 : 1048 : char *xdg = getenv ("XDG_CACHE_HOME");
1442 : :
1443 [ + + + + ]: 1048 : if (xdg != NULL && strlen (xdg) > 0)
1444 : 2 : snprintf (cachedir, PATH_MAX, "%s", xdg);
1445 : : else
1446 [ - + ]: 1046 : snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
1447 : :
1448 : : /* Create XDG cache directory if it doesn't exist. */
1449 [ + + ]: 1048 : if (stat (cachedir, &st) == 0)
1450 : : {
1451 [ - + ]: 1043 : if (! S_ISDIR (st.st_mode))
1452 : : {
1453 : 0 : rc = -EEXIST;
1454 : 0 : goto out;
1455 : : }
1456 : : }
1457 : : else
1458 : : {
1459 : 5 : rc = mkdir (cachedir, 0700);
1460 : :
1461 : : /* Also check for EEXIST and S_ISDIR in case another client just
1462 : : happened to create the cache. */
1463 [ - + ]: 5 : if (rc < 0
1464 [ # # ]: 0 : && (errno != EEXIST
1465 [ # # ]: 0 : || stat (cachedir, &st) != 0
1466 [ # # ]: 0 : || ! S_ISDIR (st.st_mode)))
1467 : : {
1468 : 0 : rc = -errno;
1469 : 0 : goto out;
1470 : : }
1471 : : }
1472 : :
1473 : 1048 : free (cache_path);
1474 [ - + ]: 1048 : xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
1475 : : }
1476 : : }
1477 : :
1478 [ - + ]: 1940 : xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
1479 [ + + ]: 1940 : if (section != NULL)
1480 [ - + ]: 16 : xalloc_str (target_cache_path, "%s/%s-%s", target_cache_dir, type, suffix);
1481 : : else
1482 [ - + ]: 1924 : xalloc_str (target_cache_path, "%s/%s%s", target_cache_dir, type, suffix);
1483 [ - + ]: 1940 : xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
1484 : :
1485 : : /* XXX combine these */
1486 [ - + ]: 1940 : xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
1487 [ - + ]: 1940 : xalloc_str (cache_miss_path, "%s/%s", cache_path, cache_miss_filename);
1488 [ - + ]: 1940 : xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
1489 : :
1490 [ + + ]: 1940 : if (vfd >= 0)
1491 : 1078 : dprintf (vfd, "checking cache dir %s\n", cache_path);
1492 : :
1493 : : /* Make sure cache dir exists. debuginfo_clean_cache will then make
1494 : : sure the interval, cache_miss and maxage files exist. */
1495 [ + + ]: 1940 : if (mkdir (cache_path, ACCESSPERMS) != 0
1496 [ - + ]: 1879 : && errno != EEXIST)
1497 : : {
1498 : 0 : rc = -errno;
1499 : 0 : goto out;
1500 : : }
1501 : :
1502 : 1940 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
1503 [ - + ]: 1940 : if (rc != 0)
1504 : 0 : goto out;
1505 : :
1506 : : /* Check if the target is already in the cache. */
1507 : 1940 : int fd = open(target_cache_path, O_RDONLY);
1508 [ + + ]: 1940 : if (fd >= 0)
1509 : : {
1510 : 595 : struct stat st;
1511 [ - + ]: 595 : if (fstat(fd, &st) != 0)
1512 : : {
1513 : 0 : rc = -errno;
1514 : 0 : close (fd);
1515 : 593 : goto out;
1516 : : }
1517 : :
1518 : : /* If the file is non-empty, then we are done. */
1519 [ + + ]: 595 : if (st.st_size > 0)
1520 : : {
1521 [ + + ]: 591 : if (path != NULL)
1522 : : {
1523 : 70 : *path = strdup(target_cache_path);
1524 [ - + ]: 70 : if (*path == NULL)
1525 : : {
1526 : 0 : rc = -errno;
1527 : 0 : close (fd);
1528 : 0 : goto out;
1529 : : }
1530 : : }
1531 : : /* Success!!!! */
1532 : 591 : update_atime(fd);
1533 : 591 : rc = fd;
1534 : 591 : goto out;
1535 : : }
1536 : : else
1537 : : {
1538 : : /* The file is empty. Attempt to download only if enough time
1539 : : has passed since the last attempt. */
1540 : 4 : time_t cache_miss;
1541 : 4 : time_t target_mtime = st.st_mtime;
1542 : :
1543 : 4 : close(fd); /* no need to hold onto the negative-hit file descriptor */
1544 : :
1545 : 4 : rc = debuginfod_config_cache(c, cache_miss_path,
1546 : : cache_miss_default_s, &st);
1547 [ - + ]: 4 : if (rc < 0)
1548 : 0 : goto out;
1549 : :
1550 : 4 : cache_miss = (time_t)rc;
1551 [ + + ]: 4 : if (time(NULL) - target_mtime <= cache_miss)
1552 : : {
1553 : 2 : rc = -ENOENT;
1554 : 2 : goto out;
1555 : : }
1556 : : else
1557 : : /* TOCTOU non-problem: if another task races, puts a working
1558 : : download or an empty file in its place, unlinking here just
1559 : : means WE will try to download again as uncached. */
1560 : 2 : unlink(target_cache_path);
1561 : : }
1562 : : }
1563 [ - + ]: 1345 : else if (errno == EACCES)
1564 : : /* Ensure old 000-permission files are not lingering in the cache. */
1565 : 0 : unlink(target_cache_path);
1566 : :
1567 [ + + ]: 1347 : if (section != NULL)
1568 : : {
1569 : : /* Try to extract the section from a cached file before querying
1570 : : any servers. */
1571 : 16 : rc = cache_find_section (section, target_cache_dir, path);
1572 : :
1573 : : /* If the section was found or confirmed to not exist, then we
1574 : : are done. */
1575 [ + + ]: 16 : if (rc >= 0 || rc == -ENOENT)
1576 : 8 : goto out;
1577 : : }
1578 : :
1579 : 1339 : long timeout = default_timeout;
1580 : 1339 : const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
1581 [ - + ]: 1339 : if (timeout_envvar != NULL)
1582 : 0 : timeout = atoi (timeout_envvar);
1583 : :
1584 [ + + ]: 1339 : if (vfd >= 0)
1585 : 549 : dprintf (vfd, "using timeout %ld\n", timeout);
1586 : :
1587 : : /* make a copy of the envvar so it can be safely modified. */
1588 : 1339 : server_urls = strdup(urls_envvar);
1589 [ - + ]: 1339 : if (server_urls == NULL)
1590 : : {
1591 : 0 : rc = -ENOMEM;
1592 : 0 : goto out;
1593 : : }
1594 : : /* thereafter, goto out0 on error*/
1595 : :
1596 : : /* Because of a race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
1597 [ + - ]: 1339 : for(int i=0; i<2; i++) {
1598 : : /* (re)create target directory in cache */
1599 : 1339 : (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
1600 : :
1601 : : /* NB: write to a temporary file first, to avoid race condition of
1602 : : multiple clients checking the cache, while a partially-written or empty
1603 : : file is in there, being written from libcurl. */
1604 : 1339 : fd = mkstemp (target_cache_tmppath);
1605 [ - + ]: 1339 : if (fd >= 0) break;
1606 : : }
1607 [ - + ]: 1339 : if (fd < 0) /* Still failed after two iterations. */
1608 : : {
1609 : 0 : rc = -errno;
1610 : 0 : goto out0;
1611 : : }
1612 : :
1613 : : /* Initialize the memory to zero */
1614 : 1339 : char *strtok_saveptr;
1615 : 1339 : char **server_url_list = NULL;
1616 : 1339 : ima_policy_t* url_ima_policies = NULL;
1617 : 1339 : char* server_url;
1618 : : /* Count number of URLs. */
1619 : 1339 : int num_urls = 0;
1620 : :
1621 : 1339 : ima_policy_t verification_mode = ignore; // The default mode
1622 : 1339 : for(server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
1623 [ + + ]: 2732 : server_url != NULL; server_url = strtok_r(NULL, url_delim, &strtok_saveptr))
1624 : : {
1625 : : // When we encounted a (well-formed) token off the form ima:foo, we update the policy
1626 : : // under which results from that server will be ima verified
1627 [ - + ]: 1393 : if(startswith(server_url, "ima:"))
1628 : : {
1629 : : #ifdef ENABLE_IMA_VERIFICATION
1630 : : ima_policy_t m = ima_policy_str2enum(server_url + strlen("ima:"));
1631 : : if(m != undefined)
1632 : : verification_mode = m;
1633 : : else if (vfd >= 0)
1634 : : dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_url);
1635 : : #else
1636 [ # # ]: 0 : if (vfd >= 0)
1637 : 0 : dprintf(vfd, "IMA signature verification is not enabled, skipping %s\n", server_url);
1638 : : #endif
1639 : 0 : continue; // Not a url, just a mode change so keep going
1640 : : }
1641 : :
1642 : 1393 : if (verification_mode==enforcing && 0==strcmp(type,"section"))
1643 : : {
1644 : : if (vfd >= 0)
1645 : : dprintf(vfd, "skipping server %s section query in IMA enforcing mode\n", server_url);
1646 : : continue;
1647 : : }
1648 : :
1649 : : /* PR 27983: If the url is already set to be used use, skip it */
1650 : 1393 : char *slashbuildid;
1651 [ + - + + ]: 1393 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
1652 : : slashbuildid = "buildid";
1653 : : else
1654 : 812 : slashbuildid = "/buildid";
1655 : :
1656 : 1393 : char *tmp_url;
1657 [ - + ]: 1393 : if (asprintf(&tmp_url, "%s%s", server_url, slashbuildid) == -1)
1658 : : {
1659 : 0 : rc = -ENOMEM;
1660 : 0 : goto out1;
1661 : : }
1662 : : int url_index;
1663 [ + + ]: 1515 : for (url_index = 0; url_index < num_urls; ++url_index)
1664 : : {
1665 [ + + ]: 126 : if(strcmp(tmp_url, server_url_list[url_index]) == 0)
1666 : : {
1667 : : url_index = -1;
1668 : : break;
1669 : : }
1670 : : }
1671 [ + + ]: 1393 : if (url_index == -1)
1672 : : {
1673 [ + - ]: 4 : if (vfd >= 0)
1674 : 4 : dprintf(vfd, "duplicate url: %s, skipping\n", tmp_url);
1675 : 4 : free(tmp_url);
1676 : : }
1677 : : else
1678 : : {
1679 : 1389 : num_urls++;
1680 [ - + ]: 1389 : if (NULL == (server_url_list = reallocarray(server_url_list, num_urls, sizeof(char*)))
1681 : : #ifdef ENABLE_IMA_VERIFICATION
1682 : : || NULL == (url_ima_policies = reallocarray(url_ima_policies, num_urls, sizeof(ima_policy_t)))
1683 : : #endif
1684 : : )
1685 : : {
1686 : 0 : free (tmp_url);
1687 : 0 : rc = -ENOMEM;
1688 : 0 : goto out1;
1689 : : }
1690 : 1389 : server_url_list[num_urls-1] = tmp_url;
1691 : 1389 : if(NULL != url_ima_policies) url_ima_policies[num_urls-1] = verification_mode;
1692 : : }
1693 : : }
1694 : :
1695 : : /* No URLs survived parsing / filtering? Abort abort abort. */
1696 [ - + ]: 1339 : if (num_urls == 0)
1697 : : {
1698 : 0 : rc = -ENOSYS;
1699 : 0 : goto out1;
1700 : : }
1701 : :
1702 : 1339 : int retry_limit = default_retry_limit;
1703 : 1339 : const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR);
1704 [ + + ]: 1339 : if (retry_limit_envvar != NULL)
1705 : 4 : retry_limit = atoi (retry_limit_envvar);
1706 : :
1707 : 1339 : CURLM *curlm = c->server_mhandle;
1708 [ - + ]: 1339 : assert (curlm != NULL);
1709 : :
1710 : : /* Tracks which handle should write to fd. Set to the first
1711 : : handle that is ready to write the target file to the cache. */
1712 : 1339 : CURL *target_handle = NULL;
1713 : 1339 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
1714 [ - + ]: 1339 : if (data == NULL)
1715 : : {
1716 : 0 : rc = -ENOMEM;
1717 : 0 : goto out1;
1718 : : }
1719 : :
1720 : : /* thereafter, goto out2 on error. */
1721 : :
1722 : : /*The beginning of goto block query_in_parallel.*/
1723 : 1339 : query_in_parallel:
1724 : 2427 : rc = -ENOENT; /* Reset rc to default.*/
1725 : :
1726 : : /* Initialize handle_data with default values. */
1727 [ + + ]: 4908 : for (int i = 0; i < num_urls; i++)
1728 : : {
1729 : 2481 : data[i].handle = NULL;
1730 : 2481 : data[i].fd = -1;
1731 : 2481 : data[i].errbuf[0] = '\0';
1732 : 2481 : data[i].response_data = NULL;
1733 : 2481 : data[i].response_data_size = 0;
1734 : : }
1735 : :
1736 : 2427 : char *escaped_string = NULL;
1737 : 2427 : size_t escaped_strlen = 0;
1738 [ + + ]: 2427 : if (filename)
1739 : : {
1740 : 579 : escaped_string = curl_easy_escape(&target_handle, filename+1, 0);
1741 [ - + ]: 579 : if (!escaped_string)
1742 : : {
1743 : 0 : rc = -ENOMEM;
1744 : 0 : goto out2;
1745 : : }
1746 : 579 : char *loc = escaped_string;
1747 : 579 : escaped_strlen = strlen(escaped_string);
1748 [ + + ]: 4467 : while ((loc = strstr(loc, "%2F")))
1749 : : {
1750 : 3888 : loc[0] = '/';
1751 : : //pull the string back after replacement
1752 : : // loc-escaped_string finds the distance from the origin to the new location
1753 : : // - 2 accounts for the 2F which remain and don't need to be measured.
1754 : : // The two above subtracted from escaped_strlen yields the remaining characters
1755 : : // in the string which we want to pull back
1756 : 3888 : memmove(loc+1, loc+3,escaped_strlen - (loc-escaped_string) - 2);
1757 : : //Because the 2F was overwritten in the memmove (as desired) escaped_strlen is
1758 : : // now two shorter.
1759 : 3888 : escaped_strlen -= 2;
1760 : : }
1761 : : }
1762 : : /* Initialize each handle. */
1763 [ + + ]: 4908 : for (int i = 0; i < num_urls; i++)
1764 : : {
1765 [ + - ]: 2481 : if ((server_url = server_url_list[i]) == NULL)
1766 : : break;
1767 [ + + ]: 2481 : if (vfd >= 0)
1768 : : #ifdef ENABLE_IMA_VERIFICATION
1769 : : dprintf (vfd, "init server %d %s [IMA verification policy: %s]\n", i, server_url, ima_policy_enum2str(url_ima_policies[i]));
1770 : : #else
1771 : 579 : dprintf (vfd, "init server %d %s\n", i, server_url);
1772 : : #endif
1773 : :
1774 : 2481 : data[i].fd = fd;
1775 : 2481 : data[i].target_handle = &target_handle;
1776 : 2481 : data[i].handle = curl_easy_init();
1777 [ - + ]: 2481 : if (data[i].handle == NULL)
1778 : : {
1779 [ # # ]: 0 : if (filename) curl_free (escaped_string);
1780 : 0 : rc = -ENETUNREACH;
1781 : 0 : goto out2;
1782 : : }
1783 : 2481 : data[i].client = c;
1784 : :
1785 [ + + ]: 2481 : if (filename) /* must start with / */
1786 : : {
1787 : : /* PR28034 escape characters in completed url to %hh format. */
1788 : 579 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
1789 : : build_id_bytes, type, escaped_string);
1790 : : }
1791 [ + + ]: 1902 : else if (section)
1792 : 8 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
1793 : : build_id_bytes, type, section);
1794 : : else
1795 : 1894 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
1796 [ + + ]: 2481 : if (vfd >= 0)
1797 : 579 : dprintf (vfd, "url %d %s\n", i, data[i].url);
1798 : :
1799 : : /* Some boilerplate for checking curl_easy_setopt. */
1800 : : #define curl_easy_setopt_ck(H,O,P) do { \
1801 : : CURLcode curl_res = curl_easy_setopt (H,O,P); \
1802 : : if (curl_res != CURLE_OK) \
1803 : : { \
1804 : : if (vfd >= 0) \
1805 : : dprintf (vfd, \
1806 : : "Bad curl_easy_setopt: %s\n", \
1807 : : curl_easy_strerror(curl_res)); \
1808 : : rc = -EINVAL; \
1809 : : goto out2; \
1810 : : } \
1811 : : } while (0)
1812 : :
1813 : : /* Only allow http:// + https:// + file:// so we aren't being
1814 : : redirected to some unsupported protocol.
1815 : : libcurl will fail if we request a single protocol that is not
1816 : : available. https missing is the most likely issue */
1817 : : #if CURL_AT_LEAST_VERSION(7, 85, 0)
1818 [ - + - + : 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_PROTOCOLS_STR,
- - ]
1819 : : curl_has_https ? "https,http,file" : "http,file");
1820 : : #else
1821 : : curl_easy_setopt_ck(data[i].handle, CURLOPT_PROTOCOLS,
1822 : : ((curl_has_https ? CURLPROTO_HTTPS : 0) | CURLPROTO_HTTP | CURLPROTO_FILE));
1823 : : #endif
1824 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_URL, data[i].url);
1825 [ + + ]: 2481 : if (vfd >= 0)
1826 [ - + ]: 579 : curl_easy_setopt_ck(data[i].handle, CURLOPT_ERRORBUFFER,
1827 : : data[i].errbuf);
1828 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle,
1829 : : CURLOPT_WRITEFUNCTION,
1830 : : debuginfod_write_callback);
1831 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_WRITEDATA, (void*)&data[i]);
1832 [ + - ]: 2481 : if (timeout > 0)
1833 : : {
1834 : : /* Make sure there is at least some progress,
1835 : : try to get at least 100K per timeout seconds. */
1836 [ - + - - ]: 2481 : curl_easy_setopt_ck (data[i].handle, CURLOPT_LOW_SPEED_TIME,
1837 : : timeout);
1838 [ - + - - ]: 2481 : curl_easy_setopt_ck (data[i].handle, CURLOPT_LOW_SPEED_LIMIT,
1839 : : 100 * 1024L);
1840 : : }
1841 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_FILETIME, (long) 1);
1842 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_FOLLOWLOCATION, (long) 1);
1843 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_FAILONERROR, (long) 1);
1844 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_NOSIGNAL, (long) 1);
1845 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_HEADERFUNCTION,
1846 : : header_callback);
1847 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_HEADERDATA,
1848 : : (void *) &(data[i]));
1849 : : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
1850 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_PATH_AS_IS, (long) 1);
1851 : : #else
1852 : : /* On old curl; no big deal, canonicalization here is almost the
1853 : : same, except perhaps for ? # type decorations at the tail. */
1854 : : #endif
1855 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_AUTOREFERER, (long) 1);
1856 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_ACCEPT_ENCODING, "");
1857 [ - + - - ]: 2481 : curl_easy_setopt_ck(data[i].handle, CURLOPT_HTTPHEADER, c->headers);
1858 : :
1859 : 2481 : curl_multi_add_handle(curlm, data[i].handle);
1860 : : }
1861 : :
1862 [ + + ]: 2427 : if (filename) curl_free(escaped_string);
1863 : : /* Query servers in parallel. */
1864 [ + + ]: 2427 : if (vfd >= 0)
1865 : 573 : dprintf (vfd, "query %d urls in parallel\n", num_urls);
1866 : 2427 : int still_running;
1867 : 2427 : long loops = 0;
1868 : 2427 : int committed_to = -1;
1869 : 2427 : bool verbose_reported = false;
1870 : 2427 : struct timespec start_time, cur_time;
1871 : :
1872 : 2427 : free (c->winning_headers);
1873 : 2427 : c->winning_headers = NULL;
1874 [ + + - + ]: 2427 : if ( maxtime > 0 && clock_gettime(CLOCK_MONOTONIC_RAW, &start_time) == -1)
1875 : : {
1876 : 0 : rc = -errno;
1877 : 0 : goto out2;
1878 : : }
1879 : 5054 : long delta = 0;
1880 : 5054 : do
1881 : : {
1882 : : /* Check to see how long querying is taking. */
1883 [ + + ]: 5054 : if (maxtime > 0)
1884 : : {
1885 [ - + ]: 4 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time) == -1)
1886 : : {
1887 : 0 : rc = -errno;
1888 : 0 : goto out2;
1889 : : }
1890 : 4 : delta = cur_time.tv_sec - start_time.tv_sec;
1891 [ - + ]: 4 : if ( delta > maxtime)
1892 : : {
1893 : 0 : dprintf(vfd, "Timeout with max time=%lds and transfer time=%lds\n", maxtime, delta );
1894 : 0 : rc = -ETIME;
1895 : 0 : goto out2;
1896 : : }
1897 : : }
1898 : : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
1899 : 5054 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
1900 : 5054 : CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
1901 : :
1902 : : /* If the target file has been found, abort the other queries. */
1903 [ + + ]: 5054 : if (target_handle != NULL)
1904 : : {
1905 [ + + ]: 1804 : for (int i = 0; i < num_urls; i++)
1906 [ + + ]: 986 : if (data[i].handle != target_handle)
1907 : 168 : curl_multi_remove_handle(curlm, data[i].handle);
1908 : : else
1909 : : {
1910 : 818 : committed_to = i;
1911 [ + + ]: 818 : if (c->winning_headers == NULL)
1912 : : {
1913 : 763 : c->winning_headers = data[committed_to].response_data;
1914 : 763 : data[committed_to].response_data = NULL;
1915 : 763 : data[committed_to].response_data_size = 0;
1916 : : }
1917 : :
1918 : : }
1919 : : }
1920 : :
1921 [ + + + + ]: 5054 : if (vfd >= 0 && !verbose_reported && committed_to >= 0)
1922 : : {
1923 [ - + - - ]: 1078 : bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
1924 : 539 : dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
1925 : : committed_to);
1926 [ - + ]: 539 : if (pnl)
1927 : 0 : c->default_progressfn_printed_p = 0;
1928 : : verbose_reported = true;
1929 : : }
1930 : :
1931 [ - + ]: 5054 : if (curlm_res != CURLM_OK)
1932 : : {
1933 [ # # # ]: 0 : switch (curlm_res)
1934 : : {
1935 : 0 : case CURLM_CALL_MULTI_PERFORM: continue;
1936 : : case CURLM_OUT_OF_MEMORY: rc = -ENOMEM; break;
1937 : 0 : default: rc = -ENETUNREACH; break;
1938 : : }
1939 : 0 : goto out2;
1940 : : }
1941 : :
1942 : 5054 : long dl_size = -1;
1943 [ + + + + : 5054 : if (target_handle && (c->progressfn || maxsize > 0))
- + ]
1944 : : {
1945 : : /* Get size of file being downloaded. NB: If going through
1946 : : deflate-compressing proxies, this number is likely to be
1947 : : unavailable, so -1 may show. */
1948 : 26 : CURLcode curl_res;
1949 : : #if CURL_AT_LEAST_VERSION(7, 55, 0)
1950 : 26 : curl_off_t cl;
1951 : 26 : curl_res = curl_easy_getinfo(target_handle,
1952 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
1953 : : &cl);
1954 [ + - - + ]: 26 : if (curl_res == CURLE_OK && cl >= 0)
1955 : : dl_size = (cl > LONG_MAX ? LONG_MAX : (long)cl);
1956 : : #else
1957 : : double cl;
1958 : : curl_res = curl_easy_getinfo(target_handle,
1959 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD,
1960 : : &cl);
1961 : : if (curl_res == CURLE_OK && cl >= 0)
1962 : : dl_size = (cl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)cl);
1963 : : #endif
1964 : : /* If Content-Length is -1, try to get the size from
1965 : : X-Debuginfod-Size */
1966 [ # # ]: 0 : if (dl_size == -1 && c->winning_headers != NULL)
1967 : : {
1968 : 0 : long xdl;
1969 : 0 : char *hdr = strcasestr(c->winning_headers, "x-debuginfod-size");
1970 : 0 : size_t off = strlen("x-debuginfod-size:");
1971 : :
1972 [ # # # # ]: 0 : if (hdr != NULL && sscanf(hdr + off, "%ld", &xdl) == 1)
1973 : 0 : dl_size = xdl;
1974 : : }
1975 : : }
1976 : :
1977 [ + + ]: 5054 : if (c->progressfn) /* inform/check progress callback */
1978 : : {
1979 : 3318 : loops ++;
1980 : 3318 : long pa = loops; /* default param for progress callback */
1981 [ + + ]: 3318 : if (target_handle) /* we've committed to a server; report its download progress */
1982 : : {
1983 : : /* PR30809: Check actual size of cached file. This same
1984 : : fd is shared by all the multi-curl handles (but only
1985 : : one will end up writing to it). Another way could be
1986 : : to tabulate totals in debuginfod_write_callback(). */
1987 : 26 : struct stat cached;
1988 : 26 : int statrc = fstat(fd, &cached);
1989 [ + - ]: 26 : if (statrc == 0)
1990 : 26 : pa = (long) cached.st_size;
1991 : : else
1992 : : {
1993 : : /* Otherwise, query libcurl for its tabulated total.
1994 : : However, that counts http body length, not
1995 : : decoded/decompressed content length, so does not
1996 : : measure quite the same thing as dl. */
1997 : 0 : CURLcode curl_res;
1998 : : #if CURL_AT_LEAST_VERSION(7, 55, 0)
1999 : 0 : curl_off_t dl;
2000 : 0 : curl_res = curl_easy_getinfo(target_handle,
2001 : : CURLINFO_SIZE_DOWNLOAD_T,
2002 : : &dl);
2003 [ # # # # ]: 0 : if (curl_res == 0 && dl >= 0)
2004 : 0 : pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
2005 : : #else
2006 : : double dl;
2007 : : curl_res = curl_easy_getinfo(target_handle,
2008 : : CURLINFO_SIZE_DOWNLOAD,
2009 : : &dl);
2010 : : if (curl_res == 0)
2011 : : pa = (dl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)dl);
2012 : : #endif
2013 : : }
2014 : : }
2015 : :
2016 [ + + - + ]: 6610 : if ((*c->progressfn) (c, pa, dl_size == -1 ? 0 : dl_size))
2017 : : {
2018 : 0 : c->progressfn_cancel = true;
2019 : 0 : break;
2020 : : }
2021 : : }
2022 : :
2023 : : /* Check to see if we are downloading something which exceeds maxsize, if set.*/
2024 [ + + - + ]: 5054 : if (target_handle && dl_size > maxsize && maxsize > 0)
2025 : : {
2026 [ # # ]: 0 : if (vfd >=0)
2027 : 0 : dprintf(vfd, "Content-Length too large.\n");
2028 : 0 : rc = -EFBIG;
2029 : 0 : goto out2;
2030 : : }
2031 [ + + ]: 5054 : } while (still_running);
2032 : :
2033 : : /* Check whether a query was successful. If so, assign its handle
2034 : : to verified_handle. */
2035 : : int num_msg;
2036 : : rc = -ENOENT;
2037 : 2433 : CURL *verified_handle = NULL;
2038 : 2433 : do
2039 : : {
2040 : 2433 : CURLMsg *msg;
2041 : :
2042 : 2433 : msg = curl_multi_info_read(curlm, &num_msg);
2043 [ + - + - ]: 2433 : if (msg != NULL && msg->msg == CURLMSG_DONE)
2044 : : {
2045 [ + + ]: 2433 : if (vfd >= 0)
2046 : : {
2047 : 1158 : bool pnl = (c->default_progressfn_printed_p
2048 [ - + - - ]: 579 : && vfd == STDERR_FILENO);
2049 [ + - ]: 579 : dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
2050 : : curl_easy_strerror (msg->data.result));
2051 [ - + ]: 579 : if (pnl)
2052 : 0 : c->default_progressfn_printed_p = 0;
2053 [ + - ]: 585 : for (int i = 0; i < num_urls; i++)
2054 [ + + ]: 585 : if (msg->easy_handle == data[i].handle)
2055 : : {
2056 [ + + ]: 579 : if (strlen (data[i].errbuf) > 0)
2057 : 38 : dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
2058 : : break;
2059 : : }
2060 : : }
2061 : :
2062 [ + + ]: 2433 : if (msg->data.result != CURLE_OK)
2063 : : {
2064 : 1670 : long resp_code;
2065 : 1670 : CURLcode ok0;
2066 : : /* Unsuccessful query, determine error code. */
2067 [ - + - - : 1670 : switch (msg->data.result)
- - - - +
+ - ]
2068 : : {
2069 : : case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
2070 : 0 : case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
2071 : 1624 : case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
2072 : 1624 : case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
2073 : 0 : case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
2074 : 0 : case CURLE_WRITE_ERROR: rc = -EIO; break;
2075 : 0 : case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
2076 : 0 : case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
2077 : 0 : case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
2078 : 0 : case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
2079 : 0 : case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
2080 : : case CURLE_HTTP_RETURNED_ERROR:
2081 : 44 : ok0 = curl_easy_getinfo (msg->easy_handle,
2082 : : CURLINFO_RESPONSE_CODE,
2083 : : &resp_code);
2084 : : /* 406 signals that the requested file was too large */
2085 [ + - + + ]: 44 : if ( ok0 == CURLE_OK && resp_code == 406)
2086 : : rc = -EFBIG;
2087 [ - + - - ]: 42 : else if (section != NULL && resp_code == 503)
2088 : : rc = -EINVAL;
2089 : : else
2090 : 44 : rc = -ENOENT;
2091 : : break;
2092 : 44 : default: rc = -ENOENT; break;
2093 : : }
2094 : : }
2095 : : else
2096 : : {
2097 : : /* Query completed without an error. Confirm that the
2098 : : response code is 200 when using HTTP/HTTPS and 0 when
2099 : : using file:// and set verified_handle. */
2100 : :
2101 [ + - ]: 763 : if (msg->easy_handle != NULL)
2102 : : {
2103 : 763 : char *effective_url = NULL;
2104 : 763 : long resp_code = 500;
2105 : 763 : CURLcode ok1 = curl_easy_getinfo (target_handle,
2106 : : CURLINFO_EFFECTIVE_URL,
2107 : : &effective_url);
2108 : 763 : CURLcode ok2 = curl_easy_getinfo (target_handle,
2109 : : CURLINFO_RESPONSE_CODE,
2110 : : &resp_code);
2111 [ + - + - ]: 763 : if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
2112 : : {
2113 [ + + ]: 763 : if (strncasecmp (effective_url, "HTTP", 4) == 0)
2114 [ + - ]: 761 : if (resp_code == 200)
2115 : : {
2116 : 761 : verified_handle = msg->easy_handle;
2117 : 763 : break;
2118 : : }
2119 [ + - ]: 2 : if (strncasecmp (effective_url, "FILE", 4) == 0)
2120 [ + - ]: 2 : if (resp_code == 0)
2121 : : {
2122 : 2 : verified_handle = msg->easy_handle;
2123 : 2 : break;
2124 : : }
2125 : : }
2126 : : /* - libcurl since 7.52.0 version start to support
2127 : : CURLINFO_SCHEME;
2128 : : - before 7.61.0, effective_url would give us a
2129 : : url with upper case SCHEME added in the front;
2130 : : - effective_url between 7.61 and 7.69 can be lack
2131 : : of scheme if the original url doesn't include one;
2132 : : - since version 7.69 effective_url will be provide
2133 : : a scheme in lower case. */
2134 : : #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
2135 : : #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
2136 : : char *scheme = NULL;
2137 : : CURLcode ok3 = curl_easy_getinfo (target_handle,
2138 : : CURLINFO_SCHEME,
2139 : : &scheme);
2140 : : if(ok3 == CURLE_OK && scheme)
2141 : : {
2142 : : if (startswith (scheme, "HTTP"))
2143 : : if (resp_code == 200)
2144 : : {
2145 : : verified_handle = msg->easy_handle;
2146 : : break;
2147 : : }
2148 : : }
2149 : : #endif
2150 : : #endif
2151 : : }
2152 : : }
2153 : : }
2154 [ + + ]: 1670 : } while (num_msg > 0);
2155 : :
2156 : : /* Create an empty file in the cache if the query fails with ENOENT and
2157 : : it wasn't cancelled early. */
2158 [ + + + - ]: 2427 : if (rc == -ENOENT && !c->progressfn_cancel)
2159 : : {
2160 : 801 : int efd = open (target_cache_path, O_CREAT|O_EXCL, DEFFILEMODE);
2161 [ + + ]: 801 : if (efd >= 0)
2162 : 799 : close(efd);
2163 : : }
2164 [ + + ]: 1626 : else if (rc == -EFBIG)
2165 : 2 : goto out2;
2166 : :
2167 : : /* If the verified_handle is NULL and rc != -ENOENT, the query fails with
2168 : : * an error code other than 404, then do several retry within the retry_limit.
2169 : : * Clean up all old handles and jump back to the beginning of query_in_parallel,
2170 : : * reinitialize handles and query again.*/
2171 [ + + ]: 2425 : if (verified_handle == NULL)
2172 : : {
2173 [ + + + + ]: 1662 : if (rc != -ENOENT && retry_limit-- > 0)
2174 : : {
2175 [ + + ]: 1088 : if (vfd >= 0)
2176 : 24 : dprintf (vfd, "Retry failed query, %d attempt(s) remaining\n", retry_limit);
2177 : : /* remove all handles from multi */
2178 [ + + ]: 2180 : for (int i = 0; i < num_urls; i++)
2179 : : {
2180 : 1092 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2181 : 1092 : curl_easy_cleanup (data[i].handle);
2182 : 1092 : free(data[i].response_data);
2183 : : }
2184 : 1088 : free(c->winning_headers);
2185 : 1088 : c->winning_headers = NULL;
2186 : 1088 : goto query_in_parallel;
2187 : : }
2188 : : else
2189 : 574 : goto out2;
2190 : : }
2191 : :
2192 [ + + ]: 763 : if (vfd >= 0)
2193 : : {
2194 [ + - - - ]: 1078 : bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
2195 : 539 : dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
2196 [ - + ]: 539 : if (pnl)
2197 : 0 : c->default_progressfn_printed_p = 0;
2198 : : }
2199 : :
2200 : : /* we've got one!!!! */
2201 : 763 : time_t mtime;
2202 : : #if defined(_TIME_BITS) && _TIME_BITS == 64
2203 : : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME_T, (void*) &mtime);
2204 : : #else
2205 : 763 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
2206 : : #endif
2207 [ + - ]: 763 : if (curl_res == CURLE_OK)
2208 : : {
2209 : 763 : struct timespec tvs[2];
2210 : 763 : tvs[0].tv_sec = 0;
2211 : 763 : tvs[0].tv_nsec = UTIME_OMIT;
2212 : 763 : tvs[1].tv_sec = mtime;
2213 : 763 : tvs[1].tv_nsec = 0;
2214 : 763 : (void) futimens (fd, tvs); /* best effort */
2215 : : }
2216 : :
2217 : : /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
2218 : 763 : (void) fchmod(fd, 0400);
2219 : : /* PR31248: lseek back to beginning */
2220 : 763 : (void) lseek(fd, 0, SEEK_SET);
2221 : :
2222 : 763 : if(NULL != url_ima_policies && ignore != url_ima_policies[committed_to])
2223 : : {
2224 : : #ifdef ENABLE_IMA_VERIFICATION
2225 : : int result = debuginfod_validate_imasig(c, fd);
2226 : : #else
2227 : : int result = -ENOSYS;
2228 : : #endif
2229 : : if(0 == result)
2230 : : {
2231 : : if (vfd >= 0) dprintf (vfd, "valid signature\n");
2232 : : }
2233 : : else if (enforcing == url_ima_policies[committed_to])
2234 : : {
2235 : : // All invalid signatures are rejected.
2236 : : // Additionally in enforcing mode any non-valid signature is rejected, so by reaching
2237 : : // this case we do so since we know it is not valid. Note - this not just invalid signatures
2238 : : // but also signatures that cannot be validated
2239 : : if (vfd >= 0) dprintf (vfd, "error: invalid or missing signature (%d)\n", result);
2240 : : rc = result;
2241 : : goto out2;
2242 : : }
2243 : : }
2244 : :
2245 : : /* rename tmp->real */
2246 : 763 : rc = rename (target_cache_tmppath, target_cache_path);
2247 [ - + ]: 763 : if (rc < 0)
2248 : : {
2249 : 0 : rc = -errno;
2250 : 0 : goto out2;
2251 : : /* Perhaps we need not give up right away; could retry or something ... */
2252 : : }
2253 : :
2254 : : /* remove all handles from multi */
2255 [ + + ]: 1574 : for (int i = 0; i < num_urls; i++)
2256 : : {
2257 : 811 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2258 : 811 : curl_easy_cleanup (data[i].handle);
2259 : 811 : free (data[i].response_data);
2260 : : }
2261 : :
2262 [ + + ]: 1574 : for (int i = 0; i < num_urls; ++i)
2263 : 811 : free(server_url_list[i]);
2264 : 763 : free(server_url_list);
2265 : 763 : free(url_ima_policies);
2266 : 763 : free (data);
2267 : 763 : free (server_urls);
2268 : :
2269 : : /* don't close fd - we're returning it */
2270 : : /* don't unlink the tmppath; it's already been renamed. */
2271 [ + + ]: 763 : if (path != NULL)
2272 : 238 : *path = strdup(target_cache_path);
2273 : :
2274 : 763 : rc = fd;
2275 : 763 : goto out;
2276 : :
2277 : : /* error exits */
2278 : 576 : out2:
2279 : : /* remove all handles from multi */
2280 [ + + ]: 1154 : for (int i = 0; i < num_urls; i++)
2281 : : {
2282 [ + - ]: 578 : if (data[i].handle != NULL)
2283 : : {
2284 : 578 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2285 : 578 : curl_easy_cleanup (data[i].handle);
2286 : 578 : free (data[i].response_data);
2287 : : }
2288 : : }
2289 : :
2290 : 576 : unlink (target_cache_tmppath);
2291 : 576 : close (fd); /* before the rmdir, otherwise it'll fail */
2292 : 576 : (void) rmdir (target_cache_dir); /* nop if not empty */
2293 : 576 : free(data);
2294 : :
2295 : 576 : out1:
2296 [ + + ]: 1154 : for (int i = 0; i < num_urls; ++i)
2297 : 578 : free(server_url_list[i]);
2298 : 576 : free(server_url_list);
2299 : 576 : free(url_ima_policies);
2300 : :
2301 : 576 : out0:
2302 : 576 : free (server_urls);
2303 : :
2304 : : /* general purpose exit */
2305 : 6178 : out:
2306 : : /* Reset sent headers */
2307 : 6178 : curl_slist_free_all (c->headers);
2308 : 6178 : c->headers = NULL;
2309 : 6178 : c->user_agent_set_p = 0;
2310 : :
2311 : : /* Conclude the last \r status line */
2312 : : /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
2313 : : code. That way, the previously printed messages would be erased,
2314 : : and without a newline. */
2315 [ + + ]: 6178 : if (c->default_progressfn_printed_p)
2316 : 2 : dprintf(STDERR_FILENO, "\n");
2317 : :
2318 [ + + ]: 6178 : if (vfd >= 0)
2319 : : {
2320 [ + + ]: 1078 : if (rc < 0)
2321 : 10 : dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
2322 : : else
2323 : 1068 : dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
2324 : : }
2325 : :
2326 : 6178 : free (cache_path);
2327 : 6178 : free (maxage_path);
2328 : 6178 : free (interval_path);
2329 : 6178 : free (cache_miss_path);
2330 : 6178 : free (target_cache_dir);
2331 : 6178 : free (target_cache_path);
2332 [ + + + + ]: 6178 : if (rc < 0 && target_cache_tmppath != NULL)
2333 : 578 : (void)unlink (target_cache_tmppath);
2334 : 6178 : free (target_cache_tmppath);
2335 : :
2336 : :
2337 : 6178 : return rc;
2338 : : }
2339 : :
2340 : :
2341 : :
2342 : : /* See debuginfod.h */
2343 : : debuginfod_client *
2344 : 390 : debuginfod_begin (void)
2345 : : {
2346 : : /* Initialize libcurl lazily, but only once. */
2347 : 390 : pthread_once (&init_control, libcurl_init);
2348 : :
2349 : 390 : debuginfod_client *client;
2350 : 390 : size_t size = sizeof (struct debuginfod_client);
2351 : 390 : client = calloc (1, size);
2352 : :
2353 [ + - ]: 390 : if (client != NULL)
2354 : : {
2355 [ + + ]: 390 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
2356 : 2 : client->progressfn = default_progressfn;
2357 [ + + ]: 390 : if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
2358 : 4 : client->verbose_fd = STDERR_FILENO;
2359 : : else
2360 : 386 : client->verbose_fd = -1;
2361 : :
2362 : : // allocate 1 curl multi handle
2363 : 390 : client->server_mhandle = curl_multi_init ();
2364 [ - + ]: 390 : if (client->server_mhandle == NULL)
2365 : 0 : goto out1;
2366 : : }
2367 : :
2368 : : #ifdef ENABLE_IMA_VERIFICATION
2369 : : load_ima_public_keys (client);
2370 : : #endif
2371 : :
2372 : : // extra future initialization
2373 : :
2374 : 390 : goto out;
2375 : :
2376 : 0 : out1:
2377 : 0 : free (client);
2378 : 0 : client = NULL;
2379 : :
2380 : 390 : out:
2381 : 390 : return client;
2382 : : }
2383 : :
2384 : : void
2385 : 354 : debuginfod_set_user_data(debuginfod_client *client,
2386 : : void *data)
2387 : : {
2388 : 354 : client->user_data = data;
2389 : 354 : }
2390 : :
2391 : : void *
2392 : 0 : debuginfod_get_user_data(debuginfod_client *client)
2393 : : {
2394 : 0 : return client->user_data;
2395 : : }
2396 : :
2397 : : const char *
2398 : 40 : debuginfod_get_url(debuginfod_client *client)
2399 : : {
2400 [ + + ]: 4 : return client->url;
2401 : : }
2402 : :
2403 : : const char *
2404 : 40 : debuginfod_get_headers(debuginfod_client *client)
2405 : : {
2406 : 40 : return client->winning_headers;
2407 : : }
2408 : :
2409 : : void
2410 : 388 : debuginfod_end (debuginfod_client *client)
2411 : : {
2412 [ + - ]: 388 : if (client == NULL)
2413 : : return;
2414 : :
2415 : 388 : curl_multi_cleanup (client->server_mhandle);
2416 : 388 : curl_slist_free_all (client->headers);
2417 : 388 : free (client->winning_headers);
2418 : 388 : free (client->url);
2419 : : #ifdef ENABLE_IMA_VERIFICATION
2420 : : free_ima_public_keys (client);
2421 : : #endif
2422 : 388 : free (client);
2423 : : }
2424 : :
2425 : : int
2426 : 210 : debuginfod_find_debuginfo (debuginfod_client *client,
2427 : : const unsigned char *build_id, int build_id_len,
2428 : : char **path)
2429 : : {
2430 : 210 : return debuginfod_query_server(client, build_id, build_id_len,
2431 : : "debuginfo", NULL, path);
2432 : : }
2433 : :
2434 : :
2435 : : /* See debuginfod.h */
2436 : : int
2437 : 678 : debuginfod_find_executable(debuginfod_client *client,
2438 : : const unsigned char *build_id, int build_id_len,
2439 : : char **path)
2440 : : {
2441 : 678 : return debuginfod_query_server(client, build_id, build_id_len,
2442 : : "executable", NULL, path);
2443 : : }
2444 : :
2445 : : /* See debuginfod.h */
2446 : 5274 : int debuginfod_find_source(debuginfod_client *client,
2447 : : const unsigned char *build_id, int build_id_len,
2448 : : const char *filename, char **path)
2449 : : {
2450 : 5274 : return debuginfod_query_server(client, build_id, build_id_len,
2451 : : "source", filename, path);
2452 : : }
2453 : :
2454 : : int
2455 : 16 : debuginfod_find_section (debuginfod_client *client,
2456 : : const unsigned char *build_id, int build_id_len,
2457 : : const char *section, char **path)
2458 : : {
2459 : 16 : int rc = debuginfod_query_server(client, build_id, build_id_len,
2460 : : "section", section, path);
2461 [ - + ]: 16 : if (rc != -EINVAL && rc != -ENOSYS)
2462 : : return rc;
2463 : : /* NB: we fall through in case of ima:enforcing-filtered DEBUGINFOD_URLS servers,
2464 : : so we can download the entire file, verify it locally, then slice it. */
2465 : :
2466 : : /* The servers may have lacked support for section queries. Attempt to
2467 : : download the debuginfo or executable containing the section in order
2468 : : to extract it. */
2469 : 0 : rc = -EEXIST;
2470 : 0 : int fd = -1;
2471 : 0 : char *tmp_path = NULL;
2472 : :
2473 : 0 : fd = debuginfod_find_debuginfo (client, build_id, build_id_len, &tmp_path);
2474 [ # # ]: 0 : if (client->progressfn_cancel)
2475 : : {
2476 [ # # ]: 0 : if (fd >= 0)
2477 : : {
2478 : : /* This shouldn't happen, but we'll check this condition
2479 : : just in case. */
2480 : 0 : close (fd);
2481 : 0 : free (tmp_path);
2482 : : }
2483 : 0 : return -ENOENT;
2484 : : }
2485 [ # # ]: 0 : if (fd >= 0)
2486 : : {
2487 : 0 : rc = extract_section (fd, section, tmp_path, path);
2488 : 0 : close (fd);
2489 : : }
2490 : :
2491 [ # # ]: 0 : if (rc == -EEXIST)
2492 : : {
2493 : : /* Either the debuginfo couldn't be found or the section should
2494 : : be in the executable. */
2495 : 0 : fd = debuginfod_find_executable (client, build_id,
2496 : : build_id_len, &tmp_path);
2497 [ # # ]: 0 : if (fd >= 0)
2498 : : {
2499 : 0 : rc = extract_section (fd, section, tmp_path, path);
2500 : 0 : close (fd);
2501 : : }
2502 : : else
2503 : : /* Update rc so that we return the most recent error code. */
2504 : : rc = fd;
2505 : : }
2506 : :
2507 : 0 : free (tmp_path);
2508 : 0 : return rc;
2509 : : }
2510 : :
2511 : : /* Add an outgoing HTTP header. */
2512 : 2630 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
2513 : : {
2514 : : /* Sanity check header value is of the form Header: Value.
2515 : : It should contain at least one colon that isn't the first or
2516 : : last character. */
2517 : 2630 : char *colon = strchr (header, ':'); /* first colon */
2518 : 2630 : if (colon == NULL /* present */
2519 [ + - ]: 2630 : || colon == header /* not at beginning - i.e., have a header name */
2520 [ + - ]: 2630 : || *(colon + 1) == '\0') /* not at end - i.e., have a value */
2521 : : /* NB: but it's okay for a value to contain other colons! */
2522 : : return -EINVAL;
2523 : :
2524 : 2630 : struct curl_slist *temp = curl_slist_append (client->headers, header);
2525 [ + - ]: 2630 : if (temp == NULL)
2526 : : return -ENOMEM;
2527 : :
2528 : : /* Track if User-Agent: is being set. If so, signal not to add the
2529 : : default one. */
2530 [ + + ]: 2630 : if (startswith (header, "User-Agent:"))
2531 : 2012 : client->user_agent_set_p = 1;
2532 : :
2533 : 2630 : client->headers = temp;
2534 : 2630 : return 0;
2535 : : }
2536 : :
2537 : :
2538 : : void
2539 : 722 : debuginfod_set_progressfn(debuginfod_client *client,
2540 : : debuginfod_progressfn_t fn)
2541 : : {
2542 : 722 : client->progressfn = fn;
2543 : 722 : }
2544 : :
2545 : : void
2546 : 64 : debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
2547 : : {
2548 : 64 : client->verbose_fd = fd;
2549 : 64 : }
2550 : :
2551 : : #endif /* DUMMY_LIBDEBUGINFOD */
|