minix/commands/pkg_install/lib/vulnerabilities-file.c

659 lines
17 KiB
C

/* $NetBSD: vulnerabilities-file.c,v 1.7 2010/06/16 23:02:49 joerg Exp $ */
/*-
* Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>.
* 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
#ifndef __minix
#include <nbcompat.h>
#endif
#if HAVE_SYS_CDEFS_H
#include <sys/cdefs.h>
#endif
#ifndef __minix
__RCSID("$NetBSD: vulnerabilities-file.c,v 1.7 2010/06/16 23:02:49 joerg Exp $");
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_SYS_WAIT_H
#include <sys/wait.h>
#endif
#ifndef BOOTSTRAP
#include <archive.h>
#endif
#include <ctype.h>
#if HAVE_ERR_H
#include <err.h>
#endif
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#if defined(NETBSD)
#include <sha1.h>
#include <sha2.h>
#elif defined(__minix)
#include <minix/sha1.h>
#include <minix/sha2.h>
#else
#include <nbcompat/sha1.h>
#include <nbcompat/sha2.h>
#endif
#include <unistd.h>
#include "lib.h"
static struct pkg_vulnerabilities *read_pkg_vulnerabilities_archive(struct archive *, int);
static struct pkg_vulnerabilities *parse_pkg_vuln(const char *, size_t, int);
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, gpg_keyring_pkgvuln);
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_memory(void *buf, size_t len, int check_sum)
{
#ifdef BOOTSTRAP
errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
#else
struct archive *a;
struct pkg_vulnerabilities *pv;
if ((a = archive_read_new()) == NULL)
errx(EXIT_FAILURE, "memory allocation failed");
if (archive_read_support_compression_all(a) != ARCHIVE_OK ||
archive_read_support_format_raw(a) != ARCHIVE_OK ||
archive_read_open_memory(a, buf, len) != ARCHIVE_OK)
errx(EXIT_FAILURE, "Cannot open pkg_vulnerabilies buffer: %s",
archive_error_string(a));
pv = read_pkg_vulnerabilities_archive(a, check_sum);
return pv;
#endif
}
struct pkg_vulnerabilities *
read_pkg_vulnerabilities_file(const char *path, int ignore_missing, int check_sum)
{
#ifdef BOOTSTRAP
errx(EXIT_FAILURE, "Audit functions are unsupported during bootstrap");
#else
struct archive *a;
struct pkg_vulnerabilities *pv;
int fd;
if ((fd = open(path, O_RDONLY)) == -1) {
if (errno == ENOENT && ignore_missing)
return NULL;
err(EXIT_FAILURE, "Cannot open %s", path);
}
if ((a = archive_read_new()) == NULL)
errx(EXIT_FAILURE, "memory allocation failed");
if (archive_read_support_compression_all(a) != ARCHIVE_OK ||
archive_read_support_format_raw(a) != ARCHIVE_OK ||
archive_read_open_fd(a, fd, 65536) != ARCHIVE_OK)
errx(EXIT_FAILURE, "Cannot open ``%s'': %s", path,
archive_error_string(a));
pv = read_pkg_vulnerabilities_archive(a, check_sum);
close(fd);
return pv;
#endif
}
#ifndef BOOTSTRAP
static struct pkg_vulnerabilities *
read_pkg_vulnerabilities_archive(struct archive *a, int check_sum)
{
struct archive_entry *ae;
struct pkg_vulnerabilities *pv;
char *buf;
size_t buf_len, off;
ssize_t r;
if (archive_read_next_header(a, &ae) != ARCHIVE_OK)
errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
archive_error_string(a));
off = 0;
buf_len = 65536;
buf = xmalloc(buf_len + 1);
for (;;) {
r = archive_read_data(a, buf + off, buf_len - off);
if (r <= 0)
break;
off += r;
if (off == buf_len) {
buf_len *= 2;
if (buf_len < off)
errx(EXIT_FAILURE, "pkg_vulnerabilties too large");
buf = xrealloc(buf, buf_len + 1);
}
}
if (r != ARCHIVE_OK)
errx(EXIT_FAILURE, "Cannot read pkg_vulnerabilities: %s",
archive_error_string(a));
archive_read_close(a);
buf[off] = '\0';
pv = parse_pkg_vuln(buf, off, check_sum);
free(buf);
return pv;
}
static struct pkg_vulnerabilities *
parse_pkg_vuln(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;
}
#endif
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);
}
static int
check_ignored_entry(struct pkg_vulnerabilities *pv, size_t i)
{
const char *iter, *next;
size_t entry_len, url_len;
if (ignore_advisories == NULL)
return 0;
url_len = strlen(pv->advisory[i]);
for (iter = ignore_advisories; *iter; iter = next) {
if ((next = strchr(iter, '\n')) == NULL) {
entry_len = strlen(iter);
next = iter + entry_len;
} else {
entry_len = next - iter;
++next;
}
if (url_len != entry_len)
continue;
if (strncmp(pv->advisory[i], iter, entry_len) == 0)
return 1;
}
return 0;
}
int
audit_package(struct pkg_vulnerabilities *pv, const char *pkgname,
const char *limit_vul_types, int output_type)
{
FILE *output = output_type == 1 ? stdout : stderr;
size_t i;
int retval, do_eol;
retval = 0;
do_eol = (strcasecmp(check_eol, "yes") == 0);
for (i = 0; i < pv->entries; ++i) {
if (check_ignored_entry(pv, i))
continue;
if (limit_vul_types != NULL &&
strcmp(limit_vul_types, pv->classification[i]))
continue;
if (!pkg_match(pv->vulnerability[i], pkgname))
continue;
if (strcmp("eol", pv->classification[i]) == 0) {
if (!do_eol)
continue;
retval = 1;
if (output_type == 0) {
puts(pkgname);
continue;
}
fprintf(output,
"Package %s has reached end-of-life (eol), "
"see %s/eol-packages\n", pkgname,
tnf_vulnerability_base);
continue;
}
retval = 1;
if (output_type == 0) {
puts(pkgname);
} else {
fprintf(output,
"Package %s has a %s vulnerability, see %s\n",
pkgname, pv->classification[i], pv->advisory[i]);
}
}
return retval;
}