tar_extractor.c (23254B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/tar_extractor.c 22 * @brief plugin to support tar files 23 * @author Christian Grothoff 24 * 25 * There is already an `archive' plugin wrapping libarchive, but it is 26 * optional and reports what is *in* the archive rather than who made 27 * it. This one is unconditional, reads nothing but the 512-byte member 28 * headers, and concentrates on the ownership fields, which leak the 29 * usernames, group names and numeric ids of the machine the archive was 30 * created on -- exactly the provenance a first pass wants. 31 * 32 * References: POSIX.1-1988 ustar, GNU tar's extensions and POSIX.1-2001 33 * pax extended headers. 34 */ 35 #include "platform.h" 36 #include "extractor.h" 37 #include "forensics.h" 38 39 /** 40 * Size of a tar header and of the blocks the archive is padded to. 41 */ 42 #define TAR_BLOCK 512 43 44 /** 45 * Largest number of member headers we will walk. Each one costs a 46 * seek and a 512-byte read, so this bounds us at 128 KiB of reads even 47 * for an archive with a million members; we report a characterisation, 48 * not an inventory. 49 */ 50 #define TAR_MAX_MEMBERS 256 51 52 /** 53 * How many *distinct* values of a repeating attribute (owner, group, 54 * mode) we remember and report. Archives are made by one or two users; 55 * more than a handful means the value is not identifying anyway. 56 */ 57 #define TAR_MAX_DISTINCT 16 58 59 /** 60 * Longest GNU long name (typeflag 'L'/'K') payload we will read. The 61 * field it replaces is 100 bytes, so anything past this is a path no 62 * filesystem would accept. 63 */ 64 #define TAR_MAX_LONGNAME 1024 65 66 /** 67 * Largest pax extended header payload we will read and parse. 68 */ 69 #define TAR_MAX_PAX 4096 70 71 72 /** 73 * What the ownership fields of the archive looked like, accumulated 74 * over the members we walked. 75 */ 76 struct tar_state 77 { 78 /** 79 * Distinct owner user names, NUL-terminated. 80 */ 81 char unames[TAR_MAX_DISTINCT][33]; 82 83 /** 84 * Distinct owner group names, NUL-terminated. 85 */ 86 char gnames[TAR_MAX_DISTINCT][33]; 87 88 /** 89 * Distinct numeric user ids. 90 */ 91 uint64_t uids[TAR_MAX_DISTINCT]; 92 93 /** 94 * Distinct numeric group ids. 95 */ 96 uint64_t gids[TAR_MAX_DISTINCT]; 97 98 /** 99 * Distinct permission bits. 100 */ 101 uint64_t modes[TAR_MAX_DISTINCT]; 102 103 /** 104 * Number of entries used in @e unames. 105 */ 106 unsigned int n_unames; 107 108 /** 109 * Number of entries used in @e gnames. 110 */ 111 unsigned int n_gnames; 112 113 /** 114 * Number of entries used in @e uids. 115 */ 116 unsigned int n_uids; 117 118 /** 119 * Number of entries used in @e gids. 120 */ 121 unsigned int n_gids; 122 123 /** 124 * Number of entries used in @e modes. 125 */ 126 unsigned int n_modes; 127 }; 128 129 130 /** 131 * Is @a block entirely zero? Two such blocks end an archive; we stop 132 * at the first, which is what every reader does in practice. 133 * 134 * @param block the 512 bytes to check 135 * @return 1 if all bytes are zero 136 */ 137 static int 138 is_zero_block (const unsigned char *block) 139 { 140 for (unsigned int i = 0; i < TAR_BLOCK; i++) 141 if (0 != block[i]) 142 return 0; 143 return 1; 144 } 145 146 147 /** 148 * Verify the header checksum of @a block. 149 * 150 * The checksum is the sum of all 512 header bytes with the checksum 151 * field itself read as spaces. Historical tars disagreed on whether 152 * the bytes are signed, so accept either reading. 153 * 154 * @param block the header block 155 * @return 1 if the stored checksum matches 156 */ 157 static int 158 checksum_ok (const unsigned char *block) 159 { 160 uint64_t stored; 161 uint32_t usum = 0; 162 int32_t ssum = 0; 163 164 if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[148], 165 8, 166 &stored)) 167 return 0; 168 for (unsigned int i = 0; i < TAR_BLOCK; i++) 169 { 170 if ( (i >= 148) && 171 (i < 156) ) 172 { 173 usum += (uint32_t) ' '; 174 ssum += (int32_t) ' '; 175 continue; 176 } 177 usum += (uint32_t) block[i]; 178 ssum += (int32_t) (signed char) block[i]; 179 } 180 return ( (stored == (uint64_t) usum) || 181 ( (ssum > 0) && 182 (stored == (uint64_t) ssum) ) ); 183 } 184 185 186 /** 187 * Read a numeric header field. 188 * 189 * Fields are normally NUL- or space-terminated octal. GNU tar escapes 190 * values that do not fit by setting the high bit of the first byte and 191 * storing the rest base-256, big-endian; we accept the positive form 192 * and refuse the rest rather than misparse it. 193 * 194 * @param field the field 195 * @param len width of the field 196 * @param[out] value where to store the result 197 * @return 1 on success, 0 if the field cannot be read 198 */ 199 static int 200 tar_number (const unsigned char *field, 201 size_t len, 202 uint64_t *value) 203 { 204 if (0 == len) 205 return 0; 206 if (0 != (field[0] & 0x80)) 207 { 208 uint64_t v = 0; 209 size_t i = 1; 210 211 if (0x80 != field[0]) 212 return 0; /* 0xff is the negative form; nothing else is defined */ 213 while ( (i < len) && 214 (0 == field[i]) ) 215 i++; 216 if (len - i > 8) 217 return 0; /* does not fit in 64 bits */ 218 for (; i < len; i++) 219 v = (v << 8) | field[i]; 220 *value = v; 221 return 1; 222 } 223 if (0 == EXTRACTOR_forensic_trim_ ((const char *) field, 224 len)) 225 { 226 /* an all-NUL field is how several tars spell "zero" */ 227 *value = 0; 228 return 1; 229 } 230 return EXTRACTOR_forensic_parse_octal_ ((const char *) field, 231 len, 232 value); 233 } 234 235 236 /** 237 * Could @a block be a header of a pre-POSIX (v7) tar? 238 * 239 * v7 has no magic at all, so the only defence against claiming every 240 * file in the world is to insist that the fields we do understand make 241 * sense. The caller has already verified the checksum. 242 * 243 * @param block the header block 244 * @return 1 if this looks like a v7 header 245 */ 246 static int 247 plausible_v7 (const unsigned char *block) 248 { 249 uint64_t v; 250 251 if ('\0' == block[0]) 252 return 0; /* a member with no name */ 253 for (unsigned int i = 0; i < 100; i++) 254 { 255 if ('\0' == block[i]) 256 break; 257 if ( (block[i] < 0x20) || 258 (0x7f == block[i]) ) 259 return 0; /* control characters in a file name */ 260 } 261 if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[100], 262 8, 263 &v)) 264 return 0; /* mode */ 265 if (! EXTRACTOR_forensic_parse_octal_ ((const char *) &block[136], 266 12, 267 &v)) 268 return 0; /* mtime */ 269 switch (block[156]) 270 { 271 case '\0': 272 case '0': 273 case '1': 274 case '2': 275 case '3': 276 case '4': 277 case '5': 278 case '6': 279 case '7': 280 return 1; 281 default: 282 return 0; 283 } 284 } 285 286 287 /** 288 * Remember @a value if it is new. 289 * 290 * @param seen array of values seen so far 291 * @param[in,out] count number of used slots in @a seen 292 * @param value the value to record 293 * @return 1 if @a value had not been seen before and was recorded 294 */ 295 static int 296 add_distinct_num (uint64_t *seen, 297 unsigned int *count, 298 uint64_t value) 299 { 300 for (unsigned int i = 0; i < *count; i++) 301 if (seen[i] == value) 302 return 0; 303 if (*count >= TAR_MAX_DISTINCT) 304 return 0; 305 seen[(*count)++] = value; 306 return 1; 307 } 308 309 310 /** 311 * Remember the fixed-width string @a value if it is new and not empty. 312 * 313 * @param seen array of 33-byte slots seen so far 314 * @param[in,out] count number of used slots in @a seen 315 * @param value the field, not necessarily NUL-terminated 316 * @param len width of the field, at most 32 317 * @return 1 if @a value had not been seen before and was recorded 318 */ 319 static int 320 add_distinct_str (char seen[][33], 321 unsigned int *count, 322 const char *value, 323 size_t len) 324 { 325 char tmp[33]; 326 327 if (len > sizeof (tmp) - 1) 328 len = sizeof (tmp) - 1; 329 memcpy (tmp, 330 value, 331 len); 332 tmp[len] = '\0'; 333 len = strlen (tmp); 334 len = EXTRACTOR_forensic_trim_ (tmp, 335 len); 336 tmp[len] = '\0'; 337 if (0 == len) 338 return 0; 339 for (unsigned int i = 0; i < *count; i++) 340 if (0 == strcmp (seen[i], 341 tmp)) 342 return 0; 343 if (*count >= TAR_MAX_DISTINCT) 344 return 0; 345 memcpy (seen[(*count)++], 346 tmp, 347 len + 1); 348 return 1; 349 } 350 351 352 /** 353 * Pull the overriding path and link target out of a pax extended 354 * header payload. 355 * 356 * The payload is a sequence of "<len> <key>=<value>\n" records where 357 * @a len counts the whole record including its own digits and the 358 * newline. We only care about the two keys that override a field we 359 * would otherwise report truncated; a zero or nonsensical length ends 360 * the parse so that a crafted payload cannot spin here. 361 * 362 * @param data the payload 363 * @param len number of bytes in @a data 364 * @param[out] path buffer of #TAR_MAX_LONGNAME + 1 bytes for "path" 365 * @param[out] linkpath buffer of #TAR_MAX_LONGNAME + 1 bytes for 366 * "linkpath" 367 */ 368 static void 369 parse_pax (const char *data, 370 size_t len, 371 char *path, 372 char *linkpath) 373 { 374 size_t pos = 0; 375 376 while (pos < len) 377 { 378 size_t reclen = 0; 379 size_t digits = 0; 380 size_t key; 381 size_t eq; 382 char *dst = NULL; 383 384 while ( (pos + digits < len) && 385 ('0' <= data[pos + digits]) && 386 ('9' >= data[pos + digits]) ) 387 { 388 if (reclen > TAR_MAX_PAX) 389 return; /* absurd record length */ 390 reclen = reclen * 10 + (size_t) (data[pos + digits] - '0'); 391 digits++; 392 } 393 if ( (0 == digits) || 394 (reclen <= digits + 1) || 395 (reclen > len - pos) ) 396 return; /* no forward progress possible */ 397 key = pos + digits + 1; /* skip the space after the length */ 398 for (eq = key; eq < pos + reclen; eq++) 399 if ('=' == data[eq]) 400 break; 401 if (eq < pos + reclen) 402 { 403 size_t klen = eq - key; 404 405 if ( (4 == klen) && 406 (0 == memcmp (&data[key], 407 "path", 408 4)) ) 409 dst = path; 410 if ( (8 == klen) && 411 (0 == memcmp (&data[key], 412 "linkpath", 413 8)) ) 414 dst = linkpath; 415 if (NULL != dst) 416 { 417 size_t vlen = pos + reclen - eq - 1; 418 419 if ( (vlen > 0) && 420 ('\n' == data[eq + vlen]) ) 421 vlen--; /* drop the record terminator */ 422 if (vlen > TAR_MAX_LONGNAME) 423 vlen = TAR_MAX_LONGNAME; 424 memcpy (dst, 425 &data[eq + 1], 426 vlen); 427 dst[vlen] = '\0'; 428 } 429 } 430 pos += reclen; 431 } 432 } 433 434 435 /** 436 * Main entry method for the tar extraction plugin. 437 * 438 * @param ec extraction context provided to the plugin 439 */ 440 void 441 EXTRACTOR_tar_extract_method (struct EXTRACTOR_ExtractContext *ec); 442 443 void 444 EXTRACTOR_tar_extract_method (struct EXTRACTOR_ExtractContext *ec) 445 { 446 unsigned char block[TAR_BLOCK]; 447 struct tar_state st; 448 char longname[TAR_MAX_LONGNAME + 1]; 449 char longlink[TAR_MAX_LONGNAME + 1]; 450 char name[TAR_BLOCK]; 451 const char *format; 452 uint64_t file_size; 453 uint64_t offset = 0; 454 uint64_t total_size = 0; 455 int64_t newest = 0; 456 unsigned int members = 0; 457 unsigned int names_emitted = 0; 458 unsigned int links_emitted = 0; 459 int have_ustar; 460 int truncated_walk = 0; 461 462 if (! EXTRACTOR_forensic_read_ (ec, 463 0, 464 block, 465 TAR_BLOCK)) 466 return; /* too short for even one header */ 467 have_ustar = (0 == memcmp (&block[257], 468 "ustar", 469 5)) && 470 ( ('\0' == block[262]) || 471 (' ' == block[262]) ); 472 if (have_ustar) 473 { 474 /* POSIX writes "ustar\0" followed by "00"; GNU writes "ustar \0" */ 475 format = (' ' == block[262]) ? "gnu" : "ustar"; 476 } 477 else 478 { 479 if (! checksum_ok (block)) 480 return; /* not a tar */ 481 if (! plausible_v7 (block)) 482 return; /* not a tar */ 483 format = "v7"; 484 } 485 if (0 != 486 EXTRACTOR_forensic_emit_ (ec, 487 "tar", 488 EXTRACTOR_METATYPE_MIMETYPE, 489 "%s", 490 "application/x-tar")) 491 return; 492 memset (&st, 493 0, 494 sizeof (st)); 495 longname[0] = '\0'; 496 longlink[0] = '\0'; 497 file_size = ec->get_size (ec->cls); 498 while (members < TAR_MAX_MEMBERS) 499 { 500 uint64_t size; 501 uint64_t mode; 502 uint64_t num; 503 unsigned char typeflag; 504 505 if ( (UINT64_MAX != file_size) && 506 (offset + TAR_BLOCK > file_size) ) 507 break; 508 if (0 != offset) 509 { 510 if (! EXTRACTOR_forensic_read_ (ec, 511 (int64_t) offset, 512 block, 513 TAR_BLOCK)) 514 break; 515 } 516 if (is_zero_block (block)) 517 break; /* end-of-archive marker */ 518 if (! checksum_ok (block)) 519 break; /* corrupt or not a member header after all */ 520 if (! tar_number (&block[124], 521 12, 522 &size)) 523 break; /* unreadable size: we can no longer find the next header */ 524 if ( (size > UINT64_MAX - 2 * TAR_BLOCK) || 525 ( (UINT64_MAX != file_size) && 526 (size > file_size) ) ) 527 break; /* would overflow the walk or run past the file */ 528 typeflag = block[156]; 529 switch (typeflag) 530 { 531 case 'x': 532 case 'g': 533 { 534 char pax[TAR_MAX_PAX]; 535 size_t want = (size > TAR_MAX_PAX) ? TAR_MAX_PAX : (size_t) size; 536 537 /* pax extended header; the payload is metadata for the member 538 whose header comes next, not a member of its own */ 539 format = "pax"; 540 if ( (0 != want) && 541 (EXTRACTOR_forensic_read_ (ec, 542 (int64_t) (offset + TAR_BLOCK), 543 pax, 544 want)) ) 545 parse_pax (pax, 546 want, 547 longname, 548 longlink); 549 break; 550 } 551 case 'L': 552 case 'K': 553 { 554 size_t want = (size > TAR_MAX_LONGNAME) ? TAR_MAX_LONGNAME 555 : (size_t) size; 556 557 /* GNU long name/link: the payload is the real name of the 558 member whose header comes next */ 559 format = "gnu"; 560 if ( (0 != want) && 561 (EXTRACTOR_forensic_read_ (ec, 562 (int64_t) (offset + TAR_BLOCK), 563 ('L' == typeflag) ? longname 564 : longlink, 565 want)) ) 566 { 567 if ('L' == typeflag) 568 longname[want] = '\0'; 569 else 570 longlink[want] = '\0'; 571 } 572 break; 573 } 574 default: 575 { 576 const char *member_name; 577 size_t member_len; 578 579 members++; 580 /* regular files carry content; everything else has size 0 or a 581 size that does not describe stored bytes */ 582 if ( ('0' == typeflag) || 583 ('\0' == typeflag) || 584 ('7' == typeflag) ) 585 total_size += size; 586 if ('\0' != longname[0]) 587 { 588 member_name = longname; 589 member_len = strlen (longname); 590 } 591 else if ( (have_ustar) && 592 ('\0' != block[345]) ) 593 { 594 size_t plen; 595 size_t nlen; 596 597 /* ustar splits long paths as prefix + '/' + name */ 598 plen = strnlen ((const char *) &block[345], 599 155); 600 nlen = strnlen ((const char *) &block[0], 601 100); 602 if (plen > sizeof (name) - 2) 603 plen = sizeof (name) - 2; 604 if (nlen > sizeof (name) - 2 - plen) 605 nlen = sizeof (name) - 2 - plen; 606 memcpy (name, 607 &block[345], 608 plen); 609 name[plen] = '/'; 610 memcpy (&name[plen + 1], 611 block, 612 nlen); 613 name[plen + 1 + nlen] = '\0'; 614 member_name = name; 615 member_len = plen + 1 + nlen; 616 } 617 else 618 { 619 member_name = (const char *) block; 620 member_len = strnlen ((const char *) block, 621 100); 622 } 623 if (names_emitted < EXTRACTOR_FORENSIC_MAX_ITEMS) 624 { 625 names_emitted++; 626 if (0 != 627 EXTRACTOR_forensic_emit_text_ (ec, 628 "tar", 629 EXTRACTOR_METATYPE_FILENAME, 630 member_name, 631 member_len)) 632 return; 633 } 634 if ( ( ('1' == typeflag) || 635 ('2' == typeflag) ) && 636 (links_emitted < EXTRACTOR_FORENSIC_MAX_ITEMS) ) 637 { 638 const char *target; 639 size_t target_len; 640 641 if ('\0' != longlink[0]) 642 { 643 target = longlink; 644 target_len = strlen (longlink); 645 } 646 else 647 { 648 target = (const char *) &block[157]; 649 target_len = strnlen (target, 650 100); 651 } 652 links_emitted++; 653 if (0 != 654 EXTRACTOR_forensic_emit_text_ (ec, 655 "tar", 656 EXTRACTOR_METATYPE_TARGET_PATH, 657 target, 658 target_len)) 659 return; 660 } 661 /* -- ownership: the reason this plugin exists -- */ 662 if ( (have_ustar) && 663 (add_distinct_str (st.unames, 664 &st.n_unames, 665 (const char *) &block[265], 666 32)) && 667 (0 != EXTRACTOR_forensic_emit_text_ (ec, 668 "tar", 669 EXTRACTOR_METATYPE_OWNER_USER, 670 (const char *) &block[265], 671 32)) ) 672 return; 673 if ( (have_ustar) && 674 (add_distinct_str (st.gnames, 675 &st.n_gnames, 676 (const char *) &block[297], 677 32)) && 678 (0 != EXTRACTOR_forensic_emit_text_ (ec, 679 "tar", 680 EXTRACTOR_METATYPE_OWNER_GROUP, 681 (const char *) &block[297], 682 32)) ) 683 return; 684 if ( (tar_number (&block[108], 685 8, 686 &num)) && 687 (add_distinct_num (st.uids, 688 &st.n_uids, 689 num)) && 690 (0 != EXTRACTOR_forensic_emit_ (ec, 691 "tar", 692 EXTRACTOR_METATYPE_OWNER_UID, 693 "%llu", 694 (unsigned long long) num)) ) 695 return; 696 if ( (tar_number (&block[116], 697 8, 698 &num)) && 699 (add_distinct_num (st.gids, 700 &st.n_gids, 701 num)) && 702 (0 != EXTRACTOR_forensic_emit_ (ec, 703 "tar", 704 EXTRACTOR_METATYPE_OWNER_GID, 705 "%llu", 706 (unsigned long long) num)) ) 707 return; 708 if ( (tar_number (&block[100], 709 8, 710 &mode)) && 711 (add_distinct_num (st.modes, 712 &st.n_modes, 713 mode & 07777)) && 714 (0 != EXTRACTOR_forensic_emit_ (ec, 715 "tar", 716 EXTRACTOR_METATYPE_PERMISSIONS, 717 "0%llo", 718 (unsigned long long) (mode 719 & 07777))) ) 720 return; 721 if ( (tar_number (&block[136], 722 12, 723 &num)) && 724 (num < (uint64_t) INT64_MAX) && 725 (((int64_t) num) > newest) ) 726 newest = (int64_t) num; 727 longname[0] = '\0'; 728 longlink[0] = '\0'; 729 break; 730 } 731 } 732 offset += TAR_BLOCK + ((size + TAR_BLOCK - 1) / TAR_BLOCK) * TAR_BLOCK; 733 } 734 if (members >= TAR_MAX_MEMBERS) 735 truncated_walk = 1; 736 if (0 != 737 EXTRACTOR_forensic_emit_ (ec, 738 "tar", 739 EXTRACTOR_METATYPE_FORMAT, 740 "%s", 741 format)) 742 return; 743 if (0 != 744 EXTRACTOR_forensic_emit_ (ec, 745 "tar", 746 EXTRACTOR_METATYPE_ENTRY_COUNT, 747 "%u", 748 members)) 749 return; 750 if (0 != 751 EXTRACTOR_forensic_emit_size_ (ec, 752 "tar", 753 EXTRACTOR_METATYPE_UNCOMPRESSED_SIZE, 754 total_size)) 755 return; 756 /* the newest member: when the archive was assembled, near enough */ 757 if (0 != 758 EXTRACTOR_forensic_emit_unix_time_ (ec, 759 "tar", 760 EXTRACTOR_METATYPE_MODIFICATION_DATE, 761 newest)) 762 return; 763 if ( (truncated_walk) && 764 (0 != EXTRACTOR_forensic_emit_ (ec, 765 "tar", 766 EXTRACTOR_METATYPE_COMMENT, 767 "member walk stopped at the cap of %u;" 768 " counts and sizes are lower bounds", 769 (unsigned int) TAR_MAX_MEMBERS)) ) 770 return; 771 } 772 773 774 /* end of tar_extractor.c */