Merge patch series "fs: fat: Handle 'FAT sector size mismatch'"

Varadarajan Narayanan <varadarajan.narayanan@oss.qualcomm.com> says:

The disk_read() and disk_write() functions of the FAT driver use the
blk_dread() and blk_dwrite() respectively. The blk_* APIs read and write
to the devices in terms of the device block size. However, the FAT
driver reads in terms of the device block size (from fat_set_blk_dev and
read_bootsectandvi) and sector size in the rest of the places.

This causes buffer overflows or partial reads when the FAT sector size
is not equal to device block size. Fix this by using blk_dread in
fat_set_blk_dev and read_bootsectandvi instead of disk_read. And update
disk_read/disk_write to handle FAT sector size and block size mismatch.

Tested on
	blksz | FAT sector size
	------+----------------
	4096  | 4096
	512   | 512
	4096  | 512
	512   | 4096

CI test results
---------------
	https://github.com/u-boot/u-boot/pull/871
	All checks have passed
	93 successful checks
	No conflicts with base branch

Code size change info
---------------------
       arm: (for 1/1 boards) all +32.0 text +32.0
            qemu_arm       : all +32 text +32
               u-boot: add: 0/0, grow: 2/0 bytes: 24/0 (24)
                 function                                   old     new   delta
                 read_bootsectandvi                         420     432     +12
                 fat_set_blk_dev                            204     216     +12

   aarch64: (for 1/1 boards) all +12.0 rodata -8.0 text +20.0
            qemu_arm64     : all +12 rodata -8 text +20
               u-boot: add: 0/0, grow: 2/0 bytes: 20/0 (20)
                 function                                   old     new   delta
                 read_bootsectandvi                         408     420     +12
                 fat_set_blk_dev                            204     212      +8

   aarch64: (for 1/1 boards) all -2.0 data -8.0 rodata +6.0
            qcom_qcs9100   : all -2 data -8 rodata +6
               u-boot: add: 1/-1, grow: 8/-1 bytes: 708/-224 (484)
                 function                                   old     new   delta
                 disk_rw                                      -     628    +628
                 read_bootsectandvi                         408     428     +20
                 fat_itr_root                               500     520     +20
                 get_cluster                                376     388     +12
                 set_contents                              2076    2084      +8
                 fat_set_blk_dev                            204     212      +8
                 static.set_fatent_value                    536     540      +4
                 get_fatent                                 420     424      +4
                 fat_next_cluster                           368     372      +4
                 disk_read                                  100       -    -100
                 disk_write                                 132       8    -124

Link: https://lore.kernel.org/r/20260224035000.1617869-1-varadarajan.narayanan@oss.qualcomm.com
This commit is contained in:
Tom Rini 2026-03-10 11:52:16 -06:00
commit 3d252b7beb
4 changed files with 158 additions and 5 deletions

View File

@ -147,3 +147,4 @@ CONFIG_NO_FB_CLEAR=y
CONFIG_VIDEO_SIMPLE=y
CONFIG_WDT=y
CONFIG_WDT_QCOM=y
CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH=y

View File

@ -29,3 +29,11 @@ config FS_FAT_MAX_CLUSTSIZE
is the smallest amount of disk space that can be used to hold a
file. Unless you have an extremely tight memory memory constraints,
leave the default.
config FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH
bool "Handle FAT sector size mismatch"
default n
depends on FS_FAT
help
Handle filesystems on media where the hardware block size and
the sector size in the FAT metadata do not match.

View File

