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 : : int debuginfod_find_metadata (debuginfod_client *c,
75 : : const char *k, const char *v, char **p) { return -ENOSYS; }
76 : : void debuginfod_set_progressfn(debuginfod_client *c,
77 : : debuginfod_progressfn_t fn) { }
78 : : void debuginfod_set_verbose_fd(debuginfod_client *c, int fd) { }
79 : : void debuginfod_set_user_data (debuginfod_client *c, void *d) { }
80 : : void* debuginfod_get_user_data (debuginfod_client *c) { return NULL; }
81 : : const char* debuginfod_get_url (debuginfod_client *c) { return NULL; }
82 : : int debuginfod_add_http_header (debuginfod_client *c,
83 : : const char *h) { return -ENOSYS; }
84 : : const char* debuginfod_get_headers (debuginfod_client *c) { return NULL; }
85 : : int debuginfod_default_progressfn (debuginfod_client *c, long a, long b)
86 : : { return 0; }
87 : :
88 : : void debuginfod_end (debuginfod_client *c) { }
89 : :
90 : : #else /* DUMMY_LIBDEBUGINFOD */
91 : :
92 : : #include <assert.h>
93 : : #include <dirent.h>
94 : : #include <stdio.h>
95 : : #include <errno.h>
96 : : #include <unistd.h>
97 : : #include <fcntl.h>
98 : : #include <fts.h>
99 : : #include <regex.h>
100 : : #include <string.h>
101 : : #include <stdbool.h>
102 : : #include <limits.h>
103 : : #include <time.h>
104 : : #include <utime.h>
105 : : #include <sys/syscall.h>
106 : : #include <sys/types.h>
107 : : #include <sys/stat.h>
108 : : #include <sys/utsname.h>
109 : : #include <curl/curl.h>
110 : : #include <fnmatch.h>
111 : : #include <json-c/json.h>
112 : :
113 : : #ifndef PATH_MAX
114 : : #define PATH_MAX 4096
115 : : #endif
116 : :
117 : : /* If fts.h is included before config.h, its indirect inclusions may not
118 : : give us the right LFS aliases of these functions, so map them manually. */
119 : : #ifdef BAD_FTS
120 : : #ifdef _FILE_OFFSET_BITS
121 : : #define open open64
122 : : #define fopen fopen64
123 : : #endif
124 : : #else
125 : : #include <sys/types.h>
126 : : #include <fts.h>
127 : : #endif
128 : :
129 : : /* Older curl.h don't define CURL_AT_LEAST_VERSION. */
130 : : #ifndef CURL_AT_LEAST_VERSION
131 : : #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|(z))
132 : : #define CURL_AT_LEAST_VERSION(x,y,z) \
133 : : (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
134 : : #endif
135 : :
136 : : #include <pthread.h>
137 : :
138 : : static pthread_once_t init_control = PTHREAD_ONCE_INIT;
139 : : static bool curl_has_https; // = false
140 : :
141 : : static void
142 : 528 : libcurl_init(void)
143 : : {
144 : 528 : curl_global_init(CURL_GLOBAL_DEFAULT);
145 : :
146 : 528 : for (const char *const *protocol = curl_version_info(CURLVERSION_NOW)->protocols;
147 [ + + ]: 17424 : *protocol != NULL; ++protocol)
148 : : {
149 [ + + ]: 16896 : if(strcmp("https", *protocol) == 0)
150 : 528 : curl_has_https = true;
151 : : }
152 : 528 : }
153 : :
154 : :
155 : : #ifdef ENABLE_IMA_VERIFICATION
156 : : struct public_key_entry
157 : : {
158 : : struct public_key_entry *next; /* singly-linked list */
159 : : uint32_t keyid; /* last 4 bytes of sha1 of public key */
160 : : EVP_PKEY *key; /* openssl */
161 : : };
162 : : #endif
163 : :
164 : :
165 : : struct debuginfod_client
166 : : {
167 : : /* Progress/interrupt callback function. */
168 : : debuginfod_progressfn_t progressfn;
169 : :
170 : : /* Stores user data. */
171 : : void* user_data;
172 : :
173 : : /* Stores current/last url, if any. */
174 : : char* url;
175 : :
176 : : /* Accumulates outgoing http header names/values. */
177 : : int user_agent_set_p; /* affects add_default_headers */
178 : : struct curl_slist *headers;
179 : :
180 : : /* Flags the default_progressfn having printed something that
181 : : debuginfod_end needs to terminate. */
182 : : int default_progressfn_printed_p;
183 : :
184 : : /* Indicates whether the last query was cancelled by progressfn. */
185 : : bool progressfn_cancel;
186 : :
187 : : /* File descriptor to output any verbose messages if > 0. */
188 : : int verbose_fd;
189 : :
190 : : /* Maintain a long-lived curl multi-handle, which keeps a
191 : : connection/tls/dns cache to recently seen servers. */
192 : : CURLM *server_mhandle;
193 : :
194 : : /* Can contain all other context, like cache_path, server_urls,
195 : : timeout or other info gotten from environment variables, the
196 : : handle data, etc. So those don't have to be reparsed and
197 : : recreated on each request. */
198 : : char * winning_headers;
199 : :
200 : : #ifdef ENABLE_IMA_VERIFICATION
201 : : /* IMA public keys */
202 : : struct public_key_entry *ima_public_keys;
203 : : #endif
204 : : };
205 : :
206 : :
207 : : /* The cache_clean_interval_s file within the debuginfod cache specifies
208 : : how frequently the cache should be cleaned. The file's st_mtime represents
209 : : the time of last cleaning. */
210 : : static const char *cache_clean_interval_filename = "cache_clean_interval_s";
211 : : static const long cache_clean_default_interval_s = 86400; /* 1 day */
212 : :
213 : : /* The cache_miss_default_s within the debuginfod cache specifies how
214 : : frequently the empty file should be released.*/
215 : : static const long cache_miss_default_s = 600; /* 10 min */
216 : : static const char *cache_miss_filename = "cache_miss_s";
217 : :
218 : : /* The cache_max_unused_age_s file within the debuginfod cache specifies the
219 : : the maximum time since last access that a file will remain in the cache. */
220 : : static const char *cache_max_unused_age_filename = "max_unused_age_s";
221 : : static const long cache_default_max_unused_age_s = 604800; /* 1 week */
222 : :
223 : : /* The metadata_retention_default_s file within the debuginfod cache
224 : : specifies how long metadata query results should be cached. */
225 : : static const long metadata_retention_default_s = 3600; /* 1 hour */
226 : : static const char *metadata_retention_filename = "metadata_retention_s";
227 : :
228 : : /* Location of the cache of files downloaded from debuginfods.
229 : : The default parent directory is $HOME, or '/' if $HOME doesn't exist. */
230 : : static const char *cache_default_name = ".debuginfod_client_cache";
231 : : static const char *cache_xdg_name = "debuginfod_client";
232 : :
233 : : /* URLs of debuginfods, separated by url_delim. */
234 : : static const char *url_delim = " ";
235 : :
236 : : /* Timeout for debuginfods, in seconds. Applies separately to the server
237 : : connect phase and to download at least 100K once connection is
238 : : established. */
239 : : static const long default_timeout = 90;
240 : :
241 : : /* Default retry count for download error. */
242 : : static const long default_retry_limit = 2;
243 : :
244 : : /* Data associated with a particular CURL easy handle. Passed to
245 : : the write callback. */
246 : : struct handle_data
247 : : {
248 : : /* Cache file to be written to in case query is successful. */
249 : : int fd;
250 : :
251 : : /* URL queried by this handle. */
252 : : char url[PATH_MAX];
253 : :
254 : : /* error buffer for this handle. */
255 : : char errbuf[CURL_ERROR_SIZE];
256 : :
257 : : /* This handle. */
258 : : CURL *handle;
259 : :
260 : : /* The client object whom we're serving. */
261 : : debuginfod_client *client;
262 : :
263 : : /* Pointer to handle that should write to fd. Initially points to NULL,
264 : : then points to the first handle that begins writing the target file
265 : : to the cache. Used to ensure that a file is not downloaded from
266 : : multiple servers unnecessarily. */
267 : : CURL **target_handle;
268 : :
269 : : /* Response http headers for this client handle, sent from the server */
270 : : char *response_data;
271 : : size_t response_data_size;
272 : :
273 : : /* Response metadata values for this client handle, sent from the server */
274 : : char *metadata;
275 : : size_t metadata_size;
276 : : };
277 : :
278 : :
279 : :
280 : : #ifdef ENABLE_IMA_VERIFICATION
281 : : static inline unsigned char hex2dec(char c)
282 : : {
283 : : if (c >= '0' && c <= '9') return (c - '0');
284 : : if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
285 : : if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
286 : : return 0;
287 : : }
288 : :
289 : : static inline ima_policy_t ima_policy_str2enum(const char* ima_pol)
290 : : {
291 : : if (NULL == ima_pol) return undefined;
292 : : if (0 == strcmp(ima_pol, "ignore")) return ignore;
293 : : if (0 == strcmp(ima_pol, "enforcing")) return enforcing;
294 : : return undefined;
295 : : }
296 : :
297 : : static inline const char* ima_policy_enum2str(ima_policy_t ima_pol)
298 : : {
299 : : switch (ima_pol)
300 : : {
301 : : case ignore:
302 : : return "ignore";
303 : : case enforcing:
304 : : return "enforcing";
305 : : case undefined:
306 : : return "undefined";
307 : : }
308 : : return "";
309 : : }
310 : :
311 : :
312 : : static uint32_t extract_skid_pk(EVP_PKEY *pkey) // compute keyid by public key hashing
313 : : {
314 : : if (!pkey) return 0;
315 : : uint32_t keyid = 0;
316 : : X509_PUBKEY *pk = NULL;
317 : : const unsigned char *public_key = NULL;
318 : : int len;
319 : : if (X509_PUBKEY_set(&pk, pkey) &&
320 : : X509_PUBKEY_get0_param(NULL, &public_key, &len, NULL, pk))
321 : : {
322 : : uint8_t sha1[SHA_DIGEST_LENGTH];
323 : : SHA1(public_key, len, sha1);
324 : : memcpy(&keyid, sha1 + 16, 4);
325 : : }
326 : : X509_PUBKEY_free(pk);
327 : : return ntohl(keyid);
328 : : }
329 : :
330 : :
331 : : static uint32_t extract_skid(X509* x509) // compute keyid from cert or its public key
332 : : {
333 : : if (!x509) return 0;
334 : : uint32_t keyid = 0;
335 : : // Attempt to get the skid from the certificate
336 : : const ASN1_OCTET_STRING *skid_asn1_str = X509_get0_subject_key_id(x509);
337 : : if (skid_asn1_str)
338 : : {
339 : : int skid_len = ASN1_STRING_length(skid_asn1_str);
340 : : memcpy(&keyid, ASN1_STRING_get0_data(skid_asn1_str) + skid_len - sizeof(keyid), sizeof(keyid));
341 : : }
342 : : else // compute keyid ourselves by hashing public key
343 : : {
344 : : EVP_PKEY *pkey = X509_get0_pubkey(x509);
345 : : keyid = htonl(extract_skid_pk(pkey));
346 : : }
347 : : return ntohl(keyid);
348 : : }
349 : :
350 : :
351 : : static void load_ima_public_keys (debuginfod_client *c)
352 : : {
353 : : /* Iterate over the directories in DEBUGINFOD_IMA_CERT_PATH. */
354 : : char *cert_paths = getenv(DEBUGINFOD_IMA_CERT_PATH_ENV_VAR);
355 : : if (cert_paths == NULL || cert_paths[0] == '\0')
356 : : return;
357 : : cert_paths = strdup(cert_paths); // Modified during tokenization
358 : : if (cert_paths == NULL)
359 : : return;
360 : :
361 : : char* cert_dir_path;
362 : : DIR *dp;
363 : : struct dirent *entry;
364 : : int vfd = c->verbose_fd;
365 : :
366 : : char *strtok_context = NULL;
367 : : for(cert_dir_path = strtok_r(cert_paths, ":", &strtok_context);
368 : : cert_dir_path != NULL;
369 : : cert_dir_path = strtok_r(NULL, ":", &strtok_context))
370 : : {
371 : : dp = opendir(cert_dir_path);
372 : : if(!dp) continue;
373 : : while((entry = readdir(dp)))
374 : : {
375 : : // Only consider regular files with common x509 cert extensions
376 : : if(entry->d_type != DT_REG || 0 != fnmatch("*.@(der|pem|crt|cer|cert)", entry->d_name, FNM_EXTMATCH)) continue;
377 : : char certfile[PATH_MAX];
378 : : strncpy(certfile, cert_dir_path, PATH_MAX - 1);
379 : : if(certfile[strlen(certfile)-1] != '/') certfile[strlen(certfile)] = '/';
380 : : strncat(certfile, entry->d_name, PATH_MAX - strlen(certfile) - 1);
381 : : certfile[strlen(certfile)] = '\0';
382 : :
383 : : FILE *cert_fp = fopen(certfile, "r");
384 : : if(!cert_fp) continue;
385 : :
386 : : X509 *x509 = NULL;
387 : : EVP_PKEY *pkey = NULL;
388 : : char *fmt = "";
389 : : // Attempt to read the fp as DER
390 : : if(d2i_X509_fp(cert_fp, &x509))
391 : : fmt = "der ";
392 : : // Attempt to read the fp as PEM and assuming the key matches that of the signature add this key to be used
393 : : // Note we fseek since this is the second time we read from the fp
394 : : else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_X509(cert_fp, &x509, NULL, NULL))
395 : : fmt = "pem "; // PEM with full certificate
396 : : else if(0 == fseek(cert_fp, 0, SEEK_SET) && PEM_read_PUBKEY(cert_fp, &pkey, NULL, NULL))
397 : : fmt = "pem "; // some PEM files have just a PUBLIC KEY in them
398 : : fclose(cert_fp);
399 : :
400 : : if (x509)
401 : : {
402 : : struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
403 : : if (ne)
404 : : {
405 : : ne->key = X509_extract_key(x509);
406 : : ne->keyid = extract_skid(x509);
407 : : ne->next = c->ima_public_keys;
408 : : c->ima_public_keys = ne;
409 : : if (vfd >= 0)
410 : : dprintf(vfd, "Loaded %scertificate %s, keyid = %04x\n", fmt, certfile, ne->keyid);
411 : : }
412 : : X509_free (x509);
413 : : }
414 : : else if (pkey)
415 : : {
416 : : struct public_key_entry *ne = calloc(1, sizeof(struct public_key_entry));
417 : : if (ne)
418 : : {
419 : : ne->key = pkey; // preserve refcount
420 : : ne->keyid = extract_skid_pk(pkey);
421 : : ne->next = c->ima_public_keys;
422 : : c->ima_public_keys = ne;
423 : : if (vfd >= 0)
424 : : dprintf(vfd, "Loaded %spubkey %s, keyid %04x\n", fmt, certfile, ne->keyid);
425 : : }
426 : : }
427 : : else
428 : : {
429 : : if (vfd >= 0)
430 : : dprintf(vfd, "Cannot load certificate %s\n", certfile);
431 : : }
432 : : } /* for each file in directory */
433 : : closedir(dp);
434 : : } /* for each directory */
435 : :
436 : : free(cert_paths);
437 : : }
438 : :
439 : :
440 : : static void free_ima_public_keys (debuginfod_client *c)
441 : : {
442 : : while (c->ima_public_keys)
443 : : {
444 : : EVP_PKEY_free (c->ima_public_keys->key);
445 : : struct public_key_entry *oen = c->ima_public_keys->next;
446 : : free (c->ima_public_keys);
447 : : c->ima_public_keys = oen;
448 : : }
449 : : }
450 : : #endif
451 : :
452 : :
453 : :
454 : : static size_t
455 : 10443 : debuginfod_write_callback (char *ptr, size_t size, size_t nmemb, void *data)
456 : : {
457 : 10443 : ssize_t count = size * nmemb;
458 : :
459 : 10443 : struct handle_data *d = (struct handle_data*)data;
460 : :
461 : : /* Indicate to other handles that they can abort their transfer. */
462 [ + + ]: 10443 : if (*d->target_handle == NULL)
463 : : {
464 : 1464 : *d->target_handle = d->handle;
465 : : /* update the client object */
466 : 1464 : const char *url = NULL;
467 : 1464 : CURLcode curl_res = curl_easy_getinfo (d->handle,
468 : : CURLINFO_EFFECTIVE_URL, &url);
469 [ + - + - ]: 1464 : if (curl_res == CURLE_OK && url)
470 : : {
471 : 1464 : free (d->client->url);
472 : 1464 : d->client->url = strdup(url); /* ok if fails */
473 : : }
474 : : }
475 : :
476 : : /* If this handle isn't the target handle, abort transfer. */
477 [ + - ]: 10443 : if (*d->target_handle != d->handle)
478 : : return -1;
479 : :
480 : 10443 : return (size_t) write(d->fd, (void*)ptr, count);
481 : : }
482 : :
483 : : /* handle config file read and write */
484 : : static int
485 : 2122 : debuginfod_config_cache(debuginfod_client *c, char *config_path,
486 : : long cache_config_default_s,
487 : : struct stat *st)
488 : : {
489 : 2122 : int fd = open(config_path, O_CREAT | O_RDWR, DEFFILEMODE);
490 [ - + ]: 2122 : if (fd < 0)
491 : 0 : return -errno;
492 : :
493 [ - + ]: 2122 : if (fstat (fd, st) < 0)
494 : : {
495 : 0 : int ret = -errno;
496 : 0 : close (fd);
497 : 0 : return ret;
498 : : }
499 : :
500 [ + + ]: 2122 : if (st->st_size == 0)
501 : : {
502 [ - + ]: 84 : if (dprintf(fd, "%ld", cache_config_default_s) < 0)
503 : : {
504 : 0 : int ret = -errno;
505 : 0 : close (fd);
506 : 0 : return ret;
507 : : }
508 : :
509 : 84 : close (fd);
510 : 84 : return cache_config_default_s;
511 : : }
512 : :
513 : 2038 : long cache_config;
514 : : /* PR29696 - NB: When using fdopen, the file descriptor is NOT
515 : : dup'ed and will be closed when the stream is closed. Manually
516 : : closing fd after fclose is called will lead to a race condition
517 : : where, if reused, the file descriptor will compete for its
518 : : regular use before being incorrectly closed here. */
519 : 2038 : FILE *config_file = fdopen(fd, "r");
520 [ + - ]: 2038 : if (config_file)
521 : : {
522 [ - + ]: 2038 : if (fscanf(config_file, "%ld", &cache_config) != 1)
523 : 0 : cache_config = cache_config_default_s;
524 [ - + - - ]: 2038 : if (0 != fclose (config_file) && c->verbose_fd >= 0)
525 : 0 : dprintf (c->verbose_fd, "fclose failed with %s (err=%d)\n",
526 : 0 : strerror (errno), errno);
527 : : }
528 : : else
529 : : {
530 : 0 : cache_config = cache_config_default_s;
531 [ # # # # ]: 0 : if (0 != close (fd) && c->verbose_fd >= 0)
532 : 0 : dprintf (c->verbose_fd, "close failed with %s (err=%d)\n",
533 : 0 : strerror (errno), errno);
534 : : }
535 : 2038 : return cache_config;
536 : : }
537 : :
538 : : /* Delete any files that have been unmodied for a period
539 : : longer than $DEBUGINFOD_CACHE_CLEAN_INTERVAL_S. */
540 : : static int
541 : 2104 : debuginfod_clean_cache(debuginfod_client *c,
542 : : char *cache_path, char *interval_path,
543 : : char *max_unused_path)
544 : : {
545 : 2104 : time_t clean_interval, max_unused_age;
546 : 2104 : int rc = -1;
547 : 2104 : struct stat st;
548 : :
549 : : /* Create new interval file. */
550 : 2104 : rc = debuginfod_config_cache(c, interval_path,
551 : : cache_clean_default_interval_s, &st);
552 [ + - ]: 2104 : if (rc < 0)
553 : : return rc;
554 : 2104 : clean_interval = (time_t)rc;
555 : :
556 : : /* Check timestamp of interval file to see whether cleaning is necessary. */
557 [ + + ]: 2104 : if (time(NULL) - st.st_mtime < clean_interval)
558 : : /* Interval has not passed, skip cleaning. */
559 : : return 0;
560 : :
561 : : /* Update timestamp representing when the cache was last cleaned.
562 : : Do it at the start to reduce the number of threads trying to do a
563 : : cleanup simultaneously. */
564 : 2 : utime (interval_path, NULL);
565 : :
566 : : /* Read max unused age value from config file. */
567 : 2 : rc = debuginfod_config_cache(c, max_unused_path,
568 : : cache_default_max_unused_age_s, &st);
569 [ + - ]: 2 : if (rc < 0)
570 : : return rc;
571 : 2 : max_unused_age = (time_t)rc;
572 : :
573 : 2 : char * const dirs[] = { cache_path, NULL, };
574 : :
575 : 2 : FTS *fts = fts_open(dirs, 0, NULL);
576 [ - + ]: 2 : if (fts == NULL)
577 : 0 : return -errno;
578 : :
579 : 2 : regex_t re;
580 : 2 : const char * pattern = ".*/(metadata.*|[a-f0-9]+(/hdr.*|/debuginfo|/executable|/source.*|))$"; /* include dirs */
581 : : /* NB: also matches .../section/ subdirs, so extracted section files also get cleaned. */
582 [ + - ]: 2 : if (regcomp (&re, pattern, REG_EXTENDED | REG_NOSUB) != 0)
583 : : return -ENOMEM;
584 : :
585 : 2 : FTSENT *f;
586 : 2 : long files = 0;
587 : 2 : time_t now = time(NULL);
588 [ + + ]: 28 : while ((f = fts_read(fts)) != NULL)
589 : : {
590 : : /* ignore any files that do not match the pattern. */
591 [ + + ]: 24 : if (regexec (&re, f->fts_path, 0, NULL, 0) != 0)
592 : 16 : continue;
593 : :
594 : 8 : files++;
595 [ + - ]: 8 : if (c->progressfn) /* inform/check progress callback */
596 [ + - ]: 8 : if ((c->progressfn) (c, files, 0))
597 : : break;
598 : :
599 [ + - + ]: 8 : switch (f->fts_info)
600 : : {
601 : 0 : case FTS_F:
602 : : /* delete file if max_unused_age has been met or exceeded w.r.t. atime. */
603 [ # # ]: 0 : if (now - f->fts_statp->st_atime >= max_unused_age)
604 : 0 : (void) unlink (f->fts_path);
605 : : break;
606 : :
607 : 4 : case FTS_DP:
608 : : /* Remove if old & empty. Weaken race against concurrent creation by
609 : : checking mtime. */
610 [ - + ]: 4 : if (now - f->fts_statp->st_mtime >= max_unused_age)
611 : 4 : (void) rmdir (f->fts_path);
612 : : break;
613 : :
614 : : default:
615 : 26 : ;
616 : : }
617 : : }
618 : 2 : fts_close (fts);
619 : 2 : regfree (&re);
620 : :
621 : 2 : return 0;
622 : : }
623 : :
624 : :
625 : : #define MAX_BUILD_ID_BYTES 64
626 : :
627 : :
628 : : static void
629 : 2128 : add_default_headers(debuginfod_client *client)
630 : : {
631 [ + + ]: 2128 : if (client->user_agent_set_p)
632 : 560 : return;
633 : :
634 : : /* Compute a User-Agent: string to send. The more accurately this
635 : : describes this host, the likelier that the debuginfod servers
636 : : might be able to locate debuginfo for us. */
637 : :
638 : 1568 : char* utspart = NULL;
639 : 1568 : struct utsname uts;
640 : 1568 : int rc = 0;
641 : 1568 : rc = uname (&uts);
642 [ + - ]: 1568 : if (rc == 0)
643 : 1568 : rc = asprintf(& utspart, "%s/%s", uts.sysname, uts.machine);
644 [ - + ]: 1568 : if (rc < 0)
645 : 0 : utspart = NULL;
646 : :
647 : 1568 : FILE *f = fopen ("/etc/os-release", "r");
648 [ - + ]: 1568 : if (f == NULL)
649 : 0 : f = fopen ("/usr/lib/os-release", "r");
650 : 1568 : char *id = NULL;
651 : 1568 : char *version = NULL;
652 [ - + ]: 1568 : if (f != NULL)
653 : : {
654 [ + + ]: 12544 : while (id == NULL || version == NULL)
655 : : {
656 : 10976 : char buf[128];
657 : 10976 : char *s = &buf[0];
658 [ + - ]: 10976 : if (fgets (s, sizeof(buf), f) == NULL)
659 : : break;
660 : :
661 : 10976 : int len = strlen (s);
662 [ - + ]: 10976 : if (len < 3)
663 : 0 : continue;
664 [ + - ]: 10976 : if (s[len - 1] == '\n')
665 : : {
666 : 10976 : s[len - 1] = '\0';
667 : 10976 : len--;
668 : : }
669 : :
670 : 10976 : char *v = strchr (s, '=');
671 [ + - - + ]: 10976 : if (v == NULL || strlen (v) < 2)
672 : 0 : continue;
673 : :
674 : : /* Split var and value. */
675 : 10976 : *v = '\0';
676 : 10976 : v++;
677 : :
678 : : /* Remove optional quotes around value string. */
679 [ + + ]: 10976 : if (*v == '"' || *v == '\'')
680 : : {
681 : 6272 : v++;
682 : 6272 : s[len - 1] = '\0';
683 : : }
684 [ + - + + ]: 10976 : if (id == NULL && strcmp (s, "ID") == 0)
685 : 1568 : id = strdup (v);
686 [ + + + + ]: 10976 : if (version == NULL && strcmp (s, "VERSION_ID") == 0)
687 : 1568 : version = strdup (v);
688 : : }
689 : 1568 : fclose (f);
690 : : }
691 : :
692 : 1568 : char *ua = NULL;
693 [ + - + - ]: 3136 : rc = asprintf(& ua, "User-Agent: %s/%s,%s,%s/%s",
694 : : PACKAGE_NAME, PACKAGE_VERSION,
695 [ - + ]: 1568 : utspart ?: "",
696 : : id ?: "",
697 : : version ?: "");
698 [ - + ]: 1568 : if (rc < 0)
699 : 0 : ua = NULL;
700 : :
701 [ + - ]: 1568 : if (ua)
702 : 1568 : (void) debuginfod_add_http_header (client, ua);
703 : :
704 : 1568 : free (ua);
705 : 1568 : free (id);
706 : 1568 : free (version);
707 : 1568 : free (utspart);
708 : : }
709 : :
710 : : /* Add HTTP headers found in the given file, one per line. Blank lines or invalid
711 : : * headers are ignored.
712 : : */
713 : : static void
714 : 0 : add_headers_from_file(debuginfod_client *client, const char* filename)
715 : : {
716 : 0 : int vds = client->verbose_fd;
717 : 0 : FILE *f = fopen (filename, "r");
718 [ # # ]: 0 : if (f == NULL)
719 : : {
720 [ # # ]: 0 : if (vds >= 0)
721 : 0 : dprintf(vds, "header file %s: %s\n", filename, strerror(errno));
722 : 0 : return;
723 : : }
724 : :
725 : 0 : while (1)
726 : 0 : {
727 : 0 : char buf[8192];
728 : 0 : char *s = &buf[0];
729 [ # # ]: 0 : if (feof(f))
730 : : break;
731 [ # # ]: 0 : if (fgets (s, sizeof(buf), f) == NULL)
732 : : break;
733 [ # # ]: 0 : for (char *c = s; *c != '\0'; ++c)
734 [ # # ]: 0 : if (!isspace(*c))
735 : 0 : goto nonempty;
736 : 0 : continue;
737 : 0 : nonempty:
738 : 0 : ;
739 : 0 : size_t last = strlen(s)-1;
740 [ # # ]: 0 : if (s[last] == '\n')
741 : 0 : s[last] = '\0';
742 : 0 : int rc = debuginfod_add_http_header(client, s);
743 [ # # ]: 0 : if (rc < 0 && vds >= 0)
744 : 0 : dprintf(vds, "skipping bad header: %s\n", strerror(-rc));
745 : : }
746 : 0 : fclose (f);
747 : : }
748 : :
749 : :
750 : : #define xalloc_str(p, fmt, args...) \
751 : : do \
752 : : { \
753 : : if (asprintf (&p, fmt, args) < 0) \
754 : : { \
755 : : p = NULL; \
756 : : rc = -ENOMEM; \
757 : : goto out; \
758 : : } \
759 : : } while (0)
760 : :
761 : :
762 : : /* Offer a basic form of progress tracing */
763 : : int
764 : 2 : debuginfod_default_progressfn (debuginfod_client *c, long a, long b)
765 : : {
766 : 2 : const char* url = debuginfod_get_url (c);
767 : 2 : int len = 0;
768 : :
769 : : /* We prefer to print the host part of the URL to keep the
770 : : message short. */
771 : 2 : if (url != NULL)
772 : : {
773 : 2 : const char* buildid = strstr(url, "buildid/");
774 [ + - ]: 2 : if (buildid != NULL)
775 : 2 : len = (buildid - url);
776 : : else
777 : 0 : len = strlen(url);
778 : : }
779 : :
780 [ - + ]: 2 : if (b == 0 || url==NULL) /* early stage */
781 : 0 : dprintf(STDERR_FILENO,
782 : 0 : "\rDownloading %c", "-/|\\"[a % 4]);
783 [ - + ]: 2 : else if (b < 0) /* download in progress but unknown total length */
784 : 0 : dprintf(STDERR_FILENO,
785 : : "\rDownloading from %.*s %ld",
786 : : len, url, a);
787 : : else /* download in progress, and known total length */
788 : 2 : dprintf(STDERR_FILENO,
789 : : "\rDownloading from %.*s %ld/%ld",
790 : : len, url, a, b);
791 : 2 : c->default_progressfn_printed_p = 1;
792 : :
793 : 2 : return 0;
794 : : }
795 : :
796 : : /* This is a callback function that receives http response headers in buffer for use
797 : : * in this program. https://curl.se/libcurl/c/CURLOPT_HEADERFUNCTION.html is the
798 : : * online documentation.
799 : : */
800 : : static size_t
801 : 13831 : header_callback (char * buffer, size_t size, size_t numitems, void * userdata)
802 : : {
803 : 13831 : struct handle_data *data = (struct handle_data *) userdata;
804 [ - + ]: 13831 : if (size != 1)
805 : : return 0;
806 [ + - ]: 13831 : if (data->client
807 [ + + ]: 13831 : && data->client->verbose_fd >= 0
808 [ + + ]: 9980 : && numitems > 2)
809 : 8870 : dprintf (data->client->verbose_fd, "header %.*s", (int)numitems, buffer);
810 : : // Some basic checks to ensure the headers received are of the expected format
811 [ + + ]: 13831 : if (strncasecmp(buffer, "X-DEBUGINFOD", 11)
812 [ + + ]: 3226 : || buffer[numitems-2] != '\r'
813 [ + - ]: 3224 : || buffer[numitems-1] != '\n'
814 [ - + ]: 3224 : || (buffer == strstr(buffer, ":")) ){
815 : : return numitems;
816 : : }
817 : : /* Temporary buffer for realloc */
818 : 3224 : char *temp = NULL;
819 : 3224 : temp = realloc(data->response_data, data->response_data_size + numitems);
820 [ - + ]: 3224 : if (temp == NULL)
821 : : return 0;
822 : :
823 : 3224 : memcpy(temp + data->response_data_size, buffer, numitems-1);
824 : 3224 : data->response_data = temp;
825 : 3224 : data->response_data_size += numitems-1;
826 : 3224 : data->response_data[data->response_data_size-1] = '\n';
827 : 3224 : data->response_data[data->response_data_size] = '\0';
828 : 3224 : return numitems;
829 : : }
830 : :
831 : :
832 : : static size_t
833 : 20 : metadata_callback (char * buffer, size_t size, size_t numitems, void * userdata)
834 : : {
835 [ - + ]: 20 : if (size != 1)
836 : : return 0;
837 : : /* Temporary buffer for realloc */
838 : 20 : char *temp = NULL;
839 : 20 : struct handle_data *data = (struct handle_data *) userdata;
840 : 20 : temp = realloc(data->metadata, data->metadata_size + numitems + 1);
841 [ - + ]: 20 : if (temp == NULL)
842 : : return 0;
843 : :
844 : 20 : memcpy(temp + data->metadata_size, buffer, numitems);
845 : 20 : data->metadata = temp;
846 : 20 : data->metadata_size += numitems;
847 : 20 : data->metadata[data->metadata_size] = '\0';
848 : 20 : return numitems;
849 : : }
850 : :
851 : :
852 : : /* This function takes a copy of DEBUGINFOD_URLS, server_urls, and
853 : : * separates it into an array of urls to query, each with a
854 : : * corresponding IMA policy. The url_subdir is either 'buildid' or
855 : : * 'metadata', corresponding to the query type. Returns 0 on success
856 : : * and -Posix error on failure.
857 : : */
858 : : static int
859 : 2064 : init_server_urls(char* url_subdir, const char* type,
860 : : char *server_urls, char ***server_url_list, ima_policy_t **url_ima_policies,
861 : : int *num_urls, int vfd)
862 : : {
863 : : /* Initialize the memory to zero */
864 : 2064 : char *strtok_saveptr;
865 : 2064 : ima_policy_t verification_mode = ignore; // The default mode
866 : 2064 : char *server_url = strtok_r(server_urls, url_delim, &strtok_saveptr);
867 : : /* Count number of URLs. */
868 : 2064 : int n = 0;
869 : :
870 [ + + ]: 4194 : while (server_url != NULL)
871 : : {
872 : : // When we encountered a (well-formed) token off the form
873 : : // ima:foo, we update the policy under which results from that
874 : : // server will be ima verified
875 [ - + ]: 2130 : if (startswith(server_url, "ima:"))
876 : : {
877 : : #ifdef ENABLE_IMA_VERIFICATION
878 : : ima_policy_t m = ima_policy_str2enum(server_url + strlen("ima:"));
879 : : if(m != undefined)
880 : : verification_mode = m;
881 : : else if (vfd >= 0)
882 : : dprintf(vfd, "IMA mode not recognized, skipping %s\n", server_url);
883 : : #else
884 [ # # ]: 0 : if (vfd >= 0)
885 : 0 : dprintf(vfd, "IMA signature verification is not enabled, treating %s as ima:ignore\n", server_url);
886 : : #endif
887 : 0 : goto continue_next_url;
888 : : }
889 : :
890 : 2130 : if (verification_mode==enforcing &&
891 : : 0==strcmp(url_subdir, "buildid") &&
892 : : 0==strcmp(type,"section")) // section queries are unsecurable
893 : : {
894 : : if (vfd >= 0)
895 : : dprintf(vfd, "skipping server %s section query in IMA enforcing mode\n", server_url);
896 : : goto continue_next_url;
897 : : }
898 : :
899 : : // Construct actual URL for libcurl
900 : 2130 : int r;
901 : 2130 : char *tmp_url;
902 [ + - + + ]: 2130 : if (strlen(server_url) > 1 && server_url[strlen(server_url)-1] == '/')
903 : 1116 : r = asprintf(&tmp_url, "%s%s", server_url, url_subdir);
904 : : else
905 : 1014 : r = asprintf(&tmp_url, "%s/%s", server_url, url_subdir);
906 : :
907 [ + - ]: 2130 : if (r == -1)
908 : 0 : return -ENOMEM;
909 : :
910 : : /* PR 27983: If the url is duplicate, skip it */
911 : : int url_index;
912 [ + + ]: 2264 : for (url_index = 0; url_index < n; ++url_index)
913 : : {
914 [ + + ]: 138 : if(strcmp(tmp_url, (*server_url_list)[url_index]) == 0)
915 : : {
916 : : url_index = -1;
917 : : break;
918 : : }
919 : : }
920 [ + + ]: 2130 : if (url_index == -1)
921 : : {
922 [ + - ]: 4 : if (vfd >= 0)
923 : 4 : dprintf(vfd, "duplicate url: %s, skipping\n", tmp_url);
924 : 4 : free(tmp_url);
925 : : }
926 : : else
927 : : {
928 : : /* Have unique URL, save it, along with its IMA verification tag. */
929 : 2126 : n ++;
930 [ + - ]: 2126 : if (NULL == (*server_url_list = reallocarray(*server_url_list, n, sizeof(char*)))
931 [ - + ]: 2126 : || NULL == (*url_ima_policies = reallocarray(*url_ima_policies, n, sizeof(ima_policy_t))))
932 : : {
933 : 0 : free (tmp_url);
934 : 0 : return -ENOMEM;
935 : : }
936 : 2126 : (*server_url_list)[n-1] = tmp_url;
937 : 2126 : if(NULL != url_ima_policies) (*url_ima_policies)[n-1] = verification_mode;
938 : : }
939 : :
940 : 2130 : continue_next_url:
941 : 2130 : server_url = strtok_r(NULL, url_delim, &strtok_saveptr);
942 : : }
943 : 2064 : *num_urls = n;
944 : 2064 : return 0;
945 : : }
946 : :
947 : : /* Some boilerplate for checking curl_easy_setopt. */
948 : : #define curl_easy_setopt_ck(H,O,P) do { \
949 : : CURLcode curl_res = curl_easy_setopt (H,O,P); \
950 : : if (curl_res != CURLE_OK) \
951 : : { \
952 : : if (vfd >= 0) \
953 : : dprintf (vfd, \
954 : : "Bad curl_easy_setopt: %s\n", \
955 : : curl_easy_strerror(curl_res)); \
956 : : return -EINVAL; \
957 : : } \
958 : : } while (0)
959 : :
960 : :
961 : : /*
962 : : * This function initializes a CURL handle. It takes optional callbacks for the write
963 : : * function and the header function, which if defined will use userdata of type struct handle_data*.
964 : : * Specifically the data[i] within an array of struct handle_data's.
965 : : * Returns 0 on success and -Posix error on failure.
966 : : */
967 : : static int
968 : 3210 : init_handle(debuginfod_client *client,
969 : : size_t (*w_callback)(char *buffer, size_t size, size_t nitems, void *userdata),
970 : : size_t (*h_callback)(char *buffer, size_t size, size_t nitems, void *userdata),
971 : : struct handle_data *data, int i, long timeout,
972 : : int vfd)
973 : : {
974 : 3210 : data->handle = curl_easy_init();
975 [ + - ]: 3210 : if (data->handle == NULL)
976 : : return -ENETUNREACH;
977 : :
978 [ + + ]: 3210 : if (vfd >= 0)
979 : 1136 : dprintf (vfd, "url %d %s\n", i, data->url);
980 : :
981 : : /* Only allow http:// + https:// + file:// so we aren't being
982 : : redirected to some unsupported protocol.
983 : : libcurl will fail if we request a single protocol that is not
984 : : available. https missing is the most likely issue */
985 : : #if CURL_AT_LEAST_VERSION(7, 85, 0)
986 [ - + - + : 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_PROTOCOLS_STR,
- - ]
987 : : curl_has_https ? "https,http,file" : "http,file");
988 : : #else
989 : : curl_easy_setopt_ck(data->handle, CURLOPT_PROTOCOLS,
990 : : ((curl_has_https ? CURLPROTO_HTTPS : 0) | CURLPROTO_HTTP | CURLPROTO_FILE));
991 : : #endif
992 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_URL, data->url);
993 [ + + ]: 3210 : if (vfd >= 0)
994 [ - + ]: 1136 : curl_easy_setopt_ck(data->handle, CURLOPT_ERRORBUFFER,
995 : : data->errbuf);
996 [ + - ]: 3210 : if (w_callback)
997 : : {
998 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle,
999 : : CURLOPT_WRITEFUNCTION, w_callback);
1000 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_WRITEDATA, data);
1001 : : }
1002 [ + - ]: 3210 : if (timeout > 0)
1003 : : {
1004 : : /* Make sure there is at least some progress,
1005 : : try to get at least 100K per timeout seconds. */
1006 [ - + - - ]: 3210 : curl_easy_setopt_ck (data->handle, CURLOPT_LOW_SPEED_TIME,
1007 : : timeout);
1008 [ - + - - ]: 3210 : curl_easy_setopt_ck (data->handle, CURLOPT_LOW_SPEED_LIMIT,
1009 : : 100 * 1024L);
1010 : :
1011 : : /* CURLOPT_LOW_SPEED_LIMIT/_TIME don't apply during the server connection
1012 : : phase. Set CURLOPT_CONNECTTIMEOUT_MS as well so the timeout also
1013 : : limits how long we wait for an unresponsive server. */
1014 : 6420 : long connect_timeout_ms = (timeout > LONG_MAX / 1000L
1015 [ + - ]: 3210 : ? LONG_MAX : timeout * 1000L);
1016 [ - + - - ]: 3210 : curl_easy_setopt_ck (data->handle, CURLOPT_CONNECTTIMEOUT_MS,
1017 : : connect_timeout_ms);
1018 : : }
1019 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_FILETIME, (long) 1);
1020 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_FOLLOWLOCATION, (long) 1);
1021 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_MAXREDIRS, (long) 6);
1022 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_FAILONERROR, (long) 1);
1023 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_NOSIGNAL, (long) 1);
1024 [ + - ]: 3210 : if (h_callback)
1025 : : {
1026 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle,
1027 : : CURLOPT_HEADERFUNCTION, h_callback);
1028 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_HEADERDATA, data);
1029 : : }
1030 : : #if LIBCURL_VERSION_NUM >= 0x072a00 /* 7.42.0 */
1031 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_PATH_AS_IS, (long) 1);
1032 : : #else
1033 : : /* On old curl; no big deal, canonicalization here is almost the
1034 : : same, except perhaps for ? # type decorations at the tail. */
1035 : : #endif
1036 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_AUTOREFERER, (long) 1);
1037 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_ACCEPT_ENCODING, "");
1038 [ - + - - ]: 3210 : curl_easy_setopt_ck(data->handle, CURLOPT_HTTPHEADER, client->headers);
1039 : :
1040 : : return 0;
1041 : : }
1042 : :
1043 : :
1044 : : /*
1045 : : * This function busy-waits on one or more curl queries to complete. This can
1046 : : * be controlled via only_one, which, if true, will find the first winner and exit
1047 : : * once found. If positive maxtime and maxsize dictate the maximum allowed wait times
1048 : : * and download sizes respectively. Returns 0 on success and -Posix error on failure.
1049 : : */
1050 : : static int
1051 : 3148 : perform_queries(CURLM *curlm, CURL **target_handle, struct handle_data *data, debuginfod_client *c,
1052 : : int num_urls, long maxtime, long maxsize, bool only_one, int vfd, int *committed_to)
1053 : : {
1054 : 3148 : int still_running = -1;
1055 : 3148 : long loops = 0;
1056 : 3148 : *committed_to = -1;
1057 : 3148 : bool verbose_reported = false;
1058 : 3148 : struct timespec start_time, cur_time;
1059 [ - + ]: 3148 : if (c->winning_headers != NULL)
1060 : : {
1061 : 0 : free (c->winning_headers);
1062 : 0 : c->winning_headers = NULL;
1063 : : }
1064 [ + + - + ]: 3148 : if (maxtime > 0 && clock_gettime(CLOCK_MONOTONIC_RAW, &start_time) == -1)
1065 : 0 : return -errno;
1066 : 7285 : long delta = 0;
1067 : 7285 : do
1068 : : {
1069 : : /* Check to see how long querying is taking. */
1070 [ + + ]: 7285 : if (maxtime > 0)
1071 : : {
1072 [ - + ]: 5 : if (clock_gettime(CLOCK_MONOTONIC_RAW, &cur_time) == -1)
1073 : 0 : return -errno;
1074 : 5 : delta = cur_time.tv_sec - start_time.tv_sec;
1075 [ - + ]: 5 : if ( delta > maxtime)
1076 : : {
1077 : 0 : dprintf(vfd, "Timeout with max time=%lds and transfer time=%lds\n", maxtime, delta );
1078 : 0 : return -ETIME;
1079 : : }
1080 : : }
1081 : : /* Wait 1 second, the minimum DEBUGINFOD_TIMEOUT. */
1082 : 7285 : curl_multi_wait(curlm, NULL, 0, 1000, NULL);
1083 : 7285 : CURLMcode curlm_res = curl_multi_perform(curlm, &still_running);
1084 : :
1085 [ + + ]: 7285 : if (only_one)
1086 : : {
1087 : : /* If the target file has been found, abort the other queries. */
1088 [ + - + + ]: 7214 : if (target_handle && *target_handle != NULL)
1089 : : {
1090 [ + + ]: 5588 : for (int i = 0; i < num_urls; i++)
1091 [ + + ]: 3466 : if (data[i].handle != *target_handle)
1092 : 1344 : curl_multi_remove_handle(curlm, data[i].handle);
1093 : : else
1094 : : {
1095 : 2122 : *committed_to = i;
1096 [ + + ]: 2122 : if (c->winning_headers == NULL)
1097 : : {
1098 : 1464 : c->winning_headers = data[*committed_to].response_data;
1099 [ + + + - ]: 1464 : if (vfd >= 0 && c->winning_headers != NULL)
1100 : 1104 : dprintf(vfd, "\n%s", c->winning_headers);
1101 : 1464 : data[*committed_to].response_data = NULL;
1102 : 1464 : data[*committed_to].response_data_size = 0;
1103 : : }
1104 : : }
1105 : : }
1106 : :
1107 [ + + + + ]: 7214 : if (vfd >= 0 && !verbose_reported && *committed_to >= 0)
1108 : : {
1109 [ - + - - ]: 2208 : bool pnl = (c->default_progressfn_printed_p && vfd == STDERR_FILENO);
1110 : 1104 : dprintf (vfd, "%scommitted to url %d\n", pnl ? "\n" : "",
1111 : : *committed_to);
1112 [ - + ]: 1104 : if (pnl)
1113 : 0 : c->default_progressfn_printed_p = 0;
1114 : : verbose_reported = true;
1115 : : }
1116 : : }
1117 : :
1118 [ - + ]: 7285 : if (curlm_res != CURLM_OK)
1119 : : {
1120 [ # # # ]: 0 : switch (curlm_res)
1121 : : {
1122 : 0 : case CURLM_CALL_MULTI_PERFORM: continue;
1123 : : case CURLM_OUT_OF_MEMORY: return -ENOMEM;
1124 : 0 : default: return -ENETUNREACH;
1125 : : }
1126 : : }
1127 : :
1128 : 7285 : long dl_size = -1;
1129 [ + + + + : 7285 : if (target_handle && *target_handle && (c->progressfn || maxsize > 0))
+ + - + ]
1130 : : {
1131 : : /* Get size of file being downloaded. NB: If going through
1132 : : deflate-compressing proxies, this number is likely to be
1133 : : unavailable, so -1 may show. */
1134 : 1030 : CURLcode curl_res;
1135 : : #if CURL_AT_LEAST_VERSION(7, 55, 0)
1136 : 1030 : curl_off_t cl;
1137 : 1030 : curl_res = curl_easy_getinfo(*target_handle,
1138 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
1139 : : &cl);
1140 [ + - - + ]: 1030 : if (curl_res == CURLE_OK && cl >= 0)
1141 : : dl_size = (cl > LONG_MAX ? LONG_MAX : (long)cl);
1142 : : #else
1143 : : double cl;
1144 : : curl_res = curl_easy_getinfo(*target_handle,
1145 : : CURLINFO_CONTENT_LENGTH_DOWNLOAD,
1146 : : &cl);
1147 : : if (curl_res == CURLE_OK && cl >= 0)
1148 : : dl_size = (cl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)cl);
1149 : : #endif
1150 : : /* If Content-Length is -1, try to get the size from
1151 : : X-Debuginfod-Size */
1152 [ # # ]: 0 : if (dl_size == -1 && c->winning_headers != NULL)
1153 : : {
1154 : 0 : long xdl;
1155 : 0 : char *hdr = strcasestr(c->winning_headers, "x-debuginfod-size");
1156 : 0 : size_t off = strlen("x-debuginfod-size:");
1157 : :
1158 [ # # # # ]: 0 : if (hdr != NULL && sscanf(hdr + off, "%ld", &xdl) == 1)
1159 : 0 : dl_size = xdl;
1160 : : }
1161 : : }
1162 : :
1163 [ + + ]: 7285 : if (c->progressfn) /* inform/check progress callback */
1164 : : {
1165 : 5026 : loops ++;
1166 : 5026 : long pa = loops; /* default param for progress callback */
1167 [ + + + + ]: 5026 : if (target_handle && *target_handle) /* we've committed to a server; report its download progress */
1168 : : {
1169 : : /* PR30809: Check actual size of cached file. This same
1170 : : fd is shared by all the multi-curl handles (but only
1171 : : one will end up writing to it). Another way could be
1172 : : to tabulate totals in debuginfod_write_callback(). */
1173 : 1030 : struct stat cached;
1174 : 1030 : int statrc = fstat(data[*committed_to].fd, &cached);
1175 [ + - ]: 1030 : if (statrc == 0)
1176 : 1030 : pa = (long) cached.st_size;
1177 : : else
1178 : : {
1179 : : /* Otherwise, query libcurl for its tabulated total.
1180 : : However, that counts http body length, not
1181 : : decoded/decompressed content length, so does not
1182 : : measure quite the same thing as dl. */
1183 : 0 : CURLcode curl_res;
1184 : : #if CURL_AT_LEAST_VERSION(7, 55, 0)
1185 : 0 : curl_off_t dl;
1186 : 0 : curl_res = curl_easy_getinfo(target_handle,
1187 : : CURLINFO_SIZE_DOWNLOAD_T,
1188 : : &dl);
1189 [ # # # # ]: 0 : if (curl_res == 0 && dl >= 0)
1190 : 0 : pa = (dl > LONG_MAX ? LONG_MAX : (long)dl);
1191 : : #else
1192 : : double dl;
1193 : : curl_res = curl_easy_getinfo(target_handle,
1194 : : CURLINFO_SIZE_DOWNLOAD,
1195 : : &dl);
1196 : : if (curl_res == 0)
1197 : : pa = (dl >= (double)(LONG_MAX+1UL) ? LONG_MAX : (long)dl);
1198 : : #endif
1199 : : }
1200 : :
1201 [ - + - + ]: 1030 : if ((*c->progressfn) (c, pa, dl_size == -1 ? 0 : dl_size))
1202 : : {
1203 : 0 : c->progressfn_cancel = true;
1204 : 0 : break;
1205 : : }
1206 : : }
1207 : : }
1208 : : /* Check to see if we are downloading something which exceeds maxsize, if set.*/
1209 [ + + + + : 7253 : if (target_handle && *target_handle && dl_size > maxsize && maxsize > 0)
- + ]
1210 : : {
1211 [ # # ]: 0 : if (vfd >=0)
1212 : 0 : dprintf(vfd, "Content-Length too large.\n");
1213 : 0 : return -EFBIG;
1214 : : }
1215 [ + + ]: 7285 : } while (still_running);
1216 : :
1217 : : return 0;
1218 : : }
1219 : :
1220 : :
1221 : : /* Copy SRC to DEST, s,/,#,g */
1222 : :
1223 : : static void
1224 : 1162 : path_escape (const char *src, char *dest, size_t dest_len)
1225 : : {
1226 : : /* PR32218: Reversibly-escaping src character-by-character, for
1227 : : large enough strings, risks ENAMETOOLONG errors. For long names,
1228 : : a simple hash based generated name instead, while still
1229 : : attempting to preserve the as much of the content as possible.
1230 : : It's possible that absurd choices of incoming names will collide
1231 : : or still get truncated, but c'est la vie.
1232 : : */
1233 : :
1234 : : /* Compute a three-way min() for the actual output string generated. */
1235 [ - + ]: 1162 : assert (dest_len > 10); /* Space enough for degenerate case of
1236 : : "HASHHASH-\0". NB: dest_len is not
1237 : : user-controlled. */
1238 : : /* Use only NAME_MAX/2 characters in the output file name.
1239 : : ENAMETOOLONG has been observed even on 300-ish character names on
1240 : : some filesystems. */
1241 : 1162 : const size_t max_dest_len = NAME_MAX/2;
1242 : 1162 : dest_len = dest_len > max_dest_len ? max_dest_len : dest_len;
1243 : : /* Use only strlen(src)+10 bytes, if that's smaller. Yes, we could
1244 : : just fit an entire escaped name in there in theory, without the
1245 : : hash+etc. But then again the hashing protects against #-escape
1246 : : aliasing collisions: "foo[bar" "foo]bar" both escape to
1247 : : "foo#bar", thus aliasing, but have different "HASH-foo#bar".
1248 : : */
1249 : 1162 : const size_t hash_prefix_destlen = strlen(src)+10; /* DEADBEEF-src\0 */
1250 : 1162 : dest_len = dest_len > hash_prefix_destlen ? hash_prefix_destlen : dest_len;
1251 : :
1252 : 1162 : char *dest_write = dest + dest_len - 1;
1253 : 1162 : (*dest_write--) = '\0'; /* Ensure a \0 there. */
1254 : :
1255 : : /* Copy from back toward front, preferring to keep the .extension. */
1256 [ + + ]: 87968 : for (int fi=strlen(src)-1; fi >= 0 && dest_write >= dest; fi--)
1257 : : {
1258 : 86806 : char src_char = src[fi];
1259 [ + + ]: 86806 : switch (src_char)
1260 : : {
1261 : : /* Pass through ordinary identifier chars. */
1262 : 77748 : case '.': case '-': case '_':
1263 : : case 'a'...'z':
1264 : : case 'A'...'Z':
1265 : : case '0'...'9':
1266 : 77748 : *dest_write-- = src_char;
1267 : 77748 : break;
1268 : :
1269 : : /* Replace everything else, esp. security-sensitive /. */
1270 : 9058 : default:
1271 : 9058 : *dest_write-- = '#';
1272 : 9058 : break;
1273 : : }
1274 : : }
1275 : :
1276 : : /* djb2 hash algorithm: DJBX33A */
1277 : : unsigned long hash = 5381;
1278 : : const char *c = src;
1279 [ + + ]: 88770 : while (*c)
1280 : 87608 : hash = ((hash << 5) + hash) + *c++;
1281 : 1162 : char name_hash_str [9];
1282 : : /* Truncate to 4 bytes; along with the remaining hundredish bytes of text,
1283 : : should be ample against accidental collisions. */
1284 : 1162 : snprintf (name_hash_str, sizeof(name_hash_str), "%08x", (unsigned int) hash);
1285 : 1162 : memcpy (&dest[0], name_hash_str, 8); /* Overwrite the first few characters */
1286 : 1162 : dest[8] = '-'; /* Add a bit of punctuation to make hash stand out */
1287 : 1162 : }
1288 : :
1289 : : /* Attempt to update the atime */
1290 : : static void
1291 : 114 : update_atime (int fd)
1292 : : {
1293 : 114 : struct timespec tvs[2];
1294 : :
1295 : 114 : tvs[0].tv_sec = tvs[1].tv_sec = 0;
1296 : 114 : tvs[0].tv_nsec = UTIME_NOW;
1297 : 114 : tvs[1].tv_nsec = UTIME_OMIT;
1298 : :
1299 : 114 : (void) futimens (fd, tvs); /* best effort */
1300 : 114 : }
1301 : :
1302 : : /* Attempt to read an ELF/DWARF section with name SECTION from FD and write
1303 : : it to a separate file in the debuginfod cache. If successful the absolute
1304 : : path of the separate file containing SECTION will be stored in USR_PATH.
1305 : : FD_PATH is the absolute path for FD.
1306 : :
1307 : : If the section cannot be extracted, then return a negative error code.
1308 : : -ENOENT indicates that the parent file was able to be read but the
1309 : : section name was not found. -EEXIST indicates that the section was
1310 : : found but had type SHT_NOBITS. */
1311 : :
1312 : : static int
1313 : 12 : extract_section (int fd, const char *section, char *fd_path, char **usr_path)
1314 : : {
1315 : 12 : elf_version (EV_CURRENT);
1316 : :
1317 : 12 : Elf *elf = elf_begin (fd, ELF_C_READ_MMAP_PRIVATE, NULL);
1318 [ + - ]: 12 : if (elf == NULL)
1319 : : return -EIO;
1320 : :
1321 : 12 : size_t shstrndx;
1322 : 12 : int rc = elf_getshdrstrndx (elf, &shstrndx);
1323 [ - + ]: 12 : if (rc < 0)
1324 : : {
1325 : 0 : rc = -EIO;
1326 : 0 : goto out;
1327 : : }
1328 : :
1329 : 12 : int sec_fd = -1;
1330 : 12 : char *sec_path_tmp = NULL;
1331 : 12 : Elf_Scn *scn = NULL;
1332 : :
1333 : : /* Try to find the target section and copy the contents into a
1334 : : separate file. */
1335 : 424 : while (true)
1336 : 206 : {
1337 : 218 : scn = elf_nextscn (elf, scn);
1338 [ - + ]: 218 : if (scn == NULL)
1339 : : {
1340 : 0 : rc = -ENOENT;
1341 : 4 : goto out;
1342 : : }
1343 : 218 : GElf_Shdr shdr_storage;
1344 : 218 : GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_storage);
1345 [ - + ]: 218 : if (shdr == NULL)
1346 : : {
1347 : 0 : rc = -EIO;
1348 : 0 : goto out;
1349 : : }
1350 : :
1351 : 218 : const char *scn_name = elf_strptr (elf, shstrndx, shdr->sh_name);
1352 [ - + ]: 218 : if (scn_name == NULL)
1353 : : {
1354 : 0 : rc = -EIO;
1355 : 0 : goto out;
1356 : : }
1357 [ + + ]: 218 : if (strcmp (scn_name, section) == 0)
1358 : : {
1359 : : /* We found the desired section. */
1360 [ + + ]: 12 : if (shdr->sh_type == SHT_NOBITS)
1361 : : {
1362 : 4 : rc = -EEXIST;
1363 : 4 : goto out;
1364 : : }
1365 : :
1366 : 8 : Elf_Data *data = NULL;
1367 : 8 : data = elf_rawdata (scn, NULL);
1368 [ - + ]: 8 : if (data == NULL)
1369 : : {
1370 : 0 : rc = -EIO;
1371 : 0 : goto out;
1372 : : }
1373 : :
1374 [ - + ]: 8 : if (data->d_buf == NULL)
1375 : : {
1376 : 0 : rc = -EIO;
1377 : 0 : goto out;
1378 : : }
1379 : :
1380 : : /* Compute the absolute filename we'll write the section to.
1381 : : Replace the last component of FD_PATH with the path-escaped
1382 : : section filename. */
1383 : 8 : int i = strlen (fd_path);
1384 [ + - ]: 92 : while (i >= 0)
1385 : : {
1386 [ + + ]: 92 : if (fd_path[i] == '/')
1387 : : {
1388 : 8 : fd_path[i] = '\0';
1389 : 8 : break;
1390 : : }
1391 : 84 : --i;
1392 : : }
1393 : :
1394 : 8 : char suffix[NAME_MAX];
1395 : 8 : path_escape (section, suffix, sizeof(suffix));
1396 : 8 : rc = asprintf (&sec_path_tmp, "%s/section-%s.XXXXXX",
1397 : : fd_path, suffix);
1398 [ - + ]: 8 : if (rc == -1)
1399 : : {
1400 : 0 : rc = -ENOMEM;
1401 : 0 : goto out1;
1402 : : }
1403 : :
1404 : 8 : sec_fd = mkstemp (sec_path_tmp);
1405 [ - + ]: 8 : if (sec_fd < 0)
1406 : : {
1407 : 0 : rc = -EIO;
1408 : 8 : goto out2;
1409 : : }
1410 : :
1411 : 8 : ssize_t res = write_retry (sec_fd, data->d_buf, data->d_size);
1412 [ + - - + ]: 8 : if (res < 0 || (size_t) res != data->d_size)
1413 : : {
1414 : 0 : rc = -EIO;
1415 : 0 : goto out3;
1416 : : }
1417 : :
1418 : : /* Success. Rename tmp file and update USR_PATH. */
1419 : 8 : char *sec_path;
1420 [ - + ]: 8 : if (asprintf (&sec_path, "%s/section-%s", fd_path, section) == -1)
1421 : : {
1422 : 0 : rc = -ENOMEM;
1423 : 0 : goto out3;
1424 : : }
1425 : :
1426 : 8 : rc = rename (sec_path_tmp, sec_path);
1427 [ - + ]: 8 : if (rc < 0)
1428 : : {
1429 : 0 : free (sec_path);
1430 : 0 : rc = -EIO;
1431 : 0 : goto out3;
1432 : : }
1433 : :
1434 [ + - ]: 8 : if (usr_path != NULL)
1435 : 8 : *usr_path = sec_path;
1436 : : else
1437 : 0 : free (sec_path);
1438 : 8 : update_atime(fd);
1439 : 8 : rc = sec_fd;
1440 : 8 : goto out2;
1441 : : }
1442 : : }
1443 : :
1444 : 0 : out3:
1445 : 0 : close (sec_fd);
1446 : 0 : unlink (sec_path_tmp);
1447 : :
1448 : 8 : out2:
1449 : 8 : free (sec_path_tmp);
1450 : :
1451 : 12 : out1:
1452 : 12 : out:
1453 : 12 : elf_end (elf);
1454 : 12 : return rc;
1455 : : }
1456 : :
1457 : : /* Search TARGET_CACHE_DIR for a debuginfo or executable file containing
1458 : : an ELF/DWARF section with name SCN_NAME. If found, extract the section
1459 : : to a separate file in TARGET_CACHE_DIR and return a file descriptor
1460 : : for the section file. The path for this file will be stored in USR_PATH.
1461 : : Return a negative errno if unsuccessful. -ENOENT indicates that SCN_NAME
1462 : : is confirmed to not exist. */
1463 : :
1464 : : static int
1465 : 16 : cache_find_section (const char *scn_name, const char *target_cache_dir,
1466 : : char **usr_path)
1467 : : {
1468 : 16 : int debug_fd;
1469 : 16 : int rc = -EEXIST;
1470 : 16 : char parent_path[PATH_MAX];
1471 : :
1472 : : /* Check the debuginfo first. */
1473 : 16 : snprintf (parent_path, PATH_MAX, "%s/debuginfo", target_cache_dir);
1474 : 16 : debug_fd = open (parent_path, O_RDONLY);
1475 [ + + ]: 16 : if (debug_fd >= 0)
1476 : : {
1477 : 8 : rc = extract_section (debug_fd, scn_name, parent_path, usr_path);
1478 : 8 : close (debug_fd);
1479 : : }
1480 : :
1481 : : /* If the debuginfo file couldn't be found or the section type was
1482 : : SHT_NOBITS, check the executable. */
1483 [ + + ]: 8 : if (rc == -EEXIST)
1484 : : {
1485 : 12 : snprintf (parent_path, PATH_MAX, "%s/executable", target_cache_dir);
1486 : 12 : int exec_fd = open (parent_path, O_RDONLY);
1487 : :
1488 [ + + ]: 12 : if (exec_fd >= 0)
1489 : : {
1490 : 4 : rc = extract_section (exec_fd, scn_name, parent_path, usr_path);
1491 : 4 : close (exec_fd);
1492 : :
1493 : : /* Don't return -ENOENT if the debuginfo wasn't opened. The
1494 : : section may exist in the debuginfo but not the executable. */
1495 [ - + ]: 4 : if (debug_fd < 0 && rc == -ENOENT)
1496 : 0 : rc = -EREMOTE;
1497 : : }
1498 : : }
1499 : :
1500 : 16 : return rc;
1501 : : }
1502 : :
1503 : :
1504 : : #ifdef ENABLE_IMA_VERIFICATION
1505 : : /* Extract the hash algorithm name from the signature header, of which
1506 : : there are several types. The name will be used for openssl hashing
1507 : : of the file content. The header doesn't need to be super carefully
1508 : : parsed, because if any part of it is wrong, be it the hash
1509 : : algorithm number or hash value or whatever, it will fail
1510 : : computation or verification. Return NULL in case of error. */
1511 : : static const char*
1512 : : get_signature_params(debuginfod_client *c, unsigned char *bin_sig)
1513 : : {
1514 : : int hashalgo = 0;
1515 : :
1516 : : switch (bin_sig[0])
1517 : : {
1518 : : case EVM_IMA_XATTR_DIGSIG:
1519 : : #ifdef IMA_VERITY_DIGSIG /* missing on debian-i386 trybot */
1520 : : case IMA_VERITY_DIGSIG:
1521 : : #endif
1522 : : break;
1523 : : default:
1524 : : if (c->verbose_fd >= 0)
1525 : : dprintf (c->verbose_fd, "Unknown ima digsig %d\n", (int)bin_sig[0]);
1526 : : return NULL;
1527 : : }
1528 : :
1529 : : switch (bin_sig[1])
1530 : : {
1531 : : case DIGSIG_VERSION_2:
1532 : : {
1533 : : struct signature_v2_hdr hdr_v2;
1534 : : memcpy(& hdr_v2, & bin_sig[1], sizeof(struct signature_v2_hdr));
1535 : : hashalgo = hdr_v2.hash_algo;
1536 : : break;
1537 : : }
1538 : : default:
1539 : : if (c->verbose_fd >= 0)
1540 : : dprintf (c->verbose_fd, "Unknown ima signature version %d\n", (int)bin_sig[1]);
1541 : : return NULL;
1542 : : }
1543 : :
1544 : : switch (hashalgo)
1545 : : {
1546 : : case PKEY_HASH_SHA1: return "sha1";
1547 : : case PKEY_HASH_SHA256: return "sha256";
1548 : : // (could add many others from enum pkey_hash_algo)
1549 : : default:
1550 : : if (c->verbose_fd >= 0)
1551 : : dprintf (c->verbose_fd, "Unknown ima pkey hash %d\n", hashalgo);
1552 : : return NULL;
1553 : : }
1554 : : }
1555 : :
1556 : :
1557 : : /* Verify given hash against given signature blob. Return 0 on ok, -errno otherwise. */
1558 : : static int
1559 : : debuginfod_verify_hash(debuginfod_client *c, const unsigned char *hash, int size,
1560 : : const char *hash_algo, unsigned char *sig, int siglen)
1561 : : {
1562 : : int ret = -EBADMSG;
1563 : : struct public_key_entry *pkey;
1564 : : struct signature_v2_hdr hdr;
1565 : : EVP_PKEY_CTX *ctx;
1566 : : const EVP_MD *md;
1567 : :
1568 : : if (siglen < (int) sizeof(struct signature_v2_hdr))
1569 : : return -EBADMSG;
1570 : :
1571 : : memcpy(&hdr, sig, sizeof(struct signature_v2_hdr)); /* avoid just aliasing */
1572 : :
1573 : : if (c->verbose_fd >= 0)
1574 : : dprintf (c->verbose_fd, "Searching for ima keyid %04x\n", ntohl(hdr.keyid));
1575 : :
1576 : : /* Find the matching public key. */
1577 : : for (pkey = c->ima_public_keys; pkey != NULL; pkey = pkey->next)
1578 : : if (pkey->keyid == ntohl(hdr.keyid)) break;
1579 : : if (!pkey)
1580 : : return -ENOKEY;
1581 : :
1582 : : if (!(ctx = EVP_PKEY_CTX_new(pkey->key, NULL)))
1583 : : goto err;
1584 : : if (!EVP_PKEY_verify_init(ctx))
1585 : : goto err;
1586 : : if (!(md = EVP_get_digestbyname(hash_algo)))
1587 : : goto err;
1588 : : if (!EVP_PKEY_CTX_set_signature_md(ctx, md))
1589 : : goto err;
1590 : : ret = EVP_PKEY_verify(ctx, sig + sizeof(hdr),
1591 : : siglen - sizeof(hdr), hash, size);
1592 : : if (ret == 1)
1593 : : ret = 0; // success!
1594 : : else if (ret == 0)
1595 : : ret = -EBADMSG;
1596 : : err:
1597 : : EVP_PKEY_CTX_free(ctx);
1598 : : return ret;
1599 : : }
1600 : :
1601 : :
1602 : :
1603 : : /* Validate an IMA file signature.
1604 : : * Returns 0 on signature validity, -EINVAL on signature invalidity, -ENOSYS on undefined imaevm machinery,
1605 : : * -ENOKEY on key issues, or other -errno.
1606 : : */
1607 : :
1608 : : static int
1609 : : debuginfod_validate_imasig (debuginfod_client *c, int fd)
1610 : : {
1611 : : int rc = ENOSYS;
1612 : :
1613 : : char* sig_buf = NULL;
1614 : : EVP_MD_CTX *ctx = NULL;
1615 : : if (!c || !c->winning_headers)
1616 : : {
1617 : : rc = -ENODATA;
1618 : : goto exit_validate;
1619 : : }
1620 : : // Extract the HEX IMA-signature from the header
1621 : : char* hdr_ima_sig = strcasestr(c->winning_headers, "x-debuginfod-imasignature");
1622 : : if (!hdr_ima_sig || 1 != sscanf(hdr_ima_sig + strlen("x-debuginfod-imasignature:"), "%ms", &sig_buf))
1623 : : {
1624 : : rc = -ENODATA;
1625 : : goto exit_validate;
1626 : : }
1627 : : if (strlen(sig_buf) > MAX_SIGNATURE_SIZE) // reject if too long
1628 : : {
1629 : : rc = -EBADMSG;
1630 : : goto exit_validate;
1631 : : }
1632 : : // Convert the hex signature to bin
1633 : : size_t bin_sig_len = strlen(sig_buf)/2;
1634 : : unsigned char bin_sig[MAX_SIGNATURE_SIZE/2];
1635 : : for (size_t b = 0; b < bin_sig_len; b++)
1636 : : bin_sig[b] = (hex2dec(sig_buf[2*b]) << 4) | hex2dec(sig_buf[2*b+1]);
1637 : :
1638 : : // Compute the binary digest of the cached file (with file descriptor fd)
1639 : : ctx = EVP_MD_CTX_new();
1640 : : const char* sighash_name = get_signature_params(c, bin_sig) ?: "";
1641 : : const EVP_MD *md = EVP_get_digestbyname(sighash_name);
1642 : : if (!ctx || !md || !EVP_DigestInit(ctx, md))
1643 : : {
1644 : : rc = -EBADMSG;
1645 : : goto exit_validate;
1646 : : }
1647 : :
1648 : : long data_len;
1649 : : char* hdr_data_len = strcasestr(c->winning_headers, "x-debuginfod-size");
1650 : : if (!hdr_data_len || 1 != sscanf(hdr_data_len + strlen("x-debuginfod-size:") , "%ld", &data_len))
1651 : : {
1652 : : rc = -ENODATA;
1653 : : goto exit_validate;
1654 : : }
1655 : :
1656 : : /* Don't trust the size the server sent us, double check against the
1657 : : file size that we actually got. That way we calculate the hash
1658 : : over the whole file and not a shorter (possibly empty) data size. */
1659 : : struct stat st;
1660 : : if (fstat (fd, &st) == -1)
1661 : : {
1662 : : rc = -errno;
1663 : : goto exit_validate;
1664 : : }
1665 : : if (data_len != st.st_size)
1666 : : {
1667 : : rc = -EBADMSG;
1668 : : goto exit_validate;
1669 : : }
1670 : :
1671 : : char file_data[DATA_SIZE]; // imaevm.h data chunk hash size
1672 : : ssize_t n;
1673 : : for(off_t k = 0; k < data_len; k += n)
1674 : : {
1675 : : if (-1 == (n = pread(fd, file_data, DATA_SIZE, k)))
1676 : : {
1677 : : rc = -errno;
1678 : : goto exit_validate;
1679 : : }
1680 : :
1681 : : if (!EVP_DigestUpdate(ctx, file_data, n))
1682 : : {
1683 : : rc = -EBADMSG;
1684 : : goto exit_validate;
1685 : : }
1686 : : }
1687 : :
1688 : : uint8_t bin_dig[MAX_DIGEST_SIZE];
1689 : : unsigned int bin_dig_len;
1690 : : if (!EVP_DigestFinal(ctx, bin_dig, &bin_dig_len))
1691 : : {
1692 : : rc = -EBADMSG;
1693 : : goto exit_validate;
1694 : : }
1695 : :
1696 : : // XXX: in case of DIGSIG_VERSION_3, need to hash the file hash, yo dawg
1697 : :
1698 : : int res = debuginfod_verify_hash(c,
1699 : : bin_dig, bin_dig_len,
1700 : : sighash_name,
1701 : : & bin_sig[1], bin_sig_len-1); // skip over first byte of signature
1702 : : if (c->verbose_fd >= 0)
1703 : : dprintf (c->verbose_fd, "Computed ima signature verification res=%d\n", res);
1704 : : rc = res;
1705 : :
1706 : : exit_validate:
1707 : : free (sig_buf);
1708 : : EVP_MD_CTX_free(ctx);
1709 : : return rc;
1710 : : }
1711 : : #endif /* ENABLE_IMA_VERIFICATION */
1712 : :
1713 : :
1714 : :
1715 : :
1716 : : /* Helper function to create client cache directory.
1717 : : $XDG_CACHE_HOME takes priority over $HOME/.cache.
1718 : : $DEBUGINFOD_CACHE_PATH takes priority over $HOME/.cache and $XDG_CACHE_HOME.
1719 : :
1720 : : Return resulting path name or NULL on error. Caller must free resulting string.
1721 : : */
1722 : : static char *
1723 : 2138 : make_cache_path(void)
1724 : : {
1725 : 2138 : char* cache_path = NULL;
1726 : 2138 : int rc = 0;
1727 : : /* Determine location of the cache. The path specified by the debuginfod
1728 : : cache environment variable takes priority. */
1729 : 2138 : char *cache_var = getenv(DEBUGINFOD_CACHE_PATH_ENV_VAR);
1730 [ + - + + ]: 2138 : if (cache_var != NULL && strlen (cache_var) > 0)
1731 [ - + ]: 2130 : xalloc_str (cache_path, "%s", cache_var);
1732 : : else
1733 : : {
1734 : : /* If a cache already exists in $HOME ('/' if $HOME isn't set), then use
1735 : : that. Otherwise use the XDG cache directory naming format. */
1736 [ - + - + ]: 16 : xalloc_str (cache_path, "%s/%s", getenv ("HOME") ?: "/", cache_default_name);
1737 : :
1738 : 8 : struct stat st;
1739 [ + + ]: 8 : if (stat (cache_path, &st) < 0)
1740 : : {
1741 : 6 : char cachedir[PATH_MAX];
1742 : 6 : char *xdg = getenv ("XDG_CACHE_HOME");
1743 : :
1744 [ + - + + ]: 6 : if (xdg != NULL && strlen (xdg) > 0)
1745 : 2 : snprintf (cachedir, PATH_MAX, "%s", xdg);
1746 : : else
1747 [ - + ]: 4 : snprintf (cachedir, PATH_MAX, "%s/.cache", getenv ("HOME") ?: "/");
1748 : :
1749 : : /* Create XDG cache directory if it doesn't exist. */
1750 [ + + ]: 6 : if (stat (cachedir, &st) == 0)
1751 : : {
1752 [ - + ]: 2 : if (! S_ISDIR (st.st_mode))
1753 : : {
1754 : 0 : rc = -EEXIST;
1755 : 0 : goto out1;
1756 : : }
1757 : : }
1758 : : else
1759 : : {
1760 : 4 : rc = mkdir (cachedir, 0700);
1761 : :
1762 : : /* Also check for EEXIST and S_ISDIR in case another client just
1763 : : happened to create the cache. */
1764 [ - + ]: 4 : if (rc < 0
1765 [ # # ]: 0 : && (errno != EEXIST
1766 [ # # ]: 0 : || stat (cachedir, &st) != 0
1767 [ # # ]: 0 : || ! S_ISDIR (st.st_mode)))
1768 : : {
1769 : 0 : rc = -errno;
1770 : 0 : goto out1;
1771 : : }
1772 : : }
1773 : :
1774 : 6 : free (cache_path);
1775 [ - + ]: 6 : xalloc_str (cache_path, "%s/%s", cachedir, cache_xdg_name);
1776 : : }
1777 : : }
1778 : :
1779 : 2138 : goto out;
1780 : :
1781 : 0 : out1:
1782 : 0 : (void) rc;
1783 : 0 : free (cache_path);
1784 : 0 : cache_path = NULL;
1785 : :
1786 : 2138 : out:
1787 [ + - ]: 2138 : if (cache_path != NULL)
1788 : 2138 : (void) mkdir (cache_path, 0700); // failures with this mkdir would be caught later too
1789 : 2138 : return cache_path;
1790 : : }
1791 : :
1792 : :
1793 : : /* Query each of the server URLs found in $DEBUGINFOD_URLS for the file
1794 : : with the specified build-id and type (debuginfo, executable, source or
1795 : : section). If type is source, then type_arg should be a filename. If
1796 : : type is section, then type_arg should be the name of an ELF/DWARF
1797 : : section. Otherwise type_arg may be NULL. Return a file descriptor
1798 : : for the target if successful, otherwise return an error code.
1799 : : */
1800 : : static int
1801 : 6522 : debuginfod_query_server_by_buildid (debuginfod_client *c,
1802 : : const unsigned char *build_id,
1803 : : int build_id_len,
1804 : : const char *type,
1805 : : const char *type_arg,
1806 : : char **path)
1807 : : {
1808 : 6522 : char *server_urls;
1809 : 6522 : char *urls_envvar;
1810 : 6522 : const char *section = NULL;
1811 : 6522 : const char *filename = NULL;
1812 : 6522 : char *cache_path = NULL;
1813 : 6522 : char *maxage_path = NULL;
1814 : 6522 : char *interval_path = NULL;
1815 : 6522 : char *cache_miss_path = NULL;
1816 : 6522 : char *target_cache_dir = NULL;
1817 : 6522 : char *target_cache_path = NULL;
1818 : 6522 : char *target_cachehdr_path = NULL;
1819 : 6522 : char *target_cache_tmppath = NULL;
1820 : 6522 : char *target_cachehdr_tmppath = NULL;
1821 : 6522 : char suffix[NAME_MAX];
1822 : 6522 : char build_id_bytes[MAX_BUILD_ID_BYTES * 2 + 1];
1823 : 6522 : int vfd = c->verbose_fd;
1824 : 6522 : int rc, r;
1825 : :
1826 : 6522 : c->progressfn_cancel = false;
1827 : :
1828 [ + + ]: 6522 : if (strcmp (type, "source") == 0)
1829 : : filename = type_arg;
1830 [ + + ]: 1052 : else if (strcmp (type, "section") == 0)
1831 : : {
1832 : 16 : section = type_arg;
1833 [ + - ]: 16 : if (section == NULL)
1834 : : return -EINVAL;
1835 : : }
1836 : :
1837 [ + + ]: 6522 : if (vfd >= 0)
1838 : : {
1839 : 1124 : dprintf (vfd, "debuginfod_find_%s ", type);
1840 [ + + ]: 1124 : if (build_id_len == 0) /* expect clean hexadecimal */
1841 : 32 : dprintf (vfd, "%s", (const char *) build_id);
1842 : : else
1843 [ + + ]: 22932 : for (int i = 0; i < build_id_len; i++)
1844 : 21840 : dprintf (vfd, "%02x", build_id[i]);
1845 [ + + ]: 1124 : if (filename != NULL)
1846 : 1088 : dprintf (vfd, " %s\n", filename);
1847 : 1124 : dprintf (vfd, "\n");
1848 : : }
1849 : :
1850 : : /* Is there any server we can query? If not, don't do any work,
1851 : : just return with ENOSYS. Don't even access the cache. */
1852 : 6522 : urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR);
1853 [ + + ]: 6522 : if (vfd >= 0)
1854 [ - + ]: 1124 : dprintf (vfd, "server urls \"%s\"\n",
1855 : : urls_envvar != NULL ? urls_envvar : "");
1856 [ + + + + ]: 6522 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
1857 : : {
1858 : 4418 : rc = -ENOSYS;
1859 : 4418 : goto out;
1860 : : }
1861 : :
1862 : : /* Clear the obsolete data from a previous _find operation. */
1863 : 2104 : free (c->url);
1864 : 2104 : c->url = NULL;
1865 : 2104 : free (c->winning_headers);
1866 : 2104 : c->winning_headers = NULL;
1867 : :
1868 : : /* PR 27982: Add max size if DEBUGINFOD_MAXSIZE is set. */
1869 : 2104 : long maxsize = 0;
1870 : 2104 : const char *maxsize_envvar;
1871 : 2104 : maxsize_envvar = getenv(DEBUGINFOD_MAXSIZE_ENV_VAR);
1872 [ + + ]: 2104 : if (maxsize_envvar != NULL)
1873 : 2 : maxsize = atol (maxsize_envvar);
1874 : :
1875 : : /* PR 27982: Add max time if DEBUGINFOD_MAXTIME is set. */
1876 : 2104 : long maxtime = 0;
1877 : 2104 : const char *maxtime_envvar;
1878 : 2104 : maxtime_envvar = getenv(DEBUGINFOD_MAXTIME_ENV_VAR);
1879 [ + + ]: 2104 : if (maxtime_envvar != NULL)
1880 : 2 : maxtime = atol (maxtime_envvar);
1881 [ + + ]: 2104 : if (maxtime && vfd >= 0)
1882 : 2 : dprintf(vfd, "using max time %lds\n", maxtime);
1883 : :
1884 : 2104 : const char *headers_file_envvar;
1885 : 2104 : headers_file_envvar = getenv(DEBUGINFOD_HEADERS_FILE_ENV_VAR);
1886 [ - + ]: 2104 : if (headers_file_envvar != NULL)
1887 : 0 : add_headers_from_file(c, headers_file_envvar);
1888 : :
1889 : : /* Maxsize is valid*/
1890 [ + + ]: 2104 : if (maxsize > 0)
1891 : : {
1892 [ + - ]: 2 : if (vfd)
1893 : 2 : dprintf (vfd, "using max size %ldB\n", maxsize);
1894 : 2 : char *size_header = NULL;
1895 : 2 : rc = asprintf (&size_header, "X-DEBUGINFOD-MAXSIZE: %ld", maxsize);
1896 [ - + ]: 2 : if (rc < 0)
1897 : : {
1898 : 0 : rc = -ENOMEM;
1899 : 0 : goto out;
1900 : : }
1901 : 2 : rc = debuginfod_add_http_header(c, size_header);
1902 : 2 : free(size_header);
1903 [ - + ]: 2 : if (rc < 0)
1904 : 0 : goto out;
1905 : : }
1906 : 2104 : add_default_headers(c);
1907 : :
1908 : : /* Copy lowercase hex representation of build_id into buf. */
1909 [ + + ]: 2104 : if (vfd >= 0)
1910 : 1124 : dprintf (vfd, "checking build-id\n");
1911 [ + - + + ]: 2104 : if ((build_id_len >= MAX_BUILD_ID_BYTES) ||
1912 : 1010 : (build_id_len == 0 &&
1913 [ - + ]: 1010 : strlen ((const char *) build_id) > MAX_BUILD_ID_BYTES*2))
1914 : : {
1915 : 0 : rc = -EINVAL;
1916 : 0 : goto out;
1917 : : }
1918 : :
1919 [ + + ]: 2104 : if (build_id_len == 0) /* expect clean hexadecimal */
1920 : 1010 : strcpy (build_id_bytes, (const char *) build_id);
1921 : : else
1922 [ + + ]: 22974 : for (int i = 0; i < build_id_len; i++)
1923 : 21880 : sprintf(build_id_bytes + (i * 2), "%02x", build_id[i]);
1924 : :
1925 [ + + ]: 2104 : if (filename != NULL)
1926 : : {
1927 [ + + ]: 1138 : if (vfd >= 0)
1928 : 1088 : dprintf (vfd, "checking filename\n");
1929 [ - + ]: 1138 : if (filename[0] == '\0') // must not be empty
1930 : : {
1931 : 0 : rc = -EINVAL;
1932 : 0 : goto out;
1933 : : }
1934 : :
1935 : 1138 : path_escape (filename, suffix, sizeof(suffix));
1936 : : /* If the DWARF filenames are super long, this could exceed
1937 : : PATH_MAX and truncate/collide. Oh well, that'll teach
1938 : : them! */
1939 : : }
1940 [ + + ]: 966 : else if (section != NULL)
1941 : 16 : path_escape (section, suffix, sizeof(suffix));
1942 : : else
1943 : 950 : suffix[0] = '\0';
1944 : :
1945 [ + + + + ]: 2104 : if (suffix[0] != '\0' && vfd >= 0)
1946 : 1104 : dprintf (vfd, "suffix %s\n", suffix);
1947 : :
1948 : : /* set paths needed to perform the query
1949 : : example format:
1950 : : cache_path: $HOME/.cache
1951 : : target_cache_dir: $HOME/.cache/0123abcd
1952 : : target_cache_path: $HOME/.cache/0123abcd/debuginfo
1953 : : target_cache_path: $HOME/.cache/0123abcd/executable-.debug_info
1954 : : target_cache_path: $HOME/.cache/0123abcd/source-HASH-#PATH#TO#SOURCE
1955 : : target_cachehdr_path: $HOME/.cache/0123abcd/hdr-debuginfo
1956 : : target_cachehdr_path: $HOME/.cache/0123abcd/hdr-executable...
1957 : : */
1958 : :
1959 : 2104 : cache_path = make_cache_path();
1960 [ - + ]: 2104 : if (!cache_path)
1961 : : {
1962 : 0 : rc = -ENOMEM;
1963 : 0 : goto out;
1964 : : }
1965 [ - + ]: 2104 : xalloc_str (target_cache_dir, "%s/%s", cache_path, build_id_bytes);
1966 : 2104 : (void) mkdir (target_cache_dir, 0700); // failures with this mkdir would be caught later too
1967 : :
1968 [ + + ]: 2104 : if (suffix[0] != '\0') { /* section, source queries */
1969 [ - + ]: 1154 : xalloc_str (target_cache_path, "%s/%s-%s", target_cache_dir, type, suffix);
1970 [ - + ]: 1154 : xalloc_str (target_cachehdr_path, "%s/hdr-%s-%s", target_cache_dir, type, suffix);
1971 : : } else {
1972 [ - + ]: 950 : xalloc_str (target_cache_path, "%s/%s", target_cache_dir, type);
1973 [ - + ]: 950 : xalloc_str (target_cachehdr_path, "%s/hdr-%s", target_cache_dir, type);
1974 : : }
1975 [ - + ]: 2104 : xalloc_str (target_cache_tmppath, "%s.XXXXXX", target_cache_path);
1976 [ - + ]: 2104 : xalloc_str (target_cachehdr_tmppath, "%s.XXXXXX", target_cachehdr_path);
1977 : :
1978 : : /* XXX combine these */
1979 [ - + ]: 2104 : xalloc_str (interval_path, "%s/%s", cache_path, cache_clean_interval_filename);
1980 [ - + ]: 2104 : xalloc_str (cache_miss_path, "%s/%s", cache_path, cache_miss_filename);
1981 [ - + ]: 2104 : xalloc_str (maxage_path, "%s/%s", cache_path, cache_max_unused_age_filename);
1982 : :
1983 [ + + ]: 2104 : if (vfd >= 0)
1984 : 1124 : dprintf (vfd, "checking cache dir %s\n", cache_path);
1985 : :
1986 : : /* Make sure cache dir exists. debuginfo_clean_cache will then make
1987 : : sure the interval, cache_miss and maxage files exist. */
1988 [ + - ]: 2104 : if (mkdir (cache_path, ACCESSPERMS) != 0
1989 [ - + ]: 2104 : && errno != EEXIST)
1990 : : {
1991 : 0 : rc = -errno;
1992 : 0 : goto out;
1993 : : }
1994 : :
1995 : 2104 : rc = debuginfod_clean_cache(c, cache_path, interval_path, maxage_path);
1996 [ - + ]: 2104 : if (rc != 0)
1997 : 0 : goto out;
1998 : :
1999 : : /* Check if the target is already in the cache. */
2000 : 2104 : int fd = open(target_cache_path, O_RDONLY);
2001 [ + + ]: 2104 : if (fd >= 0)
2002 : : {
2003 : 58 : struct stat st;
2004 [ - + ]: 58 : if (fstat(fd, &st) != 0)
2005 : : {
2006 : 0 : rc = -errno;
2007 : 0 : close (fd);
2008 : 56 : goto out;
2009 : : }
2010 : :
2011 : : /* If the file is non-empty, then we are done. */
2012 [ + + ]: 58 : if (st.st_size > 0)
2013 : : {
2014 [ + - ]: 54 : if (path != NULL)
2015 : : {
2016 : 54 : *path = strdup(target_cache_path);
2017 [ - + ]: 54 : if (*path == NULL)
2018 : : {
2019 : 0 : rc = -errno;
2020 : 0 : close (fd);
2021 : 0 : goto out;
2022 : : }
2023 : : }
2024 : : /* Success!!!! */
2025 : 54 : update_atime(fd);
2026 : 54 : rc = fd;
2027 : :
2028 : : /* Attempt to transcribe saved headers. */
2029 : 54 : int fdh = open (target_cachehdr_path, O_RDONLY);
2030 [ + + ]: 54 : if (fdh >= 0)
2031 : : {
2032 [ + - + - ]: 52 : if (fstat (fdh, &st) == 0 && st.st_size > 0)
2033 : : {
2034 : 52 : c->winning_headers = malloc(st.st_size);
2035 [ + - ]: 52 : if (NULL != c->winning_headers)
2036 : : {
2037 : 52 : ssize_t bytes_read = pread_retry(fdh, c->winning_headers, st.st_size, 0);
2038 [ - + ]: 52 : if (bytes_read <= 0)
2039 : : {
2040 : 0 : free (c->winning_headers);
2041 : 0 : c->winning_headers = NULL;
2042 : 0 : (void) unlink (target_cachehdr_path);
2043 : : }
2044 [ + + ]: 52 : if (vfd >= 0)
2045 : 2 : dprintf (vfd, "found %s (bytes=%ld)\n", target_cachehdr_path, (long)bytes_read);
2046 : : }
2047 : : }
2048 : :
2049 : 52 : update_atime (fdh);
2050 : 52 : close (fdh);
2051 : : }
2052 : :
2053 : 54 : goto out;
2054 : : }
2055 : : else
2056 : : {
2057 : : /* The file is empty. Attempt to download only if enough time
2058 : : has passed since the last attempt. */
2059 : 4 : time_t cache_miss;
2060 : 4 : time_t target_mtime = st.st_mtime;
2061 : :
2062 : 4 : close(fd); /* no need to hold onto the negative-hit file descriptor */
2063 : :
2064 : 4 : rc = debuginfod_config_cache(c, cache_miss_path,
2065 : : cache_miss_default_s, &st);
2066 [ - + ]: 4 : if (rc < 0)
2067 : 0 : goto out;
2068 : :
2069 : 4 : cache_miss = (time_t)rc;
2070 [ + + ]: 4 : if (time(NULL) - target_mtime <= cache_miss)
2071 : : {
2072 : 2 : rc = -ENOENT;
2073 : 2 : goto out;
2074 : : }
2075 : : else
2076 : : /* TOCTOU non-problem: if another task races, puts a working
2077 : : download or an empty file in its place, unlinking here just
2078 : : means WE will try to download again as uncached. */
2079 : 2 : (void) unlink(target_cache_path);
2080 : : }
2081 : : }
2082 [ - + ]: 2046 : else if (errno == EACCES)
2083 : : /* Ensure old 000-permission files are not lingering in the cache. */
2084 : 0 : (void) unlink(target_cache_path);
2085 : :
2086 [ + + ]: 2048 : if (section != NULL)
2087 : : {
2088 : : /* Try to extract the section from a cached file before querying
2089 : : any servers. */
2090 : 16 : rc = cache_find_section (section, target_cache_dir, path);
2091 : :
2092 : : /* If the section was found or confirmed to not exist, then we
2093 : : are done. */
2094 [ + + ]: 16 : if (rc >= 0 || rc == -ENOENT)
2095 : 8 : goto out;
2096 : : }
2097 : :
2098 : 2040 : long timeout = default_timeout;
2099 : 2040 : const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
2100 [ - + ]: 2040 : if (timeout_envvar != NULL)
2101 : 0 : timeout = atoi (timeout_envvar);
2102 : :
2103 [ + + ]: 2040 : if (vfd >= 0)
2104 : 1114 : dprintf (vfd, "using timeout %ld\n", timeout);
2105 : :
2106 : : /* make a copy of the envvar so it can be safely modified. */
2107 : 2040 : server_urls = strdup(urls_envvar);
2108 [ - + ]: 2040 : if (server_urls == NULL)
2109 : : {
2110 : 0 : rc = -ENOMEM;
2111 : 0 : goto out;
2112 : : }
2113 : : /* thereafter, goto out0 on error*/
2114 : :
2115 : : /* Because of a race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
2116 [ + - ]: 2040 : for(int i=0; i<2; i++)
2117 : : {
2118 : : /* (re)create target directory in cache */
2119 : 2040 : (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
2120 : :
2121 : : /* NB: write to a temporary file first, to avoid race condition of
2122 : : multiple clients checking the cache, while a partially-written or empty
2123 : : file is in there, being written from libcurl. */
2124 : 2040 : fd = mkstemp (target_cache_tmppath);
2125 [ - + ]: 2040 : if (fd >= 0) break;
2126 : : }
2127 [ - + ]: 2040 : if (fd < 0) /* Still failed after two iterations. */
2128 : : {
2129 : 0 : rc = -errno;
2130 : 0 : goto out0;
2131 : : }
2132 : :
2133 : 2040 : char **server_url_list = NULL;
2134 : 2040 : ima_policy_t* url_ima_policies = NULL;
2135 : 2040 : char *server_url;
2136 : 2040 : int num_urls;
2137 : 2040 : r = init_server_urls("buildid", type, server_urls, &server_url_list, &url_ima_policies, &num_urls, vfd);
2138 [ - + ]: 2040 : if (0 != r)
2139 : : {
2140 : 0 : rc = r;
2141 : 0 : goto out1;
2142 : : }
2143 : :
2144 : : /* No URLs survived parsing / filtering? Abort abort abort. */
2145 [ - + ]: 2040 : if (num_urls == 0)
2146 : : {
2147 : 0 : rc = -ENOSYS;
2148 : 0 : goto out1;
2149 : : }
2150 : :
2151 : 2040 : int retry_limit = default_retry_limit;
2152 : 2040 : const char* retry_limit_envvar = getenv(DEBUGINFOD_RETRY_LIMIT_ENV_VAR);
2153 [ + + ]: 2040 : if (retry_limit_envvar != NULL)
2154 : 4 : retry_limit = atoi (retry_limit_envvar);
2155 : :
2156 : 2040 : CURLM *curlm = c->server_mhandle;
2157 : :
2158 : : /* Tracks which handle should write to fd. Set to the first
2159 : : handle that is ready to write the target file to the cache. */
2160 : 2040 : CURL *target_handle = NULL;
2161 : 2040 : struct handle_data *data = malloc(sizeof(struct handle_data) * num_urls);
2162 [ - + ]: 2040 : if (data == NULL)
2163 : : {
2164 : 0 : rc = -ENOMEM;
2165 : 0 : goto out1;
2166 : : }
2167 : :
2168 : : /* thereafter, goto out2 on error. */
2169 : :
2170 : : /*The beginning of goto block query_in_parallel.*/
2171 : 2040 : query_in_parallel:
2172 : 3124 : rc = -ENOENT; /* Reset rc to default.*/
2173 : :
2174 : : /* Initialize handle_data with default values. */
2175 [ + + ]: 6298 : for (int i = 0; i < num_urls; i++)
2176 : : {
2177 : 3174 : data[i].handle = NULL;
2178 : 3174 : data[i].fd = -1;
2179 : 3174 : data[i].errbuf[0] = '\0';
2180 : 3174 : data[i].response_data = NULL;
2181 : 3174 : data[i].response_data_size = 0;
2182 : : }
2183 : :
2184 : 3124 : char *escaped_string = NULL;
2185 : 3124 : size_t escaped_strlen = 0;
2186 [ + + ]: 3124 : if (filename)
2187 : : {
2188 : 1138 : escaped_string = curl_easy_escape(&target_handle,
2189 [ + + ]: 1138 : filename[0] == '/' ? filename+1 : filename,
2190 : : 0);
2191 [ - + ]: 1138 : if (!escaped_string)
2192 : : {
2193 : 0 : rc = -ENOMEM;
2194 : 0 : goto out2;
2195 : : }
2196 : 1138 : char *loc = escaped_string;
2197 : 1138 : escaped_strlen = strlen(escaped_string);
2198 [ + + ]: 8834 : while ((loc = strstr(loc, "%2F")))
2199 : : {
2200 : 7696 : loc[0] = '/';
2201 : : //pull the string back after replacement
2202 : : // loc-escaped_string finds the distance from the origin to the new location
2203 : : // - 2 accounts for the 2F which remain and don't need to be measured.
2204 : : // The two above subtracted from escaped_strlen yields the remaining characters
2205 : : // in the string which we want to pull back
2206 : 7696 : memmove(loc+1, loc+3,escaped_strlen - (loc-escaped_string) - 2);
2207 : : //Because the 2F was overwritten in the memmove (as desired) escaped_strlen is
2208 : : // now two shorter.
2209 : 7696 : escaped_strlen -= 2;
2210 : : }
2211 : : }
2212 : : /* Initialize each handle. */
2213 [ + + ]: 6298 : for (int i = 0; i < num_urls; i++)
2214 : : {
2215 [ + - ]: 3174 : if ((server_url = server_url_list[i]) == NULL)
2216 : : break;
2217 [ + + ]: 3174 : if (vfd >= 0)
2218 : : #ifdef ENABLE_IMA_VERIFICATION
2219 : : dprintf (vfd, "init server %d %s [IMA verification policy: %s]\n", i, server_url, ima_policy_enum2str(url_ima_policies[i]));
2220 : : #else
2221 : 1136 : dprintf (vfd, "init server %d %s\n", i, server_url);
2222 : : #endif
2223 : :
2224 : 3174 : data[i].fd = fd;
2225 : 3174 : data[i].target_handle = &target_handle;
2226 : 3174 : data[i].client = c;
2227 : :
2228 [ + + ]: 3174 : if (filename) /* must start with / */
2229 : : {
2230 : : /* PR28034 escape characters in completed url to %hh format. */
2231 : 1138 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
2232 : : build_id_bytes, type, escaped_string);
2233 : : }
2234 [ + + ]: 2036 : else if (section)
2235 : 8 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s/%s", server_url,
2236 : : build_id_bytes, type, section);
2237 : : else
2238 : 2028 : snprintf(data[i].url, PATH_MAX, "%s/%s/%s", server_url, build_id_bytes, type);
2239 : :
2240 : 3174 : r = init_handle(c, debuginfod_write_callback, header_callback, &data[i], i, timeout, vfd);
2241 [ - + ]: 3174 : if (0 != r)
2242 : : {
2243 : 0 : rc = r;
2244 [ # # ]: 0 : if (filename) curl_free (escaped_string);
2245 : 0 : goto out2;
2246 : : }
2247 : :
2248 : 3174 : curl_multi_add_handle(curlm, data[i].handle);
2249 : : }
2250 : :
2251 [ + + ]: 3124 : if (filename) curl_free(escaped_string);
2252 : :
2253 : : /* Query servers in parallel. */
2254 [ + + ]: 3124 : if (vfd >= 0)
2255 : 1134 : dprintf (vfd, "query %d urls in parallel\n", num_urls);
2256 : 3124 : int committed_to;
2257 : 3124 : r = perform_queries(curlm, &target_handle, data, c, num_urls, maxtime, maxsize, true, vfd, &committed_to);
2258 [ - + ]: 3124 : if (0 != r)
2259 : : {
2260 : 0 : rc = r;
2261 : 0 : goto out2;
2262 : : }
2263 : :
2264 : : /* Check whether a query was successful. If so, assign its handle
2265 : : to verified_handle. */
2266 : : int num_msg;
2267 : : rc = -ENOENT;
2268 : 3126 : CURL *verified_handle = NULL;
2269 : 3126 : do
2270 : : {
2271 : 3126 : CURLMsg *msg;
2272 : :
2273 : 3126 : msg = curl_multi_info_read(curlm, &num_msg);
2274 [ + - + - ]: 3126 : if (msg != NULL && msg->msg == CURLMSG_DONE)
2275 : : {
2276 [ + + ]: 3126 : if (vfd >= 0)
2277 : : {
2278 : 2272 : bool pnl = (c->default_progressfn_printed_p
2279 [ - + - - ]: 1136 : && vfd == STDERR_FILENO);
2280 [ + - ]: 1136 : dprintf (vfd, "%sserver response %s\n", pnl ? "\n" : "",
2281 : : curl_easy_strerror (msg->data.result));
2282 [ - + ]: 1136 : if (pnl)
2283 : 0 : c->default_progressfn_printed_p = 0;
2284 [ + - ]: 1138 : for (int i = 0; i < num_urls; i++)
2285 [ + + ]: 1138 : if (msg->easy_handle == data[i].handle)
2286 : : {
2287 [ + + ]: 1136 : if (strlen (data[i].errbuf) > 0)
2288 : 32 : dprintf (vfd, "url %d %s\n", i, data[i].errbuf);
2289 : : break;
2290 : : }
2291 : : }
2292 : :
2293 [ + + ]: 3126 : if (msg->data.result != CURLE_OK)
2294 : : {
2295 : 1662 : long resp_code;
2296 : 1662 : CURLcode ok0;
2297 : : /* Unsuccessful query, determine error code. */
2298 [ - + - - : 1662 : switch (msg->data.result)
- - - - +
+ - ]
2299 : : {
2300 : : case CURLE_COULDNT_RESOLVE_HOST: rc = -EHOSTUNREACH; break; // no NXDOMAIN
2301 : 0 : case CURLE_URL_MALFORMAT: rc = -EINVAL; break;
2302 : 1620 : case CURLE_COULDNT_CONNECT: rc = -ECONNREFUSED; break;
2303 : 1620 : case CURLE_PEER_FAILED_VERIFICATION: rc = -ECONNREFUSED; break;
2304 : 0 : case CURLE_REMOTE_ACCESS_DENIED: rc = -EACCES; break;
2305 : 0 : case CURLE_WRITE_ERROR: rc = -EIO; break;
2306 : 0 : case CURLE_OUT_OF_MEMORY: rc = -ENOMEM; break;
2307 : 0 : case CURLE_TOO_MANY_REDIRECTS: rc = -EMLINK; break;
2308 : 0 : case CURLE_SEND_ERROR: rc = -ECONNRESET; break;
2309 : 0 : case CURLE_RECV_ERROR: rc = -ECONNRESET; break;
2310 : 0 : case CURLE_OPERATION_TIMEDOUT: rc = -ETIME; break;
2311 : : case CURLE_HTTP_RETURNED_ERROR:
2312 : 40 : ok0 = curl_easy_getinfo (msg->easy_handle,
2313 : : CURLINFO_RESPONSE_CODE,
2314 : : &resp_code);
2315 : : /* 406 signals that the requested file was too large */
2316 [ + - + + ]: 40 : if ( ok0 == CURLE_OK && resp_code == 406)
2317 : : rc = -EFBIG;
2318 [ - + - - ]: 38 : else if (section != NULL && resp_code == 503)
2319 : : rc = -EINVAL;
2320 : : else
2321 : 40 : rc = -ENOENT;
2322 : : break;
2323 : 40 : default: rc = -ENOENT; break;
2324 : : }
2325 : : }
2326 : : else
2327 : : {
2328 : : /* Query completed without an error. Confirm that the
2329 : : response code is 200 when using HTTP/HTTPS and 0 when
2330 : : using file:// and set verified_handle. */
2331 : :
2332 [ + - ]: 1464 : if (msg->easy_handle != NULL)
2333 : : {
2334 : 1464 : char *effective_url = NULL;
2335 : 1464 : long resp_code = 500;
2336 : 1464 : CURLcode ok1 = curl_easy_getinfo (target_handle,
2337 : : CURLINFO_EFFECTIVE_URL,
2338 : : &effective_url);
2339 : 1464 : CURLcode ok2 = curl_easy_getinfo (target_handle,
2340 : : CURLINFO_RESPONSE_CODE,
2341 : : &resp_code);
2342 [ + - + - ]: 1464 : if(ok1 == CURLE_OK && ok2 == CURLE_OK && effective_url)
2343 : : {
2344 [ + + ]: 1464 : if (strncasecmp (effective_url, "HTTP", 4) == 0)
2345 [ + - ]: 1462 : if (resp_code == 200)
2346 : : {
2347 : 1462 : verified_handle = msg->easy_handle;
2348 : 1464 : break;
2349 : : }
2350 [ + - ]: 2 : if (strncasecmp (effective_url, "FILE", 4) == 0)
2351 [ + - ]: 2 : if (resp_code == 0)
2352 : : {
2353 : 2 : verified_handle = msg->easy_handle;
2354 : 2 : break;
2355 : : }
2356 : : }
2357 : : /* - libcurl since 7.52.0 version start to support
2358 : : CURLINFO_SCHEME;
2359 : : - before 7.61.0, effective_url would give us a
2360 : : url with upper case SCHEME added in the front;
2361 : : - effective_url between 7.61 and 7.69 can be lack
2362 : : of scheme if the original url doesn't include one;
2363 : : - since version 7.69 effective_url will be provide
2364 : : a scheme in lower case. */
2365 : : #if LIBCURL_VERSION_NUM >= 0x073d00 /* 7.61.0 */
2366 : : #if LIBCURL_VERSION_NUM <= 0x074500 /* 7.69.0 */
2367 : : char *scheme = NULL;
2368 : : CURLcode ok3 = curl_easy_getinfo (target_handle,
2369 : : CURLINFO_SCHEME,
2370 : : &scheme);
2371 : : if(ok3 == CURLE_OK && scheme)
2372 : : {
2373 : : if (startswith (scheme, "HTTP"))
2374 : : if (resp_code == 200)
2375 : : {
2376 : : verified_handle = msg->easy_handle;
2377 : : break;
2378 : : }
2379 : : }
2380 : : #endif
2381 : : #endif
2382 : : }
2383 : : }
2384 : : }
2385 [ + + ]: 1662 : } while (num_msg > 0);
2386 : :
2387 : : /* Create an empty file in the cache if the query fails with ENOENT and
2388 : : it wasn't cancelled early. */
2389 [ + + + - ]: 3124 : if (rc == -ENOENT && !c->progressfn_cancel)
2390 : : {
2391 : 1504 : int efd = open (target_cache_path, O_CREAT|O_EXCL, DEFFILEMODE);
2392 [ + + ]: 1504 : if (efd >= 0)
2393 : 1502 : close(efd);
2394 : : }
2395 [ + + ]: 1620 : else if (rc == -EFBIG)
2396 : 2 : goto out2;
2397 : :
2398 : : /* If the verified_handle is NULL and rc != -ENOENT, the query fails with
2399 : : * an error code other than 404, then do several retry within the retry_limit.
2400 : : * Clean up all old handles and jump back to the beginning of query_in_parallel,
2401 : : * reinitialize handles and query again.*/
2402 [ + + ]: 3122 : if (verified_handle == NULL)
2403 : : {
2404 [ + + + + ]: 1658 : if (rc != -ENOENT && retry_limit-- > 0)
2405 : : {
2406 [ + + ]: 1084 : if (vfd >= 0)
2407 : 20 : dprintf (vfd, "Retry failed query, %d attempt(s) remaining\n", retry_limit);
2408 : : /* remove all handles from multi */
2409 [ + + ]: 2168 : for (int i = 0; i < num_urls; i++)
2410 : : {
2411 : 1084 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2412 : 1084 : curl_easy_cleanup (data[i].handle);
2413 : 1084 : free(data[i].response_data);
2414 : 1084 : data[i].response_data = NULL;
2415 : : }
2416 : 1084 : free(c->winning_headers);
2417 : 1084 : c->winning_headers = NULL;
2418 : 1084 : goto query_in_parallel;
2419 : : }
2420 : : else
2421 : 574 : goto out2;
2422 : : }
2423 : :
2424 [ + + ]: 1464 : if (vfd >= 0)
2425 : : {
2426 [ - + - - ]: 2208 : bool pnl = c->default_progressfn_printed_p && vfd == STDERR_FILENO;
2427 : 1104 : dprintf (vfd, "%sgot file from server\n", pnl ? "\n" : "");
2428 [ - + ]: 1104 : if (pnl)
2429 : 0 : c->default_progressfn_printed_p = 0;
2430 : : }
2431 : :
2432 : : /* we've got one!!!! */
2433 : 1464 : time_t mtime;
2434 : : #if defined(_TIME_BITS) && _TIME_BITS == 64
2435 : : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME_T, (void*) &mtime);
2436 : : #else
2437 : 1464 : CURLcode curl_res = curl_easy_getinfo(verified_handle, CURLINFO_FILETIME, (void*) &mtime);
2438 : : #endif
2439 [ + - ]: 1464 : if (curl_res == CURLE_OK)
2440 : : {
2441 : 1464 : struct timespec tvs[2];
2442 : 1464 : tvs[0].tv_sec = 0;
2443 : 1464 : tvs[0].tv_nsec = UTIME_OMIT;
2444 : 1464 : tvs[1].tv_sec = mtime;
2445 : 1464 : tvs[1].tv_nsec = 0;
2446 : 1464 : (void) futimens (fd, tvs); /* best effort */
2447 : : }
2448 : :
2449 : : /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
2450 : 1464 : (void) fchmod(fd, 0400);
2451 : : /* PR31248: lseek back to beginning */
2452 : 1464 : (void) lseek(fd, 0, SEEK_SET);
2453 : :
2454 [ + - - + ]: 1464 : if(NULL != url_ima_policies && ignore != url_ima_policies[committed_to])
2455 : : {
2456 : : #ifdef ENABLE_IMA_VERIFICATION
2457 : : int result = debuginfod_validate_imasig(c, fd);
2458 : : #else
2459 : 0 : int result = -ENOSYS;
2460 : : #endif
2461 : 0 : if(0 == result)
2462 : : {
2463 : : if (vfd >= 0) dprintf (vfd, "valid signature\n");
2464 : : }
2465 [ # # ]: 0 : else if (enforcing == url_ima_policies[committed_to])
2466 : : {
2467 : : // All invalid signatures are rejected.
2468 : : // Additionally in enforcing mode any non-valid signature is rejected, so by reaching
2469 : : // this case we do so since we know it is not valid. Note - this not just invalid signatures
2470 : : // but also signatures that cannot be validated
2471 [ # # ]: 0 : if (vfd >= 0) dprintf (vfd, "error: invalid or missing signature (%d)\n", result);
2472 : 0 : rc = result;
2473 : 0 : goto out2;
2474 : : }
2475 : : }
2476 : :
2477 : : /* rename tmp->real */
2478 : 1464 : rc = rename (target_cache_tmppath, target_cache_path);
2479 [ - + ]: 1464 : if (rc < 0)
2480 : : {
2481 : 0 : rc = -errno;
2482 : 0 : goto out2;
2483 : : /* Perhaps we need not give up right away; could retry or something ... */
2484 : : }
2485 : :
2486 : : /* write out the headers, best effort basis */
2487 [ + + ]: 1464 : if (c->winning_headers) {
2488 : 1462 : int fdh = mkstemp (target_cachehdr_tmppath);
2489 [ + - ]: 1462 : if (fdh >= 0) {
2490 : 1462 : size_t bytes_to_write = strlen(c->winning_headers)+1; // include \0
2491 : 1462 : size_t bytes = pwrite_retry (fdh, c->winning_headers, bytes_to_write, 0);
2492 : 1462 : (void) close (fdh);
2493 [ + - ]: 1462 : if (bytes == bytes_to_write)
2494 : 1462 : (void) rename (target_cachehdr_tmppath, target_cachehdr_path);
2495 : : else
2496 : 0 : (void) unlink (target_cachehdr_tmppath);
2497 [ + + ]: 1462 : if (vfd >= 0)
2498 : 1104 : dprintf (vfd, "saved %ld bytes of headers to %s\n", (long)bytes, target_cachehdr_path);
2499 : : }
2500 : : }
2501 : :
2502 : : /* remove all handles from multi */
2503 [ + + ]: 2976 : for (int i = 0; i < num_urls; i++)
2504 : : {
2505 : 1512 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2506 : 1512 : curl_easy_cleanup (data[i].handle);
2507 : 1512 : free (data[i].response_data);
2508 : : }
2509 : :
2510 [ + + ]: 2976 : for (int i = 0; i < num_urls; ++i)
2511 : 1512 : free(server_url_list[i]);
2512 : 1464 : free(server_url_list);
2513 : 1464 : free(url_ima_policies);
2514 : 1464 : free (data);
2515 : 1464 : free (server_urls);
2516 : :
2517 : : /* don't close fd - we're returning it */
2518 : : /* don't unlink the tmppath; it's already been renamed. */
2519 [ + + ]: 1464 : if (path != NULL)
2520 : 378 : *path = strdup(target_cache_path);
2521 : :
2522 : 1464 : rc = fd;
2523 : 1464 : goto out;
2524 : :
2525 : : /* error exits */
2526 : 576 : out2:
2527 : : /* remove all handles from multi */
2528 [ + + ]: 1154 : for (int i = 0; i < num_urls; i++)
2529 : : {
2530 [ + - ]: 578 : if (data[i].handle != NULL)
2531 : : {
2532 : 578 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
2533 : 578 : curl_easy_cleanup (data[i].handle);
2534 : 578 : free (data[i].response_data);
2535 : : }
2536 : : }
2537 : :
2538 : 576 : unlink (target_cache_tmppath);
2539 : 576 : close (fd); /* before the rmdir, otherwise it'll fail */
2540 : 576 : (void) rmdir (target_cache_dir); /* nop if not empty */
2541 : 576 : free(data);
2542 : :
2543 : 576 : out1:
2544 [ + + ]: 1154 : for (int i = 0; i < num_urls; ++i)
2545 : 578 : free(server_url_list[i]);
2546 : 576 : free(server_url_list);
2547 : 576 : free(url_ima_policies);
2548 : :
2549 : 576 : out0:
2550 : 576 : free (server_urls);
2551 : :
2552 : : /* general purpose exit */
2553 : 6522 : out:
2554 : : /* Reset sent headers */
2555 : 6522 : curl_slist_free_all (c->headers);
2556 : 6522 : c->headers = NULL;
2557 : 6522 : c->user_agent_set_p = 0;
2558 : :
2559 : : /* Conclude the last \r status line */
2560 : : /* Another possibility is to use the ANSI CSI n K EL "Erase in Line"
2561 : : code. That way, the previously printed messages would be erased,
2562 : : and without a newline. */
2563 [ + + ]: 6522 : if (c->default_progressfn_printed_p)
2564 : 2 : dprintf(STDERR_FILENO, "\n");
2565 : :
2566 [ + + ]: 6522 : if (vfd >= 0)
2567 : : {
2568 [ + + ]: 1124 : if (rc < 0)
2569 : 10 : dprintf (vfd, "not found %s (err=%d)\n", strerror (-rc), rc);
2570 : : else
2571 : 1114 : dprintf (vfd, "found %s (fd=%d)\n", target_cache_path, rc);
2572 : : }
2573 : :
2574 : 6522 : free (cache_path);
2575 : 6522 : free (maxage_path);
2576 : 6522 : free (interval_path);
2577 : 6522 : free (cache_miss_path);
2578 : 6522 : free (target_cache_dir);
2579 : 6522 : free (target_cache_path);
2580 [ + + + + ]: 6522 : if (rc < 0 && target_cache_tmppath != NULL)
2581 : 578 : (void)unlink (target_cache_tmppath);
2582 : 6522 : free (target_cache_tmppath);
2583 [ + + + + ]: 6522 : if (rc < 0 && target_cachehdr_tmppath != NULL)
2584 : 578 : (void)unlink (target_cachehdr_tmppath);
2585 : 6522 : free (target_cachehdr_tmppath);
2586 : 6522 : free (target_cachehdr_path);
2587 : :
2588 : 6522 : return rc;
2589 : : }
2590 : :
2591 : :
2592 : :
2593 : : /* See debuginfod.h */
2594 : : debuginfod_client *
2595 : 532 : debuginfod_begin (void)
2596 : : {
2597 : : /* Initialize libcurl lazily, but only once. */
2598 : 532 : pthread_once (&init_control, libcurl_init);
2599 : :
2600 : 532 : debuginfod_client *client;
2601 : 532 : size_t size = sizeof (struct debuginfod_client);
2602 : 532 : client = calloc (1, size);
2603 : :
2604 [ + - ]: 532 : if (client != NULL)
2605 : : {
2606 [ + + ]: 532 : if (getenv(DEBUGINFOD_PROGRESS_ENV_VAR))
2607 : 4 : client->progressfn = debuginfod_default_progressfn;
2608 [ + + ]: 532 : if (getenv(DEBUGINFOD_VERBOSE_ENV_VAR))
2609 : 6 : client->verbose_fd = STDERR_FILENO;
2610 : : else
2611 : 526 : client->verbose_fd = -1;
2612 : :
2613 : : // allocate 1 curl multi handle
2614 : 532 : client->server_mhandle = curl_multi_init ();
2615 [ - + ]: 532 : if (client->server_mhandle == NULL)
2616 : 0 : goto out1;
2617 : : }
2618 : :
2619 : : #ifdef ENABLE_IMA_VERIFICATION
2620 : : load_ima_public_keys (client);
2621 : : #endif
2622 : :
2623 : : // extra future initialization
2624 : :
2625 : 532 : goto out;
2626 : :
2627 : 0 : out1:
2628 : 0 : free (client);
2629 : 0 : client = NULL;
2630 : :
2631 : 532 : out:
2632 : 532 : return client;
2633 : : }
2634 : :
2635 : : void
2636 : 490 : debuginfod_set_user_data(debuginfod_client *client,
2637 : : void *data)
2638 : : {
2639 : 490 : client->user_data = data;
2640 : 490 : }
2641 : :
2642 : : void *
2643 : 0 : debuginfod_get_user_data(debuginfod_client *client)
2644 : : {
2645 : 0 : return client->user_data;
2646 : : }
2647 : :
2648 : : const char *
2649 : 46 : debuginfod_get_url(debuginfod_client *client)
2650 : : {
2651 [ + - ]: 2 : return client->url;
2652 : : }
2653 : :
2654 : : const char *
2655 : 48 : debuginfod_get_headers(debuginfod_client *client)
2656 : : {
2657 : 48 : return client->winning_headers;
2658 : : }
2659 : :
2660 : : void
2661 : 530 : debuginfod_end (debuginfod_client *client)
2662 : : {
2663 [ + - ]: 530 : if (client == NULL)
2664 : : return;
2665 : :
2666 : 530 : curl_multi_cleanup (client->server_mhandle);
2667 : 530 : curl_slist_free_all (client->headers);
2668 : 530 : free (client->winning_headers);
2669 : 530 : free (client->url);
2670 : : #ifdef ENABLE_IMA_VERIFICATION
2671 : : free_ima_public_keys (client);
2672 : : #endif
2673 : 530 : free (client);
2674 : : }
2675 : :
2676 : : int
2677 : 300 : debuginfod_find_debuginfo (debuginfod_client *client,
2678 : : const unsigned char *build_id, int build_id_len,
2679 : : char **path)
2680 : : {
2681 : 300 : return debuginfod_query_server_by_buildid(client, build_id, build_id_len,
2682 : : "debuginfo", NULL, path);
2683 : : }
2684 : :
2685 : :
2686 : : /* See debuginfod.h */
2687 : : int
2688 : 736 : debuginfod_find_executable(debuginfod_client *client,
2689 : : const unsigned char *build_id, int build_id_len,
2690 : : char **path)
2691 : : {
2692 : 736 : return debuginfod_query_server_by_buildid(client, build_id, build_id_len,
2693 : : "executable", NULL, path);
2694 : : }
2695 : :
2696 : : /* See debuginfod.h */
2697 : 5470 : int debuginfod_find_source(debuginfod_client *client,
2698 : : const unsigned char *build_id, int build_id_len,
2699 : : const char *filename, char **path)
2700 : : {
2701 : 5470 : return debuginfod_query_server_by_buildid(client, build_id, build_id_len,
2702 : : "source", filename, path);
2703 : : }
2704 : :
2705 : : int
2706 : 16 : debuginfod_find_section (debuginfod_client *client,
2707 : : const unsigned char *build_id, int build_id_len,
2708 : : const char *section, char **path)
2709 : : {
2710 : 16 : int rc = debuginfod_query_server_by_buildid(client, build_id, build_id_len,
2711 : : "section", section, path);
2712 [ - + ]: 16 : if (rc != -EINVAL && rc != -ENOSYS)
2713 : : return rc;
2714 : : /* NB: we fall through in case of ima:enforcing-filtered DEBUGINFOD_URLS servers,
2715 : : so we can download the entire file, verify it locally, then slice it. */
2716 : :
2717 : : /* The servers may have lacked support for section queries. Attempt to
2718 : : download the debuginfo or executable containing the section in order
2719 : : to extract it. */
2720 : 0 : rc = -EEXIST;
2721 : 0 : int fd = -1;
2722 : 0 : char *tmp_path = NULL;
2723 : :
2724 : 0 : fd = debuginfod_find_debuginfo (client, build_id, build_id_len, &tmp_path);
2725 [ # # ]: 0 : if (client->progressfn_cancel)
2726 : : {
2727 [ # # ]: 0 : if (fd >= 0)
2728 : : {
2729 : : /* This shouldn't happen, but we'll check this condition
2730 : : just in case. */
2731 : 0 : close (fd);
2732 : 0 : free (tmp_path);
2733 : : }
2734 : 0 : return -ENOENT;
2735 : : }
2736 [ # # ]: 0 : if (fd >= 0)
2737 : : {
2738 : 0 : rc = extract_section (fd, section, tmp_path, path);
2739 : 0 : close (fd);
2740 : : }
2741 : :
2742 [ # # ]: 0 : if (rc == -EEXIST)
2743 : : {
2744 : : /* Either the debuginfo couldn't be found or the section should
2745 : : be in the executable. */
2746 : 0 : fd = debuginfod_find_executable (client, build_id,
2747 : : build_id_len, &tmp_path);
2748 [ # # ]: 0 : if (fd >= 0)
2749 : : {
2750 : 0 : rc = extract_section (fd, section, tmp_path, path);
2751 : 0 : close (fd);
2752 : : }
2753 : : else
2754 : : /* Update rc so that we return the most recent error code. */
2755 : : rc = fd;
2756 : : }
2757 : :
2758 : 0 : free (tmp_path);
2759 : 0 : return rc;
2760 : : }
2761 : :
2762 : :
2763 : 52 : int debuginfod_find_metadata (debuginfod_client *client,
2764 : : const char* key, const char* value, char **path)
2765 : : {
2766 : 52 : char *server_urls = NULL;
2767 : 52 : char *urls_envvar = NULL;
2768 : 52 : char *cache_path = NULL;
2769 : 52 : char *target_cache_dir = NULL;
2770 : 52 : char *target_cache_path = NULL;
2771 : 52 : char *target_cache_tmppath = NULL;
2772 : 52 : char *target_file_name = NULL;
2773 : 52 : char *key_and_value = NULL;
2774 : 52 : int rc = 0, r;
2775 : 52 : int vfd = client->verbose_fd;
2776 : 52 : struct handle_data *data = NULL;
2777 : :
2778 : 52 : client->progressfn_cancel = false;
2779 : :
2780 : 52 : json_object *json_metadata = json_object_new_object();
2781 : 52 : json_bool json_metadata_complete = true;
2782 : 52 : json_object *json_metadata_arr = json_object_new_array();
2783 [ - + ]: 52 : if (NULL == json_metadata)
2784 : : {
2785 : 0 : rc = -ENOMEM;
2786 : 0 : goto out;
2787 : : }
2788 [ - + ]: 52 : json_object_object_add(json_metadata, "results",
2789 : 0 : json_metadata_arr ?: json_object_new_array() /* Empty array */);
2790 : :
2791 [ - + ]: 52 : if (NULL == value || NULL == key)
2792 : : {
2793 : 0 : rc = -EINVAL;
2794 : 0 : goto out;
2795 : : }
2796 : :
2797 [ - + ]: 52 : if (vfd >= 0)
2798 : 0 : dprintf (vfd, "debuginfod_find_metadata %s %s\n", key, value);
2799 : :
2800 : : /* Without query-able URL, we can stop here*/
2801 : 52 : urls_envvar = getenv(DEBUGINFOD_URLS_ENV_VAR);
2802 [ - + ]: 52 : if (vfd >= 0)
2803 [ # # ]: 0 : dprintf (vfd, "server urls \"%s\"\n",
2804 : : urls_envvar != NULL ? urls_envvar : "");
2805 [ + - + + ]: 52 : if (urls_envvar == NULL || urls_envvar[0] == '\0')
2806 : : {
2807 : 18 : rc = -ENOSYS;
2808 : 18 : goto out;
2809 : : }
2810 : :
2811 : : /* set paths needed to perform the query
2812 : : example format:
2813 : : cache_path: $HOME/.cache
2814 : : target_cache_dir: $HOME/.cache/metadata
2815 : : target_cache_path: $HOME/.cache/metadata/KEYENCODED_VALUEENCODED
2816 : : target_cache_path: $HOME/.cache/metadata/KEYENCODED_VALUEENCODED.XXXXXX
2817 : : */
2818 : :
2819 : : // libcurl > 7.62ish has curl_url_set()/etc. to construct these things more properly.
2820 : : // curl_easy_escape() is older
2821 : : {
2822 : 34 : CURL *c = curl_easy_init();
2823 [ - + ]: 34 : if (!c)
2824 : : {
2825 : 0 : rc = -ENOMEM;
2826 : 0 : goto out;
2827 : : }
2828 : 34 : char *key_escaped = curl_easy_escape(c, key, 0);
2829 : 34 : char *value_escaped = curl_easy_escape(c, value, 0);
2830 : :
2831 : : // fallback to unescaped values in unlikely case of error
2832 [ - + - + : 68 : xalloc_str (key_and_value, "key=%s&value=%s", key_escaped ?: key, value_escaped ?: value);
- + ]
2833 [ - + ]: 34 : xalloc_str (target_file_name, "%s_%s", key_escaped ?: key, value_escaped ?: value);
2834 : 34 : curl_free(value_escaped);
2835 : 34 : curl_free(key_escaped);
2836 : 34 : curl_easy_cleanup(c);
2837 : : }
2838 : :
2839 : : /* Check if we have a recent result already in the cache. */
2840 : 34 : cache_path = make_cache_path();
2841 [ - + ]: 34 : if (! cache_path)
2842 : : {
2843 : 0 : rc = -ENOMEM;
2844 : 0 : goto out;
2845 : : }
2846 [ - + ]: 34 : xalloc_str (target_cache_dir, "%s/metadata", cache_path);
2847 : 34 : (void) mkdir (target_cache_dir, 0700);
2848 [ - + ]: 34 : xalloc_str (target_cache_path, "%s/%s", target_cache_dir, target_file_name);
2849 [ - + ]: 34 : xalloc_str (target_cache_tmppath, "%s/%s.XXXXXX", target_cache_dir, target_file_name);
2850 : :
2851 : 34 : int fd = open(target_cache_path, O_RDONLY);
2852 [ + + ]: 34 : if (fd >= 0)
2853 : : {
2854 : 12 : struct stat st;
2855 : 12 : int metadata_retention = 0;
2856 : 12 : time_t now = time(NULL);
2857 : 12 : char *metadata_retention_path = 0;
2858 : :
2859 [ - + ]: 22 : xalloc_str (metadata_retention_path, "%s/%s", cache_path, metadata_retention_filename);
2860 [ + - ]: 12 : if (metadata_retention_path)
2861 : : {
2862 : 12 : rc = debuginfod_config_cache(client, metadata_retention_path,
2863 : : metadata_retention_default_s, &st);
2864 : 12 : free (metadata_retention_path);
2865 [ - + ]: 12 : if (rc < 0)
2866 : 0 : rc = 0;
2867 : : }
2868 : : else
2869 : : rc = 0;
2870 : 12 : metadata_retention = rc;
2871 : :
2872 [ - + ]: 12 : if (fstat(fd, &st) != 0)
2873 : : {
2874 : 0 : rc = -errno;
2875 : 0 : close (fd);
2876 : 0 : goto out;
2877 : : }
2878 : :
2879 [ + + + - ]: 12 : if (metadata_retention > 0 && (now - st.st_mtime <= metadata_retention))
2880 : : {
2881 [ - + ]: 10 : if (client && client->verbose_fd >= 0)
2882 : 0 : dprintf (client->verbose_fd, "cached metadata %s", target_file_name);
2883 : :
2884 [ + - ]: 10 : if (path != NULL)
2885 : : {
2886 : 10 : *path = target_cache_path; // pass over the pointer
2887 : 10 : target_cache_path = NULL; // prevent free() in our own cleanup
2888 : : }
2889 : :
2890 : : /* Success!!!! */
2891 : 10 : rc = fd;
2892 : 10 : goto out;
2893 : : }
2894 : :
2895 : : /* We don't have to clear the likely-expired cached object here
2896 : : by unlinking. We will shortly make a new request and save
2897 : : results right on top. Erasing here could trigger a TOCTOU
2898 : : race with another thread just finishing a query and passing
2899 : : its results back.
2900 : : */
2901 : : // (void) unlink (target_cache_path);
2902 : :
2903 : 2 : close (fd);
2904 : : }
2905 : :
2906 : : /* No valid cached metadata found: time to make the queries. */
2907 : :
2908 : 24 : free (client->url);
2909 : 24 : client->url = NULL;
2910 : :
2911 : 24 : long maxtime = 0;
2912 : 24 : const char *maxtime_envvar;
2913 : 24 : maxtime_envvar = getenv(DEBUGINFOD_MAXTIME_ENV_VAR);
2914 [ - + ]: 24 : if (maxtime_envvar != NULL)
2915 : 0 : maxtime = atol (maxtime_envvar);
2916 [ - + ]: 24 : if (maxtime && vfd >= 0)
2917 : 0 : dprintf(vfd, "using max time %lds\n", maxtime);
2918 : :
2919 : 24 : long timeout = default_timeout;
2920 : 24 : const char* timeout_envvar = getenv(DEBUGINFOD_TIMEOUT_ENV_VAR);
2921 [ - + ]: 24 : if (timeout_envvar != NULL)
2922 : 0 : timeout = atoi (timeout_envvar);
2923 [ - + ]: 24 : if (vfd >= 0)
2924 : 0 : dprintf (vfd, "using timeout %ld\n", timeout);
2925 : :
2926 : 24 : add_default_headers(client);
2927 : :
2928 : : /* Make a copy of the envvar so it can be safely modified. */
2929 : 24 : server_urls = strdup(urls_envvar);
2930 [ - + ]: 24 : if (server_urls == NULL)
2931 : : {
2932 : 0 : rc = -ENOMEM;
2933 : 0 : goto out;
2934 : : }
2935 : :
2936 : : /* Thereafter, goto out1 on error*/
2937 : :
2938 : 24 : char **server_url_list = NULL;
2939 : 24 : ima_policy_t* url_ima_policies = NULL;
2940 : 24 : char *server_url;
2941 : 24 : int num_urls = 0;
2942 : 24 : r = init_server_urls("metadata", NULL, server_urls, &server_url_list, &url_ima_policies, &num_urls, vfd);
2943 [ - + ]: 24 : if (0 != r)
2944 : : {
2945 : 0 : rc = r;
2946 : 0 : goto out1;
2947 : : }
2948 : :
2949 : 24 : CURLM *curlm = client->server_mhandle;
2950 : :
2951 : 24 : CURL *target_handle = NULL;
2952 : 24 : data = malloc(sizeof(struct handle_data) * num_urls);
2953 [ - + ]: 24 : if (data == NULL)
2954 : : {
2955 : 0 : rc = -ENOMEM;
2956 : 0 : goto out1;
2957 : : }
2958 : :
2959 : : /* thereafter, goto out2 on error. */
2960 : :
2961 : : /* Initialize handle_data */
2962 [ + + ]: 60 : for (int i = 0; i < num_urls; i++)
2963 : : {
2964 [ + - ]: 36 : if ((server_url = server_url_list[i]) == NULL)
2965 : : break;
2966 [ - + ]: 36 : if (vfd >= 0)
2967 : 0 : dprintf (vfd, "init server %d %s\n", i, server_url);
2968 : :
2969 : 36 : data[i].errbuf[0] = '\0';
2970 : 36 : data[i].target_handle = &target_handle;
2971 : 36 : data[i].client = client;
2972 : 36 : data[i].metadata = NULL;
2973 : 36 : data[i].metadata_size = 0;
2974 : 36 : data[i].response_data = NULL;
2975 : 36 : data[i].response_data_size = 0;
2976 : :
2977 : 36 : snprintf(data[i].url, PATH_MAX, "%s?%s", server_url, key_and_value);
2978 : :
2979 : 36 : r = init_handle(client, metadata_callback, header_callback, &data[i], i, timeout, vfd);
2980 [ - + ]: 36 : if (0 != r)
2981 : : {
2982 : 0 : rc = r;
2983 : 0 : goto out2;
2984 : : }
2985 : 36 : curl_multi_add_handle(curlm, data[i].handle);
2986 : : }
2987 : :
2988 : : /* Query servers */
2989 [ - + ]: 24 : if (vfd >= 0)
2990 : 0 : dprintf (vfd, "Starting %d queries\n",num_urls);
2991 : 24 : int committed_to;
2992 : 24 : r = perform_queries(curlm, NULL, data, client, num_urls, maxtime, 0, false, vfd, &committed_to);
2993 [ - + ]: 24 : if (0 != r)
2994 : : {
2995 : 0 : rc = r;
2996 : 0 : goto out2;
2997 : : }
2998 : :
2999 : : /* NOTE: We don't check the return codes of the curl messages since
3000 : : a metadata query failing silently is just fine. We want to know what's
3001 : : available from servers which can be connected with no issues.
3002 : : If running with additional verbosity, the failure will be noted in stderr */
3003 : :
3004 : : /* Building the new json array from all the upstream data and
3005 : : cleanup while at it.
3006 : : */
3007 [ + + ]: 60 : for (int i = 0; i < num_urls; i++)
3008 : : {
3009 : 36 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
3010 : 36 : curl_easy_cleanup (data[i].handle);
3011 : 36 : free (data[i].response_data);
3012 : :
3013 [ + + ]: 36 : if (NULL == data[i].metadata)
3014 : : {
3015 [ - + ]: 16 : if (vfd >= 0)
3016 : 0 : dprintf (vfd, "Query to %s failed with error message:\n\t\"%s\"\n",
3017 : 0 : data[i].url, data[i].errbuf);
3018 : 16 : json_metadata_complete = false;
3019 : 16 : continue;
3020 : : }
3021 : :
3022 : 20 : json_object *upstream_metadata = json_tokener_parse(data[i].metadata);
3023 : 20 : json_object *upstream_complete;
3024 : 20 : json_object *upstream_metadata_arr;
3025 [ + - + - ]: 40 : if (NULL == upstream_metadata ||
3026 [ - + ]: 40 : !json_object_object_get_ex(upstream_metadata, "results", &upstream_metadata_arr) ||
3027 : 20 : !json_object_object_get_ex(upstream_metadata, "complete", &upstream_complete))
3028 : 0 : continue;
3029 : 20 : json_metadata_complete &= json_object_get_boolean(upstream_complete);
3030 : : // Combine the upstream metadata into the json array
3031 [ + + ]: 38 : for (int j = 0, n = json_object_array_length(upstream_metadata_arr); j < n; j++)
3032 : : {
3033 : 18 : json_object *entry = json_object_array_get_idx(upstream_metadata_arr, j);
3034 : 18 : json_object_get(entry); // increment reference count
3035 : 18 : json_object_array_add(json_metadata_arr, entry);
3036 : : }
3037 : 20 : json_object_put(upstream_metadata);
3038 : :
3039 : 20 : free (data[i].metadata);
3040 : : }
3041 : :
3042 : : /* Because of race with cache cleanup / rmdir, try to mkdir/mkstemp up to twice. */
3043 [ + - ]: 24 : for (int i=0; i<2; i++)
3044 : : {
3045 : : /* (re)create target directory in cache */
3046 : 24 : (void) mkdir(target_cache_dir, 0700); /* files will be 0400 later */
3047 : :
3048 : : /* NB: write to a temporary file first, to avoid race condition of
3049 : : multiple clients checking the cache, while a partially-written or empty
3050 : : file is in there, being written from libcurl. */
3051 : 24 : fd = mkstemp (target_cache_tmppath);
3052 [ - + ]: 24 : if (fd >= 0) break;
3053 : : }
3054 [ - + ]: 24 : if (fd < 0) /* Still failed after two iterations. */
3055 : : {
3056 : 0 : rc = -errno;
3057 : 0 : goto out1;
3058 : : }
3059 : :
3060 : : /* Plop the complete json_metadata object into the cache. */
3061 : 24 : json_object_object_add(json_metadata, "complete", json_object_new_boolean(json_metadata_complete));
3062 : 24 : const char* json_string = json_object_to_json_string_ext(json_metadata, JSON_C_TO_STRING_PRETTY);
3063 [ - + ]: 24 : if (json_string == NULL)
3064 : : {
3065 : 0 : rc = -ENOMEM;
3066 : 0 : goto out1;
3067 : : }
3068 : 24 : ssize_t res = write_retry (fd, json_string, strlen(json_string));
3069 : 24 : (void) lseek(fd, 0, SEEK_SET); // rewind file so client can read it from the top
3070 : :
3071 : : /* NB: json_string is auto deleted when json_metadata object is nuked */
3072 [ + - - + ]: 24 : if (res < 0 || (size_t) res != strlen(json_string))
3073 : : {
3074 : 0 : rc = -EIO;
3075 : 0 : goto out1;
3076 : : }
3077 : : /* PR27571: make cache files casually unwriteable; dirs are already 0700 */
3078 : 24 : (void) fchmod(fd, 0400);
3079 : :
3080 : : /* rename tmp->real */
3081 : 24 : rc = rename (target_cache_tmppath, target_cache_path);
3082 [ - + ]: 24 : if (rc < 0)
3083 : : {
3084 : 0 : rc = -errno;
3085 : 0 : goto out1;
3086 : : /* Perhaps we need not give up right away; could retry or something ... */
3087 : : }
3088 : :
3089 : : /* don't close fd - we're returning it */
3090 : : /* don't unlink the tmppath; it's already been renamed. */
3091 [ + - ]: 24 : if (path != NULL)
3092 : 24 : *path = strdup(target_cache_path);
3093 : :
3094 : 24 : rc = fd;
3095 : 24 : goto out1;
3096 : :
3097 : : /* error exits */
3098 : 0 : out2:
3099 : : /* remove all handles from multi */
3100 [ # # ]: 0 : for (int i = 0; i < num_urls; i++)
3101 : : {
3102 [ # # ]: 0 : if (data[i].handle != NULL)
3103 : : {
3104 : 0 : curl_multi_remove_handle(curlm, data[i].handle); /* ok to repeat */
3105 : 0 : curl_easy_cleanup (data[i].handle);
3106 : 0 : free (data[i].response_data);
3107 : 0 : free (data[i].metadata);
3108 : : }
3109 : : }
3110 : :
3111 : 0 : out1:
3112 : 24 : free(data);
3113 : :
3114 [ + + ]: 60 : for (int i = 0; i < num_urls; ++i)
3115 : 36 : free(server_url_list[i]);
3116 : 24 : free(server_url_list);
3117 : 24 : free(url_ima_policies);
3118 : :
3119 : 52 : out:
3120 : 52 : free (server_urls);
3121 : 52 : json_object_put(json_metadata);
3122 : : /* Reset sent headers */
3123 : 52 : curl_slist_free_all (client->headers);
3124 : 52 : client->headers = NULL;
3125 : 52 : client->user_agent_set_p = 0;
3126 : :
3127 : 52 : free (target_cache_dir);
3128 : 52 : free (target_cache_path);
3129 : 52 : free (target_cache_tmppath);
3130 : 52 : free (key_and_value);
3131 : 52 : free (target_file_name);
3132 : 52 : free (cache_path);
3133 : :
3134 : 52 : return rc;
3135 : : }
3136 : :
3137 : :
3138 : : /* Add an outgoing HTTP header. */
3139 : 2886 : int debuginfod_add_http_header (debuginfod_client *client, const char* header)
3140 : : {
3141 : : /* Sanity check header value is of the form Header: Value.
3142 : : It should contain at least one colon that isn't the first or
3143 : : last character. */
3144 : 2886 : const char *colon = strchr (header, ':'); /* first colon */
3145 : 2886 : if (colon == NULL /* present */
3146 [ + - ]: 2886 : || colon == header /* not at beginning - i.e., have a header name */
3147 [ + - ]: 2886 : || *(colon + 1) == '\0') /* not at end - i.e., have a value */
3148 : : /* NB: but it's okay for a value to contain other colons! */
3149 : : return -EINVAL;
3150 : :
3151 : 2886 : struct curl_slist *temp = curl_slist_append (client->headers, header);
3152 [ + - ]: 2886 : if (temp == NULL)
3153 : : return -ENOMEM;
3154 : :
3155 : : /* Track if User-Agent: is being set. If so, signal not to add the
3156 : : default one. */
3157 [ + + ]: 2886 : if (startswith (header, "User-Agent:"))
3158 : 2228 : client->user_agent_set_p = 1;
3159 : :
3160 : 2886 : client->headers = temp;
3161 : 2886 : return 0;
3162 : : }
3163 : :
3164 : :
3165 : : void
3166 : 1130 : debuginfod_set_progressfn(debuginfod_client *client,
3167 : : debuginfod_progressfn_t fn)
3168 : : {
3169 : 1130 : client->progressfn = fn;
3170 : 1130 : }
3171 : :
3172 : : void
3173 : 72 : debuginfod_set_verbose_fd(debuginfod_client *client, int fd)
3174 : : {
3175 : 72 : client->verbose_fd = fd;
3176 : 72 : }
3177 : :
3178 : : #endif /* DUMMY_LIBDEBUGINFOD */
|