1 /* $NetBSD: vulnerabilities-file.c,v 1.5 2009/03/02 14:59:14 joerg Exp $ */
4 * Copyright (c) 2008 Joerg Sonnenberger <joerg@NetBSD.org>.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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
39 #include <sys/cdefs.h>
41 __RCSID("$NetBSD: vulnerabilities-file.c,v 1.5 2009/03/02 14:59:14 joerg Exp $");
59 #include <nbcompat/sha1.h>
60 #include <nbcompat/sha2.h>
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";
75 verify_signature_pkcs7(const char *input
)
78 const char *begin_pkgvul
, *end_pkgvul
, *begin_sig
, *end_sig
;
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");
88 if ((begin_sig
= strstr(begin_pkgvul
, pkcs7_begin
)) == NULL
)
89 errx(EXIT_FAILURE
, "No PKCS7 signature");
90 end_pkgvul
= begin_sig
;
92 if ((end_sig
= strstr(begin_sig
, pkcs7_end
)) == NULL
)
93 errx(EXIT_FAILURE
, "Invalid PKCS7 signature");
94 end_sig
+= strlen(pkcs7_end
);
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");
100 errx(EXIT_FAILURE
, "OpenSSL support is not compiled in");
105 verify_signature(const char *input
, size_t input_len
)
107 if (gpg_cmd
== NULL
&& certs_pkg_vulnerabilities
== NULL
)
109 "At least GPG or CERTIFICATE_ANCHOR_PKGVULN "
110 "must be configured");
112 inline_gpg_verify(input
, input_len
, gpg_keyring_pkgvuln
);
113 if (certs_pkg_vulnerabilities
!= NULL
)
114 verify_signature_pkcs7(input
);
118 sha512_hash_init(void)
120 static SHA512_CTX hash_ctx
;
122 SHA512_Init(&hash_ctx
);
127 sha512_hash_update(void *ctx
, const void *data
, size_t len
)
129 SHA512_CTX
*hash_ctx
= ctx
;
131 SHA512_Update(hash_ctx
, data
, len
);
135 sha512_hash_finish(void *ctx
)
137 static char hash
[SHA512_DIGEST_STRING_LENGTH
];
138 unsigned char digest
[SHA512_DIGEST_LENGTH
];
139 SHA512_CTX
*hash_ctx
= ctx
;
142 SHA512_Final(digest
, hash_ctx
);
143 for (i
= 0; i
< SHA512_DIGEST_LENGTH
; ++i
) {
148 hash
[2 * i
] = '0' + c
;
150 hash
[2 * i
] = 'a' - 10 + c
;
154 hash
[2 * i
+ 1] = '0' + c
;
156 hash
[2 * i
+ 1] = 'a' - 10 + c
;
166 static SHA1_CTX hash_ctx
;
173 sha1_hash_update(void *ctx
, const void *data
, size_t len
)
175 SHA1_CTX
*hash_ctx
= ctx
;
177 SHA1Update(hash_ctx
, data
, len
);
181 sha1_hash_finish(void *ctx
)
183 static char hash
[SHA1_DIGEST_STRING_LENGTH
];
184 SHA1_CTX
*hash_ctx
= ctx
;
186 SHA1End(hash_ctx
, hash
);
191 static const struct hash_algorithm
{
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
,
202 { NULL
, 0, NULL
, NULL
, NULL
}
206 verify_hash(const char *input
, const char *hash_line
)
208 const struct hash_algorithm
*hash
;
210 const char *last_start
, *next
, *hash_value
;
213 for (hash
= hash_algorithms
; hash
->name
!= NULL
; ++hash
) {
214 if (strncmp(hash_line
, hash
->name
, hash
->name_len
))
216 if (isspace((unsigned char)hash_line
[hash
->name_len
]))
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
))
225 warnx("Unsupported hash algorithm: %.*s",
226 (int)(end_name
- hash_line
), hash_line
);
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')
236 if (*hash_line
== '\n')
237 errx(EXIT_FAILURE
, "Invalid #CHECKSUM");
239 ctx
= (*hash
->init
)();
240 if (strncmp(input
, pgp_msg_start
, strlen(pgp_msg_start
)) == 0) {
241 input
+= strlen(pgp_msg_start
);
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");
250 if (in_pgp_msg
&& strncmp(input
, pgp_msg_end
, strlen(pgp_msg_end
)) == 0)
252 if (!in_pgp_msg
&& strncmp(input
, pkcs7_begin
, strlen(pkcs7_begin
)) == 0)
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
);
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
);
268 while (isspace((unsigned char)*hash_line
) && *hash_line
!= '\n')
271 if (!isspace((unsigned char)*hash_line
))
272 errx(EXIT_FAILURE
, "Invalid #CHECKSUM");
276 add_vulnerability(struct pkg_vulnerabilities
*pv
, size_t *allocated
, const char *line
)
278 size_t len_pattern
, len_class
, len_url
;
279 const char *start_pattern
, *start_class
, *start_url
;
281 start_pattern
= line
;
284 while (*start_class
!= '\0' && !isspace((unsigned char)*start_class
))
286 len_pattern
= start_class
- line
;
288 while (*start_class
!= '\n' && isspace((unsigned char)*start_class
))
291 if (*start_class
== '0' || *start_class
== '\n')
292 errx(EXIT_FAILURE
, "Input error: missing classification");
294 start_url
= start_class
;
295 while (*start_url
!= '\0' && !isspace((unsigned char)*start_url
))
297 len_class
= start_url
- start_class
;
299 while (*start_url
!= '\n' && isspace((unsigned char)*start_url
))
302 if (*start_url
== '0' || *start_url
== '\n')
303 errx(EXIT_FAILURE
, "Input error: missing URL");
306 while (*line
!= '\0' && !isspace((unsigned char)*line
))
308 len_url
= line
- start_url
;
310 if (pv
->entries
== *allocated
) {
313 else if (*allocated
<= SSIZE_MAX
/ 2)
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
);
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';
338 struct pkg_vulnerabilities
*
339 read_pkg_vulnerabilities(const char *path
, int ignore_missing
, int check_sum
)
341 struct pkg_vulnerabilities
*pv
;
344 char *input
, *decompressed_input
;
345 size_t input_len
, decompressed_len
;
348 if ((fd
= open(path
, O_RDONLY
)) == -1) {
349 if (errno
== ENOENT
&& ignore_missing
)
351 err(EXIT_FAILURE
, "Cannot open %s", path
);
354 if (fstat(fd
, &st
) == -1)
355 err(EXIT_FAILURE
, "Cannot stat %s", path
);
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");
362 input_len
= (size_t)st
.st_size
;
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");
373 if (decompress_buffer(input
, input_len
, &decompressed_input
,
374 &decompressed_len
)) {
376 input
= decompressed_input
;
377 input_len
= decompressed_len
;
379 pv
= parse_pkg_vulnerabilities(input
, input_len
, check_sum
);
385 struct pkg_vulnerabilities
*
386 parse_pkg_vulnerabilities(const char *input
, size_t input_len
, int check_sum
)
388 struct pkg_vulnerabilities
*pv
;
391 const char *iter
, *next
;
392 size_t allocated_vulns
;
395 pv
= xmalloc(sizeof(*pv
));
397 allocated_vulns
= pv
->entries
= 0;
398 pv
->vulnerability
= NULL
;
399 pv
->classification
= NULL
;
402 if (strlen(input
) != input_len
)
403 errx(1, "Invalid input (NUL character found)");
406 verify_signature(input
, input_len
);
408 if (strncmp(input
, pgp_msg_start
, strlen(pgp_msg_start
)) == 0) {
409 iter
= input
+ strlen(pgp_msg_start
);
416 for (; *iter
; iter
= next
) {
417 if ((next
= strchr(iter
, '\n')) == NULL
)
418 errx(EXIT_FAILURE
, "Missing newline in pkg-vulnerabilities");
420 if (*iter
== '\0' || *iter
== '\n')
422 if (strncmp(iter
, "Hash:", 5) == 0)
424 if (strncmp(iter
, "# $NetBSD", 9) == 0)
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");
434 if (strncmp(iter
, "#FORMAT", 7) != 0)
435 errx(EXIT_FAILURE
, "Input header is malformed");
438 if (!isspace((unsigned char)*iter
))
439 errx(EXIT_FAILURE
, "Invalid #FORMAT");
441 version
= strtol(iter
, &end
, 10);
442 if (iter
== end
|| version
!= 1 || *end
!= '.')
443 errx(EXIT_FAILURE
, "Input #FORMAT");
445 version
= strtol(iter
, &end
, 10);
446 if (iter
== end
|| version
!= 1 || *end
!= '.')
447 errx(EXIT_FAILURE
, "Input #FORMAT");
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");
459 errx(EXIT_FAILURE
, "Missing #CHECKSUM or content");
461 for (iter
= next
; *iter
; iter
= next
) {
462 if ((next
= strchr(iter
, '\n')) == NULL
)
463 errx(EXIT_FAILURE
, "Missing newline in pkg-vulnerabilities");
465 if (*iter
== '\0' || *iter
== '\n')
467 if (in_pgp_msg
&& strncmp(iter
, pgp_msg_end
, strlen(pgp_msg_end
)) == 0)
469 if (!in_pgp_msg
&& strncmp(iter
, pkcs7_begin
, strlen(pkcs7_begin
)) == 0)
472 (iter
[1] == '\0' || iter
[1] == '\n' || isspace((unsigned char)iter
[1])))
474 if (strncmp(iter
, "#CHECKSUM", 9) == 0) {
476 if (!isspace((unsigned char)*iter
))
477 errx(EXIT_FAILURE
, "Invalid #CHECKSUM");
478 while (isspace((unsigned char)*iter
))
480 verify_hash(input
, iter
);
485 * This should really be an error,
486 * but it is still used.
488 /* errx(EXIT_FAILURE, "Invalid data line starting with #"); */
491 add_vulnerability(pv
, &allocated_vulns
, iter
);
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
);
507 free_pkg_vulnerabilities(struct pkg_vulnerabilities
*pv
)
511 for (i
= 0; i
< pv
->entries
; ++i
) {
512 free(pv
->vulnerability
[i
]);
513 free(pv
->classification
[i
]);
514 free(pv
->advisory
[i
]);
516 free(pv
->vulnerability
);
517 free(pv
->classification
);
523 check_ignored_entry(struct pkg_vulnerabilities
*pv
, size_t i
)
525 const char *iter
, *next
;
526 size_t entry_len
, url_len
;
528 if (ignore_advisories
== NULL
)
531 url_len
= strlen(pv
->advisory
[i
]);
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
;
538 entry_len
= next
- iter
;
541 if (url_len
!= entry_len
)
543 if (strncmp(pv
->advisory
[i
], iter
, entry_len
) == 0)
550 audit_package(struct pkg_vulnerabilities
*pv
, const char *pkgname
,
551 const char *limit_vul_types
, int check_eol
, int output_type
)
553 FILE *output
= output_type
== 1 ? stdout
: stderr
;
559 for (i
= 0; i
< pv
->entries
; ++i
) {
560 if (check_ignored_entry(pv
, i
))
562 if (limit_vul_types
!= NULL
&&
563 strcmp(limit_vul_types
, pv
->classification
[i
]))
565 if (!pkg_match(pv
->vulnerability
[i
], pkgname
))
567 if (strcmp("eol", pv
->classification
[i
]) == 0) {
570 if (output_type
== 0) {
575 "Package %s has reached end-of-life (eol), "
576 "see %s/eol-packages\n", pkgname
,
577 tnf_vulnerability_base
);
581 if (output_type
== 0) {
585 "Package %s has a %s vulnerability, see %s\n",
586 pkgname
, pv
->classification
[i
], pv
->advisory
[i
]);