xref: /netbsd-src/external/bsd/pkg_install/dist/lib/vulnerabilities-file.c (revision 404fbe5fb94ca1e054339640cabb2801ce52dd30)
1 /*-
2  * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in
13  *    the documentation and/or other materials provided with the
14  *    distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
20  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
22  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #if HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 
34 #include <nbcompat.h>
35 
36 #if HAVE_SYS_CDEFS_H
37 #include <sys/cdefs.h>
38 #endif
39 __RCSID("$NetBSD: vulnerabilities-file.c,v 1.1.1.1 2008/09/30 19:00:27 joerg Exp $");
40 
41 #if HAVE_SYS_STAT_H
42 #include <sys/stat.h>
43 #endif
44 #if HAVE_SYS_WAIT_H
45 #include <sys/wait.h>
46 #endif
47 #include <ctype.h>
48 #if HAVE_ERR_H
49 #include <err.h>
50 #endif
51 #include <errno.h>
52 #include <fcntl.h>
53 #include <limits.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #ifndef NETBSD
57 #include <nbcompat/sha1.h>
58 #include <nbcompat/sha2.h>
59 #else
60 #include <sha1.h>
61 #include <sha2.h>
62 #endif
63 #include <unistd.h>
64 
65 #include "lib.h"
66 
67 /*
68  * We explicitely initialize this to NULL to stop Mac OS X Leopard's linker
69  * from turning this into a common symbol which causes a link failure.
70  */
71 const char *gpg_cmd = NULL;
72 
73 static void
74 verify_signature(const char *input, size_t input_len)
75 {
76 	pid_t child;
77 	int fd[2], status;
78 
79 	if (gpg_cmd == NULL)
80 		errx(EXIT_FAILURE, "GPG variable not set in configuration file");
81 
82 	if (pipe(fd) == -1)
83 		err(EXIT_FAILURE, "cannot create input pipes");
84 
85 	child = vfork();
86 	if (child == -1)
87 		err(EXIT_FAILURE, "cannot fork GPG process");
88 	if (child == 0) {
89 		close(fd[1]);
90 		close(STDIN_FILENO);
91 		if (dup2(fd[0], STDIN_FILENO) == -1) {
92 			static const char err_msg[] =
93 			    "cannot redirect stdin of GPG process\n";
94 			write(STDERR_FILENO, err_msg, sizeof(err_msg) - 1);
95 			_exit(255);
96 		}
97 		close(fd[0]);
98 		execlp(gpg_cmd, gpg_cmd, "--verify", "-", (char *)NULL);
99 		_exit(255);
100 	}
101 	close(fd[0]);
102 	if (write(fd[1], input, input_len) != input_len)
103 		errx(EXIT_FAILURE, "Short read from GPG");
104 	close(fd[1]);
105 	waitpid(child, &status, 0);
106 	if (status)
107 		errx(EXIT_FAILURE, "GPG could not verify the signature");
108 }
109 
110 static void *
111 sha512_hash_init(void)
112 {
113 	static SHA512_CTX hash_ctx;
114 
115 	SHA512_Init(&hash_ctx);
116 	return &hash_ctx;
117 }
118 
119 static void
120 sha512_hash_update(void *ctx, const void *data, size_t len)
121 {
122 	SHA512_CTX *hash_ctx = ctx;
123 
124 	SHA512_Update(hash_ctx, data, len);
125 }
126 
127 static const char *
128 sha512_hash_finish(void *ctx)
129 {
130 	static char hash[SHA512_DIGEST_STRING_LENGTH];
131 	SHA512_CTX *hash_ctx = ctx;
132 
133 	SHA512_End(hash_ctx, hash);
134 
135 	return hash;
136 }
137 
138 static void *
139 sha1_hash_init(void)
140 {
141 	static SHA1_CTX hash_ctx;
142 
143 	SHA1Init(&hash_ctx);
144 	return &hash_ctx;
145 }
146 
147 static void
148 sha1_hash_update(void *ctx, const void *data, size_t len)
149 {
150 	SHA1_CTX *hash_ctx = ctx;
151 
152 	SHA1Update(hash_ctx, data, len);
153 }
154 
155 static const char *
156 sha1_hash_finish(void *ctx)
157 {
158 	static char hash[SHA1_DIGEST_STRING_LENGTH];
159 	SHA1_CTX *hash_ctx = ctx;
160 
161 	SHA1End(hash_ctx, hash);
162 
163 	return hash;
164 }
165 
166 static const struct hash_algorithm {
167 	const char *name;
168 	size_t name_len;
169 	void * (*init)(void);
170 	void (*update)(void *, const void *, size_t);
171 	const char * (* finish)(void *);
172 } hash_algorithms[] = {
173 	{ "SHA512", 6, sha512_hash_init, sha512_hash_update,
174 	  sha512_hash_finish },
175 	{ "SHA1", 4, sha1_hash_init, sha1_hash_update,
176 	  sha1_hash_finish },
177 	{ NULL, 0, NULL, NULL, NULL }
178 };
179 
180 static void
181 verify_hash(const char *input, const char *hash_line)
182 {
183 	const struct hash_algorithm *hash;
184 	void *ctx;
185 	const char *last_start, *next, *hash_value;
186 
187 	for (hash = hash_algorithms; hash->name != NULL; ++hash) {
188 		if (strncmp(hash_line, hash->name, hash->name_len))
189 			continue;
190 		if (isspace((unsigned char)hash_line[hash->name_len]))
191 			break;
192 	}
193 	if (hash->name == NULL) {
194 		const char *end_name;
195 		for (end_name = hash_line; *end_name != '\0'; ++end_name) {
196 			if (!isalnum((unsigned char)*end_name))
197 				break;
198 		}
199 		warnx("Unsupported hash algorithm: %.*s",
200 		    (int)(end_name - hash_line), hash_line);
201 		return;
202 	}
203 
204 	hash_line += hash->name_len;
205 	if (!isspace((unsigned char)*hash_line))
206 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
207 	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
208 		++hash_line;
209 
210 	if (*hash_line == '\n')
211 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
212 
213 	ctx = (*hash->init)();
214 	for (last_start = input; *input != '\0'; input = next) {
215 		if ((next = strchr(input, '\n')) == NULL)
216 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
217 		++next;
218 		if (*input == '\n' ||
219 		    strncmp(input, "-----BEGIN", 10) == 0 ||
220 		    strncmp(input, "Hash:", 5) == 0 ||
221 		    strncmp(input, "# $NetBSD", 9) == 0 ||
222 		    strncmp(input, "#CHECKSUM", 9) == 0) {
223 			(*hash->update)(ctx, last_start, input - last_start);
224 			last_start = next;
225 		} else if (strncmp(input, "Version:", 8) == 0)
226 			break;
227 	}
228 	(*hash->update)(ctx, last_start, input - last_start);
229 	hash_value = (*hash->finish)(ctx);
230 	if (strncmp(hash_line, hash_value, strlen(hash_value)))
231 		errx(EXIT_FAILURE, "%s hash doesn't match", hash->name);
232 	hash_line += strlen(hash_value);
233 
234 	while (isspace((unsigned char)*hash_line) && *hash_line != '\n')
235 		++hash_line;
236 
237 	if (!isspace((unsigned char)*hash_line))
238 		errx(EXIT_FAILURE, "Invalid #CHECKSUM");
239 }
240 
241 static void
242 add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line)
243 {
244 	size_t len_pattern, len_class, len_url;
245 	const char *start_pattern, *start_class, *start_url;
246 
247 	start_pattern = line;
248 
249 	start_class = line;
250 	while (*start_class != '\0' && !isspace((unsigned char)*start_class))
251 		++start_class;
252 	len_pattern = start_class - line;
253 
254 	while (*start_class != '\n' && isspace((unsigned char)*start_class))
255 		++start_class;
256 
257 	if (*start_class == '0' || *start_class == '\n')
258 		errx(EXIT_FAILURE, "Input error: missing classification");
259 
260 	start_url = start_class;
261 	while (*start_url != '\0' && !isspace((unsigned char)*start_url))
262 		++start_url;
263 	len_class = start_url - start_class;
264 
265 	while (*start_url != '\n' && isspace((unsigned char)*start_url))
266 		++start_url;
267 
268 	if (*start_url == '0' || *start_url == '\n')
269 		errx(EXIT_FAILURE, "Input error: missing URL");
270 
271 	line = start_url;
272 	while (*line != '\0' && !isspace((unsigned char)*line))
273 		++line;
274 	len_url = line - start_url;
275 
276 	if (pv->entries == *allocated) {
277 		if (*allocated == 0)
278 			*allocated = 16;
279 		else if (*allocated <= SSIZE_MAX / 2)
280 			*allocated *= 2;
281 		else
282 			errx(EXIT_FAILURE, "Too many vulnerabilities");
283 		pv->vulnerability = realloc(pv->vulnerability,
284 		    sizeof(char *) * *allocated);
285 		pv->classification = realloc(pv->classification,
286 		    sizeof(char *) * *allocated);
287 		pv->advisory = realloc(pv->advisory,
288 		    sizeof(char *) * *allocated);
289 		if (pv->vulnerability == NULL ||
290 		    pv->classification == NULL || pv->advisory == NULL)
291 			errx(EXIT_FAILURE, "realloc failed");
292 	}
293 
294 	if ((pv->vulnerability[pv->entries] = malloc(len_pattern + 1)) == NULL)
295 		errx(EXIT_FAILURE, "malloc failed");
296 	memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern);
297 	pv->vulnerability[pv->entries][len_pattern] = '\0';
298 	if ((pv->classification[pv->entries] = malloc(len_class + 1)) == NULL)
299 		errx(EXIT_FAILURE, "malloc failed");
300 	memcpy(pv->classification[pv->entries], start_class, len_class);
301 	pv->classification[pv->entries][len_class] = '\0';
302 	if ((pv->advisory[pv->entries] = malloc(len_url + 1)) == NULL)
303 		errx(EXIT_FAILURE, "malloc failed");
304 	memcpy(pv->advisory[pv->entries], start_url, len_url);
305 	pv->advisory[pv->entries][len_url] = '\0';
306 
307 	++pv->entries;
308 }
309 
310 struct pkg_vulnerabilities *
311 read_pkg_vulnerabilities(const char *path, int ignore_missing, int check_sum)
312 {
313 	struct pkg_vulnerabilities *pv;
314 	struct stat st;
315 	int fd;
316 	char *input, *decompressed_input;
317 	size_t input_len, decompressed_len;
318 	ssize_t bytes_read;
319 
320 	if ((fd = open(path, O_RDONLY)) == -1) {
321 		if (errno == ENOENT && ignore_missing)
322 			return NULL;
323 		err(EXIT_FAILURE, "Cannot open %s", path);
324 	}
325 
326 	if (fstat(fd, &st) == -1)
327 		err(EXIT_FAILURE, "Cannot stat %s", path);
328 
329 	if ((st.st_mode & S_IFMT) != S_IFREG)
330 		errx(EXIT_FAILURE, "Input is not regular file");
331 	if (st.st_size > SSIZE_MAX - 1)
332 		errx(EXIT_FAILURE, "Input too large");
333 
334 	input_len = (size_t)st.st_size;
335 	if (input_len < 4)
336 		err(EXIT_FAILURE, "Input too short for a pkg_vulnerability file");
337 	if ((input = malloc(input_len + 1)) == NULL)
338 		err(EXIT_FAILURE, "malloc failed");
339 	if ((bytes_read = read(fd, input, input_len)) == -1)
340 		err(1, "Failed to read input");
341 	if (bytes_read != st.st_size)
342 		errx(1, "Unexpected short read");
343 
344 	if (decompress_buffer(input, input_len, &decompressed_input,
345 	    &decompressed_len)) {
346 		free(input);
347 		input = decompressed_input;
348 		input_len = decompressed_len;
349 	}
350 	pv = parse_pkg_vulnerabilities(input, input_len, check_sum);
351 	free(input);
352 
353 	return pv;
354 }
355 
356 struct pkg_vulnerabilities *
357 parse_pkg_vulnerabilities(const char *input, size_t input_len, int check_sum)
358 {
359 	struct pkg_vulnerabilities *pv;
360 	long version;
361 	char *end;
362 	const char *iter, *next;
363 	size_t allocated_vulns;
364 
365 	pv = malloc(sizeof(*pv));
366 	if (pv == NULL)
367 		err(EXIT_FAILURE, "malloc failed");
368 
369 	allocated_vulns = pv->entries = 0;
370 	pv->vulnerability = NULL;
371 	pv->classification = NULL;
372 	pv->advisory = NULL;
373 
374 	if (strlen(input) != input_len)
375 		errx(1, "Invalid input (NUL character found)");
376 
377 	if (check_sum)
378 		verify_signature(input, input_len);
379 
380 	for (iter = input; *iter; iter = next) {
381 		if ((next = strchr(iter, '\n')) == NULL)
382 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
383 		++next;
384 		if (*iter == '\0' || *iter == '\n')
385 			continue;
386 		if (strncmp(iter, "-----BEGIN", 10) == 0)
387 			continue;
388 		if (strncmp(iter, "Hash:", 5) == 0)
389 			continue;
390 		if (strncmp(iter, "# $NetBSD", 9) == 0)
391 			continue;
392 		if (*iter == '#' && isspace((unsigned char)iter[1])) {
393 			for (++iter; iter != next; ++iter) {
394 				if (!isspace((unsigned char)*iter))
395 					errx(EXIT_FAILURE, "Invalid header");
396 			}
397 			continue;
398 		}
399 
400 		if (strncmp(iter, "#FORMAT", 7) != 0)
401 			errx(EXIT_FAILURE, "Input header is malformed");
402 
403 		iter += 7;
404 		if (!isspace((unsigned char)*iter))
405 			errx(EXIT_FAILURE, "Invalid #FORMAT");
406 		++iter;
407 		version = strtol(iter, &end, 10);
408 		if (iter == end || version != 1 || *end != '.')
409 			errx(EXIT_FAILURE, "Input #FORMAT");
410 		iter = end + 1;
411 		version = strtol(iter, &end, 10);
412 		if (iter == end || version != 1 || *end != '.')
413 			errx(EXIT_FAILURE, "Input #FORMAT");
414 		iter = end + 1;
415 		version = strtol(iter, &end, 10);
416 		if (iter == end || version != 0)
417 			errx(EXIT_FAILURE, "Input #FORMAT");
418 		for (iter = end; iter != next; ++iter) {
419 			if (!isspace((unsigned char)*iter))
420 				errx(EXIT_FAILURE, "Input #FORMAT");
421 		}
422 		break;
423 	}
424 	if (*iter == '\0')
425 		errx(EXIT_FAILURE, "Missing #CHECKSUM or content");
426 
427 	for (iter = next; *iter; iter = next) {
428 		if ((next = strchr(iter, '\n')) == NULL)
429 			errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities");
430 		++next;
431 		if (*iter == '\0' || *iter == '\n')
432 			continue;
433 		if (strncmp(iter, "Version:", 5) == 0)
434 			break;
435 		if (*iter == '#' &&
436 		    (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1])))
437 			continue;
438 		if (strncmp(iter, "#CHECKSUM", 9) == 0) {
439 			iter += 9;
440 			if (!isspace((unsigned char)*iter))
441 				errx(EXIT_FAILURE, "Invalid #CHECKSUM");
442 			while (isspace((unsigned char)*iter))
443 				++iter;
444 			verify_hash(input, iter);
445 			continue;
446 		}
447 		if (*iter == '#') {
448 			/*
449 			 * This should really be an error,
450 			 * but it is still used.
451 			 */
452 			/* errx(EXIT_FAILURE, "Invalid data line starting with #"); */
453 			continue;
454 		}
455 		add_vulnerability(pv, &allocated_vulns, iter);
456 	}
457 
458 	if (pv->entries != allocated_vulns) {
459 		pv->vulnerability = realloc(pv->vulnerability,
460 		    sizeof(char *) * pv->entries);
461 		pv->classification = realloc(pv->classification,
462 		    sizeof(char *) * pv->entries);
463 		pv->advisory = realloc(pv->advisory,
464 		    sizeof(char *) * pv->entries);
465 		if (pv->vulnerability == NULL ||
466 		    pv->classification == NULL || pv->advisory == NULL)
467 			errx(EXIT_FAILURE, "realloc failed");
468 	}
469 
470 	return pv;
471 }
472 
473 void
474 free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv)
475 {
476 	size_t i;
477 
478 	for (i = 0; i < pv->entries; ++i) {
479 		free(pv->vulnerability[i]);
480 		free(pv->classification[i]);
481 		free(pv->advisory[i]);
482 	}
483 	free(pv->vulnerability);
484 	free(pv->classification);
485 	free(pv->advisory);
486 	free(pv);
487 }
488