@ -45,11 +45,146 @@ static void downcase(char *str, size_t len)
static struct blk_desc *cur_dev;
static struct disk_partition cur_part_info;
static int fat_sect_size;
#define DOS_BOOT_MAGIC_OFFSET 0x1fe
#define DOS_FS_TYPE_OFFSET 0x36
#define DOS_FS32_TYPE_OFFSET 0x52
#if IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)
static inline __u32 sect_to_block(__u32 sect, __u32 *off)
{
const ulong blksz = cur_part_info.blksz;
*off = 0;
if (fat_sect_size && fat_sect_size < blksz) {
int div = blksz / fat_sect_size;
*off = sect % div;
return sect / div;
} else if (fat_sect_size && (fat_sect_size > blksz)) {
return sect * (fat_sect_size / blksz);
}
return sect;
}
static int disk_rw(__u32 sect, __u32 nr_sect, void *buf, bool read)
{
int ret;
__u8 *block = NULL;
__u32 rem, size, s, n;
const ulong blksz = cur_part_info.blksz;
const lbaint_t start = cur_part_info.start;
rem = nr_sect * fat_sect_size;
/*
* block N block N + 1 block N + 2
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | | | | |s|e|c|t|o|r| | |s|e|c|t|o|r| | |s|e|c|t|o|r| | | | |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* . . . | | | | . . .
* ------+---------------+---------------+---------------+------
* |<--- FAT reads in sectors --->|
*
* | part 1 | part 2 | part 3 |
*
*/
/* Do part 1 */
if (fat_sect_size) {
__u32 offset;
/* Read one block and overwrite the leading sectors */
block = malloc_cache_aligned(cur_dev->blksz);
if (!block) {
printf("Error: allocating block: %lu\n", cur_dev->blksz);
return -1;
}
s = sect_to_block(sect, &offset);
offset = offset * fat_sect_size;
ret = blk_dread(cur_dev, start + s, 1, block);
if (ret != 1) {
ret = -1;
goto exit;
}
if (rem > (blksz - offset))
size = blksz - offset;
else
size = rem;
if (read) {
memcpy(buf, block + offset, size);
} else {
memcpy(block + offset, buf, size);
ret = blk_dwrite(cur_dev, start + s, 1, block);
if (ret != 1) {
ret = -1;
goto exit;
}
}
rem -= size;
buf += size;
s++;
}
/* Do part 2, read/write directly to/from the given buffer */
if (rem > blksz) {
n = rem / blksz;
if (read)
ret = blk_dread(cur_dev, start + s, n, buf);
else
ret = blk_dwrite(cur_dev, start + s, n, buf);
if (ret != n) {
ret = -1;
goto exit;
}
buf += n * blksz;
rem = rem % blksz;
s += n;
}
/* Do part 3, read a block and copy the trailing sectors */
if (rem) {
ret = blk_dread(cur_dev, start + s, 1, block);
if (ret != 1) {
ret = -1;
goto exit;
}
if (read) {
memcpy(buf, block, rem);
} else {
memcpy(block, buf, rem);
ret = blk_dwrite(cur_dev, start + s, 1, block);
if (ret != 1) {
ret = -1;
goto exit;
}
}
}
exit:
if (block)
free(block);
return (ret == -1) ? -1 : nr_sect;
}
static int disk_read(__u32 sect, __u32 nr_sect, void *buf)
{
return disk_rw(sect, nr_sect, buf, true);
}
int disk_write(__u32 sect, __u32 nr_sect, void *buf)
{
return disk_rw(sect, nr_sect, buf, false);
}
#else
static int disk_read(__u32 block, __u32 nr_blocks, void *buf)
{
ulong ret;
@ -64,6 +199,7 @@ static int disk_read(__u32 block, __u32 nr_blocks, void *buf)
return ret;
}
#endif /* CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH */
int fat_set_blk_dev(struct blk_desc *dev_desc, struct disk_partition *info)
{
@ -73,7 +209,7 @@ int fat_set_blk_dev(struct blk_desc *dev_desc, struct disk_partition *info)
cur_part_info = *info;
/* Make sure it has a valid FAT header */
if (disk_read(0, 1, buffer) != 1) {
if (blk_dread(cur_dev, cur_part_info.start, 1, buffer) != 1) {
cur_dev = NULL;
return -1;
}
@ -581,7 +717,8 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize)
return -1;
}
if (disk_read(0, 1, block) < 0) {
fat_sect_size = 0;
if (blk_dread(cur_dev, cur_part_info.start, 1, block) != 1) {
debug("Error: reading block\n");
ret = -1;
goto out_free;
@ -651,11 +788,16 @@ static int get_fs_info(fsdata *mydata)
mydata->rootdir_sect = mydata->fat_sect + mydata->fatlength * bs.fats;
mydata->sect_size = get_unaligned_le16(bs.sector_size);
fat_sect_size = mydata->sect_size;
mydata->clust_size = bs.cluster_size;
if (mydata->sect_size != cur_part_info.blksz) {
log_err("FAT sector size mismatch (fs=%u, dev=%lu)\n",
mydata->sect_size, cur_part_info.blksz);
return -1;
if (!IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)) {
log_err("FAT sector size mismatch (fs=%u, dev=%lu)\n",
mydata->sect_size, cur_part_info.blksz);
return -1;
}
log_info("FAT sector size mismatch (fs=%u, dev=%lu)\n",
mydata->sect_size, cur_part_info.blksz);
}
if (mydata->clust_size == 0) {
log_err("FAT cluster size not set\n");

View File

@ -192,6 +192,7 @@ out:
}
static int total_sector;
#if !IS_ENABLED(CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH)
static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
{
ulong ret;
@ -211,6 +212,7 @@ static int disk_write(__u32 block, __u32 nr_blocks, void *buf)
return ret;
}
#endif /* CONFIG_FS_FAT_HANDLE_SECTOR_SIZE_MISMATCH */
/*
* Write fat buffer into block device