pkg_osvf.c
1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2025 The FreeBSD Foundation 5 * 6 * Portions of this software were developed by 7 * Tuukka Pasanen <tuukka.pasanen@ilmi.fi> under sponsorship from 8 * the FreeBSD Foundation 9 */ 10 11 12 #include <ctype.h> 13 #include <sys/types.h> 14 #include <sys/mman.h> 15 #include <sys/stat.h> 16 #include <sys/time.h> 17 #include <stdio.h> 18 #include <errno.h> 19 #include <unistd.h> 20 #include <fcntl.h> 21 #include <time.h> 22 #include <xmalloc.h> 23 24 #include "private/pkg_osvf.h" 25 #include "pkghash.h" 26 27 /* 28 Open Source Vulnerability format: https://ossf.github.io/osv-schema/ 29 OSVF schema: https://github.com/ossf/osv-schema/blob/main/validation/schema.json 30 OSVF schema version: 1.7.0 31 */ 32 static const char osvf_schema_str[] = "{" 33 " \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"," 34 " \"$id\": \"https://raw.githubusercontent.com/ossf/osv-schema/main/validation/schema.json\"," 35 " \"title\": \"Open Source Vulnerability\"," 36 " \"description\": \"A schema for describing a vulnerability in an open source package. See also https://ossf.github.io/osv-schema/\"," 37 " \"type\": \"object\"," 38 " \"properties\": {" 39 " \"schema_version\": {" 40 " \"type\": \"string\"" 41 " }," 42 " \"id\": {" 43 " \"$ref\": \"#/$defs/prefix\"" 44 " }," 45 " \"modified\": {" 46 " \"$ref\": \"#/$defs/timestamp\"" 47 " }," 48 " \"published\": {" 49 " \"$ref\": \"#/$defs/timestamp\"" 50 " }," 51 " \"withdrawn\": {" 52 " \"$ref\": \"#/$defs/timestamp\"" 53 " }," 54 " \"aliases\": {" 55 " \"type\": [" 56 " \"array\"," 57 " \"null\"" 58 " ]," 59 " \"items\": {" 60 " \"type\": \"string\"" 61 " }" 62 " }," 63 " \"related\": {" 64 " \"type\": \"array\"," 65 " \"items\": {" 66 " \"type\": \"string\"" 67 " }" 68 " }," 69 " \"upstream\": {" 70 " \"type\": \"array\"," 71 " \"items\": {" 72 " \"type\": \"string\"" 73 " }" 74 " }," 75 " \"summary\": {" 76 " \"type\": \"string\"" 77 " }," 78 " \"details\": {" 79 " \"type\": \"string\"" 80 " }," 81 " \"severity\": {" 82 " \"$ref\": \"#/$defs/severity\"" 83 " }," 84 " \"affected\": {" 85 " \"type\": [" 86 " \"array\"," 87 " \"null\"" 88 " ]," 89 " \"items\": {" 90 " \"type\": \"object\"," 91 " \"properties\": {" 92 " \"package\": {" 93 " \"type\": \"object\"," 94 " \"properties\": {" 95 " \"ecosystem\": {" 96 " \"$ref\": \"#/$defs/ecosystemWithSuffix\"" 97 " }," 98 " \"name\": {" 99 " \"type\": \"string\"" 100 " }," 101 " \"purl\": {" 102 " \"type\": \"string\"" 103 " }" 104 " }," 105 " \"required\": [" 106 " \"ecosystem\"," 107 " \"name\"" 108 " ]" 109 " }," 110 " \"severity\": {" 111 " \"$ref\": \"#/$defs/severity\"" 112 " }," 113 " \"ranges\": {" 114 " \"type\": \"array\"," 115 " \"items\": {" 116 " \"type\": \"object\"," 117 " \"properties\": {" 118 " \"type\": {" 119 " \"type\": \"string\"," 120 " \"enum\": [" 121 " \"GIT\"," 122 " \"SEMVER\"," 123 " \"ECOSYSTEM\"" 124 " ]" 125 " }," 126 " \"repo\": {" 127 " \"type\": \"string\"" 128 " }," 129 " \"events\": {" 130 " \"title\": \"events must contain an introduced object and may contain fixed, last_affected or limit objects\"," 131 " \"type\": \"array\"," 132 " \"contains\": {" 133 " \"required\": [" 134 " \"introduced\"" 135 " ]" 136 " }," 137 " \"items\": {" 138 " \"type\": \"object\"," 139 " \"oneOf\": [" 140 " {" 141 " \"type\": \"object\"," 142 " \"properties\": {" 143 " \"introduced\": {" 144 " \"type\": \"string\"" 145 " }" 146 " }," 147 " \"required\": [" 148 " \"introduced\"" 149 " ]" 150 " }," 151 " {" 152 " \"type\": \"object\"," 153 " \"properties\": {" 154 " \"fixed\": {" 155 " \"type\": \"string\"" 156 " }" 157 " }," 158 " \"required\": [" 159 " \"fixed\"" 160 " ]" 161 " }," 162 " {" 163 " \"type\": \"object\"," 164 " \"properties\": {" 165 " \"last_affected\": {" 166 " \"type\": \"string\"" 167 " }" 168 " }," 169 " \"required\": [" 170 " \"last_affected\"" 171 " ]" 172 " }," 173 " {" 174 " \"type\": \"object\"," 175 " \"properties\": {" 176 " \"limit\": {" 177 " \"type\": \"string\"" 178 " }" 179 " }," 180 " \"required\": [" 181 " \"limit\"" 182 " ]" 183 " }" 184 " ]" 185 " }," 186 " \"minItems\": 1" 187 " }," 188 " \"database_specific\": {" 189 " \"type\": \"object\"" 190 " }" 191 " }," 192 " \"allOf\": [" 193 " {" 194 " \"title\": \"GIT ranges require a repo\"," 195 " \"if\": {" 196 " \"properties\": {" 197 " \"type\": {" 198 " \"const\": \"GIT\"" 199 " }" 200 " }" 201 " }," 202 " \"then\": {" 203 " \"required\": [" 204 " \"repo\"" 205 " ]" 206 " }" 207 " }," 208 " {" 209 " \"title\": \"last_affected and fixed events are mutually exclusive\"," 210 " \"if\": {" 211 " \"properties\": {" 212 " \"events\": {" 213 " \"contains\": {" 214 " \"required\": [" 215 " \"last_affected\"" 216 " ]" 217 " }" 218 " }" 219 " }" 220 " }," 221 " \"then\": {" 222 " \"not\": {" 223 " \"properties\": {" 224 " \"events\": {" 225 " \"contains\": {" 226 " \"required\": [" 227 " \"fixed\"" 228 " ]" 229 " }" 230 " }" 231 " }" 232 " }" 233 " }" 234 " }" 235 " ]," 236 " \"required\": [" 237 " \"type\"," 238 " \"events\"" 239 " ]" 240 " }" 241 " }," 242 " \"versions\": {" 243 " \"type\": \"array\"," 244 " \"items\": {" 245 " \"type\": \"string\"" 246 " }" 247 " }," 248 " \"ecosystem_specific\": {" 249 " \"type\": \"object\"" 250 " }," 251 " \"database_specific\": {" 252 " \"type\": \"object\"" 253 " }" 254 " }" 255 " }" 256 " }," 257 " \"references\": {" 258 " \"type\": [" 259 " \"array\"," 260 " \"null\"" 261 " ]," 262 " \"items\": {" 263 " \"type\": \"object\"," 264 " \"properties\": {" 265 " \"type\": {" 266 " \"type\": \"string\"," 267 " \"enum\": [" 268 " \"ADVISORY\"," 269 " \"ARTICLE\"," 270 " \"DETECTION\"," 271 " \"DISCUSSION\"," 272 " \"REPORT\"," 273 " \"FIX\"," 274 " \"INTRODUCED\"," 275 " \"GIT\"," 276 " \"PACKAGE\"," 277 " \"EVIDENCE\"," 278 " \"WEB\"" 279 " ]" 280 " }," 281 " \"url\": {" 282 " \"type\": \"string\"," 283 " \"format\": \"uri\"" 284 " }" 285 " }," 286 " \"required\": [" 287 " \"type\"," 288 " \"url\"" 289 " ]" 290 " }" 291 " }," 292 " \"credits\": {" 293 " \"type\": \"array\"," 294 " \"items\": {" 295 " \"type\": \"object\"," 296 " \"properties\": {" 297 " \"name\": {" 298 " \"type\": \"string\"" 299 " }," 300 " \"contact\": {" 301 " \"type\": \"array\"," 302 " \"items\": {" 303 " \"type\": \"string\"" 304 " }" 305 " }," 306 " \"type\": {" 307 " \"type\": \"string\"," 308 " \"enum\": [" 309 " \"FINDER\"," 310 " \"REPORTER\"," 311 " \"ANALYST\"," 312 " \"COORDINATOR\"," 313 " \"REMEDIATION_DEVELOPER\"," 314 " \"REMEDIATION_REVIEWER\"," 315 " \"REMEDIATION_VERIFIER\"," 316 " \"TOOL\"," 317 " \"SPONSOR\"," 318 " \"OTHER\"" 319 " ]" 320 " }" 321 " }," 322 " \"required\": [" 323 " \"name\"" 324 " ]" 325 " }" 326 " }," 327 " \"database_specific\": {" 328 " \"type\": \"object\"" 329 " }" 330 " }," 331 " \"required\": [" 332 " \"id\"," 333 " \"modified\"" 334 " ]," 335 " \"allOf\": [" 336 " {" 337 " \"if\": {" 338 " \"required\": [" 339 " \"severity\"" 340 " ]" 341 " }," 342 " \"then\": {" 343 " \"properties\": {" 344 " \"affected\": {" 345 " \"items\": {" 346 " \"properties\": {" 347 " \"severity\": {" 348 " \"type\": \"null\"" 349 " }" 350 " }" 351 " }" 352 " }" 353 " }" 354 " }" 355 " }" 356 " ]," 357 " \"$defs\": {" 358 " \"ecosystemName\": {" 359 " \"type\": \"string\"," 360 " \"title\": \"Currently supported ecosystems\"," 361 " \"description\": \"These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field\"," 362 " \"enum\": [" 363 " \"AlmaLinux\"," 364 " \"Alpine\"," 365 " \"Android\"," 366 " \"Bioconductor\"," 367 " \"Bitnami\"," 368 " \"Chainguard\"," 369 " \"ConanCenter\"," 370 " \"CRAN\"," 371 " \"crates.io\"," 372 " \"Debian\"," 373 " \"FreeBSD\"," 374 " \"GHC\"," 375 " \"GitHub Actions\"," 376 " \"Go\"," 377 " \"Hackage\"," 378 " \"Hex\"," 379 " \"Kubernetes\"," 380 " \"Linux\"," 381 " \"Mageia\"," 382 " \"Maven\"," 383 " \"MinimOS\"," 384 " \"npm\"," 385 " \"NuGet\"," 386 " \"openSUSE\"," 387 " \"OSS-Fuzz\"," 388 " \"Packagist\"," 389 " \"Photon OS\"," 390 " \"Pub\"," 391 " \"PyPI\"," 392 " \"Red Hat\"," 393 " \"Rocky Linux\"," 394 " \"RubyGems\"," 395 " \"SUSE\"," 396 " \"SwiftURL\"," 397 " \"Ubuntu\"," 398 " \"Wolfi\"" 399 " ]" 400 " }," 401 " \"ecosystemSuffix\": {" 402 " \"type\": \"string\"," 403 " \"pattern\": \":.+\"" 404 " }," 405 " \"ecosystemWithSuffix\": {" 406 " \"type\": \"string\"," 407 " \"title\": \"Currently supported ecosystems\"," 408 " \"description\": \"These ecosystems are also documented at https://ossf.github.io/osv-schema/#affectedpackage-field\"," 409 " \"pattern\": \"^(AlmaLinux|Alpine|Android|Bioconductor|Bitnami|Chainguard|ConanCenter|CRAN|crates\\.io|Debian|FreeBSD:ports|FreeBSD|GHC|GitHub Actions|Go|Hackage|Hex|Kubernetes|Linux|Mageia|Maven|MinimOS|npm|NuGet|openSUSE|OSS-Fuzz|Packagist|Photon OS|Pub|PyPI|Red Hat|Rocky Linux|RubyGems|SUSE|SwiftURL|Ubuntu|Wolfi|GIT)(:.+)?$\"" 410 " }," 411 " \"prefix\": {" 412 " \"type\": \"string\"," 413 " \"title\": \"Currently supported home database identifier prefixes\"," 414 " \"description\": \"These home databases are also documented at https://ossf.github.io/osv-schema/#id-modified-fields\"," 415 " \"pattern\": \"^(ASB-A|PUB-A|ALSA|ALBA|ALEA|BIT|CGA|CURL|CVE|DSA|DLA|ELA|FBSD|DTSA|GHSA|GO|GSD|HSEC|KUBE|LBSEC|LSN|MAL|MGASA|OSV|openSUSE-SU|PHSA|PSF|PYSEC|RHBA|RHEA|RHSA|RLSA|RXSA|RSEC|RUSTSEC|SUSE-[SRFO]U|UBUNTU|USN|V8)-\"" 416 " }," 417 " \"severity\": {" 418 " \"type\": [" 419 " \"array\"," 420 " \"null\"" 421 " ]," 422 " \"items\": {" 423 " \"type\": \"object\"," 424 " \"properties\": {" 425 " \"type\": {" 426 " \"type\": \"string\"," 427 " \"enum\": [" 428 " \"CVSS_V2\"," 429 " \"CVSS_V3\"," 430 " \"CVSS_V4\"," 431 " \"Ubuntu\"" 432 " ]" 433 " }," 434 " \"score\": {" 435 " \"type\": \"string\"" 436 " }" 437 " }," 438 " \"allOf\": [" 439 " {" 440 " \"if\": {" 441 " \"properties\": {" 442 " \"type\": {" 443 " \"const\": \"CVSS_V2\"" 444 " }" 445 " }" 446 " }," 447 " \"then\": {" 448 " \"properties\": {" 449 " \"score\": {" 450 " \"pattern\": \"^((AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))/)*(AV:[NAL]|AC:[LMH]|Au:[MSN]|[CIA]:[NPC]|E:(U|POC|F|H|ND)|RL:(OF|TF|W|U|ND)|RC:(UC|UR|C|ND)|CDP:(N|L|LM|MH|H|ND)|TD:(N|L|M|H|ND)|[CIA]R:(L|M|H|ND))$\"" 451 " }" 452 " }" 453 " }" 454 " }," 455 " {" 456 " \"if\": {" 457 " \"properties\": {" 458 " \"type\": {" 459 " \"const\": \"CVSS_V3\"" 460 " }" 461 " }" 462 " }," 463 " \"then\": {" 464 " \"properties\": {" 465 " \"score\": {" 466 " \"pattern\": \"^CVSS:3[.][01]/((AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])/)*(AV:[NALP]|AC:[LH]|PR:[NLH]|UI:[NR]|S:[UC]|[CIA]:[NLH]|E:[XUPFH]|RL:[XOTWU]|RC:[XURC]|[CIA]R:[XLMH]|MAV:[XNALP]|MAC:[XLH]|MPR:[XNLH]|MUI:[XNR]|MS:[XUC]|M[CIA]:[XNLH])$\"" 467 " }" 468 " }" 469 " }" 470 " }," 471 " {" 472 " \"if\": {" 473 " \"properties\": {" 474 " \"type\": {" 475 " \"const\": \"CVSS_V4\"" 476 " }" 477 " }" 478 " }," 479 " \"then\": {" 480 " \"properties\": {" 481 " \"score\": {" 482 " \"pattern\": \"^CVSS:4[.]0/AV:[NALP]/AC:[LH]/AT:[NP]/PR:[NLH]/UI:[NPA]/VC:[HLN]/VI:[HLN]/VA:[HLN]/SC:[HLN]/SI:[HLN]/SA:[HLN](/E:[XAPU])?(/CR:[XHML])?(/IR:[XHML])?(/AR:[XHML])?(/MAV:[XNALP])?(/MAC:[XLH])?(/MAT:[XNP])?(/MPR:[XNLH])?(/MUI:[XNPA])?(/MVC:[XNLH])?(/MVI:[XNLH])?(/MVA:[XNLH])?(/MSC:[XNLH])?(/MSI:[XNLHS])?(/MSA:[XNLHS])?(/S:[XNP])?(/AU:[XNY])?(/R:[XAUI])?(/V:[XDC])?(/RE:[XLMH])?(/U:(X|Clear|Green|Amber|Red))?$\"" 483 " }" 484 " }" 485 " }" 486 " }," 487 " {" 488 " \"if\": {" 489 " \"properties\": {" 490 " \"type\": {" 491 " \"const\": \"Ubuntu\"" 492 " }" 493 " }" 494 " }," 495 " \"then\": {" 496 " \"properties\": {" 497 " \"score\": {" 498 " \"enum\": [" 499 " \"negligible\"," 500 " \"low\"," 501 " \"medium\"," 502 " \"high\"," 503 " \"critical\"" 504 " ]" 505 " }" 506 " }" 507 " }" 508 " }" 509 " ]," 510 " \"required\": [" 511 " \"type\"," 512 " \"score\"" 513 " ]" 514 " }" 515 " }," 516 " \"timestamp\": {" 517 " \"type\": \"string\"," 518 " \"format\": \"date-time\"," 519 " \"pattern\": \"[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]+)?Z\"" 520 " }" 521 " }," 522 " \"additionalProperties\": false" 523 "}"; 524 525 struct pkg_osvf_hash 526 { 527 unsigned int value; 528 char *name; 529 }; 530 531 static struct pkg_osvf_hash references_global[] = 532 { 533 {OSVF_REFERENCE_ADVISORY, "ADVISORY"}, 534 {OSVF_REFERENCE_ARTICLE, "ARTICLE"}, 535 {OSVF_REFERENCE_DETECTION, "DETECTION"}, 536 {OSVF_REFERENCE_DISCUSSION, "DISCUSSION"}, 537 {OSVF_REFERENCE_REPORT, "REPORT"}, 538 {OSVF_REFERENCE_FIX, "FIX"}, 539 {OSVF_REFERENCE_INTRODUCED, "INTRODUCED"}, 540 {OSVF_REFERENCE_PACKAGE, "PACKAGE"}, 541 {OSVF_REFERENCE_EVIDENCE, "EVIDENCE"}, 542 {OSVF_REFERENCE_WEB, "WEB"}, 543 {OSVF_REFERENCE_UNKNOWN, NULL} 544 }; 545 546 static struct pkg_osvf_hash event_global[] = 547 { 548 {OSVF_EVENT_VERSION_SEMVER, "SEMVER"}, 549 {OSVF_EVENT_VERSION_ECOSYSTEM, "ECOSYSTEM"}, 550 {OSVF_EVENT_VERSION_GIT, "GIT"}, 551 {OSVF_EVENT_VERSION_UNKNOWN, NULL} 552 }; 553 554 static ucl_object_t * 555 create_schema_obj(void) 556 { 557 struct ucl_parser *uclparser; 558 ucl_object_t *obj = NULL; 559 560 uclparser = ucl_parser_new (0); 561 ucl_parser_add_string(uclparser, osvf_schema_str, 0); 562 if (ucl_parser_get_error(uclparser) != NULL) 563 { 564 pkg_emit_error("Error occurred: %s\n", ucl_parser_get_error (uclparser)); 565 ucl_parser_free (uclparser); 566 return (NULL); 567 } 568 569 obj = ucl_parser_get_object(uclparser); 570 ucl_parser_free(uclparser); 571 return obj; 572 } 573 574 575 ucl_object_t * 576 pkg_osvf_open(const char *location) 577 { 578 struct ucl_parser *uclparser; 579 ucl_object_t *obj = NULL; 580 int fd; 581 ucl_object_t *schema = NULL; 582 struct ucl_schema_error err; 583 584 fd = open(location, O_RDONLY); 585 if (fd == -1) 586 { 587 pkg_emit_error("Unable to open OSVF file: %s", location); 588 return (NULL); 589 } 590 591 uclparser = ucl_parser_new(0); 592 if (!ucl_parser_add_fd(uclparser, fd)) 593 { 594 pkg_emit_error("Error parsing UCL file '%s': %s'", 595 location, ucl_parser_get_error(uclparser)); 596 ucl_parser_free(uclparser); 597 close(fd); 598 return (NULL); 599 } 600 close(fd); 601 602 obj = ucl_parser_get_object(uclparser); 603 ucl_parser_free(uclparser); 604 605 if (obj == NULL) 606 { 607 pkg_emit_error("UCL definition %s cannot be validated: %s", 608 location, err.msg); 609 return (NULL); 610 } 611 612 schema = create_schema_obj(); 613 614 if (schema == NULL) 615 { 616 return (NULL); 617 } 618 619 if (!ucl_object_validate(schema, obj, &err)) 620 { 621 pkg_emit_error("UCL definition %s cannot be validated: %s", 622 location, err.msg); 623 ucl_object_unref(schema); 624 ucl_object_unref(obj); 625 return (NULL); 626 } 627 628 ucl_object_unref(schema); 629 630 return (obj); 631 } 632 633 static struct pkg_audit_entry * 634 pkg_osvf_new_entry(void) 635 { 636 struct pkg_audit_entry *entry = xcalloc(1, sizeof(struct pkg_audit_entry)); 637 638 entry->packages = xcalloc(1, sizeof(struct pkg_audit_package)); 639 entry->names = xcalloc(1, sizeof(struct pkg_audit_pkgname)); 640 entry->versions = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 641 entry->cve = xcalloc(1, sizeof(struct pkg_audit_cve)); 642 entry->references = xcalloc(1, sizeof(struct pkg_audit_reference)); 643 644 return entry; 645 } 646 647 static void 648 pkg_osvf_free_pkgname(struct pkg_audit_pkgname *pkgname) 649 { 650 if(!pkgname) 651 { 652 return; 653 } 654 655 free(pkgname->pkgname); 656 pkgname->pkgname = NULL; 657 658 pkg_osvf_free_pkgname(pkgname->next); 659 pkgname->next = NULL; 660 661 free(pkgname); 662 } 663 664 static void 665 pkg_osvf_free_version(struct pkg_audit_version *ver) 666 { 667 if(!ver) 668 { 669 return; 670 } 671 672 free(ver->version); 673 ver->version = NULL; 674 675 free(ver); 676 } 677 678 static void 679 pkg_osvf_free_range(struct pkg_audit_versions_range *range) 680 { 681 free(range); 682 } 683 684 void 685 pkg_osvf_free_ecosystem(struct pkg_audit_ecosystem *ecosystem) 686 { 687 if(!ecosystem) 688 { 689 return; 690 } 691 692 free(ecosystem->original); 693 ecosystem->original = NULL; 694 695 free(ecosystem->name); 696 ecosystem->name = NULL; 697 698 699 ucl_object_unref(ecosystem->params); 700 ecosystem->params = NULL; 701 702 free(ecosystem); 703 } 704 705 static void 706 pkg_osvf_free_package(struct pkg_audit_package *package) 707 { 708 if(!package) 709 { 710 return; 711 } 712 713 pkg_osvf_free_pkgname(package->names); 714 package->names = NULL; 715 716 pkg_osvf_free_range(package->versions); 717 package->versions = NULL; 718 719 pkg_osvf_free_ecosystem(package->ecosystem); 720 package->ecosystem = NULL; 721 722 pkg_osvf_free_package(package->next); 723 package->next = NULL; 724 725 free(package); 726 } 727 728 static void 729 pkg_osvf_free_cve(struct pkg_audit_cve *cve) 730 { 731 if(!cve) 732 { 733 return; 734 } 735 736 free(cve->cvename); 737 cve->cvename = NULL; 738 739 pkg_osvf_free_cve(cve->next); 740 cve->next = NULL; 741 742 free(cve); 743 } 744 745 static void 746 pkg_osvf_free_reference(struct pkg_audit_reference *reference) 747 { 748 if(!reference) 749 { 750 return; 751 } 752 753 free(reference->url); 754 reference->url = NULL; 755 756 pkg_osvf_free_reference(reference->next); 757 reference->next = NULL; 758 759 free(reference); 760 } 761 762 void 763 pkg_osvf_free_entry(struct pkg_audit_entry *entry) 764 { 765 struct pkg_audit_versions_range *versions = NULL; 766 struct pkg_audit_versions_range *next_versions = NULL; 767 768 struct pkg_audit_pkgname *names = NULL; 769 struct pkg_audit_pkgname *next_names = NULL; 770 771 if(!entry) 772 { 773 return; 774 } 775 776 versions = entry->versions; 777 names = entry->names; 778 779 if(entry->id) 780 { 781 free(entry->id); 782 entry->id = NULL; 783 } 784 if(entry->desc) 785 { 786 free(entry->desc); 787 entry->desc = NULL; 788 } 789 790 while(versions) 791 { 792 next_versions = versions->next; 793 free(versions); 794 versions = next_versions; 795 } 796 797 while(names) 798 { 799 next_names = names->next; 800 free(names); 801 names = next_names; 802 } 803 804 pkg_osvf_free_package(entry->packages); 805 entry->packages = NULL; 806 807 pkg_osvf_free_cve(entry->cve); 808 entry->cve = NULL; 809 810 pkg_osvf_free_reference(entry->references); 811 entry->references = NULL; 812 813 free(entry); 814 } 815 816 static struct pkghash * 817 pkg_osvf_create_seek_hash(struct pkg_osvf_hash *osvf_ptr) 818 { 819 struct pkghash *hash_table = pkghash_new(); 820 821 while(osvf_ptr->name) 822 { 823 pkghash_add(hash_table, osvf_ptr->name, osvf_ptr, NULL); 824 osvf_ptr ++; 825 } 826 827 return hash_table; 828 } 829 830 static unsigned int 831 pkg_osvf_get_hash(const char *key, struct pkg_osvf_hash *global, unsigned int unknow) 832 { 833 struct pkghash *hash = NULL; 834 pkghash_entry *entry = NULL; 835 struct pkg_osvf_hash *rtn_struct = NULL; 836 unsigned int rtn_value = unknow; 837 838 if(!key) 839 { 840 return rtn_value; 841 } 842 843 /* 844 * Create seek table with struct 845 * Make easier to seek 846 */ 847 hash = pkg_osvf_create_seek_hash(global); 848 849 entry = pkghash_get(hash, key); 850 851 /* 852 * If there was key then get it and 853 * free hash table as we don't need it anymore 854 */ 855 if(entry) 856 { 857 rtn_struct = (struct pkg_osvf_hash *) entry->value; 858 rtn_value = rtn_struct->value; 859 pkghash_destroy(hash); 860 } 861 862 return rtn_value; 863 } 864 865 866 struct pkg_audit_ecosystem * 867 pkg_osvf_get_ecosystem(const char *ecosystem) 868 { 869 char ecosystem_delimiter[] = ":"; 870 char *ecosystem_copy = NULL; 871 char *ecosystem_token = NULL; 872 struct pkg_audit_ecosystem *rtn_ecosystem = NULL; 873 874 if(!ecosystem) 875 { 876 return NULL; 877 } 878 879 ecosystem_copy = xstrdup(ecosystem); 880 ecosystem_token = strtok(ecosystem_copy, ecosystem_delimiter); 881 882 if(!ecosystem_token) 883 { 884 free(ecosystem_copy); 885 return NULL; 886 } 887 888 rtn_ecosystem = xcalloc(1, sizeof(struct pkg_audit_ecosystem)); 889 rtn_ecosystem->original = xstrdup(ecosystem); 890 rtn_ecosystem->name = xstrdup(ecosystem_token); 891 rtn_ecosystem->params = ucl_object_typed_new(UCL_ARRAY); 892 893 /* 894 * Parse other information out of Ecosystem tags like 895 * Alpine:v3.16 896 * FreeBSD:ports 897 * FreeBSD:kernel:14.3 898 * FreeBSD:src:14.3 899 * Mageia:9 900 * Maven:https://repo1.maven.org/maven2/ 901 * Photon OS:3.0 902 * Red Hat:rhel_aus:8.4::appstream 903 * Ubuntu:22.04:LTS 904 * Ubuntu:Pro:18.04:LTS 905 * to array for more processing further 906 */ 907 while(ecosystem_token) 908 { 909 ecosystem_token = strtok(NULL, ecosystem_delimiter); 910 if(ecosystem_token) 911 { 912 ucl_array_append(rtn_ecosystem->params, ucl_object_fromstring(ecosystem_token)); 913 } 914 } 915 916 free(ecosystem_copy); 917 ecosystem_copy = NULL; 918 919 return rtn_ecosystem; 920 } 921 922 unsigned int 923 pkg_osvf_get_reference(const char *reference_type) 924 { 925 return pkg_osvf_get_hash(reference_type, references_global, OSVF_REFERENCE_UNKNOWN); 926 } 927 928 unsigned int 929 pkg_osvf_get_event(const char *event_type) 930 { 931 return pkg_osvf_get_hash(event_type, event_global, OSVF_EVENT_VERSION_UNKNOWN); 932 } 933 934 935 static const char * 936 pkg_osvf_ucl_string(const ucl_object_t *obj, const char *key) 937 { 938 const ucl_object_t *key_obj = ucl_object_find_key(obj, key); 939 940 if(key_obj && ucl_object_type(key_obj) == UCL_STRING) 941 { 942 return ucl_object_tostring(key_obj); 943 } 944 945 return ""; 946 } 947 948 static void 949 pkg_osvf_parse_package(struct pkg_audit_package *package, const ucl_object_t *package_obj) 950 { 951 /* Parses package structure: 952 "package": { 953 "ecosystem": "FreeBSD:ports", 954 "name": "packagename" 955 }, 956 */ 957 958 if(!package_obj || ucl_object_type(package_obj) != UCL_OBJECT) 959 { 960 return; 961 } 962 963 package->names->pkgname = xstrdup(pkg_osvf_ucl_string(package_obj, "name")); 964 package->ecosystem = pkg_osvf_get_ecosystem(pkg_osvf_ucl_string(package_obj, "ecosystem")); 965 } 966 967 static void 968 pkg_osvf_parse_events(struct pkg_audit_versions_range *range, const ucl_object_t *event_array, const char *type) 969 { 970 ucl_object_iter_t it = NULL; 971 const ucl_object_t *cur = NULL; 972 973 if(!event_array || ucl_object_type(event_array) != UCL_ARRAY) 974 { 975 return; 976 } 977 978 if(!type) 979 { 980 return; 981 } 982 983 range->type = pkg_osvf_get_event(type); 984 985 /* Parses package structure from events: 986 { 987 "fixed|introduced": "1.0.0" 988 } 989 */ 990 991 while ((cur = ucl_iterate_object(event_array, &it, true))) 992 { 993 if(ucl_object_find_key(cur, "fixed")) 994 { 995 range->v2.version = xstrdup(pkg_osvf_ucl_string(cur, "fixed")); 996 printf("Fixed: %s\n", range->v2.version); 997 range->v2.type = OSVF_EVENT_FIXED; 998 } 999 else if(ucl_object_find_key(cur, "introduced")) 1000 { 1001 range->v1.version = xstrdup(pkg_osvf_ucl_string(cur, "introduced")); 1002 printf("Intro: %s\n", range->v1.version); 1003 range->v1.type = OSVF_EVENT_INTRODUCED; 1004 } 1005 } 1006 } 1007 1008 1009 static void 1010 pkg_osvf_parse_ranges(struct pkg_audit_versions_range *range, const ucl_object_t *range_array) 1011 { 1012 ucl_object_iter_t it = NULL; 1013 const ucl_object_t *cur = NULL; 1014 struct pkg_audit_versions_range *next_range = NULL; 1015 const ucl_object_t *sub_obj = NULL; 1016 bool is_first = true; 1017 1018 if(!range_array || ucl_object_type(range_array) != UCL_ARRAY) 1019 { 1020 return; 1021 } 1022 1023 /* Parses events structure 1024 [ 1025 "type": "SEMVER", 1026 "events": [ 1027 { 1028 "fixed": "1.0.0" 1029 }, 1030 { 1031 "introduced": "0.0.1" 1032 }, 1033 ] 1034 */ 1035 1036 while ((cur = ucl_iterate_object(range_array, &it, true))) 1037 { 1038 if(is_first == false) 1039 { 1040 next_range = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 1041 range->next = next_range; 1042 range = next_range; 1043 } 1044 1045 sub_obj = ucl_object_find_key(cur, "events"); 1046 1047 if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY) 1048 { 1049 pkg_osvf_parse_events(range, ucl_object_find_key(cur, "events"), pkg_osvf_ucl_string(cur, "type")); 1050 } 1051 1052 is_first = false; 1053 } 1054 } 1055 1056 static void 1057 pkg_osvf_parse_reference(struct pkg_audit_reference *ref, const ucl_object_t *ref_obj) 1058 { 1059 if(!ref_obj || ucl_object_type(ref_obj) != UCL_OBJECT) 1060 { 1061 return; 1062 } 1063 1064 /* 1065 Parses refrence to struct 1066 { 1067 "type": "ADVISORY", 1068 "url": "https://www.freebsd.org/" 1069 } 1070 */ 1071 ref->url = xstrdup(pkg_osvf_ucl_string(ref_obj, "url")); 1072 ref->type = pkg_osvf_get_reference(pkg_osvf_ucl_string(ref_obj, "type")); 1073 } 1074 1075 static void 1076 pkg_osvf_parse_references(struct pkg_audit_entry *entry, const ucl_object_t *ref_obj) 1077 { 1078 ucl_object_iter_t it = NULL; 1079 const ucl_object_t *cur = NULL; 1080 bool is_first = true; 1081 struct pkg_audit_reference *reference = entry->references; 1082 struct pkg_audit_reference *next_package = NULL; 1083 1084 1085 if(!ref_obj || ucl_object_type(ref_obj) != UCL_ARRAY) 1086 { 1087 return; 1088 } 1089 1090 /* 1091 Parses refereces array to linked list 1092 "references": [ 1093 { 1094 "type": "ADVISORY", 1095 "url": "https://www.freebsd.org/" 1096 } 1097 ] 1098 */ 1099 1100 while ((cur = ucl_iterate_object(ref_obj, &it, true))) 1101 { 1102 if(is_first == false) 1103 { 1104 next_package = xcalloc(1, sizeof(struct pkg_audit_reference)); 1105 reference->next = next_package; 1106 reference = next_package; 1107 } 1108 1109 if(ucl_object_type(cur) == UCL_OBJECT) 1110 { 1111 pkg_osvf_parse_reference(reference, cur); 1112 } 1113 1114 is_first = false; 1115 } 1116 1117 } 1118 1119 static void 1120 pkg_osvf_parse_affected(struct pkg_audit_entry *entry, const ucl_object_t *aff_obj) 1121 { 1122 ucl_object_iter_t it = NULL; 1123 const ucl_object_t *cur = NULL; 1124 const ucl_object_t *array_obj = NULL; 1125 bool is_first = true; 1126 struct pkg_audit_package *package = entry->packages; 1127 struct pkg_audit_package *next_package = NULL; 1128 1129 if(!aff_obj || ucl_object_type(aff_obj) != UCL_ARRAY) 1130 { 1131 return; 1132 } 1133 1134 /* Parse affected to correct structs 1135 "affected": [ 1136 { 1137 "package": { 1138 "ecosystem": "FreeBSD:ports", 1139 "name": "osvf-test-package10" 1140 }, 1141 "ranges": [ 1142 { 1143 "type": "SEMVER", 1144 "events": [ 1145 { 1146 "fixed": "1.0.0" 1147 }, 1148 { 1149 "introduced": "0.0.1" 1150 }, 1151 ] 1152 } 1153 ] 1154 } 1155 ] 1156 */ 1157 while ((cur = ucl_iterate_object(aff_obj, &it, true))) 1158 { 1159 if(is_first == false) 1160 { 1161 next_package = xcalloc(1, sizeof(struct pkg_audit_package)); 1162 package->next = next_package; 1163 package = next_package; 1164 } 1165 1166 array_obj = ucl_object_find_key(cur, "package"); 1167 1168 if(array_obj && ucl_object_type(aff_obj) == UCL_ARRAY) 1169 { 1170 package->names = xcalloc(1, sizeof(struct pkg_audit_pkgname)); 1171 pkg_osvf_parse_package(package, array_obj); 1172 } 1173 1174 array_obj = ucl_object_find_key(cur, "ranges"); 1175 1176 if(array_obj && ucl_object_type(array_obj) == UCL_ARRAY) 1177 { 1178 package->versions = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 1179 pkg_osvf_parse_ranges(package->versions, array_obj); 1180 } 1181 1182 is_first = false; 1183 } 1184 } 1185 1186 static void 1187 pkg_osvf_append_version_range(struct pkg_audit_versions_range *to, struct pkg_audit_versions_range *from) 1188 { 1189 struct pkg_audit_versions_range *ptr_from = from; 1190 struct pkg_audit_versions_range *ptr_to = to; 1191 1192 to->v1.type = from->v1.type; 1193 to->v1.version = from->v1.version; 1194 to->v2.type = from->v2.type; 1195 to->v2.version = from->v2.version; 1196 to->type = from->type; 1197 1198 while(ptr_from->next) 1199 { 1200 ptr_to->next = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 1201 1202 ptr_to = ptr_to->next; 1203 ptr_from = ptr_from->next; 1204 1205 ptr_to->v1.type = ptr_from->v1.type; 1206 ptr_to->v1.version = ptr_from->v1.version; 1207 ptr_to->v2.type = ptr_from->v2.type; 1208 ptr_to->v2.version = ptr_from->v2.version; 1209 ptr_to->type = ptr_from->type; 1210 } 1211 } 1212 1213 static void 1214 pkg_osvf_print_version_type(struct pkg_audit_versions_range *versions) 1215 { 1216 if(!versions) 1217 { 1218 return; 1219 } 1220 1221 printf("\t\tVersion type: "); 1222 switch(versions->type) 1223 { 1224 case OSVF_EVENT_VERSION_UNKNOWN: 1225 printf("UNKNOWN\n"); 1226 break; 1227 case OSVF_EVENT_VERSION_SEMVER: 1228 printf("Sematic Version 2.0\n"); 1229 break; 1230 case OSVF_EVENT_VERSION_ECOSYSTEM: 1231 printf("Ecosystem\n"); 1232 break; 1233 case OSVF_EVENT_VERSION_GIT: 1234 printf("Git hash\n"); 1235 break; 1236 } 1237 } 1238 1239 static void 1240 pkg_osvf_print_version(struct pkg_audit_version *version) 1241 { 1242 if(!version) 1243 { 1244 return; 1245 } 1246 1247 switch(version->type) 1248 { 1249 case OSVF_EVENT_UNKNOWN: 1250 printf("\t\tUnknown type: "); 1251 break; 1252 case OSVF_EVENT_INTRODUCED: 1253 printf("\t\tIntroduced: "); 1254 break; 1255 case OSVF_EVENT_FIXED: 1256 printf("\t\tFixed: "); 1257 break; 1258 case OSVF_EVENT_LAST_AFFECTED: 1259 printf("\t\tAffected: "); 1260 break; 1261 case OSVF_EVENT_LIMIT: 1262 printf("\t\tLimit: "); 1263 break; 1264 } 1265 1266 printf("%s\n", version->version); 1267 } 1268 1269 static void 1270 pkg_osvf_print_ecosystem(struct pkg_audit_ecosystem *ecosystem) 1271 { 1272 ucl_object_iter_t it = NULL; 1273 const ucl_object_t *cur = NULL; 1274 int loc = 0; 1275 1276 if(!ecosystem) 1277 { 1278 return; 1279 } 1280 1281 printf("\t\tEcosystem: "); 1282 1283 if(ecosystem->name) 1284 { 1285 printf("%s (", ecosystem->name); 1286 } 1287 1288 while ((cur = ucl_iterate_object(ecosystem->params, &it, true))) 1289 { 1290 if(loc) 1291 { 1292 printf(","); 1293 } 1294 1295 if(ucl_object_type(cur) == UCL_STRING) 1296 { 1297 printf("%s", ucl_object_tostring(cur)); 1298 } 1299 1300 loc ++; 1301 } 1302 1303 printf(")\n"); 1304 } 1305 1306 void 1307 pkg_osvf_print_entry(struct pkg_audit_entry *entry) 1308 { 1309 char buf[255]; 1310 struct pkg_audit_package *packages = NULL; 1311 struct pkg_audit_versions_range *versions = NULL; 1312 struct pkg_audit_reference *references; 1313 struct pkg_audit_pkgname *names = NULL; 1314 bool is_first = true; 1315 1316 if(!entry) 1317 { 1318 return; 1319 } 1320 1321 names = entry->names; 1322 1323 printf("OSVF Vulnerability information:\n"); 1324 printf("\tPackage name: %s\n", entry->pkgname); 1325 printf("\tPackage names: "); 1326 while(names) 1327 { 1328 if(is_first == false) 1329 { 1330 printf(", "); 1331 } 1332 printf("%s", names->pkgname); 1333 names = names->next; 1334 is_first = false; 1335 } 1336 1337 printf("\n"); 1338 1339 printf("\tPackage id: %s\n", entry->id); 1340 printf("\tPackage description: %s\n", entry->desc); 1341 printf("\tPackage url: %s\n", entry->url); 1342 1343 strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->discovery); 1344 printf("\tEntry discovered: %s\n", buf); 1345 1346 strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->published); 1347 printf("\tEntry published: %s\n", buf); 1348 1349 strftime(buf, sizeof(buf), "%d %b %Y %H:%M", &entry->modified); 1350 printf("\tEntry modified: %s\n", buf); 1351 1352 printf("Vulnerable versions:\n"); 1353 1354 versions = entry->versions; 1355 1356 while(versions) 1357 { 1358 pkg_osvf_print_version_type(versions); 1359 pkg_osvf_print_version(&versions->v1); 1360 pkg_osvf_print_version(&versions->v2); 1361 versions = versions->next; 1362 } 1363 1364 printf("Vulnerable packages:\n"); 1365 1366 packages = entry->packages; 1367 1368 while(packages) 1369 { 1370 printf("\tPackage name: %s\n", packages->names->pkgname); 1371 pkg_osvf_print_ecosystem(packages->ecosystem); 1372 versions = packages->versions; 1373 1374 while(versions) 1375 { 1376 pkg_osvf_print_version_type(versions); 1377 pkg_osvf_print_version(&versions->v1); 1378 pkg_osvf_print_version(&versions->v2); 1379 versions = versions->next; 1380 } 1381 1382 packages = packages->next; 1383 } 1384 1385 printf("Vulnerability references:\n"); 1386 1387 references = entry->references; 1388 1389 while(references) 1390 { 1391 switch(references->type) 1392 { 1393 case OSVF_REFERENCE_UNKNOWN: 1394 printf("\tUNKNOWN: %s\n", references->url); 1395 break; 1396 case OSVF_REFERENCE_ADVISORY: 1397 printf("\tADVISORY: %s\n", references->url); 1398 break; 1399 case OSVF_REFERENCE_ARTICLE: 1400 printf("\tARTICLE: %s\n", references->url); 1401 break; 1402 case OSVF_REFERENCE_DETECTION: 1403 printf("\tDETECTION: %s\n", references->url); 1404 break; 1405 case OSVF_REFERENCE_DISCUSSION: 1406 printf("\tDISCUSSIONn: %s\n", references->url); 1407 break; 1408 case OSVF_REFERENCE_REPORT: 1409 printf("\tREPORT: %s\n", references->url); 1410 break; 1411 case OSVF_REFERENCE_FIX: 1412 printf("\tFIX: %s\n", references->url); 1413 break; 1414 case OSVF_REFERENCE_INTRODUCED: 1415 printf("\tINTRODUCED: %s\n", references->url); 1416 break; 1417 case OSVF_REFERENCE_PACKAGE: 1418 printf("\tPACKAGE: %s\n", references->url); 1419 break; 1420 case OSVF_REFERENCE_EVIDENCE: 1421 printf("\tEVIDENCE: %s\n", references->url); 1422 break; 1423 case OSVF_REFERENCE_WEB: 1424 printf("\tWEB: %s\n", references->url); 1425 break; 1426 } 1427 references = references->next; 1428 } 1429 } 1430 1431 struct pkg_audit_entry * 1432 pkg_osvf_create_entry(ucl_object_t *osvf_obj) 1433 { 1434 struct pkg_audit_entry *entry = NULL; 1435 struct pkg_audit_package *packages = NULL; 1436 struct pkg_audit_pkgname *names = NULL; 1437 struct pkg_audit_versions_range *versions = NULL; 1438 const ucl_object_t *sub_obj = NULL; 1439 /* Date format is in RFC3339 */ 1440 const char *date_time_str = "%Y-%m-%dT%H:%M:%SZ"; 1441 1442 entry = pkg_osvf_new_entry(); 1443 1444 /* We are probably out of memory or JSON does not exist */ 1445 if(osvf_obj == NULL || entry == NULL) 1446 { 1447 return NULL; 1448 } 1449 1450 memset(&entry->modified, 0, sizeof(struct tm)); 1451 memset(&entry->published, 0, sizeof(struct tm)); 1452 memset(&entry->discovery, 0, sizeof(struct tm)); 1453 1454 /* Data has been validated on load so we can assume 1455 there is all needed information */ 1456 1457 entry->id = xstrdup(pkg_osvf_ucl_string(osvf_obj, "id")); 1458 entry->desc = xstrdup(pkg_osvf_ucl_string(osvf_obj, "summary")); 1459 1460 sub_obj = ucl_object_find_key(osvf_obj, "affected"); 1461 1462 if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY) 1463 { 1464 pkg_osvf_parse_affected(entry, ucl_object_find_key(osvf_obj, "affected")); 1465 } 1466 else 1467 { 1468 return NULL; 1469 } 1470 1471 sub_obj = ucl_object_find_key(osvf_obj, "references"); 1472 1473 if(sub_obj && ucl_object_type(sub_obj) == UCL_ARRAY) 1474 { 1475 pkg_osvf_parse_references(entry, ucl_object_find_key(osvf_obj, "references")); 1476 } 1477 else 1478 { 1479 return NULL; 1480 } 1481 1482 entry->url = entry->references->url; 1483 1484 packages = entry->packages; 1485 names = entry->names; 1486 versions = entry->versions; 1487 1488 names->pkgname = packages->names->pkgname; 1489 pkg_osvf_append_version_range(versions, packages->versions); 1490 1491 while(packages->next) 1492 { 1493 packages = packages->next; 1494 names->next = xcalloc(1, sizeof(struct pkg_audit_pkgname)); 1495 names = names->next; 1496 names->pkgname = packages->names->pkgname; 1497 versions->next = xcalloc(1, sizeof(struct pkg_audit_versions_range)); 1498 versions = versions->next; 1499 pkg_osvf_append_version_range(versions, packages->versions); 1500 } 1501 1502 entry->pkgname = entry->names->pkgname; 1503 1504 if(ucl_object_find_key(osvf_obj, "modified")) 1505 { 1506 strptime(ucl_object_tostring(ucl_object_find_key(osvf_obj, "modified")), date_time_str, &entry->modified); 1507 } 1508 1509 if(ucl_object_find_key(osvf_obj, "published")) 1510 { 1511 strptime(ucl_object_tostring(ucl_object_find_key(osvf_obj, "published")), date_time_str, &entry->published); 1512 } 1513 1514 if(ucl_object_find_key(osvf_obj, "database_specific")) 1515 { 1516 sub_obj = ucl_object_find_key(osvf_obj, "database_specific"); 1517 if(ucl_object_find_key(sub_obj, "discovery")) 1518 { 1519 strptime(ucl_object_tostring(ucl_object_find_key(sub_obj, "discovery")), date_time_str, &entry->discovery); 1520 } 1521 } 1522 1523 return entry; 1524 }