1/*	$NetBSD: vulnerabilities-file.c,v 1.5 2021/04/10 19:49:59 nia Exp $	*/
2
3/*-
4 * Copyright (c) 2008, 2010 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.5 2021/04/10 19:49:59 nia 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#ifndef BOOTSTRAP
50#include <archive.h>
51#endif
52#include <ctype.h>
53#if HAVE_ERR_H
54#include <err.h>
55#endif
56#include <errno.h>
57#include <fcntl.h>
58#include <limits.h>
59#include <stdlib.h>
60#include <string.h>
61#ifndef NETBSD
62#include <nbcompat/sha1.h>
63#include <nbcompat/sha2.h>
64#else
65#include <sha1.h>
66#include <sha2.h>
67#endif
68#include <unistd.h>
69
70#include "lib.h"
71
72static struct pkg_vulnerabilities *read_pkg_vulnerabilities_archive(struct archive *, int);
73static struct pkg_vulnerabilities *parse_pkg_vuln(const char *, size_t, int);
74
75static const char pgp_msg_start[] = "-----BEGIN PGP SIGNED MESSAGE-----\n";
76static const char pgp_msg_end[] = "-----BEGIN PGP SIGNATURE-----\n";
77static const char pkcs7_begin[] = "-----BEGIN PKCS7-----\n";
78static const char pkcs7_end[] = "-----END PKCS7-----\n";
79
80#ifndef BOOTSTRAP
81static struct archive *
82prepare_raw_file(void)
83{
84	struct archive *a = archive_read_new();
85	if (a == NULL)
86		errx(EXIT_FAILURE, "memory allocation failed");
87
88	archive_read_support_filter_gzip(a);
89	archive_read_support_filter_bzip2(a);
90	archive_read_support_filter_xz(a);
91	archive_read_support_format_raw(a);
92	return a;
93}
94#endif
95
96static void
97verify_signature_pkcs7(const char *input)
98{
99#ifdef HAVE_SSL
100	const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig;
101
102	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
103		begin_pkgvul = input + strlen(pgp_msg_start);
104		if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL)
105			errx(EXIT_FAILURE, "Invalid PGP signature");
106		if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL)
107			errx(EXIT_FAILURE, "No PKCS7 signature");
108	} else {
109		begin_pkgvul = input;
110		if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL)
111			errx(EXIT_FAILURE, "No PKCS7 signature");
112		end_pkgvul = begin_sig;
113	}
114	if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL)
115		errx(EXIT_FAILURE, "Invalid PKCS7 signature");
116	end_sig += strlen(pkcs7_end);
117
118	if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul,
119	    begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0))
120		errx(EXIT_FAILURE, "Unable to verify PKCS7 signature");
121#else
122	errx(EXIT_FAILURE, "OpenSSL support is not compiled in");
123#endif
124}
125
126static void
127verify_signature(const char *input, size_t input_len)
128{
129	gpg_verify(input, input_len, gpg_keyring_pkgvuln, NULL, 0);
130	if (certs_pkg_vulnerabilities != NULL)
131		verify_signature_pkcs7(input);
132}
133
134static void *
135sha512_hash_init(void)
136{
137	static SHA512_CTX hash_ctx;
138
139	SHA512_Init(&hash_ctx);
140	return &hash_ctx;
141}
142
143static void
144sha512_hash_update(void *ctx, const void *data, size_t len)
145{
146	SHA512_CTX *hash_ctx = ctx;
147
148	SHA512_Update(hash_ctx, data, len);
149}
150
151static const char *
152sha512_hash_finish(void *ctx)
153{
154	static char hash[SHA512_DIGEST_STRING_LENGTH];
155	unsigned char digest[SHA512_DIGEST_LENGTH];
156	SHA512_CTX *hash_ctx = ctx;
157	int i;
158
159	SHA512_Final(digest, hash_ctx);
160	for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
161		unsigned char c;
162
163		c = digest[i] / 16;
164		if (c < 10)
165			hash[2 * i] = '0' + c;
166		else
167			hash[2 * i] = 'a' - 10 + c;
168
169		c = digest[i] % 16;
170		if (c < 10)
171			hash[2 * i + 1] = '0' + c;
172		else
173			hash[2 * i + 1] = 'a' - 10 + c;
174	}
175	hash[2 * i] = '\0';
176
177	return hash;
178}
179
180static void *
181sha1_hash_init(void)
182{
183	static SHA1_CTX hash_ctx;
184
185	SHA1Init(&hash_ctx);
186	return &hash_ctx;
187}
188
189static void
190sha1_hash_update(void *ctx, const void *data, size_t len)
191{
192	SHA1_CTX *hash_ctx = ctx;
193
194	SHA1Update(hash_ctx, data, len);
195}
196
197static const char *
198sha1_hash_finish(void *ctx)
199{
200	static char hash[SHA1_DIGEST_STRING_LENGTH];
201	SHA1_CTX *hash_ctx = ctx;
202
203	SHA1End(hash_ctx, hash);
204
205	return hash;
206}
207
208static const struct hash_algorithm {
209	const char *name;
210	size_t name_len;
211	void * (*init)(void);
212	void (*update)(void *, const void *, size_t);
213	const char * (* finish)(void *);
214} hash_algorithms[] = {
215	{ "SHA512", 6, sha512_hash_init, sha512_hash_update,
216	  sha512_hash_finish },
217	{ "SHA1", 4, sha1_hash_init, sha1_hash_update,
218	  sha1_hash_finish },
219	{ NULL, 0, NULL, NULL, NULL }
220};
221
222static void
223verify_hash(const char *input, const char *hash_line)
224{
225	const struct hash_algorithm *hash;
226	void *ctx;
227	const char *last_start, *next, *hash_value;
228	int in_pgp_msg;
229
230	for (hash = hash_algorithms; hash->name != NULL; ++hash) {
231		if (strncmp(hash_line, hash->name, hash->name_len))
232			continue;
233		if (isspace((unsigned char)hash_line[hash->name_len]))
234			break;
235	}
236	if (hash->name == NULL) {
237		const char *end_name;
238		for (end_name = hash_line; *end_name != '\0'; ++end_name) {
239			if (!isalnum((unsigned char)*end_name))
240				break;
241		}
242		warnx("Unsupported hash algorithm: %.*s",
243		    (int)(end_name - hash_line), hash_line);
244		return;
245	}
246
247	hash_line += hash->name_len;
248	if (!isspace((unsigned char)*hash_line))
249		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
250	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
251		++hash_line;
252
253	if (*hash_line == '\n')
254		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
255
256	ctx = (*hash->init)();
257	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
258		input += strlen(pgp_msg_start);
259		in_pgp_msg = 1;
260	} else {
261		in_pgp_msg = 0;
262	}
263	for (last_start = input; *input != '\0'; input = next) {
264		if ((next = strchr(input, '\n')) == NULL)
265			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
266		++next;
267		if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0)
268			break;
269		if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0)
270			break;
271		if (*input == '\n' ||
272		    strncmp(input, "Hash:", 5) == 0 ||
273		    strncmp(input, "# $NetBSD", 9) == 0 ||
274		    strncmp(input, "#CHECKSUM", 9) == 0) {
275			(*hash->update)(ctx, last_start, input - last_start);
276			last_start = next;
277		}
278	}
279	(*hash->update)(ctx, last_start, input - last_start);
280	hash_value = (*hash->finish)(ctx);
281	if (strncmp(hash_line, hash_value, strlen(hash_value)))
282		errx(EXIT_FAILURE, "%s hash doesn't match", hash->name);
283	hash_line += strlen(hash_value);
284
285	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
286		++hash_line;
287
288	if (!isspace((unsigned char)*hash_line))
289		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
290}
291
292static void
293add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line)
294{
295	size_t len_pattern, len_class, len_url;
296	const char *start_pattern, *start_class, *start_url;
297
298	start_pattern = line;
299
300	start_class = line;
301	while (*start_class != '\0' && !isspace((unsigned char)*start_class))
302		++start_class;
303	len_pattern = start_class - line;
304
305	while (*start_class != '\n' && isspace((unsigned char)*start_class))
306		++start_class;
307
308	if (*start_class == '0' || *start_class == '\n')
309		errx(EXIT_FAILURE, "Input error: missing classification");
310
311	start_url = start_class;
312	while (*start_url != '\0' && !isspace((unsigned char)*start_url))
313		++start_url;
314	len_class = start_url - start_class;
315
316	while (*start_url != '\n' && isspace((unsigned char)*start_url))
317		++start_url;
318
319	if (*start_url == '0' || *start_url == '\n')
320		errx(EXIT_FAILURE, "Input error: missing URL");
321
322	line = start_url;
323	while (*line != '\0' && !isspace((unsigned char)*line))
324		++line;
325	len_url = line - start_url;
326
327	if (pv->entries == *allocated) {
328		if (*allocated == 0)
329			*allocated = 16;
330		else if (*allocated <= SSIZE_MAX / 2)
331			*allocated *= 2;
332		else
333			errx(EXIT_FAILURE, "Too many vulnerabilities");
334		pv->vulnerability = xrealloc(pv->vulnerability,
335		    sizeof(char *) * *allocated);
336		pv->classification = xrealloc(pv->classification,
337		    sizeof(char *) * *allocated);
338		pv->advisory = xrealloc(pv->advisory,
339		    sizeof(char *) * *allocated);
340	}
341
342	pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1);
343	memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern);
344	pv->vulnerability[pv->entries][len_pattern] = '\0';
345	pv->classification[pv->entries] = xmalloc(len_class + 1);
346	memcpy(pv->classification[pv->entries], start_class, len_class);
347	pv->classification[pv->entries][len_class] = '\0';
348	pv->advisory[pv->entries] = xmalloc(len_url + 1);
349	memcpy(pv->advisory[pv->entries], start_url, len_url);
350	pv->advisory[pv->entries][len_url] = '\0';
351
352	++pv->entries;
353}
354
355struct pkg_vulnerabilities *
356read_pkg_vulnerabilities_memory(void *buf, size_t len, int check_sum)
357{
358#ifdef BOOTSTRAP
359	errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
360#else
361	struct archive *a;
362	struct pkg_vulnerabilities *pv;
363
364	a = prepare_raw_file();
365	if (archive_read_open_memory(a, buf, len) != ARCHIVE_OK)
366		errx(EXIT_FAILURE, "Cannot open pkg_vulnerabilies buffer: %s",
367		    archive_error_string(a));
368
369	pv = read_pkg_vulnerabilities_archive(a, check_sum);
370
371	return pv;
372#endif
373}
374
375struct pkg_vulnerabilities *
376read_pkg_vulnerabilities_file(const char *path, int ignore_missing, int check_sum)
377{
378#ifdef BOOTSTRAP
379	errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
380#else
381	struct archive *a;
382	struct pkg_vulnerabilities *pv;
383	int fd;
384
385	if ((fd = open(path, O_RDONLY)) == -1) {
386		if (errno == ENOENT && ignore_missing)
387			return NULL;
388		err(EXIT_FAILURE, "Cannot open %s", path);
389	}
390
391	a = prepare_raw_file();
392	if (archive_read_open_fd(a, fd, 65536) != ARCHIVE_OK)
393		errx(EXIT_FAILURE, "Cannot open ``%s'': %s", path,
394		    archive_error_string(a));
395
396	pv = read_pkg_vulnerabilities_archive(a, check_sum);
397	close(fd);
398
399	return pv;
400#endif
401}
402
403#ifndef BOOTSTRAP
404static struct pkg_vulnerabilities *
405read_pkg_vulnerabilities_archive(struct archive *a, int check_sum)
406{
407	struct archive_entry *ae;
408	struct pkg_vulnerabilities *pv;
409	char *buf;
410	size_t buf_len, off;
411	ssize_t r;
412
413	if (archive_read_next_header(a, &ae) != ARCHIVE_OK)
414		errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
415		    archive_error_string(a));
416
417	off = 0;
418	buf_len = 65536;
419	buf = xmalloc(buf_len + 1);
420
421	for (;;) {
422		r = archive_read_data(a, buf + off, buf_len - off);
423		if (r <= 0)
424			break;
425		off += r;
426		if (off == buf_len) {
427			buf_len *= 2;
428			if (buf_len < off)
429				errx(EXIT_FAILURE, "pkg_vulnerabilties too large");
430			buf = xrealloc(buf, buf_len + 1);
431		}
432	}
433
434	if (r != ARCHIVE_OK)
435		errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
436		    archive_error_string(a));
437
438	archive_read_close(a);
439
440	buf[off] = '\0';
441	pv = parse_pkg_vuln(buf, off, check_sum);
442	free(buf);
443	return pv;
444}
445
446static struct pkg_vulnerabilities *
447parse_pkg_vuln(const char *input, size_t input_len, int check_sum)
448{
449	struct pkg_vulnerabilities *pv;
450	long version;
451	char *end;
452	const char *iter, *next;
453	size_t allocated_vulns;
454	int in_pgp_msg;
455
456	pv = xmalloc(sizeof(*pv));
457
458	allocated_vulns = pv->entries = 0;
459	pv->vulnerability = NULL;
460	pv->classification = NULL;
461	pv->advisory = NULL;
462
463	if (strlen(input) != input_len)
464		errx(1, "Invalid input (NUL character found)");
465
466	if (check_sum)
467		verify_signature(input, input_len);
468
469	if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) {
470		iter = input + strlen(pgp_msg_start);
471		in_pgp_msg = 1;
472	} else {
473		iter = input;
474		in_pgp_msg = 0;
475	}
476
477	for (; *iter; iter = next) {
478		if ((next = strchr(iter, '\n')) == NULL)
479			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
480		++next;
481		if (*iter == '\0' || *iter == '\n')
482			continue;
483		if (strncmp(iter, "Hash:", 5) == 0)
484			continue;
485		if (strncmp(iter, "# $NetBSD", 9) == 0)
486			continue;
487		if (*iter == '#' && isspace((unsigned char)iter[1])) {
488			for (++iter; iter != next; ++iter) {
489				if (!isspace((unsigned char)*iter))
490					errx(EXIT_FAILURE, "Invalid header");
491			}
492			continue;
493		}
494
495		if (strncmp(iter, "#FORMAT", 7) != 0)
496			errx(EXIT_FAILURE, "Input header is malformed");
497
498		iter += 7;
499		if (!isspace((unsigned char)*iter))
500			errx(EXIT_FAILURE, "Invalid #FORMAT");
501		++iter;
502		version = strtol(iter, &end, 10);
503		if (iter == end || version != 1 || *end != '.')
504			errx(EXIT_FAILURE, "Input #FORMAT");
505		iter = end + 1;
506		version = strtol(iter, &end, 10);
507		if (iter == end || version != 1 || *end != '.')
508			errx(EXIT_FAILURE, "Input #FORMAT");
509		iter = end + 1;
510		version = strtol(iter, &end, 10);
511		if (iter == end || version != 0)
512			errx(EXIT_FAILURE, "Input #FORMAT");
513		for (iter = end; iter != next; ++iter) {
514			if (!isspace((unsigned char)*iter))
515				errx(EXIT_FAILURE, "Input #FORMAT");
516		}
517		break;
518	}
519	if (*iter == '\0')
520		errx(EXIT_FAILURE, "Missing #CHECKSUM or content");
521
522	for (iter = next; *iter; iter = next) {
523		if ((next = strchr(iter, '\n')) == NULL)
524			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
525		++next;
526		if (*iter == '\0' || *iter == '\n')
527			continue;
528		if (in_pgp_msg && strncmp(iter, pgp_msg_end, strlen(pgp_msg_end)) == 0)
529			break;
530		if (!in_pgp_msg && strncmp(iter, pkcs7_begin, strlen(pkcs7_begin)) == 0)
531			break;
532		if (*iter == '#' &&
533		    (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1])))
534			continue;
535		if (strncmp(iter, "#CHECKSUM", 9) == 0) {
536			iter += 9;
537			if (!isspace((unsigned char)*iter))
538				errx(EXIT_FAILURE, "Invalid #CHECKSUM");
539			while (isspace((unsigned char)*iter))
540				++iter;
541			verify_hash(input, iter);
542			continue;
543		}
544		if (*iter == '#') {
545			/*
546			 * This should really be an error,
547			 * but it is still used.
548			 */
549			/* errx(EXIT_FAILURE, "Invalid data line starting with #"); */
550			continue;
551		}
552		add_vulnerability(pv, &allocated_vulns, iter);
553	}
554
555	if (pv->entries != allocated_vulns) {
556		pv->vulnerability = xrealloc(pv->vulnerability,
557		    sizeof(char *) * pv->entries);
558		pv->classification = xrealloc(pv->classification,
559		    sizeof(char *) * pv->entries);
560		pv->advisory = xrealloc(pv->advisory,
561		    sizeof(char *) * pv->entries);
562	}
563
564	return pv;
565}
566#endif
567
568void
569free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv)
570{
571	size_t i;
572
573	for (i = 0; i < pv->entries; ++i) {
574		free(pv->vulnerability[i]);
575		free(pv->classification[i]);
576		free(pv->advisory[i]);
577	}
578	free(pv->vulnerability);
579	free(pv->classification);
580	free(pv->advisory);
581	free(pv);
582}
583
584static int
585check_ignored_entry(struct pkg_vulnerabilities *pv, size_t i)
586{
587	const char *iter, *next;
588	size_t entry_len, url_len;
589
590	if (ignore_advisories == NULL)
591		return 0;
592
593	url_len = strlen(pv->advisory[i]);
594
595	for (iter = ignore_advisories; *iter; iter = next) {
596		if ((next = strchr(iter, '\n')) == NULL) {
597			entry_len = strlen(iter);
598			next = iter + entry_len;
599		} else {
600			entry_len = next - iter;
601			++next;
602		}
603		if (url_len != entry_len)
604			continue;
605		if (strncmp(pv->advisory[i], iter, entry_len) == 0)
606			return 1;
607	}
608	return 0;
609}
610
611int
612audit_package(struct pkg_vulnerabilities *pv, const char *pkgname,
613    const char *limit_vul_types, int include_ignored, int output_type)
614{
615	FILE *output = output_type == 1 ? stdout : stderr;
616	size_t i;
617	int retval, do_eol, ignored;
618
619	retval = 0;
620
621	do_eol = (strcasecmp(check_eol, "yes") == 0);
622
623	for (i = 0; i < pv->entries; ++i) {
624		ignored = check_ignored_entry(pv, i);
625		if (ignored && !include_ignored)
626			continue;
627		if (limit_vul_types != NULL &&
628		    strcmp(limit_vul_types, pv->classification[i]))
629			continue;
630		if (!pkg_match(pv->vulnerability[i], pkgname))
631			continue;
632		if (strcmp("eol", pv->classification[i]) == 0) {
633			if (!do_eol)
634				continue;
635			retval = 1;
636			if (output_type == 0) {
637				puts(pkgname);
638				continue;
639			}
640			fprintf(output,
641			    "Package %s has reached end-of-life (eol), "
642			    "see %s/eol-packages\n", pkgname,
643			    tnf_vulnerability_base);
644			continue;
645		}
646		retval = 1;
647		if (output_type == 0) {
648			fprintf(stdout, "%s%s\n",
649				pkgname, ignored ? " (ignored)" : "");
650		} else {
651			fprintf(output,
652			    "Package %s has a%s %s vulnerability, see %s\n",
653			    pkgname, ignored ? "n ignored" : "",
654			    pv->classification[i], pv->advisory[i]);
655		}
656	}
657	return retval;
658}
659