Critical Kernel FAT32 Out-of-Bounds Write Enables Ring 0 Code Execution
Severity: š“ CRITICAL | Component:
kernel/src/fs/fat32.c| CVE Class: CWE-787 (Out-of-Bounds Write)
Introduction
What happens when a kernel trusts data it reads from a disk? If that data is never validated against actual memory boundaries, the answer can be catastrophic: an attacker who controls the filesystem image can corrupt kernel memory, overwrite function pointers, and seize complete control of the operating system ā all without ever having elevated privileges to begin with.
This is exactly the vulnerability class patched in kernel/src/fs/fat32.c. The bug is a textbook example of why never trust input ā including input from storage devices ā is one of the most important rules in systems programming. A crafted FAT32 filesystem image could trigger out-of-bounds writes at lines 221 and 334 of the driver, ultimately allowing an attacker to overwrite a kernel function pointer and execute arbitrary code in ring 0, the CPU's most privileged execution mode.
If you write kernel code, driver code, or any low-level C that parses binary data structures, this post is required reading.
The Vulnerability Explained
What Is a FAT32 Filesystem Driver?
The FAT32 (File Allocation Table, 32-bit) filesystem is one of the oldest and most ubiquitous filesystem formats in existence. It's used on USB drives, SD cards, embedded systems, and bootable media. A kernel's FAT32 driver is responsible for reading the raw on-disk structures ā directory entries, cluster chains, file allocation tables ā and translating them into the file and directory abstractions that user programs rely on.
Because this driver runs inside the kernel, any memory corruption it causes happens in the most privileged context possible.
The Root Cause: Unvalidated On-Disk Values
The vulnerable code processed directory entry indices and cluster offsets that were read directly from on-disk FAT32 structures. Here's the critical mistake: these values were used to calculate memory addresses and perform write operations without ever checking whether the resulting address fell within a valid buffer or cluster boundary.
In simplified pseudocode, the pattern looked something like this:
// VULNERABLE: index comes directly from the on-disk structure
uint32_t entry_index = fat32_dir_entry->index; // attacker-controlled!
uint32_t cluster_offset = fat32_dir_entry->cluster; // attacker-controlled!
// No bounds check ā entry_index could be anything
kernel_buffer[entry_index] = parsed_value; // Line 221: OOB write
cluster_table[cluster_offset] = next_cluster; // Line 334: OOB write
Because entry_index and cluster_offset come from the disk, an attacker who crafts the filesystem image controls these values entirely. By choosing values that exceed the actual buffer size, the attacker can write to arbitrary kernel memory addresses.
The Exploitation Chain: From Disk to Ring 0
This vulnerability is the culmination of a privilege escalation chain (building on a related memory corruption issue, V-006). Here's how a real-world exploit would proceed:
Step 1: Craft a Malicious Filesystem Image
The attacker creates a FAT32 image where directory entries contain carefully chosen out-of-range index and cluster offset values. This is straightforward ā FAT32 is a well-documented format and tools to manipulate raw images are widely available.
[Malicious FAT32 Image]
āāā Boot Sector (valid, to pass initial checks)
āāā FAT Table (valid entries for plausible clusters)
āāā Root Directory
āāā Entry 0
āāā Name: "EXPLOIT "
āāā index: 0xFFFF1234 ā crafted to overflow into kernel .data
āāā cluster: 0xDEADBEEF ā crafted to target a function pointer
Step 2: Trigger the Kernel to Mount or Read the Image
The attacker needs the kernel to process this image. Depending on the threat model, this could mean:
- Physical access: Plugging in a crafted USB drive or SD card
- Virtual machine escape: A guest VM providing a malicious disk image to the host
- Privileged-but-sandboxed process: A container or restricted user mounting a loop device
- Automated mounting: Systems that auto-mount removable media (common in desktop Linux, embedded systems, and IoT devices)
Step 3: The Out-of-Bounds Write Fires
When the kernel's FAT32 driver processes the malicious directory entry, it calculates a write target based on the attacker-controlled index:
write_address = &kernel_buffer[0] + (entry_index * sizeof(entry))
= 0xFFFF800000123000 + (0xFFFF1234 * 8)
= 0xFFFF8000079141A0 ā somewhere in kernel .data or .text
With careful engineering of the offset value, this lands precisely on a kernel function pointer ā for example, a system call dispatch table entry or an interrupt descriptor table (IDT) handler.
Step 4: Function Pointer Overwrite ā Ring 0 Code Execution
The write operation overwrites the target function pointer with an address the attacker controls (e.g., shellcode placed in a known kernel memory location via a prior information leak, or a kernel ROP chain). The next time the kernel invokes that function ā say, the next system call or interrupt ā it jumps to the attacker's code, executing at ring 0 with full kernel privileges.
From here, the attacker can:
- Disable security features (SELinux, AppArmor, seccomp)
- Install a kernel rootkit
- Read or modify any memory in the system
- Escalate any process to root
- Persist across reboots by modifying the kernel itself
Real-World Impact
| Scenario | Impact |
|---|---|
| Malicious USB drive on a Linux desktop | Full kernel compromise on plug-in |
| Crafted VM disk image | Host kernel compromise (VM escape) |
| IoT device with auto-mount | Complete device takeover |
| Shared storage in multi-tenant environment | Cross-tenant privilege escalation |
The severity here cannot be overstated. This is not a user-space crash or a data leak ā it is a complete system compromise achievable by anyone who can get the kernel to read a crafted FAT32 image.
The Fix
What Changed
The fix adds explicit bounds validation for every directory entry index and cluster offset value parsed from on-disk FAT32 structures before those values are used in any memory operation. The principle is simple: reject any value that would result in an out-of-bounds access.
Here is the pattern of the fix applied at the vulnerable sites:
// BEFORE (vulnerable): direct use of on-disk value
uint32_t entry_index = fat32_dir_entry->index;
kernel_buffer[entry_index] = parsed_value; // OOB write possible
// AFTER (fixed): validate before use
uint32_t entry_index = fat32_dir_entry->index;
if (entry_index >= KERNEL_BUFFER_MAX_ENTRIES) {
// Log the anomaly and reject the filesystem image
pr_err("fat32: directory entry index %u exceeds buffer bounds (max %u), "
"possible filesystem corruption or attack\n",
entry_index, KERNEL_BUFFER_MAX_ENTRIES);
return -EIO; // Return an I/O error ā do not proceed
}
kernel_buffer[entry_index] = parsed_value; // Safe: index is validated
And for the cluster offset at line 334:
// BEFORE (vulnerable)
uint32_t cluster_offset = fat32_dir_entry->cluster;
cluster_table[cluster_offset] = next_cluster; // OOB write possible
// AFTER (fixed)
uint32_t cluster_offset = fat32_dir_entry->cluster;
if (cluster_offset < FAT32_FIRST_VALID_CLUSTER ||
cluster_offset > fat32_volume->total_clusters) {
pr_err("fat32: cluster offset %u out of valid range [%u, %u]\n",
cluster_offset,
FAT32_FIRST_VALID_CLUSTER,
fat32_volume->total_clusters);
return -EIO;
}
cluster_table[cluster_offset] = next_cluster; // Safe: cluster is validated
Why This Fix Works
The fix enforces a fundamental security invariant: no value derived from external input (including disk data) may be used as a memory index without first confirming it falls within the intended bounds.
By returning an error code (-EIO) rather than attempting to handle or sanitize the bad value, the fix also follows the fail-safe defaults principle ā when something looks wrong, stop and report the error rather than trying to continue in a potentially compromised state.
The fix is applied at both vulnerable write sites (lines 221 and 334), ensuring neither path can be exploited independently.
Prevention & Best Practices
This vulnerability is representative of an entire class of bugs that appear whenever binary data from an untrusted source is parsed in C. Here are the practices that prevent them:
1. Treat All External Data as Untrusted
Disk data, network packets, user-supplied files ā all of it is attacker-controlled input. The fact that data comes from a "filesystem" rather than a network socket does not make it trustworthy.
// Rule of thumb: if it came from outside your process boundary, validate it.
uint32_t value = read_from_disk();
VALIDATE(value, min, max); // Always.
2. Validate at the Point of Ingestion
Validate values as soon as they are read, before they propagate further into the codebase. The longer an unvalidated value travels through your code, the harder it is to reason about its safety.
// Good: validate immediately after reading
struct fat32_dir_entry entry;
if (read_dir_entry(fd, &entry) < 0) return -EIO;
// Validate all fields before using any of them
if (!fat32_validate_dir_entry(&entry, volume)) return -EIO;
// Now safe to use
process_entry(&entry, volume);
3. Use Checked Arithmetic
Integer overflow in index calculations is a common way bounds checks are bypassed. Use checked arithmetic helpers:
// Dangerous: overflow can bypass a bounds check
size_t offset = index * sizeof(struct entry); // may overflow!
if (offset > buffer_size) return -EINVAL; // check is useless if overflow occurred
// Safe: check for overflow before multiplying
if (index > buffer_size / sizeof(struct entry)) return -EINVAL;
size_t offset = index * sizeof(struct entry); // safe
4. Consider Compiler and Kernel Hardening Features
Modern compilers and kernels provide mitigations that raise the bar for exploiting memory corruption, even when bugs exist:
| Mitigation | What It Does |
|---|---|
| KASLR (Kernel Address Space Layout Randomization) | Randomizes kernel base address, making it harder to target specific function pointers |
| SMEP/SMAP | Prevents kernel from executing/accessing user-space memory |
| CFI (Control Flow Integrity) | Restricts valid targets for indirect calls/jumps |
| Stack Canaries | Detect stack buffer overflows before a return |
-fsanitize=bounds |
Compiler instrumentation that traps OOB accesses at runtime (use in testing) |
These are defense in depth ā they make exploitation harder, but they are not a substitute for fixing the root cause.
5. Fuzz Your Parsers
Filesystem parsers are an ideal target for fuzzing. Tools like syzkaller (kernel fuzzer) and AFL++ can automatically generate malformed filesystem images and detect crashes:
# Example: fuzzing a filesystem driver with syzkaller
# syzkaller will generate malformed syscall sequences including
# crafted filesystem images and report kernel panics/crashes
syzkaller -config=fat32_fuzz.cfg
A good fuzzing campaign would have caught this vulnerability during development.
6. Relevant Standards and References
- CWE-787: Out-of-bounds Write ā https://cwe.mitre.org/data/definitions/787.html
- CWE-20: Improper Input Validation ā https://cwe.mitre.org/data/definitions/20.html
- CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer ā https://cwe.mitre.org/data/definitions/119.html
- OWASP: Input Validation Cheat Sheet ā https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- Linux Kernel Security Documentation ā https://www.kernel.org/doc/html/latest/security/index.html
- CERT C Coding Standard: ARR30-C ā Do not form or use out-of-bounds pointers or array subscripts
Conclusion
This vulnerability is a stark reminder that security does not stop at the application layer. Kernel-level code that processes structured binary data ā filesystems, network protocols, hardware registers ā is just as vulnerable to classic memory safety bugs as any user-space program, and the consequences are far more severe.
The key takeaways from this fix:
- Never trust on-disk data. Filesystem images are attacker-controlled input. Validate every field before use.
- Bounds-check every index. Before using any value as an array index or memory offset, confirm it falls within the valid range.
- Fail safe. When validation fails, return an error. Do not attempt to proceed with invalid state.
- Defense in depth matters. Kernel hardening features (KASLR, CFI, SMEP) raise the exploitation bar, but they are not a substitute for correct code.
- Fuzz your parsers. Automated fuzzing is one of the most effective ways to find this class of bug before attackers do.
The patch here is small ā a few bounds checks and early returns. But the impact of those missing checks was a complete, reliable path to ring 0 code execution. In kernel security, the difference between safe and catastrophic is often just a single if statement.
Write that if statement.
This vulnerability was identified and remediated by OrbisAI Security. Security fixes like this one are part of a continuous automated security scanning and remediation pipeline.