CVE Candidate: Kernel Heap Buffer Overflow in UFS ufs_read_cylinder_structures

File

fs/ufs/super.c

Severity

Critical — Kernel heap buffer overflow, potentially exploitable for local privilege escalation (LPE) by any process that can mount a UFS filesystem.


Vulnerability Description

ufs_read_cylinder_structures() (line 454) computes the number of blocks to read as:

// super.c:467-469
size = uspi->s_cssize;
blks = (size + uspi->s_fsize - 1) >> uspi->s_fshift;
base = space = kmalloc(size, GFP_NOFS);

Then writes uspi->s_fsize bytes per iteration into that buffer:

// super.c:473-480
for (i = 0; i < blks; i++) {
    struct buffer_head *bh = sb_bread(sb, uspi->s_csaddr + i);
    ...
    memcpy(space, bh->b_data, uspi->s_fsize);
    space += uspi->s_fsize;
    ...
}

Total bytes written = blks × s_fsize, but buffer size = s_cssize.

For correctness these must be equal, which requires s_fshift == log2(s_fsize). However:

  • s_fsize is validated (power of 2, 512–4096) at lines 1007–1021.
  • s_fshift is loaded without any validation directly from disk at line 1122:
// super.c:1122
uspi->s_fshift = fs32_to_cpu(sb, usb1->fs_fshift);
  • The re-try loop at line 1037 only checks s_fsize/s_sbsize consistency — not s_fshift.

Attack Scenario

An attacker crafts a UFS superblock image with:

Field Legit value Crafted value
fs_fsize 512 512 (passes power-of-2 check)
fs_fshift 9 (log2(512)) 0 (attacker-controlled)
fs_cssize 512 512

Resulting computation:

blks = (512 + 512 - 1) >> 0  =  1023
kmalloc(512) → 512-byte heap buffer
loop: 1023 iterations × memcpy(512 bytes) = 523,776 bytes written

~511 KB written past the end of a 512-byte GFP_NOFS slab allocation, corrupting arbitrary kernel heap objects.

Similarly, s_fshift = 1 with s_fsize = 512 gives blks = 511, writing ~255 KB past the buffer. The attacker can tune the overflow size precisely.


Root Cause

// super.c:1122 — no validation
uspi->s_fshift = fs32_to_cpu(sb, usb1->fs_fshift);

s_fshift must equal ilog2(s_fsize) (e.g. 9 for 512, 10 for 1024, 11 for 2048, 12 for 4096). There is no check enforcing this invariant after the disk value overwrites the in-memory default.


Fix

After line 1122, add a consistency check:

if (uspi->s_fshift != ilog2(uspi->s_fsize)) {
    pr_err("%s(): s_fshift (%u) inconsistent with s_fsize (%u)\n",
           __func__, uspi->s_fshift, uspi->s_fsize);
    goto failed;
}

Additionally, s_cssize should be bounded (e.g. must not exceed s_ncg * sizeof(struct ufs_csum)).


Affected Code Path

mount(2) with UFS filesystem
  → ufs_fill_super()            [super.c:717]
      → reads s_fshift from disk [super.c:1122]  ← no validation
      → ufs_read_cylinder_structures() [super.c:454]
          → kmalloc(s_cssize)    [super.c:469]
          → loop blks times      [super.c:473]
              → memcpy past end  [super.c:477]    ← HEAP OVERFLOW

Exploitability

  • Requires mounting a crafted UFS image, which needs CAP_SYS_ADMIN in the initial user namespace, or an unprivileged user namespace on distros with CONFIG_USER_NS=y and CONFIG_UFS_FS=y.
  • The overflow is a controlled-size heap corruption of GFP_NOFS slab memory, making it a strong primitive for kernel LPE via techniques such as cross-cache heap spraying.