/* $NetBSD: vulnerabilities-file.c,v 1.3.4.8 2008/12/30 15:55:57 joerg Exp $ */ /*- * Copyright (c) 2008 Joerg Sonnenberger . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if HAVE_CONFIG_H #include "config.h" #endif #include #if HAVE_SYS_CDEFS_H #include #endif __RCSID("$NetBSD: vulnerabilities-file.c,v 1.3.4.8 2008/12/30 15:55:57 joerg Exp $"); #if HAVE_SYS_STAT_H #include #endif #if HAVE_SYS_WAIT_H #include #endif #include #if HAVE_ERR_H #include #endif #include #include #include #include #include #ifndef NETBSD #include #include #else #include #include #endif #include #include "lib.h" static const char pgp_msg_start[] = "-----BEGIN PGP SIGNED MESSAGE-----\n"; static const char pgp_msg_end[] = "-----BEGIN PGP SIGNATURE-----\n"; static const char pkcs7_begin[] = "-----BEGIN PKCS7-----\n"; static const char pkcs7_end[] = "-----END PKCS7-----\n"; static void verify_signature_pkcs7(const char *input) { #ifdef HAVE_SSL const char *begin_pkgvul, *end_pkgvul, *begin_sig, *end_sig; if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { begin_pkgvul = input + strlen(pgp_msg_start); if ((end_pkgvul = strstr(begin_pkgvul, pgp_msg_end)) == NULL) errx(EXIT_FAILURE, "Invalid PGP signature"); if ((begin_sig = strstr(end_pkgvul, pkcs7_begin)) == NULL) errx(EXIT_FAILURE, "No PKCS7 signature"); } else { begin_pkgvul = input; if ((begin_sig = strstr(begin_pkgvul, pkcs7_begin)) == NULL) errx(EXIT_FAILURE, "No PKCS7 signature"); end_pkgvul = begin_sig; } if ((end_sig = strstr(begin_sig, pkcs7_end)) == NULL) errx(EXIT_FAILURE, "Invalid PKCS7 signature"); end_sig += strlen(pkcs7_end); if (easy_pkcs7_verify(begin_pkgvul, end_pkgvul - begin_pkgvul, begin_sig, end_sig - begin_sig, certs_pkg_vulnerabilities, 0)) errx(EXIT_FAILURE, "Unable to verify PKCS7 signature"); #else errx(EXIT_FAILURE, "OpenSSL support is not compiled in"); #endif } static void verify_signature(const char *input, size_t input_len) { if (gpg_cmd == NULL && certs_pkg_vulnerabilities == NULL) errx(EXIT_FAILURE, "At least GPG or CERTIFICATE_ANCHOR_PKGVULN " "must be configured"); if (gpg_cmd != NULL) inline_gpg_verify(input, input_len); if (certs_pkg_vulnerabilities != NULL) verify_signature_pkcs7(input); } static void * sha512_hash_init(void) { static SHA512_CTX hash_ctx; SHA512_Init(&hash_ctx); return &hash_ctx; } static void sha512_hash_update(void *ctx, const void *data, size_t len) { SHA512_CTX *hash_ctx = ctx; SHA512_Update(hash_ctx, data, len); } static const char * sha512_hash_finish(void *ctx) { static char hash[SHA512_DIGEST_STRING_LENGTH]; unsigned char digest[SHA512_DIGEST_LENGTH]; SHA512_CTX *hash_ctx = ctx; int i; SHA512_Final(digest, hash_ctx); for (i = 0; i < SHA512_DIGEST_LENGTH; ++i) { unsigned char c; c = digest[i] / 16; if (c < 10) hash[2 * i] = '0' + c; else hash[2 * i] = 'a' - 10 + c; c = digest[i] % 16; if (c < 10) hash[2 * i + 1] = '0' + c; else hash[2 * i + 1] = 'a' - 10 + c; } hash[2 * i] = '\0'; return hash; } static void * sha1_hash_init(void) { static SHA1_CTX hash_ctx; SHA1Init(&hash_ctx); return &hash_ctx; } static void sha1_hash_update(void *ctx, const void *data, size_t len) { SHA1_CTX *hash_ctx = ctx; SHA1Update(hash_ctx, data, len); } static const char * sha1_hash_finish(void *ctx) { static char hash[SHA1_DIGEST_STRING_LENGTH]; SHA1_CTX *hash_ctx = ctx; SHA1End(hash_ctx, hash); return hash; } static const struct hash_algorithm { const char *name; size_t name_len; void * (*init)(void); void (*update)(void *, const void *, size_t); const char * (* finish)(void *); } hash_algorithms[] = { { "SHA512", 6, sha512_hash_init, sha512_hash_update, sha512_hash_finish }, { "SHA1", 4, sha1_hash_init, sha1_hash_update, sha1_hash_finish }, { NULL, 0, NULL, NULL, NULL } }; static void verify_hash(const char *input, const char *hash_line) { const struct hash_algorithm *hash; void *ctx; const char *last_start, *next, *hash_value; int in_pgp_msg; for (hash = hash_algorithms; hash->name != NULL; ++hash) { if (strncmp(hash_line, hash->name, hash->name_len)) continue; if (isspace((unsigned char)hash_line[hash->name_len])) break; } if (hash->name == NULL) { const char *end_name; for (end_name = hash_line; *end_name != '\0'; ++end_name) { if (!isalnum((unsigned char)*end_name)) break; } warnx("Unsupported hash algorithm: %.*s", (int)(end_name - hash_line), hash_line); return; } hash_line += hash->name_len; if (!isspace((unsigned char)*hash_line)) errx(EXIT_FAILURE, "Invalid #CHECKSUM"); while (isspace((unsigned char)*hash_line) && *hash_line != '\n') ++hash_line; if (*hash_line == '\n') errx(EXIT_FAILURE, "Invalid #CHECKSUM"); ctx = (*hash->init)(); if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { input += strlen(pgp_msg_start); in_pgp_msg = 1; } else { in_pgp_msg = 0; } for (last_start = input; *input != '\0'; input = next) { if ((next = strchr(input, '\n')) == NULL) errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); ++next; if (in_pgp_msg && strncmp(input, pgp_msg_end, strlen(pgp_msg_end)) == 0) break; if (!in_pgp_msg && strncmp(input, pkcs7_begin, strlen(pkcs7_begin)) == 0) break; if (*input == '\n' || strncmp(input, "Hash:", 5) == 0 || strncmp(input, "# $NetBSD", 9) == 0 || strncmp(input, "#CHECKSUM", 9) == 0) { (*hash->update)(ctx, last_start, input - last_start); last_start = next; } } (*hash->update)(ctx, last_start, input - last_start); hash_value = (*hash->finish)(ctx); if (strncmp(hash_line, hash_value, strlen(hash_value))) errx(EXIT_FAILURE, "%s hash doesn't match", hash->name); hash_line += strlen(hash_value); while (isspace((unsigned char)*hash_line) && *hash_line != '\n') ++hash_line; if (!isspace((unsigned char)*hash_line)) errx(EXIT_FAILURE, "Invalid #CHECKSUM"); } static void add_vulnerability(struct pkg_vulnerabilities *pv, size_t *allocated, const char *line) { size_t len_pattern, len_class, len_url; const char *start_pattern, *start_class, *start_url; start_pattern = line; start_class = line; while (*start_class != '\0' && !isspace((unsigned char)*start_class)) ++start_class; len_pattern = start_class - line; while (*start_class != '\n' && isspace((unsigned char)*start_class)) ++start_class; if (*start_class == '0' || *start_class == '\n') errx(EXIT_FAILURE, "Input error: missing classification"); start_url = start_class; while (*start_url != '\0' && !isspace((unsigned char)*start_url)) ++start_url; len_class = start_url - start_class; while (*start_url != '\n' && isspace((unsigned char)*start_url)) ++start_url; if (*start_url == '0' || *start_url == '\n') errx(EXIT_FAILURE, "Input error: missing URL"); line = start_url; while (*line != '\0' && !isspace((unsigned char)*line)) ++line; len_url = line - start_url; if (pv->entries == *allocated) { if (*allocated == 0) *allocated = 16; else if (*allocated <= SSIZE_MAX / 2) *allocated *= 2; else errx(EXIT_FAILURE, "Too many vulnerabilities"); pv->vulnerability = xrealloc(pv->vulnerability, sizeof(char *) * *allocated); pv->classification = xrealloc(pv->classification, sizeof(char *) * *allocated); pv->advisory = xrealloc(pv->advisory, sizeof(char *) * *allocated); } pv->vulnerability[pv->entries] = xmalloc(len_pattern + 1); memcpy(pv->vulnerability[pv->entries], start_pattern, len_pattern); pv->vulnerability[pv->entries][len_pattern] = '\0'; pv->classification[pv->entries] = xmalloc(len_class + 1); memcpy(pv->classification[pv->entries], start_class, len_class); pv->classification[pv->entries][len_class] = '\0'; pv->advisory[pv->entries] = xmalloc(len_url + 1); memcpy(pv->advisory[pv->entries], start_url, len_url); pv->advisory[pv->entries][len_url] = '\0'; ++pv->entries; } struct pkg_vulnerabilities * read_pkg_vulnerabilities(const char *path, int ignore_missing, int check_sum) { struct pkg_vulnerabilities *pv; struct stat st; int fd; char *input, *decompressed_input; size_t input_len, decompressed_len; ssize_t bytes_read; if ((fd = open(path, O_RDONLY)) == -1) { if (errno == ENOENT && ignore_missing) return NULL; err(EXIT_FAILURE, "Cannot open %s", path); } if (fstat(fd, &st) == -1) err(EXIT_FAILURE, "Cannot stat %s", path); if ((st.st_mode & S_IFMT) != S_IFREG) errx(EXIT_FAILURE, "Input is not regular file"); if (st.st_size > SSIZE_MAX - 1) errx(EXIT_FAILURE, "Input too large"); input_len = (size_t)st.st_size; if (input_len < 4) err(EXIT_FAILURE, "Input too short for a pkg_vulnerability file"); input = xmalloc(input_len + 1); if ((bytes_read = read(fd, input, input_len)) == -1) err(1, "Failed to read input"); if (bytes_read != st.st_size) errx(1, "Unexpected short read"); if (decompress_buffer(input, input_len, &decompressed_input, &decompressed_len)) { free(input); input = decompressed_input; input_len = decompressed_len; } pv = parse_pkg_vulnerabilities(input, input_len, check_sum); free(input); return pv; } struct pkg_vulnerabilities * parse_pkg_vulnerabilities(const char *input, size_t input_len, int check_sum) { struct pkg_vulnerabilities *pv; long version; char *end; const char *iter, *next; size_t allocated_vulns; int in_pgp_msg; pv = xmalloc(sizeof(*pv)); allocated_vulns = pv->entries = 0; pv->vulnerability = NULL; pv->classification = NULL; pv->advisory = NULL; if (strlen(input) != input_len) errx(1, "Invalid input (NUL character found)"); if (check_sum) verify_signature(input, input_len); if (strncmp(input, pgp_msg_start, strlen(pgp_msg_start)) == 0) { iter = input + strlen(pgp_msg_start); in_pgp_msg = 1; } else { iter = input; in_pgp_msg = 0; } for (; *iter; iter = next) { if ((next = strchr(iter, '\n')) == NULL) errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); ++next; if (*iter == '\0' || *iter == '\n') continue; if (strncmp(iter, "Hash:", 5) == 0) continue; if (strncmp(iter, "# $NetBSD", 9) == 0) continue; if (*iter == '#' && isspace((unsigned char)iter[1])) { for (++iter; iter != next; ++iter) { if (!isspace((unsigned char)*iter)) errx(EXIT_FAILURE, "Invalid header"); } continue; } if (strncmp(iter, "#FORMAT", 7) != 0) errx(EXIT_FAILURE, "Input header is malformed"); iter += 7; if (!isspace((unsigned char)*iter)) errx(EXIT_FAILURE, "Invalid #FORMAT"); ++iter; version = strtol(iter, &end, 10); if (iter == end || version != 1 || *end != '.') errx(EXIT_FAILURE, "Input #FORMAT"); iter = end + 1; version = strtol(iter, &end, 10); if (iter == end || version != 1 || *end != '.') errx(EXIT_FAILURE, "Input #FORMAT"); iter = end + 1; version = strtol(iter, &end, 10); if (iter == end || version != 0) errx(EXIT_FAILURE, "Input #FORMAT"); for (iter = end; iter != next; ++iter) { if (!isspace((unsigned char)*iter)) errx(EXIT_FAILURE, "Input #FORMAT"); } break; } if (*iter == '\0') errx(EXIT_FAILURE, "Missing #CHECKSUM or content"); for (iter = next; *iter; iter = next) { if ((next = strchr(iter, '\n')) == NULL) errx(EXIT_FAILURE, "Missing newline in pkg-vulnerabilities"); ++next; if (*iter == '\0' || *iter == '\n') continue; if (in_pgp_msg && strncmp(iter, pgp_msg_end, strlen(pgp_msg_end)) == 0) break; if (!in_pgp_msg && strncmp(iter, pkcs7_begin, strlen(pkcs7_begin)) == 0) break; if (*iter == '#' && (iter[1] == '\0' || iter[1] == '\n' || isspace((unsigned char)iter[1]))) continue; if (strncmp(iter, "#CHECKSUM", 9) == 0) { iter += 9; if (!isspace((unsigned char)*iter)) errx(EXIT_FAILURE, "Invalid #CHECKSUM"); while (isspace((unsigned char)*iter)) ++iter; verify_hash(input, iter); continue; } if (*iter == '#') { /* * This should really be an error, * but it is still used. */ /* errx(EXIT_FAILURE, "Invalid data line starting with #"); */ continue; } add_vulnerability(pv, &allocated_vulns, iter); } if (pv->entries != allocated_vulns) { pv->vulnerability = xrealloc(pv->vulnerability, sizeof(char *) * pv->entries); pv->classification = xrealloc(pv->classification, sizeof(char *) * pv->entries); pv->advisory = xrealloc(pv->advisory, sizeof(char *) * pv->entries); } return pv; } void free_pkg_vulnerabilities(struct pkg_vulnerabilities *pv) { size_t i; for (i = 0; i < pv->entries; ++i) { free(pv->vulnerability[i]); free(pv->classification[i]); free(pv->advisory[i]); } free(pv->vulnerability); free(pv->classification); free(pv->advisory); free(pv); }