]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Support ISOs with a non-standard PVD layouts (#2238)
authorTim Kientzle <kientzle@acm.org>
Mon, 17 Jun 2024 03:22:14 +0000 (20:22 -0700)
committerGitHub <noreply@github.com>
Mon, 17 Jun 2024 03:22:14 +0000 (20:22 -0700)
The CSRG ISOs have a non-standard PVD layout with a 68-byte root
directory record (rather than the 34-byte record required by
ECMA119/ISO9660). I built a test image with this change and modified the
ISO9660 reader to accept it.

While I was working on the bid logic to recognize PVDs, I added a number
of additional correctness checks that should make our bidding a bit more
accurate. In particular, this should more than compensate for the
weakened check of the root directory record size.

Resolves #2232

Makefile.am
libarchive/archive_read_support_format_iso9660.c
libarchive/test/test_read_format_iso_3.iso.Z.uu [new file with mode: 0644]
libarchive/test/test_read_format_iso_Z.c

index 1661d9c1a5ea91beaa01cbbaff3ae4e38308b0a6..7560b14fe7eb2e17522531874d6fe1da140cf3fd 100644 (file)
@@ -833,6 +833,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_huge_rpm.rpm.uu \
        libarchive/test/test_read_format_iso.iso.Z.uu \
        libarchive/test/test_read_format_iso_2.iso.Z.uu \
+       libarchive/test/test_read_format_iso_3.iso.Z.uu \
        libarchive/test/test_read_format_iso_joliet.iso.Z.uu \
        libarchive/test/test_read_format_iso_joliet_by_nero.iso.Z.uu \
        libarchive/test/test_read_format_iso_joliet_long.iso.Z.uu \
index 25ab11bf5964b286b83243b092823cc6b5bbbb0f..c38943a2fa34d5d77afd73f2a6e65bcb2a7f10e1 100644 (file)
@@ -402,6 +402,9 @@ static int  isJolietSVD(struct iso9660 *, const unsigned char *);
 static int     isSVD(struct iso9660 *, const unsigned char *);
 static int     isEVD(struct iso9660 *, const unsigned char *);
 static int     isPVD(struct iso9660 *, const unsigned char *);
+static int     isRootDirectoryRecord(const unsigned char *);
+static int     isValid723Integer(const unsigned char *);
+static int     isValid733Integer(const unsigned char *);
 static int     next_cache_entry(struct archive_read *, struct iso9660 *,
                    struct file_info **);
 static int     next_entry_seek(struct archive_read *, struct iso9660 *,
@@ -773,8 +776,9 @@ isSVD(struct iso9660 *iso9660, const unsigned char *h)
 
        /* Read Root Directory Record in Volume Descriptor. */
        p = h + SVD_root_directory_record_offset;
-       if (p[DR_length_offset] != 34)
+       if (!isRootDirectoryRecord(p)) {
                return (0);
+       }
 
        return (48);
 }
@@ -851,8 +855,9 @@ isEVD(struct iso9660 *iso9660, const unsigned char *h)
 
        /* Read Root Directory Record in Volume Descriptor. */
        p = h + PVD_root_directory_record_offset;
-       if (p[DR_length_offset] != 34)
+       if (!isRootDirectoryRecord(p)) {
                return (0);
+       }
 
        return (48);
 }
@@ -882,21 +887,43 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h)
        if (!isNull(iso9660, h, PVD_reserved2_offset, PVD_reserved2_size))
                return (0);
 
+       /* Volume space size must be encoded according to 7.3.3 */
+       if (!isValid733Integer(h + PVD_volume_space_size_offset)) {
+               return (0);
+       }
+       volume_block = archive_le32dec(h + PVD_volume_space_size_offset);
+       if (volume_block <= SYSTEM_AREA_BLOCK+4)
+               return (0);
+
        /* Reserved field must be 0. */
        if (!isNull(iso9660, h, PVD_reserved3_offset, PVD_reserved3_size))
                return (0);
 
+       /* Volume set size must be encoded according to 7.2.3 */
+       if (!isValid723Integer(h + PVD_volume_set_size_offset)) {
+               return (0);
+       }
+
+       /* Volume sequence number must be encoded according to 7.2.3 */
+       if (!isValid723Integer(h + PVD_volume_sequence_number_offset)) {
+               return (0);
+       }
+
        /* Logical block size must be > 0. */
        /* I've looked at Ecma 119 and can't find any stronger
         * restriction on this field. */
+       if (!isValid723Integer(h + PVD_logical_block_size_offset)) {
+               return (0);
+       }
        logical_block_size =
            archive_le16dec(h + PVD_logical_block_size_offset);
        if (logical_block_size <= 0)
                return (0);
 
-       volume_block = archive_le32dec(h + PVD_volume_space_size_offset);
-       if (volume_block <= SYSTEM_AREA_BLOCK+4)
+       /* Path Table size must be encoded according to 7.3.3 */
+       if (!isValid733Integer(h + PVD_path_table_size_offset)) {
                return (0);
+       }
 
        /* File structure version must be 1 for ISO9660/ECMA119. */
        if (h[PVD_file_structure_version_offset] != 1)
