mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] fat: calculate data area start without overflow
@ 2026-09-02 10:57 hengyul
  2026-09-02 12:03 ` OGAWA Hirofumi
  0 siblings, 1 reply; 3+ messages in thread
From: hengyul @ 2026-09-02 10:57 UTC (permalink / raw)
  To: hirofumi; +Cc: linux-kernel, Hengyu Liang

From: Hengyu Liang <hengyul@cs.unc.edu>

On 32-bit architectures, sbi->fat_length, sbi->dir_start and
sbi->data_start are unsigned long. The number of FATs is an 8-bit BPB
field, while the FAT32 length is a 32-bit BPB field. Therefore, the
calculation

    sbi->fat_start + sbi->fats * sbi->fat_length

can wrap before data_start is checked against total_sectors.

For example, with fat_start=32, fats=2 and fat_length=0x80000001,
the unwrapped data area start is 0x100000022 (4294967330), but the
calculation wraps to 34 on i386. With total_sectors=36, the
validation then incorrectly passes.

The following script creates an image that demonstrates the problem:

    python3 - <<'PY'
    import struct

    S = 512
    b = bytearray(36 * S)

    def p(off, fmt, value):
        struct.pack_into(fmt, b, off, value)

    # FAT32 BPB
    b[0:3] = b'\xeb\x58\x90'
    b[3:11] = b'MSWIN4.1'
    p(11, '<H', 512)
    b[13] = 1
    p(14, '<H', 32)             # reserved sectors
    b[16] = 2                   # number of FATs
    p(17, '<H', 0)
    p(19, '<H', 0)
    b[21] = 0xf8
    p(22, '<H', 0)
    p(24, '<H', 1)
    p(26, '<H', 1)
    p(32, '<I', 36)             # total sectors
    p(36, '<I', 0x80000001)     # FAT32 length
    p(44, '<I', 2)              # root cluster
    p(48, '<H', 1)              # FSINFO sector
    b[64] = 0x80
    b[66] = 0x29
    p(67, '<I', 0x12345678)
    b[71:82] = b'OVERFLOW   '
    b[82:90] = b'FAT32   '
    b[510:512] = b'\x55\xaa'

    # FSINFO
    p(S, '<I', 0x41615252)
    p(S + 484, '<I', 0x61417272)
    p(S + 488, '<I', 0xffffffff)
    p(S + 492, '<I', 0xffffffff)

    # Wrapped FAT starts at sector 32
    fat = 32 * S
    p(fat + 8, '<I', 0x0fffffff)   # FAT[2]
    p(fat + 12, '<I', 0x0fffffff)  # FAT[3]

    # Wrapped data_start == 34
    root = 34 * S
    b[root:root + 11] = b'ESCAPE  TXT'
    b[root + 11] = 0x20
    p(root + 26, '<H', 3)
    p(root + 28, '<I', 4)
    b[35 * S:35 * S + 4] = b'OOB!'

    open('fat-overflow.img', 'wb').write(b)
    PY

Attach fat-overflow.img as /dev/sdb and run:

    mount -t vfat -o ro /dev/sdb /mnt
    cat /mnt/ESCAPE.TXT

On an unpatched i386 kernel, the mount succeeds and reading the file
returns:

    OOB!

On an x86-64 kernel and on a patched i386 kernel, mounting is rejected
with:

    mount: mounting /dev/sdb on /mnt failed: Invalid argument

Calculate dir_start and data_start in u64 using mul_u32_u32(), and
perform the total_sectors check before storing them in the existing
unsigned long fields. Since a successful check guarantees that
data_start, and therefore dir_start, does not exceed the u32
total_sectors value, assigning them to unsigned long is safe on both
32-bit and 64-bit architectures.

Signed-off-by: Hengyu Liang <hengyul@cs.unc.edu>
---
 fs/fat/inode.c | 15 ++++++++++-----
 1 file changed, 10 insertions(+), 5 deletions(-)

diff --git a/fs/fat/inode.c b/fs/fat/inode.c
index f775a004cae1..b09185d204ce 100644
--- a/fs/fat/inode.c
+++ b/fs/fat/inode.c
@@ -20,6 +20,7 @@
 #include <linux/blkdev.h>
 #include <linux/backing-dev.h>
 #include <linux/unaligned.h>
+#include <linux/math64.h>
 #include <linux/random.h>
 #include <linux/iversion.h>
 #include <linux/fs_struct.h>
@@ -1577,6 +1578,7 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc,
 	struct msdos_sb_info *sbi;
 	u16 logical_sector_size;
 	u32 total_sectors, total_clusters, fat_clusters, rootdir_sectors;
+	u64 dir_start, data_start;
 	long error;
 	char buf[50];
 	struct timespec64 ts;
@@ -1752,7 +1754,6 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc,
 	sbi->dir_per_block = sb->s_blocksize / sizeof(struct msdos_dir_entry);
 	sbi->dir_per_block_bits = ffs(sbi->dir_per_block) - 1;
 
-	sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length;
 	sbi->dir_entries = bpb.fat_dir_entries;
 	if (sbi->dir_entries & (sbi->dir_per_block - 1)) {
 		if (!silent)
@@ -1763,19 +1764,23 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc,
 
 	rootdir_sectors = sbi->dir_entries
 		* sizeof(struct msdos_dir_entry) / sb->s_blocksize;
-	sbi->data_start = sbi->dir_start + rootdir_sectors;
+	dir_start = sbi->fat_start +
+		mul_u32_u32(sbi->fats, sbi->fat_length);
+	data_start = dir_start + rootdir_sectors;
 	total_sectors = bpb.fat_sectors;
 	if (total_sectors == 0)
 		total_sectors = bpb.fat_total_sect;
 
-	if (total_sectors < sbi->data_start) {
+	if (total_sectors < data_start) {
 		if (!silent)
 			fat_msg(sb, KERN_ERR,
-				"data area starts beyond volume (%lu > %u)",
-				sbi->data_start, total_sectors);
+				"data area starts beyond volume (%llu > %u)",
+				(llu)data_start, total_sectors);
 		goto out_invalid;
 	}
 
+	sbi->dir_start = dir_start;
+	sbi->data_start = data_start;
 	total_clusters = (total_sectors - sbi->data_start) / sbi->sec_per_clus;
 
 	if (!is_fat32(sbi))

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] fat: calculate data area start without overflow
  2026-09-02 10:57 [PATCH] fat: calculate data area start without overflow hengyul
@ 2026-09-02 12:03 ` OGAWA Hirofumi
  2026-09-02 15:39   ` David Laight
  0 siblings, 1 reply; 3+ messages in thread
From: OGAWA Hirofumi @ 2026-09-02 12:03 UTC (permalink / raw)
  To: hengyul; +Cc: linux-kernel

hengyul@cs.unc.edu writes:

> From: Hengyu Liang <hengyul@cs.unc.edu>
>
> On 32-bit architectures, sbi->fat_length, sbi->dir_start and
> sbi->data_start are unsigned long. The number of FATs is an 8-bit BPB
> field, while the FAT32 length is a 32-bit BPB field. Therefore, the
> calculation
>
>     sbi->fat_start + sbi->fats * sbi->fat_length
>
> can wrap before data_start is checked against total_sectors.

[...]
> -	sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length;
>  	sbi->dir_entries = bpb.fat_dir_entries;
>  	if (sbi->dir_entries & (sbi->dir_per_block - 1)) {
>  		if (!silent)
> @@ -1763,19 +1764,23 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc,
>  
>  	rootdir_sectors = sbi->dir_entries
>  		* sizeof(struct msdos_dir_entry) / sb->s_blocksize;
> -	sbi->data_start = sbi->dir_start + rootdir_sectors;
> +	dir_start = sbi->fat_start +
> +		mul_u32_u32(sbi->fats, sbi->fat_length);
> +	data_start = dir_start + rootdir_sectors;

Maybe, since u32 overflow is always bigger than total_sectors, so we
should use check_*_overflow() instead, and detect early?

Thanks.
-- 
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH] fat: calculate data area start without overflow
  2026-09-02 12:03 ` OGAWA Hirofumi
