Re: [BUG] udf: KASAN use-after-free in udf_fill_super on crafted LVID

From: Jan Kara

Date: Tue Oct 20 2026 - 05:52:05 EST


Hi!

On Tue 20-10-26 16:28:07, yangguikun wrote:
> Hi Jan,
>
> Mounting a crafted UDF image gives a KASAN use-after-free in
> udf_fill_super(), before any file operation:
>
> BUG: KASAN: use-after-free in udf_fill_super+0x1ce4/0x1e10
> Read of size 2 at addr ... by task mount
> Call Trace:
> udf_fill_super
> get_tree_bdev_flags
> path_mount
> __x64_sys_mount
>
> Kernel panic - not syncing: KASAN: panic_on_warn set ...
>
> The root cause is that udf_lvid_valid() bounds-checks the start of the
> implementation-use area but not the size of the structure read from it.
>
> static bool udf_lvid_valid(struct super_block *sb,
> struct logicalVolIntegrityDesc *lvid)
> {
> 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;
> }

Yep. Should be fixed by commit a61fb39759d7 ("udf: Make sure there's enough
space for LVID implementation use field") on its way to Linus in this merge
window. Please make sure the problem does reproduce on linux-next before
sending a report & a fix next time. Thanks!

Honza

> struct logicalVolIntegrityDescImpUse *udf_sb_lvidiu(struct super_block *sb)
> {
> partnum = le32_to_cpu(lvid->numOfPartitions);
> offset = partnum * 2 * sizeof(uint32_t);
> return (void *)(((uint8_t *)(lvid + 1)) + offset);
> }
>
> With blocksize = 2048, numOfPartitions = 246 and lengthOfImpUse = 0,
> the check computes 80 + 0 + 1968 = 2048 and passes, while
> udf_sb_lvidiu() returns lvid + 2048, i.e. exactly the end of the
> block. udf_fill_super() then reads lvidiu->minUDFReadRev at +40,
> which is past the buffer. Placing the LVID in the last block of the
> image moves that read past the end of the file.
>
> Reproducer
>
> An 80 KiB image. The generator writes:
>
> - VRS at offset 32768 (BEA01/NSR02/TEA01)
> - AVDP, PVD, LVD, USD, IUVD, PD, TD
> - the LVID as the last block, with numOfPartitions = 246 and
> lengthOfImpUse = 0
>
> Mount with:
>
> mount -t udf -o loop,ro udf_lvid_oob.img /mnt
>
> The crash is immediate; no other access is needed.
>
> The image is included below, base64-encoded gzip. Decode with:
>
> base64 -d udf_lvid_oob.b64 | gunzip > udf_lvid_oob.img
> mount -t udf -o loop,ro udf_lvid_oob.img /mnt
>
> udf_lvid_oob.b64:
>
> H4sICKsg12oCA3VkZl9sdmlkX29vYi5pbWcA7d2/btNAHAfwswu9MCD6COZfi8oSsjCwEGiRiqJW
> akIXpi5AJUgZQOI54BGYeAAW3oJHYGFh8YCY+dlNpFYIFRWkhubzOSnWxbZ0/nn43lmWnBIAAAAA
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkO6t97u3CnUAgHmyOdzu9uQ/AMyVkfU/AAAAAAAA
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAHKdIZfoQv++/1blq+wcuPVp7MNjZWPvNablIB61UQgD47yxG
> gj+LHH/7sc6Xo/8v8nypmTiM1oej07mkTrSUVreGo34Vs5jq/v6Ll8/3dsevmp1lsTA9Kl05UofJ
> 3Ge1OWWwU22Mn+wf2l/OyieSY9wrzVgXfbMZgJPLkfi7kXzfH9a5ycOFaQ521AYAzqpzkf+fIvGf
> 9up8te0fvwa+ONgbv35zakM+764BwN+Gadmu+N/F+v/aJFzbmL+5Odzu9n45flaeg0+HceMP5isA
> wFGdyP+VNk3rfF05AGAulNG+RP7feVyl5eaPuylVh7cAwJlzIfK/F/l/+3Odm/fK09LXXJzgufmP
> ybYPAMy8n4I53nYAQAEA
>
> Fix
>
> Check the size of the structure that is read afterwards:
>
> --- a/fs/udf/super.c
> +++ b/fs/udf/super.c
> @@ -1619,7 +1619,8 @@ static bool udf_lvid_valid(struct super_block *sb,
> 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)
> + 2 * parts * sizeof(u32) +
> + sizeof(struct logicalVolIntegrityDescImpUse) > sb->s_blocksize)
> return false;
> return true;
> }
>
> With this applied the same image is rejected and mount fails with
> -EINVAL; no KASAN report.
>
> Tested on 7.3-rc7. The same image also crashes 7.2-rc5.
>
> Found by syzkaller-style fuzzing of mounted filesystem images.
>
> Signed-off-by: yangguikun <yangguikun19@xxxxxx>
>
--
Jan Kara <jack@xxxxxxxx>
SUSE Labs, CR