Missing SEMVMX upper-bound in semop/semexit allows semval overflow, wrap, spurious wakeups, and rollback corruption
| Field | Value |
|---|---|
| ID | DF-0046 |
| 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:L |
| CWE | CWE-190 Integer Overflow or Wraparound; CWE-682 Incorrect Calculation |
| File | sys/kern/sysv_sem.c |
| Lines | 848-854 (semop positive), 530 (SETVAL), 1139 (semexit) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
SEMVMX (32767) is exported to userland via seminfo, and POSIX/SVID requires
semop to fail with ERANGE when an operation would make semval exceed
SEMVMX, but the kernel never enforces the upper bound. The positive-sem_op
branch (:848-854) does semptr->semval += sopptr->sem_op with no upper-
bound check (the negative branch at :827 has the lower-bound analogue), and
semexit's positive-adjval path (:1139) and SETVAL (:530, intโu_short
truncation) are likewise unchecked. The in-tree comment at :160
("SEMVMX unused - user param") documents that SEMVMX is advisory only.
Consequences: semval (a u_short) wraps past 65535; the wrap-to-0 case
spuriously satisfies the semzcnt wakeup (:834) waking waiters that believe
the semaphore drained (broken mutual exclusion); and once wrapped, the rollback
paths (:886, :1013) operate on the wrapped u_short, leaving semval in a
state unrelated to its pre-call value. No kernel memory corruption was found
(the wrap stays inside the self-contained u_short field) โ impact is IPC-state
integrity / local DoS via broken synchronization, not privilege escalation.
Root cause
} else { /* positive sem_op branch */
semptr->semval += sopptr->sem_op; /* :849 no SEMVMX upper-bound check */
if (sopptr->sem_flg & SEM_UNDO)
do_undos = 1;
if (semptr->semncnt > 0)
wakeup(semptr);
}
Compare the negative branch (:827) which has
if (semptr->semval + sopptr->sem_op < 0). semval is u_short (:40). The
same omission is at SETVAL (:530, semptr->semval = real_arg.val) and
semexit (:1139, semptr->semval += adjval).
Threat model & preconditions
- Attacker position: any local user with
SEM_Aon a semaphore set. - Privileges gained or impact: corruption of the set's
semval, including semaphores shared with other principals (other UIDs in the same group, or root-owned setuid helpers sharing the set). The corruption is silent and persistent, and lets a waiter enter a critical section believingsemval==0when its logical value is much larger โ a cross-user synchronization/DoS primitive. The rollback wrap can leave a victim's semaphore in an arbitrary state. No path to kernel memory corruption (the wrap stays inside the self-containedu_shortfield). - Required config or capabilities:
SEM_Aon the set; default kernel. - Reachability:
semop(2)with large positive ops;semctl(SETVAL).
Proof of concept
PoC source: findings/poc/DF-0046/sem_wrap.c
Build & run (unprivileged)
cc -o sem_wrap findings/poc/DF-0046/sem_wrap.c ./sem_wrap
Expected output
A wrapped semval after four +32767 ops (no ERANGE), and a non-zero
semval after a failed-and-rolled-back op (should be 0).
Impact
IPC-state integrity / POSIX non-compliance / local DoS via broken SysV semaphore synchronization. No kernel memory corruption. Rated Low.
Recommended fix
Enforce SEMVMX on the positive-op, SETVAL, and semexit paths (the
negative branch already shows the pattern):
--- a/sys/kern/sysv_sem.c
+++ b/sys/kern/sysv_sem.c
@@ -848 +848,5 @@
} else {
+ if (semptr->semval + sopptr->sem_op > seminfo.semvmx) {
+ eval = ERANGE;
+ lwkt_relpooltoken(semptr);
+ goto done;
+ }
semptr->semval += sopptr->sem_op;
(and the analogous check at SETVAL :530 and semexit :1139).
References
sys/kern/sysv_sem.c:848-854โ positive-op branch, noSEMVMXcheck.sys/kern/sysv_sem.c:160โ in-tree comment "SEMVMX unused - user param".sys/kern/sysv_sem.c:40โsemvalisu_short.- CWE-190 Integer Overflow; CWE-682 Incorrect Calculation.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/sysv_sem.c. - pending Reported to DragonFlyBSD security contact.