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