From 481ea191c83c0b1e7f2d4f80f522817e9d3f7728 Mon Sep 17 00:00:00 2001 From: Tulio A M Mendes Date: Sat, 6 Jun 2026 15:44:22 -0300 Subject: [PATCH] fs: harden fat and ext2 mount validation --- src/kernel/ext2.c | 50 ++++++++++++++++++++++++-------- src/kernel/fat.c | 44 +++++++++++++++++++++++----- tests/test_utils.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+), 19 deletions(-) diff --git a/src/kernel/ext2.c b/src/kernel/ext2.c index ca2dd10d..894b66aa 100644 --- a/src/kernel/ext2.c +++ b/src/kernel/ext2.c @@ -1472,6 +1472,12 @@ vfs_mount_result_t ext2_mount(block_device_t* bdev, uint32_t partition_lba) { return result; } + if (sb.s_log_block_size > 2) { + kprintf("[EXT2] Invalid s_log_block_size: %u\n", sb.s_log_block_size); + kfree(em); + return result; + } + em->block_size = 1024U << sb.s_log_block_size; /* Validate block_size is power of 2 and >= 1024 */ if (em->block_size < 1024 || em->block_size > 4096 || @@ -1487,6 +1493,19 @@ vfs_mount_result_t ext2_mount(block_device_t* bdev, uint32_t partition_lba) { em->total_blocks = sb.s_blocks_count; em->total_inodes = sb.s_inodes_count; + if (em->total_blocks == 0 || em->total_inodes == 0) { + kprintf("[EXT2] Invalid filesystem size: blocks=%u inodes=%u\n", + em->total_blocks, em->total_inodes); + kfree(em); + return result; + } + if (em->first_data_block >= em->total_blocks) { + kprintf("[EXT2] Invalid first_data_block %u for total blocks %u\n", + em->first_data_block, em->total_blocks); + kfree(em); + return result; + } + if (sb.s_rev_level >= 1 && sb.s_inode_size != 0) { em->inode_size = sb.s_inode_size; /* Validate inode size is reasonable */ @@ -1505,27 +1524,34 @@ vfs_mount_result_t ext2_mount(block_device_t* bdev, uint32_t partition_lba) { kfree(em); return result; } - em->num_groups = (sb.s_blocks_count + sb.s_blocks_per_group - 1) / sb.s_blocks_per_group; + uint64_t num_groups64 = ((uint64_t)sb.s_blocks_count + (uint64_t)sb.s_blocks_per_group - 1ULL) / + (uint64_t)sb.s_blocks_per_group; + if (num_groups64 == 0 || num_groups64 > 65536ULL) { + kprintf("[EXT2] Invalid num_groups: %u\n", (uint32_t)num_groups64); + kfree(em); + return result; + } + em->num_groups = (uint32_t)num_groups64; /* Read Group Descriptor Table */ - /* Validate gdt_bytes doesn't overflow */ - if (em->num_groups > 65536) { /* Reasonable limit for num_groups */ - kprintf("[EXT2] Invalid num_groups: %u\n", em->num_groups); + uint64_t gdt_bytes64 = (uint64_t)em->num_groups * (uint64_t)sizeof(struct ext2_group_desc); + if (gdt_bytes64 == 0 || gdt_bytes64 > 16ULL * 1024ULL * 1024ULL) { + kprintf("[EXT2] GDT too large: %u bytes\n", (uint32_t)gdt_bytes64); kfree(em); return result; } - em->gdt_blocks = (em->num_groups * (uint32_t)sizeof(struct ext2_group_desc) + - em->block_size - 1) / em->block_size; - uint32_t gdt_bytes = em->num_groups * (uint32_t)sizeof(struct ext2_group_desc); - /* Validate gdt_bytes doesn't overflow and is reasonable */ - if (gdt_bytes > 16 * 1024 * 1024) { /* Max 16MB for GDT */ - kprintf("[EXT2] GDT too large: %u bytes\n", gdt_bytes); + uint32_t gdt_bytes = (uint32_t)gdt_bytes64; + uint64_t gdt_blocks64 = (gdt_bytes64 + (uint64_t)em->block_size - 1ULL) / (uint64_t)em->block_size; + if (gdt_blocks64 == 0 || gdt_blocks64 > em->total_blocks) { + kprintf("[EXT2] GDT blocks %u exceed total blocks %u\n", (uint32_t)gdt_blocks64, em->total_blocks); kfree(em); return result; } + em->gdt_blocks = (uint32_t)gdt_blocks64; /* Validate gdt_blocks against device size */ - if (em->gdt_blocks > em->total_blocks) { - kprintf("[EXT2] GDT blocks %u exceed total blocks %u\n", em->gdt_blocks, em->total_blocks); + uint64_t gdt_end_block = (uint64_t)em->first_data_block + 1ULL + gdt_blocks64; + if (gdt_end_block > em->total_blocks) { + kprintf("[EXT2] GDT end %u exceeds total blocks %u\n", (uint32_t)gdt_end_block, em->total_blocks); kfree(em); return result; } diff --git a/src/kernel/fat.c b/src/kernel/fat.c index 301d167f..f3fe9be1 100644 --- a/src/kernel/fat.c +++ b/src/kernel/fat.c @@ -268,6 +268,11 @@ static uint32_t fat_eoc_mark(struct fat_mount* fm) { return 0x0FFFFFFF; } +static int fat_cluster_valid(struct fat_mount* fm, uint32_t cluster) { + if (!fm || fm->total_clusters == 0) return 0; + return cluster >= 2 && (uint64_t)cluster < (uint64_t)fm->total_clusters + 2ULL; +} + static uint32_t fat_cluster_to_lba(struct fat_mount* fm, uint32_t cluster) { return fm->data_lba + (cluster - 2) * fm->sectors_per_cluster; } @@ -280,18 +285,19 @@ static uint32_t fat_cluster_size(struct fat_mount* fm) { static uint32_t fat_follow_chain(struct fat_mount* fm, uint32_t start, uint32_t n) { uint32_t c = start; for (uint32_t i = 0; i < n; i++) { - if (c < 2 || fat_is_eoc(fm, c)) return 0; + if (!fat_cluster_valid(fm, c) || fat_is_eoc(fm, c)) return 0; c = fat_get_entry(fm, c); } - return (c >= 2 && !fat_is_eoc(fm, c)) ? c : (n == 0 ? start : 0); + if (n == 0) return fat_cluster_valid(fm, start) ? start : 0; + return (fat_cluster_valid(fm, c) && !fat_is_eoc(fm, c)) ? c : 0; } /* Count clusters in chain. */ static uint32_t fat_chain_length(struct fat_mount* fm, uint32_t start) { - if (start < 2) return 0; + if (!fat_cluster_valid(fm, start)) return 0; uint32_t count = 0; uint32_t c = start; - while (c >= 2 && !fat_is_eoc(fm, c) && count < fm->total_clusters) { + while (fat_cluster_valid(fm, c) && !fat_is_eoc(fm, c) && count < fm->total_clusters) { count++; c = fat_get_entry(fm, c); } @@ -360,12 +366,18 @@ static uint32_t fat_extend_chain(struct fat_mount* fm, uint32_t start, uint32_t /* Free a cluster chain starting at 'start'. */ static void fat_free_chain(struct fat_mount* fm, uint32_t start) { uint32_t c = start; - while (c >= 2 && !fat_is_eoc(fm, c)) { + uint32_t steps = 0; + while (fat_cluster_valid(fm, c) && !fat_is_eoc(fm, c) && steps < fm->total_clusters) { uint32_t next = fat_get_entry(fm, c); (void)fat_set_entry(fm, c, 0); + if (!fat_cluster_valid(fm, next) || fat_is_eoc(fm, next)) { + c = next; + break; + } c = next; + steps++; } - if (c >= 2) { + if (fat_cluster_valid(fm, c)) { (void)fat_set_entry(fm, c, 0); } } @@ -1263,8 +1275,20 @@ vfs_mount_result_t fat_mount(block_device_t* bdev, uint32_t partition_lba) { /* Total data sectors & cluster count determine FAT type */ uint32_t total_sectors = bpb->total_sectors_16 ? bpb->total_sectors_16 : bpb->total_sectors_32; - uint32_t data_sectors = total_sectors - (fm->data_lba - partition_lba); + uint32_t used_sectors = fm->data_lba - partition_lba; + if (used_sectors >= total_sectors) { + kprintf("[FAT] Invalid layout: used sectors %u exceed total sectors %u\n", + used_sectors, total_sectors); + kfree(fm); + return result; + } + uint32_t data_sectors = total_sectors - used_sectors; fm->total_clusters = data_sectors / fm->sectors_per_cluster; + if (fm->total_clusters == 0) { + kprintf("[FAT] Invalid cluster count: 0\n"); + kfree(fm); + return result; + } /* Microsoft FAT spec: type is determined by cluster count */ if (fm->total_clusters < 4085) { @@ -1275,6 +1299,12 @@ vfs_mount_result_t fat_mount(block_device_t* bdev, uint32_t partition_lba) { fm->type = FAT_TYPE_32; } + if (fm->type == FAT_TYPE_32 && !fat_cluster_valid(fm, fm->root_cluster)) { + kprintf("[FAT] Invalid FAT32 root_cluster %u\n", fm->root_cluster); + kfree(fm); + return result; + } + /* Build root node */ struct fat_node* root = (struct fat_node*)kmalloc(sizeof(struct fat_node)); if (!root) { diff --git a/tests/test_utils.c b/tests/test_utils.c index 5332f866..90e035a5 100644 --- a/tests/test_utils.c +++ b/tests/test_utils.c @@ -119,6 +119,48 @@ static int init_resolve_mount_device_test(const char* device, block_device_t** b return 0; } +static int fat_mount_layout_valid_test(uint32_t partition_lba, + uint32_t data_lba, + uint32_t total_sectors, + uint8_t sectors_per_cluster, + int is_fat32, + uint32_t root_cluster) { + if (sectors_per_cluster == 0) return -1; + uint32_t used_sectors = data_lba - partition_lba; + if (used_sectors >= total_sectors) return -1; + uint32_t data_sectors = total_sectors - used_sectors; + uint32_t total_clusters = data_sectors / sectors_per_cluster; + if (total_clusters == 0) return -1; + if (is_fat32) { + if (root_cluster < 2) return -1; + if ((uint64_t)root_cluster >= (uint64_t)total_clusters + 2ULL) return -1; + } + return 0; +} + +static int ext2_mount_geometry_valid_test(uint32_t s_log_block_size, + uint32_t total_blocks, + uint32_t total_inodes, + uint32_t first_data_block, + uint32_t blocks_per_group) { + if (blocks_per_group == 0) return -1; + if (s_log_block_size > 2) return -1; + uint32_t block_size = 1024U << s_log_block_size; + if (block_size < 1024 || block_size > 4096 || + (block_size & (block_size - 1)) != 0) return -1; + if (total_blocks == 0 || total_inodes == 0) return -1; + if (first_data_block >= total_blocks) return -1; + uint64_t num_groups = ((uint64_t)total_blocks + (uint64_t)blocks_per_group - 1ULL) / + (uint64_t)blocks_per_group; + if (num_groups == 0 || num_groups > 65536ULL) return -1; + uint64_t gdt_bytes = num_groups * 32ULL; + if (gdt_bytes == 0 || gdt_bytes > 16ULL * 1024ULL * 1024ULL) return -1; + uint64_t gdt_blocks = (gdt_bytes + (uint64_t)block_size - 1ULL) / (uint64_t)block_size; + if (gdt_blocks == 0 || gdt_blocks > total_blocks) return -1; + if ((uint64_t)first_data_block + 1ULL + gdt_blocks > total_blocks) return -1; + return 0; +} + /* ---- Minimal test framework ---- */ static int g_tests_run = 0; static int g_tests_passed = 0; @@ -989,6 +1031,30 @@ TEST(resolve_mount_device_unknown) { ASSERT_EQ(init_resolve_mount_device_test("/dev/doesnotexist", &bdev, &lba), -1); } +TEST(fat_layout_rejects_used_sectors_overflow) { + ASSERT_EQ(fat_mount_layout_valid_test(2048, 4096, 2048, 1, 0, 0), -1); +} + +TEST(fat_layout_rejects_zero_clusters) { + ASSERT_EQ(fat_mount_layout_valid_test(2048, 2049, 2, 4, 0, 0), -1); +} + +TEST(fat_layout_rejects_invalid_fat32_root_cluster) { + ASSERT_EQ(fat_mount_layout_valid_test(2048, 4096, 16384, 1, 1, 20000), -1); +} + +TEST(ext2_geometry_rejects_invalid_log_block_size) { + ASSERT_EQ(ext2_mount_geometry_valid_test(3, 8192, 1024, 1, 8192), -1); +} + +TEST(ext2_geometry_rejects_invalid_first_data_block) { + ASSERT_EQ(ext2_mount_geometry_valid_test(1, 4096, 1024, 4096, 1024), -1); +} + +TEST(ext2_geometry_rejects_gdt_end_overflow) { + ASSERT_EQ(ext2_mount_geometry_valid_test(0, 64, 128, 63, 1), -1); +} + /* ======== MAIN ======== */ int main(void) { printf("\n=========================================\n"); @@ -1093,6 +1159,12 @@ int main(void) { RUN(resolve_mount_device_blockdev_devpath); RUN(resolve_mount_device_partition); RUN(resolve_mount_device_unknown); + RUN(fat_layout_rejects_used_sectors_overflow); + RUN(fat_layout_rejects_zero_clusters); + RUN(fat_layout_rejects_invalid_fat32_root_cluster); + RUN(ext2_geometry_rejects_invalid_log_block_size); + RUN(ext2_geometry_rejects_invalid_first_data_block); + RUN(ext2_geometry_rejects_gdt_end_overflow); printf("\n %d/%d passed, %d failed\n", g_tests_passed, g_tests_run, g_tests_failed); -- 2.43.0