@@ -935,8 +962,9 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h)
 
        /* Read Root Directory Record in Volume Descriptor. */
        p = h + PVD_root_directory_record_offset;
-       if (p[DR_length_offset] != 34)
+       if (!isRootDirectoryRecord(p)) {
                return (0);
+       }
 
        if (!iso9660->primary.location) {
                iso9660->logical_block_size = logical_block_size;
@@ -951,6 +979,51 @@ isPVD(struct iso9660 *iso9660, const unsigned char *h)
        return (48);
 }
 
+static int
+isRootDirectoryRecord(const unsigned char *p) {
+       int flags;
+
+       /* ECMA119/ISO9660 requires that the root directory record be _exactly_ 34 bytes.
+        * However, we've seen images that have root directory records up to 68 bytes. */
+       if (p[DR_length_offset] < 34 || p[DR_length_offset] > 68) {
+               return (0);
+       }
+
+       /* The root directory location must be a 7.3.3 32-bit integer. */
+       if (!isValid733Integer(p + DR_extent_offset)) {
+               return (0);
+       }
+
+       /* The root directory size must be a 7.3.3 integer. */
+       if (!isValid733Integer(p + DR_size_offset)) {
+               return (0);
+       }
+
+       /* According to the standard, certain bits must be one or zero:
+        * Bit 1: must be 1 (this is a directory)
+        * Bit 2: must be 0 (not an associated file)
+        * Bit 3: must be 0 (doesn't use extended attribute record)
+        * Bit 7: must be 0 (final directory record for this file)
+        */
+       flags = p[DR_flags_offset];
+       if ((flags & 0x8E) != 0x02) {
+               return (0);
+       }
+
+       /* Volume sequence number must be a 7.2.3 integer. */
+       if (!isValid723Integer(p + DR_volume_sequence_number_offset)) {
+               return (0);
+       }
+
+       /* Root directory name is a single zero byte... */
+       if (p[DR_name_len_offset] != 1 || p[DR_name_offset] != 0) {
+               return (0);
+       }
+
+       /* Nothing looked wrong, so let's accept it. */
+       return (1);
+}
+
 static int
 read_children(struct archive_read *a, struct file_info *parent)
 {
@@ -3127,6 +3200,32 @@ toi(const void *p, int n)
        return (0);
 }
 
+/*
+ * ECMA119/ISO9660 stores multi-byte integers in one of
+ * three different formats:
+ *  * Little-endian (specified in section 7.2.1 and 7.3.1)
+ *  * Big-endian (specified in section 7.2.2 and 7.3.2)
+ *  * Both (specified in section 7.2.3 and 7.3.3)
+ *
+ * For values that follow section 7.2.3 (16-bit) or 7.3.3 (32-bit), we
+ * can check that the little-endian and big-endian forms agree with
+ * each other.  This helps us avoid trying to decode files that are
+ * not really ISO images.
+ */
+static int
+isValid723Integer(const unsigned char *p) {
+       return (p[0] == p[3] && p[1] == p[2]);
+}
+
+static int
+isValid733Integer(const unsigned char *p)
+{
+       return (p[0] == p[7]
+               && p[1] == p[6]
+               && p[2] == p[5]
+               && p[3] == p[4]);
+}
+
 static time_t
 isodate7(const unsigned char *v)
 {
diff --git a/libarchive/test/test_read_format_iso_3.iso.Z.uu b/libarchive/test/test_read_format_iso_3.iso.Z.uu
new file mode 100644 (file)
index 0000000..e7734ab
--- /dev/null
@@ -0,0 +1,40 @@
+Same as test_read_format_iso_2.iso.Z except that the root directory record
+size in the PVD has been changed to 68 bytes (instead of the 34 required
+by the standard).  This non-standard value was seen in the wild.
+
+begin 644 test_read_format_iso_3.iso.Z
+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
+".`$`
+`
+end
index 09b0acb804ea5c7ed610e12938d574045ea7f41c..785b00bf85df3a4b71eb158449d184afe352dccd 100644 (file)
@@ -53,11 +53,10 @@ test1(void)
 }
 
 static void
-test2(void)
+test_small(const char *name)
 {
        struct archive_entry *ae;
        struct archive *a;
-       const char *name = "test_read_format_iso_2.iso.Z";
 
        extract_reference_file(name);
 
@@ -98,5 +97,8 @@ test2(void)
 DEFINE_TEST(test_read_format_iso_Z)
 {
        test1();
-       test2();
+       /* A very small ISO image with a variety of contents. */
+       test_small("test_read_format_iso_2.iso.Z");
+       /* As above, but with a non-standard 68-byte root directory in the PVD */
+       test_small("test_read_format_iso_3.iso.Z");
 }