libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

iso9660_extractor.c (18300B)


      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/iso9660_extractor.c
     22  * @brief plugin to support iso9660 files
     23  * @author Christian Grothoff
     24  *
     25  * Reads the volume descriptor set of an ECMA-119 (ISO 9660) image.  The
     26  * descriptors carry the identifiers of the machine, the person and the
     27  * mastering program that produced the disc, which is the provenance a
     28  * first pass is after; the file tree is deliberately not walked.
     29  *
     30  * References: ECMA-119 (ISO 9660), the "El Torito" bootable CD-ROM
     31  * specification, the Joliet specification and IEEE P1282 (Rock Ridge).
     32  */
     33 #include "platform.h"
     34 #include "extractor.h"
     35 #include "forensics.h"
     36 
     37 /**
     38  * Size of a logical sector in the volume descriptor set.  ECMA-119
     39  * fixes this at 2048 regardless of the logical block size the volume
     40  * announces for its files.
     41  */
     42 #define ISO_SECTOR 2048
     43 
     44 /**
     45  * Byte offset of the first volume descriptor: sector 16, after the
     46  * 32 KiB system area reserved for boot code.
     47  */
     48 #define ISO_VD_OFFSET (16 * ISO_SECTOR)
     49 
     50 /**
     51  * How many volume descriptors we are prepared to walk before giving
     52  * up on finding the terminator.
     53  */
     54 #define ISO_MAX_DESCRIPTORS 32
     55 
     56 /**
     57  * Bytes of each descriptor we look at while walking: enough for the
     58  * type, the standard identifier, the boot system identifier of a boot
     59  * record and the escape sequences of a supplementary descriptor.
     60  */
     61 #define ISO_PROBE 96
     62 
     63 
     64 /**
     65  * Days from 1970-01-01 to @a y - @a m - @a d, proleptic Gregorian.
     66  *
     67  * Written out rather than using timegm(), which is not available
     68  * everywhere libextractor builds.
     69  *
     70  * @param y the year
     71  * @param m the month, 1 to 12
     72  * @param d the day of month, 1 to 31
     73  * @return days since the Unix epoch, negative before it
     74  */
     75 static int64_t
     76 days_from_civil (int64_t y,
     77                  unsigned int m,
     78                  unsigned int d)
     79 {
     80   int64_t era;
     81   unsigned int yoe;
     82   unsigned int doy;
     83   unsigned int doe;
     84 
     85   y -= (m <= 2) ? 1 : 0;
     86   era = ((y >= 0) ? y : (y - 399)) / 400;
     87   yoe = (unsigned int) (y - era * 400);
     88   doy = (153 * (m + ((m > 2) ? -3 : 9)) + 2) / 5 + d - 1;
     89   doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
     90   return era * 146097 + (int64_t) doe - 719468;
     91 }
     92 
     93 
     94 /**
     95  * Parse a 17-byte ECMA-119 `dec-datetime' field.
     96  *
     97  * The field is "YYYYMMDDHHMMSSss" in ASCII digits followed by one
     98  * signed byte holding the offset from GMT in 15-minute units.  A field
     99  * that is all ASCII zeroes means "not specified".
    100  *
    101  * @param f the 17 bytes
    102  * @param[out] when where to store the time in seconds since the epoch
    103  * @return 1 if @a f held a usable date
    104  */
    105 static int
    106 parse_dec_datetime (const unsigned char *f,
    107                     int64_t *when)
    108 {
    109   unsigned int v[7];
    110   static const unsigned int width[7] = { 4, 2, 2, 2, 2, 2, 2 };
    111   size_t pos = 0;
    112   int all_zero = 1;
    113 
    114   for (unsigned int i = 0; i < 16; i++)
    115   {
    116     if ( ('0' > f[i]) ||
    117          ('9' < f[i]) )
    118       return 0;   /* not a filled-in date field */
    119     if ('0' != f[i])
    120       all_zero = 0;
    121   }
    122   if (all_zero)
    123     return 0;   /* the documented spelling of "unset" */
    124   for (unsigned int i = 0; i < 7; i++)
    125   {
    126     v[i] = 0;
    127     for (unsigned int k = 0; k < width[i]; k++)
    128       v[i] = v[i] * 10 + (unsigned int) (f[pos++] - '0');
    129   }
    130   if ( (v[0] < 1900) || (v[0] > 2200) ||
    131        (v[1] < 1) || (v[1] > 12) ||
    132        (v[2] < 1) || (v[2] > 31) ||
    133        (v[3] > 23) || (v[4] > 59) || (v[5] > 60) )
    134     return 0;
    135   *when = days_from_civil (v[0],
    136                            v[1],
    137                            v[2]) * 86400LL
    138           + v[3] * 3600LL + v[4] * 60LL + v[5]
    139           /* the offset is what has to be *subtracted* to reach GMT */
    140           - ((int64_t) (signed char) f[16]) * 15LL * 60LL;
    141   return 1;
    142 }
    143 
    144 
    145 /**
    146  * Is @a bs a logical block size an ECMA-119 volume may announce?
    147  *
    148  * @param bs the raw field
    149  * @return 1 if @a bs is usable for arithmetic
    150  */
    151 static int
    152 block_size_ok (uint16_t bs)
    153 {
    154   return ( (bs >= 512) &&
    155            (bs <= 32768) &&
    156            (0 == (bs & (bs - 1))) );
    157 }
    158 
    159 
    160 /**
    161  * Emit a fixed-width text field of the primary volume descriptor.
    162  *
    163  * @param ec extraction context
    164  * @param type meta data type
    165  * @param pvd the primary volume descriptor
    166  * @param off offset of the field within @a pvd
    167  * @param len width of the field
    168  * @return 1 if the caller should stop extracting, 0 to continue
    169  */
    170 static int
    171 emit_field (struct EXTRACTOR_ExtractContext *ec,
    172             enum EXTRACTOR_MetaType type,
    173             const unsigned char *pvd,
    174             size_t off,
    175             size_t len)
    176 {
    177   return EXTRACTOR_forensic_emit_text_ (ec,
    178                                         "iso9660",
    179                                         type,
    180                                         (const char *) &pvd[off],
    181                                         len);
    182 }
    183 
    184 
    185 /**
    186  * Parse and emit one of the descriptor's `dec-datetime' fields.
    187  *
    188  * Unset fields (all ASCII zeroes) and implausible ones are dropped
    189  * without a word, which is what a bulk pass wants.
    190  *
    191  * @param ec extraction context
    192  * @param type meta data type
    193  * @param field the 17-byte field
    194  * @return 1 if the caller should stop extracting, 0 to continue
    195  */
    196 static int
    197 emit_date (struct EXTRACTOR_ExtractContext *ec,
    198            enum EXTRACTOR_MetaType type,
    199            const unsigned char *field)
    200 {
    201   int64_t when;
    202 
    203   if (! parse_dec_datetime (field,
    204                             &when))
    205     return 0;
    206   return EXTRACTOR_forensic_emit_unix_time_ (ec,
    207                                              "iso9660",
    208                                              type,
    209                                              when);
    210 }
    211 
    212 
    213 /**
    214  * Look at the root directory record's system use area for the SUSP and
    215  * Rock Ridge signatures.
    216  *
    217  * The `SP' entry that announces SUSP lives in the "." record of the
    218  * root directory, not in the copy of the root record embedded in the
    219  * descriptor, so this costs one extra sector read.  We do it because
    220  * the presence of Rock Ridge changes what a disc actually contains
    221  * (real POSIX names, permissions and symlinks) and one bounded read is
    222  * cheap.
    223  *
    224  * @param ec extraction context
    225  * @param pvd the primary volume descriptor
    226  * @param block_size the volume's logical block size
    227  * @param file_size size of the image, UINT64_MAX if unknown
    228  * @return 1 if Rock Ridge extensions are present
    229  */
    230 static int
    231 has_rock_ridge (struct EXTRACTOR_ExtractContext *ec,
    232                 const unsigned char *pvd,
    233                 uint32_t block_size,
    234                 uint64_t file_size)
    235 {
    236   unsigned char sector[ISO_SECTOR];
    237   uint32_t extent;
    238   size_t want;
    239   uint64_t at;
    240   unsigned int rec_len;
    241   unsigned int fi_len;
    242   unsigned int pos;
    243 
    244   /* the root directory record sits at offset 156 of the descriptor;
    245      its extent is a both-endian 32-bit value at offset 2 */
    246   extent = EXTRACTOR_forensic_le32_ (&pvd[156 + 2]);
    247   if (0 == extent)
    248     return 0;
    249   at = ((uint64_t) extent) * block_size;
    250   want = (block_size > ISO_SECTOR) ? ISO_SECTOR : (size_t) block_size;
    251   if ( (at > INT64_MAX - want) ||
    252        ( (UINT64_MAX != file_size) &&
    253          (at + want > file_size) ) )
    254     return 0;   /* the root directory is not inside this file */
    255   if (! EXTRACTOR_forensic_read_ (ec,
    256                                   (int64_t) at,
    257                                   sector,
    258                                   want))
    259     return 0;
    260   rec_len = sector[0];
    261   if ( (rec_len < 34) ||
    262        (rec_len > want) )
    263     return 0;
    264   fi_len = sector[32];
    265   pos = 33 + fi_len;
    266   if (0 != (pos & 1))
    267     pos++;   /* the identifier is padded to an even length */
    268   while (pos + 4 <= rec_len)
    269   {
    270     unsigned int elen = sector[pos + 2];
    271 
    272     if (elen < 4)
    273       return 0;   /* zero-length entry: refuse to spin */
    274     if (pos + elen > rec_len)
    275       return 0;
    276     if ( ('R' == sector[pos]) &&
    277          ('R' == sector[pos + 1]) )
    278       return 1;   /* the Rock Ridge marker itself */
    279     if ( ('S' == sector[pos]) &&
    280          ('P' == sector[pos + 1]) &&
    281          (7 == elen) &&
    282          (0xBE == sector[pos + 5]) &&
    283          (0xEF == sector[pos + 6]) )
    284       return 1;   /* SUSP, which on a data CD means Rock Ridge */
    285     pos += elen;
    286   }
    287   return 0;
    288 }
    289 
    290 
    291 /**
    292  * Main entry method for the iso9660 extraction plugin.
    293  *
    294  * @param ec extraction context provided to the plugin
    295  */
    296 void
    297 EXTRACTOR_iso9660_extract_method (struct EXTRACTOR_ExtractContext *ec);
    298 
    299 void
    300 EXTRACTOR_iso9660_extract_method (struct EXTRACTOR_ExtractContext *ec)
    301 {
    302   unsigned char probe[ISO_PROBE];
    303   unsigned char pvd[ISO_SECTOR];
    304   uint64_t file_size;
    305   uint64_t pvd_at = 0;
    306   uint32_t block_size;
    307   uint32_t space_size;
    308   uint16_t set_size;
    309   int64_t when;
    310   int have_pvd = 0;
    311   int joliet = 0;
    312   int el_torito = 0;
    313 
    314   file_size = ec->get_size (ec->cls);
    315   if ( (UINT64_MAX != file_size) &&
    316        (file_size < ISO_VD_OFFSET + ISO_SECTOR) )
    317     return;   /* too short to hold a volume descriptor */
    318   if (! EXTRACTOR_forensic_read_ (ec,
    319                                   ISO_VD_OFFSET,
    320                                   probe,
    321                                   sizeof (probe)))
    322     return;
    323   if (0 != memcmp (&probe[1],
    324                    "CD001",
    325                    5))
    326     return;   /* not an ISO 9660 image */
    327   if (0 !=
    328       EXTRACTOR_forensic_emit_ (ec,
    329                                 "iso9660",
    330                                 EXTRACTOR_METATYPE_MIMETYPE,
    331                                 "%s",
    332                                 "application/x-iso9660-image"))
    333     return;
    334   for (unsigned int i = 0; i < ISO_MAX_DESCRIPTORS; i++)
    335   {
    336     uint64_t at = ISO_VD_OFFSET + ((uint64_t) i) * ISO_SECTOR;
    337 
    338     if ( (UINT64_MAX != file_size) &&
    339          (at + ISO_SECTOR > file_size) )
    340       break;
    341     if ( (0 != i) &&
    342          (! EXTRACTOR_forensic_read_ (ec,
    343                                       (int64_t) at,
    344                                       probe,
    345                                       sizeof (probe))) )
    346       break;
    347     if (0 != memcmp (&probe[1],
    348                      "CD001",
    349                      5))
    350       break;   /* the descriptor set ended without a terminator */
    351     switch (probe[0])
    352     {
    353     case 0:
    354       /* boot record; El Torito puts its name in the boot system
    355          identifier at offset 7 */
    356       if (0 == memcmp (&probe[7],
    357                        "EL TORITO SPECIFICATION",
    358                        23))
    359         el_torito = 1;
    360       break;
    361     case 1:
    362       if (! have_pvd)
    363       {
    364         have_pvd = 1;
    365         pvd_at = at;
    366       }
    367       break;
    368     case 2:
    369       /* supplementary descriptor; Joliet announces itself with one of
    370          three escape sequences for UCS-2 at offset 88 */
    371       if ( (0x25 == probe[88]) &&
    372            (0x2F == probe[89]) &&
    373            ( (0x40 == probe[90]) ||
    374              (0x43 == probe[90]) ||
    375              (0x45 == probe[90]) ) )
    376         joliet = 1;
    377       break;
    378     case 255:
    379       i = ISO_MAX_DESCRIPTORS;   /* terminator */
    380       break;
    381     default:
    382       break;
    383     }
    384   }
    385   if (0 !=
    386       EXTRACTOR_forensic_emit_ (ec,
    387                                 "iso9660",
    388                                 EXTRACTOR_METATYPE_FILESYSTEM_TYPE,
    389                                 "%s",
    390                                 "ISO 9660"))
    391     return;
    392   if ( (joliet) &&
    393        (0 != EXTRACTOR_forensic_emit_ (ec,
    394                                        "iso9660",
    395                                        EXTRACTOR_METATYPE_FILESYSTEM_TYPE,
    396                                        "%s",
    397                                        "Joliet")) )
    398     return;
    399   if ( (el_torito) &&
    400        (0 != EXTRACTOR_forensic_emit_ (ec,
    401                                        "iso9660",
    402                                        EXTRACTOR_METATYPE_COMMENT,
    403                                        "%s",
    404                                        "bootable (El Torito)")) )
    405     return;
    406   if (! have_pvd)
    407     return;   /* a descriptor set without a primary descriptor */
    408   if (! EXTRACTOR_forensic_read_ (ec,
    409                                   (int64_t) pvd_at,
    410                                   pvd,
    411                                   sizeof (pvd)))
    412     return;
    413 
    414   /* -- the identifier fields, in descriptor order -- */
    415   if (0 !=
    416       emit_field (ec,
    417                   EXTRACTOR_METATYPE_SYSTEM_IDENTIFIER,
    418                   pvd,
    419                   8,
    420                   32))
    421     return;
    422   if (0 !=
    423       emit_field (ec,
    424                   EXTRACTOR_METATYPE_VOLUME_NAME,
    425                   pvd,
    426                   40,
    427                   32))
    428     return;
    429   if (0 !=
    430       emit_field (ec,
    431                   EXTRACTOR_METATYPE_PUBLISHER,
    432                   pvd,
    433                   318,
    434                   128))
    435     return;
    436   if (0 !=
    437       emit_field (ec,
    438                   EXTRACTOR_METATYPE_DATA_PREPARER,
    439                   pvd,
    440                   446,
    441                   128))
    442     return;
    443   /* the application identifier names the mastering software -- mkisofs,
    444      Nero, ImgBurn -- which is the best provenance the descriptor has */
    445   if (0 !=
    446       emit_field (ec,
    447                   EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE,
    448                   pvd,
    449                   574,
    450                   128))
    451     return;
    452   if (0 !=
    453       emit_field (ec,
    454                   EXTRACTOR_METATYPE_COPYRIGHT,
    455                   pvd,
    456                   702,
    457                   37))
    458     return;
    459   if (0 !=
    460       emit_field (ec,
    461                   EXTRACTOR_METATYPE_ABSTRACT,
    462                   pvd,
    463                   739,
    464                   37))
    465     return;
    466   /* The bibliographic file identifier has no meta type of its own; it
    467      names a file on the disc, so it only means something with a label
    468      attached. */
    469   {
    470     size_t blen = EXTRACTOR_forensic_trim_ ((const char *) &pvd[776],
    471                                             37);
    472     int printable = (0 != blen);
    473 
    474     for (size_t i = 0; i < blen; i++)
    475       if ( (pvd[776 + i] < 0x20) ||
    476            (pvd[776 + i] > 0x7e) )
    477         printable = 0;
    478     if ( (printable) &&
    479          (0 != EXTRACTOR_forensic_emit_ (ec,
    480                                          "iso9660",
    481                                          EXTRACTOR_METATYPE_COMMENT,
    482                                          "bibliographic file: %.*s",
    483                                          (int) blen,
    484                                          (const char *) &pvd[776])) )
    485       return;
    486   }
    487 
    488   /* -- geometry -- */
    489   block_size = EXTRACTOR_forensic_le16_ (&pvd[128]);
    490   if (block_size_ok ((uint16_t) block_size))
    491   {
    492     if (0 !=
    493         EXTRACTOR_forensic_emit_ (ec,
    494                                   "iso9660",
    495                                   EXTRACTOR_METATYPE_BLOCK_SIZE,
    496                                   "%u",
    497                                   (unsigned int) block_size))
    498       return;
    499     /* both factors are bounded (2^32 sectors of at most 32 KiB), so
    500        the product cannot overflow 64 bits */
    501     space_size = EXTRACTOR_forensic_le32_ (&pvd[80]);
    502     if ( (0 != space_size) &&
    503          (0 != EXTRACTOR_forensic_emit_size_ (ec,
    504                                               "iso9660",
    505                                               EXTRACTOR_METATYPE_VOLUME_SIZE,
    506                                               ((uint64_t) space_size)
    507                                               * block_size)) )
    508       return;
    509   }
    510   else
    511   {
    512     block_size = 0;
    513   }
    514   /* how many volumes the set has; only worth saying when it is a set */
    515   set_size = EXTRACTOR_forensic_le16_ (&pvd[120]);
    516   if ( (set_size > 1) &&
    517        (0 != EXTRACTOR_forensic_emit_ (ec,
    518                                        "iso9660",
    519                                        EXTRACTOR_METATYPE_ENTRY_COUNT,
    520                                        "%u",
    521                                        (unsigned int) set_size)) )
    522     return;
    523 
    524   /* -- the four dec-datetime fields -- */
    525   if (0 !=
    526       emit_date (ec,
    527                  EXTRACTOR_METATYPE_CREATION_DATE,
    528                  &pvd[813]))
    529     return;
    530   if (0 !=
    531       emit_date (ec,
    532                  EXTRACTOR_METATYPE_MODIFICATION_DATE,
    533                  &pvd[830]))
    534     return;
    535   if (0 !=
    536       emit_date (ec,
    537                  EXTRACTOR_METATYPE_EXPIRATION_DATE,
    538                  &pvd[847]))
    539     return;
    540   /* The effective date -- the date before which the volume should not
    541      be used -- has no meta type of its own, so it goes out as a
    542      comment rather than being silently dropped. */
    543   if (parse_dec_datetime (&pvd[864],
    544                           &when))
    545   {
    546     struct tm tm;
    547     time_t t = (time_t) when;
    548     char buf[32];
    549 
    550     if ( (when > 315532800LL) &&
    551          (when < 4102444800LL) &&
    552          (NULL != gmtime_r (&t,
    553                             &tm)) &&
    554          (0 != strftime (buf,
    555                          sizeof (buf),
    556                          "%Y-%m-%dT%H:%M:%SZ",
    557                          &tm)) &&
    558          (0 != EXTRACTOR_forensic_emit_ (ec,
    559                                          "iso9660",
    560                                          EXTRACTOR_METATYPE_COMMENT,
    561                                          "volume effective from %s",
    562                                          buf)) )
    563       return;
    564   }
    565 
    566   /* -- Rock Ridge, which needs one more sector -- */
    567   if ( (0 != block_size) &&
    568        (has_rock_ridge (ec,
    569                         pvd,
    570                         block_size,
    571                         file_size)) &&
    572        (0 != EXTRACTOR_forensic_emit_ (ec,
    573                                        "iso9660",
    574                                        EXTRACTOR_METATYPE_FILESYSTEM_TYPE,
    575                                        "%s",
    576                                        "Rock Ridge")) )
    577     return;
    578 }
    579 
    580 
    581 /* end of iso9660_extractor.c */