CVE-2026-45531 AI-generated analysis
platform/external/exfatprogs, fsck.exfat (fsck/fsck.c) and shared header include/libexfat.h
Hardens fsck.exfat boot-sector validation in read_boot_region() against integer overflow and underflow from the on-disk vol_length, clu_offset and clu_count fields, and makes the shared DIV_ROUND_UP macro compute in 64-bit.
Root cause
In fsck/fsck.c:read_boot_region(), the size checks on the exFAT boot sector used arithmetic that could wrap. vol_length (64-bit, from disk) was multiplied by EXFAT_SECTOR_SIZE(bs) before the comparison with bd->size, so a large vol_length could wrap past the check. Nothing checked that clu_offset is smaller than vol_length, so a later subtraction of clu_offset from vol_length could underflow (per the fix's comment). clu_count was not capped to EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5), so later computations on clu_count could overflow (per the fix's comment). DIV_ROUND_UP in include/libexfat.h also did the (__i + __d - 1) addition in the operand's own type, so that addition could overflow for large inputs.
How the fix works
- Commit 20f47ac2509f (ANDROID: Add support for building exfat tests) changes Android.bp, fsck/Android.bp and lib/Android.bp. It adds host_supported: true to libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat. This is a build-only change with no change to the code.
- Commit 54cf500d4abd, fsck/fsck.c:read_boot_region(): the vol_length check changes from 'vol_length * EXFAT_SECTOR_SIZE(bs) > bd->size' to 'vol_length > (bd->size >> bs->bsx.sect_size_bits)'. The comparison now shifts the device size instead of multiplying the untrusted field. The earlier check limiting EXFAT_SECTOR_SIZE(bs) to 512..4KB comes before it.
- fsck/fsck.c:read_boot_region(): a new check takes the error path (goto err, with ret set to -EINVAL earlier) when le32_to_cpu(bs->bsx.clu_offset) >= le64_to_cpu(bs->bsx.vol_length). This prevents underflow when clu_offset is subtracted from vol_length.
- fsck/fsck.c:read_boot_region(): a new check rejects the image when le32_to_cpu(bs->bsx.clu_count) > EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5). It runs before the existing 'clu_count * EXFAT_CLUSTER_SIZE(bs) > bd->size' check.
- include/libexfat.h: DIV_ROUND_UP now casts __i to uint64_t, so the addition and division happen in 64-bit for every user of the macro.
- tests/clu_count_overflow/exfat.img.tar.xz: a regression image is added.
Completeness
- All new checks run once, in read_boot_region(), on the boot sector fields. Code later in fsck.exfat that uses these fields relies on the new bounds.
- The DIV_ROUND_UP change is in the shared header include/libexfat.h, so every file that includes it gets the 64-bit arithmetic. The material does not show which tools call it. The other macros in the header (round_up, round_down) were not changed.
- The existing 'le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > bd->size' comparison is left as is. The new cap limits clu_count but not the product. Whether the product can wrap depends on types the material does not show.
- The fix validates only the fields named in the commit. Other boot sector fields (for example fat_offset, fat_length, root_cluster) are not touched, and the material does not show how they are validated.
Behavior changes
- fsck.exfat now fails in read_boot_region() with an exfat_err message on images where clu_offset >= vol_length or clu_count > 0xFFFFFFF5. The check that vol_length does not exceed the device size in sectors is now done without a multiplication.
- DIV_ROUND_UP now yields a 64-bit (uint64_t) result for all callers, which changes the result type of expressions that use it.
- Build: libexfatprogs-headers, exfatprogs-defaults, fsck.exfat and libexfat gain host_supported: true, so host variants are now built.
Per major
- Android 14: Same fix: 54cf500d4abd (fsck.c, libexfat.h, test image) plus build commit 20f47ac2509f at android-security-14.0.0_r29. The resolver reports 7/8 lines of the patched region present.
- Android 15: Resolved at android-security-15.0.0_r17 with 8/8 lines of the patched region present. The material shows only the build commit 2957a37c1d7f (host_supported, plus a whitespace fix to the exfat2img header entry). The functional fsck.c/libexfat.h commit for 15 is not shown.
- Android 16: Adapted fix: c0e2d5cc6945 at android-security-16.0.0_r8. It adds the same checks as on 14, but wraps each exfat_err in 'if (verbose)' to match this branch's read_boot_region(). The resolver reports 6/8 lines of the patched region present.
- Android 17: Resolved at android-security-17.0.0_r1 with 8/8 lines of the patched region present. The material shows only the build commit a6faf5dfd40a. The functional commit is not shown.
Unknowns
- The material does not show which later computation is the memory-unsafe sink (the clu_offset subtraction, a DIV_ROUND_UP user, or a clu_count-based allocation). It shows only read_boot_region() and the macro.
- The functional fsck.c commits for Android 15 and 17 are not in the material, only the build commits. The per-major facts still report the patched region present.
- Why the resolver reports 7/8 lines on 14 and 6/8 on 16 is not explained.
- The types of EXFAT_CLUSTER_SIZE(bs) and bd->size are not shown, so the material cannot confirm whether the remaining clu_count * EXFAT_CLUSTER_SIZE(bs) comparison can still wrap.
- The material does not show how fsck.exfat is invoked on the device (which caller, with what privileges, on which storage).
- Which exfatprogs tools use DIV_ROUND_UP is not shown.