summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Rini <[email protected]>2026-03-10 11:52:16 -0600
committerTom Rini <[email protected]>2026-03-10 11:52:16 -0600
commit3d252b7beb1328cd6de012d2fa92afabe87806b5 (patch)
tree48da2d8f703f09feb9da72b7ca7894981448fc18
parentd93a63acfe701aa07c9223ec454164c88e7eb43d (diff)
parent1a935da84d16716d0974be2d8802017379035a01 (diff)
Merge patch series "fs: fat: Handle 'FAT sector size mismatch'"
Varadarajan Narayanan <[email protected]> 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/[email protected]
-rw-r--r--configs/qcom_defconfig1
-rw-r--r--fs/fat/Kconfig8
-rw-r--r--fs/fat/fat.c152
-rw-r--r--fs/fat/fat_write.c2
4 files changed, 158 insertions, 5 deletions
diff --git a/configs/qcom_defconfig b/configs/qcom_defconfig
index fe5880de1fd..baad5f09455 100644
--- a/configs/qcom_defconfig
+++ b/configs/qcom_defconfig
@@ -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
diff --git a/fs/fat/Kconfig b/fs/fat/Kconfig
index 19d52238713..9606fa48bbe 100644
--- a/fs/fat/Kconfig
+++ b/fs/fat/Kconfig
@@ -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.
diff --git a/fs/fat/fat.c b/fs/fat/fat.c
index 9ce5df59f9b..85b511f75af 100644
--- a/fs/fat/fat.c
+++ b/fs/fat/fat.c
@@ -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");
diff --git a/fs/fat/fat_write.c b/fs/fat/fat_write.c
index 0b924541187..02e006f7c9e 100644
--- a/fs/fat/fat_write.c
+++ b/fs/fat/fat_write.c
@@ -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