DragonFlyBSD Kernel Audit
DF-2823 / fix.diff
← back to finding ↓ download raw
--- a/sys/kern/kern_lockf.c
+++ b/sys/kern/kern_lockf.c
@@ -91,7 +91,7 @@
 static int	lf_getlock(struct flock *, struct lockf *, struct proc *,
 			   int, int, off_t, off_t);
 
-static int	lf_count_change(struct proc *, int);
+static int	lf_count_change(struct proc *, int, int);
 
 /*
  * Return TRUE (non-zero) if the type and posix flags match.
@@ -153,8 +153,17 @@
 	}
 }
 
+/*
+ * Change the POSIX lock accounting for the given process.
+ *
+ * enforce - when non-zero, enforce the RLIMIT_POSIXLOCKS /
+ * kern.maxposixlocksperuid limit for positive diffs.  Callers servicing
+ * an F_UNLCK must pass 0: POSIX.1-2008 2.9.7 requires releasing one's
+ * own locks to succeed; a clipping unlock may transiently charge +1
+ * headroom (a split), which is settled at the end of lf_setlock().
+ */
 static int
-lf_count_change(struct proc *owner, int diff)
+lf_count_change(struct proc *owner, int diff, int enforce)
 {
 	struct uidinfo *uip;
 	int max, ret;
@@ -168,7 +177,7 @@
 	max = MIN(owner->p_rlimit[RLIMIT_POSIXLOCKS].rlim_cur,
 		  maxposixlocksperuid);
 
-	if (diff > 0 && owner->p_ucred->cr_uid != 0 && max != -1 &&
+	if (enforce && diff > 0 && owner->p_ucred->cr_uid != 0 && max != -1 &&
 	    uip->ui_posixlocks >= max ) {
 		ret = 1;
 	} else {
@@ -457,7 +466,7 @@
 		if (type == F_UNLCK)
 			goto do_wakeup;
 		if (flags & F_POSIX) {
-			if (lf_count_change(owner, 1)) {
+			if (lf_count_change(owner, 1, 1)) {
 				error = ENOLCK;
 				goto do_cleanup;
 			}
@@ -517,7 +526,8 @@
 			else
 				count = -1;
 		}
-		if (count && lf_count_change(owner, -count)) {
+		if (count &&
+		    lf_count_change(owner, -count, type != F_UNLCK)) {
 			error = ENOLCK;
 			goto do_cleanup;
 		}
@@ -748,7 +758,7 @@
 
 	KKASSERT(count <= 0);
 	if (count < 0)
-		lf_count_change(owner, count);
+		lf_count_change(owner, count, 0);
 do_wakeup:
 	lf_print_lock(lock);
 	if (wakeup_needed)