]> Projects (at) Tadryanom (dot) Me - AdrOS.git/commitdiff
fs: harden fat and ext2 mount validation
authorTulio A M Mendes <[email protected]>
Sat, 6 Jun 2026 18:44:22 +0000 (15:44 -0300)
committerTulio A M Mendes <[email protected]>
Sat, 6 Jun 2026 18:44:22 +0000 (15:44 -0300)
src/kernel/ext2.c
src/kernel/fat.c
tests/test_utils.c

index ca2dd10d877e3f9bf028afe6c360191bcc262f1f..894b66aad1177ab55027ca4ea5613666adbd24b1 100644 (file)
@@ -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;
     }
index 301d167f800d401c92108bf0cf991ff6e62a1e11..f3fe9be15d79d101e48a56c9b27c67ad88ba0b02 100644 (file)
@@ -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) {
index 5332f866c74de3849bfa5fec0280b135706a7fb4..90e035a5e8965cc2ecff9dedf46926acf945127c 100644 (file)
@@ -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);