xref: /netbsd-src/external/bsd/pkg_install/dist/lib/vulnerabilities-file.c (revision 3816d47b2c42fcd6e549e3407f842a5b1a1d23ad)
1 /*	$NetBSD: vulnerabilities-file.c,v 1.1.1.3 2009/03/02 22:31:18 joerg Exp $	*/
2 
3 /*-
4  * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>.
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
22  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
28  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31 
32 #if HAVE_CONFIG_H
33 #include "config.h"
34 #endif
35 
36 #include <nbcompat.h>
37 
38 #if HAVE_SYS_CDEFS_H
39 #include <sys/cdefs.h>
40 #endif
41 __RCSID("$NetBSD: vulnerabilities-file.c,v 1.1.1.3 2009/03/02 22:31:18 joerg Exp $");
42 
43 #if HAVE_SYS_STAT_H
44 #include <sys/stat.h>
45 #endif
46 #if HAVE_SYS_WAIT_H
47 #include <sys/wait.h>
48 #endif
49 #include <ctype.h>
50 #if HAVE_ERR_H
51 #include <err.h>
52 #endif
53 #include <errno.h>
54 #include <fcntl.h>
55 #include <limits.h>
56 #include <stdlib.h>
57 #include <string.h>
58 #ifndef NETBSD
59 #include <nbcompat/sha1.h>
60 #include <nbcompat/sha2.h>
61 #else
62 #include <sha1.h>
63 #include <sha2.h>
64 #endif
65 #include <unistd.h>
66 
67 #include "lib.h"
68 
69 static const char pgp_msg_start[] = "-----BEGIN PGP SIGNED MESSAGE-----\n";
70 static const char pgp_msg_end[] = "-----BEGIN PGP SIGNATURE-----\n";
71 static const char pkcs7_begin[] = "-----BEGIN PKCS7-----\n";
72 static const char pkcs7_end[] = "-----END PKCS7-----\n";
73 
74 static void
75 verify_signature_pkcs7(const char *input)
76 {
77 #ifdef HAVE_SSL
78 	const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig;
79 
80 	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
81 		begin_pkgvul = input + strlen(pgp_msg_start);
82 		if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL)
83 			errx(EXIT_FAILURE, "Invalid PGP signature");
84 		if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL)
85 			errx(EXIT_FAILURE, "No PKCS7 signature");
86 	} else {
87 		begin_pkgvul = input;
88 		if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL)
89 			errx(EXIT_FAILURE, "No PKCS7 signature");
90 		end_pkgvul = begin_sig;
91 	}
92 	if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL)
93 		errx(EXIT_FAILURE, "Invalid PKCS7 signature");
94 	end_sig += strlen(pkcs7_end);
95 
96 	if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul,
97 	    begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0))
98 		errx(EXIT_FAILURE, "Unable to verify PKCS7 signature");
99 #else
100 	errx(EXIT_FAILURE, "OpenSSL support is not compiled in");
101 #endif
102 }
103 
104 static void
105 verify_signature(const char *input, size_t input_len)
106 {
107 	if (gpg_cmd == NULL && certs_pkg_vulnerabilities == NULL)
108 		errx(EXIT_FAILURE,
109 		    "At least GPG or CERTIFICATE_ANCHOR_PKGVULN "
110 		    "must be configured");
111 	if (gpg_cmd != NULL)
112 		inline_gpg_verify(input, input_len, gpg_keyring_pkgvuln);
113 	if (certs_pkg_vulnerabilities != NULL)
114 		verify_signature_pkcs7(input);
115 }
116 
117 static void *
118 sha512_hash_init(void)
119 {
120 	static SHA512_CTX hash_ctx;
121 
122 	SHA512_Init(&hash_ctx);
123 	return &hash_ctx;
124 }
125 
126 static void
127 sha512_hash_update(void *ctx, const void *data, size_t len)
128 {
129 	SHA512_CTX *hash_ctx = ctx;
130 
131 	SHA512_Update(hash_ctx, data, len);
132 }
133 
134 static const char *
135 sha512_hash_finish(void *ctx)
136 {
137 	static char hash[SHA512_DIGEST_STRING_LENGTH];
138 	unsigned char digest[SHA512_DIGEST_LENGTH];
139 	SHA512_CTX *hash_ctx = ctx;
140 	int i;
141 
142 	SHA512_Final(digest, hash_ctx);
143 	for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
144 		unsigned char c;
145 
146 		c = digest[i] / 16;
147 		if (c < 10)
148 			hash[2 * i] = '0' + c;
149 		else
150 			hash[2 * i] = 'a' - 10 + c;
151 
152 		c = digest[i] % 16;
153 		if (c < 10)
154 			hash[2 * i + 1] = '0' + c;
155 		else
156 			hash[2 * i + 1] = 'a' - 10 + c;
157 	}
158 	hash[2 * i] = '\0';
159 
160 	return hash;
161 }
162 
163 static void *
164 sha1_hash_init(void)
165 {
166 	static SHA1_CTX hash_ctx;
167 
168 	SHA1Init(&hash_ctx);
169 	return &hash_ctx;
170 }
171 
172 static void
173 sha1_hash_update(void *ctx, const void *data, size_t len)
174 {
175 	SHA1_CTX *hash_ctx = ctx;
176 
177 	SHA1Update(hash_ctx, data, len);
178 }
179 
180 static const char *
181 sha1_hash_finish(void *ctx)
182 {
183 	static char hash[SHA1_DIGEST_STRING_LENGTH];
184 	SHA1_CTX *hash_ctx = ctx;
185 
186 	SHA1End(hash_ctx, hash);
187 
188 	return hash;
189 }
190 
191 static const struct hash_algorithm {
192 	const char *name;
193 	size_t name_len;
194 	void * (*init)(void);
195 	void (*update)(void *, const void *, size_t);
196 	const char * (* finish)(void *);
197 } hash_algorithms[] = {
198 	{ "SHA512", 6, sha512_hash_init, sha512_hash_update,
199 	  sha512_hash_finish },
200 	{ "SHA1", 4, sha1_hash_init, sha1_hash_update,
201 	  sha1_hash_finish },
202 	{ NULL, 0, NULL, NULL, NULL }
203 };
204 
205 static void
206 verify_hash(const char *input, const char *hash_line)
207 {
208 	const struct hash_algorithm *hash;
209 	void *ctx;
210 	const char *last_start, *next, *hash_value;
211 	int in_pgp_msg;
212 
213 	for (hash = hash_algorithms; hash->name != NULL; ++hash) {
214 		if (strncmp(hash_line, hash->name, hash->name_len))
215 			continue;
216 		if (isspace((unsigned char)hash_line[hash->name_len]))
217 			break;
218 	}
219 	if (hash->name == NULL) {
220 		const char *end_name;
221 		for (end_name = hash_line; *end_name != '\0'; ++end_name) {
222 			if (!isalnum((unsigned char)*end_name))
223 				break;
224 		}
225 		warnx("Unsupported hash algorithm: %.*s",
226 		    (int)(end_name - hash_line), hash_line);
227 		return;
228 	}
229 
230 	hash_line += hash->name_len;
231 	if (!isspace((unsigned char)*hash_line))
232 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
233 	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
234 		++hash_line;
235 
236 	if (*hash_line == '\n')
237 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
238 
239 	ctx = (*hash->init)();
240 	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
241 		input += strlen(pgp_msg_start);
242 		in_pgp_msg = 1;
243 	} else {
244 		in_pgp_msg = 0;
245 	}
246 	for (last_start = input; *input != '\0'; input = next) {
247 		if ((next = strchr(input, '\n')) == NULL)
248 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
249 		++next;
250 		if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0)
251 			break;
252 		if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0)
253 			break;
254 		if (*input == '\n' ||
255 		    strncmp(input, "Hash:", 5) == 0 ||
256 		    strncmp(input, "# $NetBSD", 9) == 0 ||
257 		    strncmp(input, "#CHECKSUM", 9) == 0) {
258 			(*hash->update)(ctx, last_start, input - last_start);
259 			last_start = next;
260 		}
261 	}
262 	(*hash->update)(ctx, last_start, input - last_start);
263 	hash_value = (*hash->finish)(ctx);
264 	if (strncmp(hash_line, hash_value, strlen(hash_value)))
265 		errx(EXIT_FAILURE, "%s hash doesn't match", hash->name);
266 	hash_line += strlen(hash_value);
267 
268 	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
269 		++hash_line;
270 
271 	if (!isspace((unsigned char)*hash_line))
272 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
273 }
274 
275 static void
276 add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line)
277 {
278 	size_t len_pattern, len_class, len_url;
279 	const char *start_pattern, *start_class, *start_url;
280 
281 	start_pattern = line;
282 
283 	start_class = line;
284 	while (*start_class != '\0' && !isspace((unsigned char)*start_class))
285 		++start_class;
286 	len_pattern = start_class - line;
287 
288 	while (*start_class != '\n' && isspace((unsigned char)*start_class))
289 		++start_class;
290 
291 	if (*start_class == '0' || *start_class == '\n')
292 		errx(EXIT_FAILURE, "Input error: missing classification");
293 
294 	start_url = start_class;
295 	while (*start_url != '\0' && !isspace((unsigned char)*start_url))
296 		++start_url;
297 	len_class = start_url - start_class;
298 
299 	while (*start_url != '\n' && isspace((unsigned char)*start_url))
300 		++start_url;
301 
302 	if (*start_url == '0' || *start_url == '\n')
303 		errx(EXIT_FAILURE, "Input error: missing URL");
304 
305 	line = start_url;
306 	while (*line != '\0' && !isspace((unsigned char)*line))
307 		++line;
308 	len_url = line - start_url;
309 
310 	if (pv->entries == *allocated) {
311 		if (*allocated == 0)
312 			*allocated = 16;
313 		else if (*allocated <= SSIZE_MAX / 2)
314 			*allocated *= 2;
315 		else
316 			errx(EXIT_FAILURE, "Too many vulnerabilities");
317 		pv->vulnerability = xrealloc(pv->vulnerability,
318 		    sizeof(char *) * *allocated);
319 		pv->classification = xrealloc(pv->classification,
320 		    sizeof(char *) * *allocated);
321 		pv->advisory = xrealloc(pv->advisory,
322 		    sizeof(char *) * *allocated);
323 	}
324 
325 	pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1);
326 	memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern);
327 	pv->vulnerability[pv->entries][len_pattern] = '\0';
328 	pv->classification[pv->entries] = xmalloc(len_class + 1);
329 	memcpy(pv->classification[pv->entries], start_class, len_class);
330 	pv->classification[pv->entries][len_class] = '\0';
331 	pv->advisory[pv->entries] = xmalloc(len_url + 1);
332 	memcpy(pv->advisory[pv->entries], start_url, len_url);
333 	pv->advisory[pv->entries][len_url] = '\0';
334 
335 	++pv->entries;
336 }
337 
338 struct pkg_vulnerabilities *
339 read_pkg_vulnerabilities(const char *path, int ignore_missing, int check_sum)
340 {
341 	struct pkg_vulnerabilities *pv;
342 	struct stat st;
343 	int fd;
344 	char *input, *decompressed_input;
345 	size_t input_len, decompressed_len;
346 	ssize_t bytes_read;
347 
348 	if ((fd = open(path, O_RDONLY)) == -1) {
349 		if (errno == ENOENT && ignore_missing)
350 			return NULL;
351 		err(EXIT_FAILURE, "Cannot open %s", path);
352 	}
353 
354 	if (fstat(fd, &st) == -1)
355 		err(EXIT_FAILURE, "Cannot stat %s", path);
356 
357 	if ((st.st_mode & S_IFMT) != S_IFREG)
358 		errx(EXIT_FAILURE, "Input is not regular file");
359 	if (st.st_size > SSIZE_MAX - 1)
360 		errx(EXIT_FAILURE, "Input too large");
361 
362 	input_len = (size_t)st.st_size;
363 	if (input_len < 4)
364 		err(EXIT_FAILURE, "Input too short for a pkg_vulnerability file");
365 	input = xmalloc(input_len + 1);
366 	if ((bytes_read = read(fd, input, input_len)) == -1)
367 		err(1, "Failed to read input");
368 	if (bytes_read != st.st_size)
369 		errx(1, "Unexpected short read");
370 
371 	close(fd);
372 
373 	if (decompress_buffer(input, input_len, &decompressed_input,
374 	    &decompressed_len)) {
375 		free(input);
376 		input = decompressed_input;
377 		input_len = decompressed_len;
378 	}
379 	pv = parse_pkg_vulnerabilities(input, input_len, check_sum);
380 	free(input);
381 
382 	return pv;
383 }
384 
385 struct pkg_vulnerabilities *
386 parse_pkg_vulnerabilities(const char *input, size_t input_len, int check_sum)
387 {
388 	struct pkg_vulnerabilities *pv;
389 	long version;
390 	char *end;
391 	const char *iter, *next;
392 	size_t allocated_vulns;
393 	int in_pgp_msg;
394 
395 	pv = xmalloc(sizeof(*pv));
396 
397 	allocated_vulns = pv->entries = 0;
398 	pv->vulnerability = NULL;
399 	pv->classification = NULL;
400 	pv->advisory = NULL;
401 
402 	if (strlen(input) != input_len)
403 		errx(1, "Invalid input (NUL character found)");
404 
405 	if (check_sum)
406 		verify_signature(input, input_len);
407 
408 	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
409 		iter = input + strlen(pgp_msg_start);
410 		in_pgp_msg = 1;
411 	} else {
412 		iter = input;
413 		in_pgp_msg = 0;
414 	}
415 
416 	for (; *iter; iter = next) {
417 		if ((next = strchr(iter, '\n')) == NULL)
418 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
419 		++next;
420 		if (*iter == '\0' || *iter == '\n')
421 			continue;
422 		if (strncmp(iter, "Hash:", 5) == 0)
423 			continue;
424 		if (strncmp(iter, "# $NetBSD", 9) == 0)
425 			continue;
426 		if (*iter == '#' && isspace((unsigned char)iter[1])) {
427 			for (++iter; iter != next; ++iter) {
428 				if (!isspace((unsigned char)*iter))
429 					errx(EXIT_FAILURE, "Invalid header");
430 			}
431 			continue;
432 		}
433 
434 		if (strncmp(iter, "#FORMAT", 7) != 0)
435 			errx(EXIT_FAILURE, "Input header is malformed");
436 
437 		iter += 7;
438 		if (!isspace((unsigned char)*iter))
439 			errx(EXIT_FAILURE, "Invalid #FORMAT");
440 		++iter;
441 		version = strtol(iter, &end, 10);
442 		if (iter == end || version != 1 || *end != '.')
443 			errx(EXIT_FAILURE, "Input #FORMAT");
444 		iter = end + 1;
445 		version = strtol(iter, &end, 10);
446 		if (iter == end || version != 1 || *end != '.')
447 			errx(EXIT_FAILURE, "Input #FORMAT");
448 		iter = end + 1;
449 		version = strtol(iter, &end, 10);
450 		if (iter == end || version != 0)
451 			errx(EXIT_FAILURE, "Input #FORMAT");
452 		for (iter = end; iter != next; ++iter) {
453 			if (!isspace((unsigned char)*iter))
454 				errx(EXIT_FAILURE, "Input #FORMAT");
455 		}
456 		break;
457 	}
458 	if (*iter == '\0')
459 		errx(EXIT_FAILURE, "Missing #CHECKSUM or content");
460 
461 	for (iter = next; *iter; iter = next) {
462 		if ((next = strchr(iter, '\n')) == NULL)
463 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
464 		++next;
465 		if (*iter == '\0' || *iter == '\n')
466 			continue;
467 		if (in_pgp_msg && strncmp(iter, pgp_msg_end, strlen(pgp_msg_end)) == 0)
468 			break;
469 		if (!in_pgp_msg && strncmp(iter, pkcs7_begin, strlen(pkcs7_begin)) == 0)
470 			break;
471 		if (*iter == '#' &&
472 		    (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1])))
473 			continue;
474 		if (strncmp(iter, "#CHECKSUM", 9) == 0) {
475 			iter += 9;
476 			if (!isspace((unsigned char)*iter))
477 				errx(EXIT_FAILURE, "Invalid #CHECKSUM");
478 			while (isspace((unsigned char)*iter))
479 				++iter;
480 			verify_hash(input, iter);
481 			continue;
482 		}
483 		if (*iter == '#') {
484 			/*
485 			 * This should really be an error,
486 			 * but it is still used.
487 			 */
488 			/* errx(EXIT_FAILURE, "Invalid data line starting with #"); */
489 			continue;
490 		}
491 		add_vulnerability(pv, &allocated_vulns, iter);
492 	}
493 
494 	if (pv->entries != allocated_vulns) {
495 		pv->vulnerability = xrealloc(pv->vulnerability,
496 		    sizeof(char *) * pv->entries);
497 		pv->classification = xrealloc(pv->classification,
498 		    sizeof(char *) * pv->entries);
499 		pv->advisory = xrealloc(pv->advisory,
500 		    sizeof(char *) * pv->entries);
501 	}
502 
503 	return pv;
504 }
505 
506 void
507 free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv)
508 {
509 	size_t i;
510 
511 	for (i = 0; i < pv->entries; ++i) {
512 		free(pv->vulnerability[i]);
513 		free(pv->classification[i]);
514 		free(pv->advisory[i]);
515 	}
516 	free(pv->vulnerability);
517 	free(pv->classification);
518 	free(pv->advisory);
519 	free(pv);
520 }
521 
522 static int
523 check_ignored_entry(struct pkg_vulnerabilities *pv, size_t i)
524 {
525 	const char *iter, *next;
526 	size_t entry_len, url_len;
527 
528 	if (ignore_advisories == NULL)
529 		return 0;
530 
531 	url_len = strlen(pv->advisory[i]);
532 
533 	for (iter = ignore_advisories; *iter; iter = next) {
534 		if ((next = strchr(iter, '\n')) == NULL) {
535 			entry_len = strlen(iter);
536 			next = iter + entry_len;
537 		} else {
538 			entry_len = next - iter;
539 			++next;
540 		}
541 		if (url_len != entry_len)
542 			continue;
543 		if (strncmp(pv->advisory[i], iter, entry_len) == 0)
544 			return 1;
545 	}
546 	return 0;
547 }
548 
549 int
550 audit_package(struct pkg_vulnerabilities *pv, const char *pkgname,
551     const char *limit_vul_types, int check_eol, int output_type)
552 {
553 	FILE *output = output_type == 1 ? stdout : stderr;
554 	size_t i;
555 	int retval;
556 
557 	retval = 0;
558 
559 	for (i = 0; i < pv->entries; ++i) {
560 		if (check_ignored_entry(pv, i))
561 			continue;
562 		if (limit_vul_types != NULL &&
563 		    strcmp(limit_vul_types, pv->classification[i]))
564 			continue;
565 		if (!pkg_match(pv->vulnerability[i], pkgname))
566 			continue;
567 		if (strcmp("eol", pv->classification[i]) == 0) {
568 			if (!check_eol)
569 				continue;
570 			if (output_type == 0) {
571 				puts(pkgname);
572 				continue;
573 			}
574 			fprintf(output,
575 			    "Package %s has reached end-of-life (eol), "
576 			    "see %s/eol-packages\n", pkgname,
577 			    tnf_vulnerability_base);
578 			continue;
579 		}
580 		retval = 1;
581 		if (output_type == 0) {
582 			puts(pkgname);
583 		} else {
584 			fprintf(output,
585 			    "Package %s has a %s vulnerability, see %s\n",
586 			    pkgname, pv->classification[i], pv->advisory[i]);
587 		}
588 	}
589 	return retval;
590 }
591