⬢ DragonFlyBSD Kernel Audit
DF-2232 / fix.diff
← back to finding ↓ download raw
--- a/sys/libkern/linux_idr.c
+++ b/sys/libkern/linux_idr.c
@@ -131,9 +131,20 @@
 /* Must be 2^n - 1 */
 #define IDR_DEFAULT_SIZE    255
 
+/*
+ * Maximum id we will grow the table for.  The growth loop below computes
+ * nf = 2 * nf + 1 in a signed int: for want >= (1<<30)-1 the loop can
+ * overflow, wrap nf to -1 and spin forever (with the idr token held),
+ * and any want >= (1<<29)-1 asks the allocator for a >= 8GB node array
+ * under M_WAITOK.  No in-tree consumer comes anywhere near this many
+ * ids (drm object handles, 16-bit amdgpu PASIDs).  Fail the caller
+ * instead of hanging or exhausting the machine.
+ */
+#define IDR_MAX_IDS    ((1 << 24) - 1)
+
 MALLOC_DEFINE(M_IDR, "idr", "Integer ID management");
 
-static void	idr_grow(struct idr *idp, int want);
+static int	idr_grow(struct idr *idp, int want);
 static void	idr_reserve(struct idr *idp, int id, int incr);
 static int	idr_find_free(struct idr *idp, int want, int lim);
 
@@ -271,8 +282,12 @@
 		/*
 		 * Grow if necessary (or if forced by the loop)
 		 */
-		if (want >= idp->idr_count)
-			idr_grow(idp, want);
+		if (want >= idp->idr_count) {
+			if (idr_grow(idp, want)) {
+				result = 0;	/* failure */
+				break;
+			}
+		}
 
 		/*
 		 * Check if a spot is available, break and return 0 if true,
@@ -371,8 +386,12 @@
 	lwkt_gettoken(&idp->idr_token);
 
 grow_again:
-	if (want >= idp->idr_count)
-		idr_grow(idp, want);
+	if (want >= idp->idr_count) {
+		if (idr_grow(idp, want)) {
+			result = -ENOMEM;
+			goto done;
+		}
+	}
 
 	/*
 	 * Check if a spot is available, break and return 0 if true,
@@ -417,12 +436,15 @@
  *
  * Caller must hold a blockable lock.
  */
-static void
+static int
 idr_grow(struct idr *idp, int want)
 {
 	struct idr_node *oldnodes, *newnodes;
 	int nf;
 
+	if (want >= IDR_MAX_IDS)
+		return (ENOMEM);
+
 	/* We want 2^n - 1 descriptors */
 	nf = idp->idr_count;
 	do {
@@ -440,7 +462,7 @@
 	/* We might race another grow */
 	if (nf <= idp->idr_count) {
 		kfree(newnodes, M_IDR);
-		return;
+		return (0);
 	}
 
 	/*
@@ -458,6 +480,7 @@
 		kfree(oldnodes, M_IDR);
 	}
 	idp->idr_nexpands++;
+	return (0);
 }
 
 void *