DragonFlyBSD Kernel Audit
DF-0017 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/sys/dmsg.h b/sys/sys/dmsg.h
--- a/sys/sys/dmsg.h
+++ b/sys/sys/dmsg.h
@@ -747,6 +747,7 @@
 	uint64_t	msgid;			/* {parent,msgid} uniq */
 	int		flags;
 	int		error;
+	int		depth;			/* circuit nesting depth */
 	void		*chain;			/* (caller's state) */
 	int (*func)(struct kdmsg_state *, struct kdmsg_msg *);
 	union {

diff --git a/sys/kern/kern_dmsg.c b/sys/kern/kern_dmsg.c
--- a/sys/kern/kern_dmsg.c
+++ b/sys/kern/kern_dmsg.c
@@ -69,6 +69,24 @@
 #define kdio_printf(iocom, level, ctl, ...)      \
         if (kdmsg_debug >= (level)) kprintf("kdmsg: " ctl, __VA_ARGS__)
 
+/*
+ * Maximum circuit nesting depth.  A DMSG peer builds parent->child
+ * circuit chains via CREATE messages; without a bound a maliciously
+ * deep chain overflows the kernel thread stack during the recursive
+ * teardown in kdmsg_simulate_failure()/kdmsg_state_dying().
+ *
+ * The per-level cost is large because kdmsg_state_abort() re-enters
+ * the receive path (kdmsg_msg_receive_handling -> kdmsg_state_msgrx
+ * -> kdmsg_state_cleanuprx -> kdmsg_simulate_failure), so each
+ * nesting level consumes a ~5-function call cycle.  Empirically, a
+ * 33-deep chain already overflows the 16 KB LWKT thread stack on
+ * this kernel (double-fault observed).  A cap of 8 keeps the worst-
+ * case recursion (~9 levels, ~4.4 KB) well within the 16 KB stack
+ * with a >3x safety margin, while remaining generous for legitimate
+ * use (typical DMSG/HAMMER2 circuit nesting is 1-3).
+ */
+#define DMSG_MAX_CIRCUIT_DEPTH	8
+
 static int kdmsg_msg_receive_handling(kdmsg_msg_t *msg);
 static int kdmsg_state_msgrx(kdmsg_msg_t *msg);
 static int kdmsg_state_msgtx(kdmsg_msg_t *msg);
@@ -889,6 +907,22 @@
 		}
 
 		/*
+		 * Cap circuit nesting depth.  Without this a peer can build
+		 * an arbitrarily deep parent->child chain (CREATE with circuit
+		 * referencing the previously created state) that overflows the
+		 * kernel stack during recursive teardown.
+		 */
+		if (pstate != &iocom->state0 &&
+		    pstate->depth >= DMSG_MAX_CIRCUIT_DEPTH) {
+			kdio_printf(iocom, 1,
+				    "circuit nesting too deep (%d), "
+				    "rejecting CREATE\n",
+				    pstate->depth + 1);
+			error = EINVAL;
+			break;
+		}
+
+		/*
 		 * Allocate new state.
 		 *
 		 * msg->state becomes the owner of the ref we inherit from
@@ -900,6 +934,8 @@
 
 		msg->state = state;		/* inherits freerd ref */
 		state->parent = pstate;
+		state->depth = (pstate == &iocom->state0) ?
+				0 : pstate->depth + 1;
 		KKASSERT(state->iocom == iocom);
 		state->flags |= KDMSG_STATE_RBINSERTED |
 				KDMSG_STATE_SUBINSERTED |
@@ -1797,6 +1833,8 @@
 		TAILQ_INIT(&state->subq);
 		state->iocom = iocom;
 		state->parent = pstate;
+		state->depth = (pstate == &iocom->state0) ?
+				0 : pstate->depth + 1;
 		state->flags = KDMSG_STATE_DYNAMIC |
 			       KDMSG_STATE_NEW;
 		state->func = func;