udf: Avoid invalid LVID used on mount
authorJan Kara <jack@suse.cz>
Mon, 5 Feb 2024 15:24:22 +0000 (16:24 +0100)
committerJan Kara <jack@suse.cz>
Mon, 5 Feb 2024 15:51:59 +0000 (16:51 +0100)
udf_load_logicalvolint() loads logical volume integrity descriptors.
Since there can be multiple blocks with LVIDs, we verify the contents of
only the last (prevailing) LVID found. However if we fail to load the
last LVID (either due to IO error or because it's checksum fails to
match), we never perform the verification of validity of the LVID we are
going to use. If such LVID contains invalid data, we can hit
out-of-bounds access or similar issues. Fix the problem by verifying
each LVID we are potentially going to accept.

Reported-by: Robert Morris <rtm@csail.mit.edu>
Signed-off-by: Jan Kara <jack@suse.cz>
fs/udf/super.c

index 0a15ea436fc259f7a663f94e123f60850dba4cdb..14ed670cfa8bb27094bf2841f6607eef71cd4dc4 100644 (file)
@@ -1539,6 +1539,20 @@ out_bh:
        return ret;
 }
 
+static bool udf_lvid_valid(struct super_block *sb,
+                          struct logicalVolIntegrityDesc *lvid)
+{
+       u32 parts, impuselen;
+
+       parts = le32_to_cpu(lvid->numOfPartitions);
+       impuselen = le32_to_cpu(lvid->lengthOfImpUse);
+       if (parts >= sb->s_blocksize || impuselen >= sb->s_blocksize ||
+           sizeof(struct logicalVolIntegrityDesc) + impuselen +
+           2 * parts * sizeof(u32) > sb->s_blocksize)
+               return false;
+       return true;
+}
+
 /*
  * Find the prevailing Logical Volume Integrity Descriptor.
  */
@@ -1549,7 +1563,6 @@ static void udf_load_logicalvolint(struct super_block *sb, struct kernel_extent_
        struct udf_sb_info *sbi = UDF_SB(sb);
        struct logicalVolIntegrityDesc *lvid;
        int indirections = 0;
-       u32 parts, impuselen;
 
        while (++indirections <= UDF_MAX_LVID_NESTING) {
                final_bh = NULL;
@@ -1571,32 +1584,27 @@ static void udf_load_logicalvolint(struct super_block *sb, struct kernel_extent_
                if (!final_bh)
                        return;
 
-               brelse(sbi->s_lvid_bh);
-               sbi->s_lvid_bh = final_bh;
-
                lvid = (struct logicalVolIntegrityDesc *)final_bh->b_data;
+               if (udf_lvid_valid(sb, lvid)) {
+                       brelse(sbi->s_lvid_bh);
+                       sbi->s_lvid_bh = final_bh;
+               } else {
+                       udf_warn(sb, "Corrupted LVID (parts=%u, impuselen=%u), "
+                                "ignoring.\n",
+                                le32_to_cpu(lvid->numOfPartitions),
+                                le32_to_cpu(lvid->lengthOfImpUse));
+               }
+
                if (lvid->nextIntegrityExt.extLength == 0)
-                       goto check;
+                       return;
 
                loc = leea_to_cpu(lvid->nextIntegrityExt);
        }
 
        udf_warn(sb, "Too many LVID indirections (max %u), ignoring.\n",
                UDF_MAX_LVID_NESTING);
-out_err:
        brelse(sbi->s_lvid_bh);
        sbi->s_lvid_bh = NULL;
-       return;
-check:
-       parts = le32_to_cpu(lvid->numOfPartitions);
-       impuselen = le32_to_cpu(lvid->lengthOfImpUse);
-       if (parts >= sb->s_blocksize || impuselen >= sb->s_blocksize ||
-           sizeof(struct logicalVolIntegrityDesc) + impuselen +
-           2 * parts * sizeof(u32) > sb->s_blocksize) {
-               udf_warn(sb, "Corrupted LVID (parts=%u, impuselen=%u), "
-                        "ignoring.\n", parts, impuselen);
-               goto out_err;
-       }
 }
 
 /*