@ 2026-09-02 15:39   ` David Laight
  0 siblings, 0 replies; 3+ messages in thread
From: David Laight @ 2026-09-02 15:39 UTC (permalink / raw)
  To: OGAWA Hirofumi; +Cc: hengyul, linux-kernel

On Wed, 02 Sep 2026 21:03:26 +0900
OGAWA Hirofumi <hirofumi@mail.parknet.co.jp> wrote:

> hengyul@cs.unc.edu writes:
> 
> > From: Hengyu Liang <hengyul@cs.unc.edu>
> >
> > On 32-bit architectures, sbi->fat_length, sbi->dir_start and
> > sbi->data_start are unsigned long. The number of FATs is an 8-bit BPB
> > field, while the FAT32 length is a 32-bit BPB field. Therefore, the
> > calculation
> >
> >     sbi->fat_start + sbi->fats * sbi->fat_length
> >
> > can wrap before data_start is checked against total_sectors.  
> 
> [...]
> > -	sbi->dir_start = sbi->fat_start + sbi->fats * sbi->fat_length;
> >  	sbi->dir_entries = bpb.fat_dir_entries;
> >  	if (sbi->dir_entries & (sbi->dir_per_block - 1)) {
> >  		if (!silent)
> > @@ -1763,19 +1764,23 @@ int fat_fill_super(struct super_block *sb, struct fs_context *fc,
> >  
> >  	rootdir_sectors = sbi->dir_entries
> >  		* sizeof(struct msdos_dir_entry) / sb->s_blocksize;
> > -	sbi->data_start = sbi->dir_start + rootdir_sectors;
> > +	dir_start = sbi->fat_start +
> > +		mul_u32_u32(sbi->fats, sbi->fat_length);
> > +	data_start = dir_start + rootdir_sectors;  
> 
> Maybe, since u32 overflow is always bigger than total_sectors, so we
> should use check_*_overflow() instead, and detect early?

Why do the work twice?

David

> 
> Thanks.


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-02 15:39 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-02 10:57 [PATCH] fat: calculate data area start without overflow hengyul
2026-09-02 12:03 ` OGAWA Hirofumi
2026-09-02 15:39   ` David Laight

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®