Missing SEMAEM enforcement: un_adjval (short) signed overflow in semundo_adjust flips semexit's clamp branch β deterministic wrong undo application
| Field | Value |
|---|---|
| ID | DF-2784 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-190 (16-bit signed undo accumulator) |
| File | sys/kern/sysv_sem.c |
| Lines | 244 (accumulate), 259 (create), 1133-1140 (exit clamp) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
semundo_adjust() accumulates SEM_UNDO values into the 16-bit signed un_adjval with no bound: seminfo.semaem (16384) is defined but enforced nowhere. Two +30000 SEM_UNDO ops wrap the accumulator from β60000 to +5536; at exit, semexit()'s positive branch skips the "clamp to 0" protection of the negative branch. Reproduced deterministically (2/2): semval after exit = 35536 where POSIX semantics require 0 β cross-process visible corruption of shared-semaphore state. Distinct from DF-0046: different variable (undo accumulator vs semaphore value), missing check (SEMAEM vs SEMVMX), mechanism (sign flip defeating the exit clamp vs u_short value wrap).
Threat model & preconditions
Any semaphore participant can leave a wrapped undo entry whose exit-time application writes an attacker-influenced arbitrary delta (Β±32767) to the shared semaphore, bypassing the clamp β synchronization-state corruption for victim applications (deadlock/livelock/mutex-bypass). No kernel memory-safety impact.
Proof of contest
VERIFIED (findings/poc/DF-2784/adjval_wrap.c): child performs {+30000,U}Γ2 then β30000 (no undo) and exits; parent GETVAL = 35536 (correct: 0) β exactly 30000 + (β60000 mod 2^16). Fixed kernel: ERANGE rejection, no corruption.
Recommended fix
Enforce seminfo.semaem on both paths of semundo_adjust returning ERANGE (validated diff in findings/poc/DF-2784/fix.diff).
Timeline
- 2026-08-31 Discovered during pass-2 audit of sysv_sem.c (GLM 5.3); deterministic corruption reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2784 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| adjval_wrap.c | β | 2.4 KB | view raw | |
| build.sh | β | 59 B | view raw | |
| run.sh | β | 24 B | view raw | |
| build.log | β | 9 B | view raw | |
| run.log | β | 214 B | view raw | |
| run.2.log | β | 209 B | view raw | |
| env.txt | β | 305 B | view raw | |
| fix.diff | β | 1005 B | view raw | |
| VERDICT.md | β | 2.2 KB | β raw | |
| verdict.json | β | 2.4 KB | view raw |
DF-2784 VERDICT
Status: reproduced (deterministic semantic corruption, certain). Fix validated (fixed).
Baseline (stock kernel #0)
Two runs, identical (run.log, run.2.log):
child : semval before exit = 30000 (undo owed: -60000) parent: semval after child exit = 35536 expected (POSIX clamp semantics): 0 BUG REPRODUCED: un_adjval wrapped to +5536, positive branch skipped the clamp
Root cause chain, all cited:
1. semop {+30000 SEM_UNDO, +30000 SEM_UNDO} applies
semundo_adjust(β¦, -30000) twice (sysv_sem.c:984-985);
2. sunptr->un_adjval += adjval at sysv_sem.c:244 accumulates into a
short: β30000 β β60000 wraps to +5536. seminfo.semaem
(16384) is defined (sysv_sem.c:91) but enforced nowhere;
3. at child exit, semexit() reads the wrapped value
(sysv_sem.c:1107), takes the adjval >= 0 branch (:1138-1139,
semval += adjval) and skips the semval < -adjval β clamp to 0
protection (:1133-1137);
4. final semval = 30000 + 5536 = 35536 instead of 0 β visible
cross-process (parent's GETVAL), i.e. attacker-controllable
corruption of shared-semaphore state for every participant.
Deterministic: 2/2 baseline runs; exact value predicted from the 16-bit wrap arithmetic (β60000 + 65536 = 5536).
Non-overlap with DF-0046: DF-0046 is the missing SEMVMX bound on
semval in semop/semexit (u_short overflow of the semaphore value).
This finding is the missing SEMAEM bound on the undo
accumulator un_adjval at sysv_sem.c:244/:259 β different variable,
missing check, and failure mode (sign flip defeating the exit clamp).
Fix validation (patched kernel #1, fix.diff enforces SEMAEM β ERANGE)
child : semop rejected with ERANGE (SEMAEM enforced) parent: child rejected at semop (ERANGE) - SEMAEM enforcement active, no corruption (rc=0)
Bad value gone: op rejected at adjustment time; semop's existing error path rolls the semaphore values back; exit applies nothing.
Impact ceiling
Low β corruption is confined to SysV semaphore values (logic/sync corruption for victim applications sharing the semaphore with the attacker; no kernel memory safety effect). Matches DF-0046's Low rating for the sibling defect.
Fix verification
fixedPatched kernel rejects the +30000 UNDO op with ERANGE ('SEMAEM enforcement active, no corruption', rc=0); the 35536 corruption is gone.
["run.fixed.log: 'parent: child rejected at semop (ERANGE) - SEMAEM enforcement active, no corruption'", 'fix.diff']
Confirmed kernel references
Detail
Evidence (decisive lines)
["run.log / run.2.log: 'parent: semval after child exit = 35536' + 'BUG REPRODUCED: un_adjval wrapped to +5536' (2/2)", 'VERDICT.md: full wrap arithmetic (-60000+65536=5536) and root-cause chain']
PoC changes
Added ERANGE detection path (child _exit(42), parent recognizes) so the same binary demonstrates both baseline corruption and post-fix rejection.
Verified recommended fix
Enforce seminfo.semaem on both the accumulate (:244) and create (:259) paths of semundo_adjust, returning ERANGE (validated: op rejected, no corruption).
Verdict
semundo_adjust() accumulates SEM_UNDO values into the 16-bit signed un_adjval with no SEMAEM enforcement (sysv_sem.c:244). Two +30000 UNDO ops wrap -60000 to +5536; at exit semexit()'s positive branch (sysv_sem.c:1138-1139) skips the clamp-to-0 protection (1133-1137), leaving semval=35536 where POSIX semantics require 0. Deterministic, 2/2 runs, cross-process visible. Distinct from DF-0046 (semval/SEMVMX): this is the undo-accumulator/SEMAEM defect.
No comments yet.