โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0046

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

sys/kern/sysv_sem.c:848-854:

} 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_A on 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 believing semval==0 when 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-contained u_short field).
  • Required config or capabilities: SEM_A on 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.

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

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/sysv_sem.c.
  • pending Reported to DragonFlyBSD security contact.