diff options
author | Tom Rini <trini@konsulko.com> | 2023-11-28 20:10:36 -0500 |
---|---|---|
committer | Tom Rini <trini@konsulko.com> | 2023-11-28 20:10:36 -0500 |
commit | e2a9edc0ced9c2ea6b3c23f2cd93b455985eb173 (patch) | |
tree | 9133046b7873d653a6347312e67f96f221215324 /fs | |
parent | 38cc6cdeb104d4835a73b8946b31e30ce762ddeb (diff) | |
parent | 8154ac16136489b19e1f1ec717611232cba249db (diff) | |
download | u-boot-e2a9edc0ced9c2ea6b3c23f2cd93b455985eb173.zip u-boot-e2a9edc0ced9c2ea6b3c23f2cd93b455985eb173.tar.gz u-boot-e2a9edc0ced9c2ea6b3c23f2cd93b455985eb173.tar.bz2 |
Merge patch series "fs: fat: calculate FAT type based on cluster count"
To quote the author:
This series fixes an issue where the FAT type (FAT12, FAT16) is not
correctly detected, e.g. when the BPB field BS_FilSysType contains the
valid value "FAT ".
This issue occures, for example, if a partition is formatted by
swupdate using its diskformat handler. swupdate uses the FAT library
from http://elm-chan.org/fsw/ff/ internally.
See https://groups.google.com/g/swupdate/c/7Yc3NupjXx8 for a
discussion in the swupdate mailing list.
Please refer to the commit messages for more details.
1. Added bootsector checks
Most tests from https://www.win.tue.nl/~aeb/linux/fs/fat/fat-2.html
are added in the commit 'fs: fat: add bootsector validity check'.
Only the tests VIII, IX and X are not implemented.
I also checked the Linux kernel code (v6.6) and did not find any
checks on 'vistart->fs_type'. This is the reason why is skipped them
here.
See section '2. Size comparisons' for the impact on the binary size.
2. Size comparisons
I executed bloat-o-meter from the Linux kernel for an arm64
target (config xilinx_zynqmp_mini_emmc0_defconfig):
Comparison of the binary spl/u-boot-spl between master (rev
e17d174773e9ba9447596708e702b7382e47a6cf) and this patch
series (including the added validity checks of the boot sector):
add/remove: 0/0 grow/shrink: 1/1 up/down: 100/-12 (88)
Function old new delta
read_bootsectandvi 308 408 +100
fat_itr_root 444 432 -12
Total: Before=67977, After=68065, chg +0.13%
When compare the size of the binary spl/u-boot-spl between master this
series without the the validity checks of the boot sector:
add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-24 (-24)
Function old new delta
read_bootsectandvi 308 296 -12
fat_itr_root 444 432 -12
Total: Before=67977, After=67953, chg -0.04%
So the size of the spl on this arm64 target increases by 88 bytes for
this series. When i remove the validity check the size decreases by 24 bytes.
Diffstat (limited to 'fs')
-rw-r--r-- | fs/fat/fat.c | 114 |
1 files changed, 87 insertions, 27 deletions
diff --git a/fs/fat/fat.c b/fs/fat/fat.c index 8ff1fd0..14e53cf 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -18,12 +18,17 @@ #include <fs.h> #include <log.h> #include <asm/byteorder.h> +#include <asm/unaligned.h> #include <part.h> #include <malloc.h> #include <memalign.h> #include <asm/cache.h> #include <linux/compiler.h> #include <linux/ctype.h> +#include <linux/log2.h> + +/* maximum number of clusters for FAT12 */ +#define MAX_FAT12 0xFF4 /* * Convert a string to lowercase. Converts at most 'len' characters, @@ -484,6 +489,73 @@ static __u8 mkcksum(struct nameext *nameext) } /* + * Determine if the FAT type is FAT12 or FAT16 + * + * Based on fat_fill_super() from the Linux kernel's fs/fat/inode.c + */ +static int determine_legacy_fat_bits(const boot_sector *bs) +{ + u16 fat_start = bs->reserved; + u32 dir_start = fat_start + bs->fats * bs->fat_length; + u32 rootdir_sectors = get_unaligned_le16(bs->dir_entries) * + sizeof(dir_entry) / + get_unaligned_le16(bs->sector_size); + u32 data_start = dir_start + rootdir_sectors; + u16 sectors = get_unaligned_le16(bs->sectors); + u32 total_sectors = sectors ? sectors : bs->total_sect; + u32 total_clusters = (total_sectors - data_start) / + bs->cluster_size; + + return (total_clusters > MAX_FAT12) ? 16 : 12; +} + +/* + * Determines if the boot sector's media field is valid + * + * Based on fat_valid_media() from Linux kernel's include/linux/msdos_fs.h + */ +static int fat_valid_media(u8 media) +{ + return media >= 0xf8 || media == 0xf0; +} + +/* + * Determines if the given boot sector is valid + * + * Based on fat_read_bpb() from the Linux kernel's fs/fat/inode.c + */ +static int is_bootsector_valid(const boot_sector *bs) +{ + u16 sector_size = get_unaligned_le16(bs->sector_size); + u16 dir_per_block = sector_size / sizeof(dir_entry); + + if (!bs->reserved) + return 0; + + if (!bs->fats) + return 0; + + if (!fat_valid_media(bs->media)) + return 0; + + if (!is_power_of_2(sector_size) || + sector_size < 512 || + sector_size > 4096) + return 0; + + if (!is_power_of_2(bs->cluster_size)) + return 0; + + if (!bs->fat_length && !bs->fat32_length) + return 0; + + if (get_unaligned_le16(bs->dir_entries) & (dir_per_block - 1)) + return 0; + + return 1; +} + +/* * Read boot sector and volume info from a FAT filesystem */ static int @@ -506,7 +578,8 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize) if (disk_read(0, 1, block) < 0) { debug("Error: reading block\n"); - goto fail; + ret = -1; + goto out_free; } memcpy(bs, block, sizeof(boot_sector)); @@ -516,8 +589,14 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize) bs->heads = FAT2CPU16(bs->heads); bs->total_sect = FAT2CPU32(bs->total_sect); + if (!is_bootsector_valid(bs)) { + debug("Error: bootsector is invalid\n"); + ret = -1; + goto out_free; + } + /* FAT32 entries */ - if (bs->fat_length == 0) { + if (!bs->fat_length && bs->fat32_length) { /* Assume FAT32 */ bs->fat32_length = FAT2CPU32(bs->fat32_length); bs->flags = FAT2CPU16(bs->flags); @@ -528,28 +607,11 @@ read_bootsectandvi(boot_sector *bs, volume_info *volinfo, int *fatsize) *fatsize = 32; } else { vistart = (volume_info *)&(bs->fat32_length); - *fatsize = 0; + *fatsize = determine_legacy_fat_bits(bs); } memcpy(volinfo, vistart, sizeof(volume_info)); - if (*fatsize == 32) { - if (strncmp(FAT32_SIGN, vistart->fs_type, SIGNLEN) == 0) - goto exit; - } else { - if (strncmp(FAT12_SIGN, vistart->fs_type, SIGNLEN) == 0) { - *fatsize = 12; - goto exit; - } - if (strncmp(FAT16_SIGN, vistart->fs_type, SIGNLEN) == 0) { - *fatsize = 16; - goto exit; - } - } - - debug("Error: broken fs_type sign\n"); -fail: - ret = -1; -exit: +out_free: free(block); return ret; } @@ -571,7 +633,7 @@ static int get_fs_info(fsdata *mydata) mydata->total_sect = bs.total_sect; } else { mydata->fatlength = bs.fat_length; - mydata->total_sect = (bs.sectors[1] << 8) + bs.sectors[0]; + mydata->total_sect = get_unaligned_le16(bs.sectors); if (!mydata->total_sect) mydata->total_sect = bs.total_sect; } @@ -583,7 +645,7 @@ static int get_fs_info(fsdata *mydata) mydata->rootdir_sect = mydata->fat_sect + mydata->fatlength * bs.fats; - mydata->sect_size = (bs.sector_size[1] << 8) + bs.sector_size[0]; + mydata->sect_size = get_unaligned_le16(bs.sector_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", @@ -607,8 +669,7 @@ static int get_fs_info(fsdata *mydata) (mydata->clust_size * 2); mydata->root_cluster = bs.root_cluster; } else { - mydata->rootdir_size = ((bs.dir_entries[1] * (int)256 + - bs.dir_entries[0]) * + mydata->rootdir_size = (get_unaligned_le16(bs.dir_entries) * sizeof(dir_entry)) / mydata->sect_size; mydata->data_begin = mydata->rootdir_sect + @@ -1157,9 +1218,8 @@ int file_fat_detectfs(void) memcpy(vol_label, volinfo.volume_label, 11); vol_label[11] = '\0'; - volinfo.fs_type[5] = '\0'; - printf("Filesystem: %s \"%s\"\n", volinfo.fs_type, vol_label); + printf("Filesystem: FAT%d \"%s\"\n", fatsize, vol_label); return 0; } |