pipe_size sysctl lacks power-of-two validation: non-pow2 buffer size silently aliases FIFO slots (in-bounds cross-process data corruption) on misconfigured systems
| Field | Value |
|---|---|
| ID | DF-2752 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:N/I:H/A:N |
| CWE | CWE-20 Improper Input Validation |
| File | sys/kern/sys_pipe.c |
| Lines | 125-127, 367-371 (indexing :539, :794) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-30 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The FIFO maps logical byte counters to physical slots with
rindex & (size-1) / windex & (size-1), which is modulo only when
size is a power of two. pipespace() page-aligns and clamps size to
[16384, 1MB] but never validates the power-of-two property, and the
writable sysctl kern.pipe.size accepts any int (e.g. 20480). With a
non-pow2 size two distinct live offsets map to the same slot (e.g.
0x5000 & 0x4FFF == 0x4000), so a writer overwrites unread data β
silent data corruption between the pipe's users. All accesses remain
in-bounds, so integrity-only; requires a privileged sysctl write (an
administrator misconfiguration, not attacker-reachable). Sibling
FreeBSD code guards this with a power-of-two check.
Recommended fix
In pipespace(), after the clamps: if (size & (size - 1)) size =
1UL << (fls(size) - 1); if (size < 16384) size = 16384; and
optionally guard the sysctl writer with the same check.
Timeline
- 2026-08-30 Discovered during pass-2 audit of sys_pipe.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2752 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| pipefinal.c | β | 780 B | view raw | |
| pipetest2.c | β | 1.4 KB | view raw | |
| pipemap.c | β | 1.0 KB | view raw | |
| run.log | β | 921 B | view raw | |
| fix.diff | β | 595 B | view raw |
Fix verification
fixedPatched kernel: pipespace rounds non-power-of-2 sizes up; with kern.pipe.size=20480 the previously-corrupting round trip now passes byte-perfect (0/20480 corrupted; baseline 8192/20480).
pipefinal on patched kernel at size=20480: corrupted bytes: 0 / 20480 PASS
Confirmed kernel references
Detail
Exploit chain
root sysctl kern.pipe.size=20480 -> pipe() -> write 20480 -> read 20480 -> 8192 bytes silently replaced by other in-flight data
Evidence (decisive lines)
pipefinal.c; df2752_pipefinal.log: 8192/20480 corrupted at 20480 and 24576, 0 at 32768; block-map aliasing proof
PoC changes
first two test patterns were degenerate (period 256 dividing the 10240 alias distance); prime-period pattern made the corruption deterministic
Verified recommended fix
--- sys/kern/sys_pipe.c 2026-06-29 12:51:19.000000000 +0000
Verdict
Reproduced live as root-only sysctl misconfiguration. kern.pipe.size accepts any int (sys/kern/sys_pipe.c:126-127); pipespace clamps only to [16384, 1MB] (:367-371) and never enforces power-of-two, but the FIFO index masks are windex & (size - 1) / rindex & (size - 1) (:539, :794). With kern.pipe.size=20480 a single full-pipe write/read round-trip corrupted 8192 of 20480 bytes (range [2048..10239]); with 24576 likewise 8192 bytes (range [4096..12287]); the 32768 control was byte-perfect. A block-map run shows the aliasing directly: reader blocks 8..17 receive source blocks 40..49 because absolute offsets 10240..20479 wrap onto slots 2048..12287 - the mask 20479 (0x4FFF) is not a modulo mask, so distinct live offsets alias the same slot and the writer overwrites unread data. All accesses stay in-bounds (silent cross-process data corruption, not memory unsafety). Root-gated; a user with kern.pipe.size write access corrupts every new pipe on the system. First test used a 256-periodic byte pattern that collided with the alias distance (10240*7 % 256 == 0) and falsely passed; prime-period pattern exposed the corruption.
No comments yet.