penguin/monok8s

k8s image for Mono Gateway Dev Kit

patches/ask/dpa/0003-cdx-avoid-kfree-of-userspace-dist-info-pointers.patch

raw ยท 3883 bytes

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: monok8s <monok8s@example.invalid>
Date: Sun, 10 May 2026 00:00:00 +0000
Subject: [PATCH 3/4] cdx: avoid kfree of userspace dist_info pointers

get_port_info() copies an array of cdx_port_info from userspace. At that
point each cdx_port_info.dist_info field is still a userspace pointer.
However release_cfg_info() treats any non-NULL dist_info as a kernel
allocation and kfree()s it on error paths.

If a later step fails before get_dist_info() has replaced every dist_info
with a kernel allocation, release_cfg_info() can kfree a raw userspace
pointer and oops in kfree()/virt_to_folio().

Stash the userspace dist_info pointers in a temporary array, clear the
kernel-side cdx_port_info.dist_info fields immediately after copy_from_user(),
and pass the saved userspace pointer explicitly to get_dist_info(). This
keeps release_cfg_info() safe on partial-initialization failures.
---
 cdx/dpa_cfg.c | 47 +++++++++++++++++++++++++++++++++++++----------
 1 file changed, 37 insertions(+), 10 deletions(-)

diff -urN a/cdx/dpa_cfg.c b/cdx/dpa_cfg.c
--- a/cdx/dpa_cfg.c	2026-05-10 00:46:34.295813594 +0000
+++ b/cdx/dpa_cfg.c	2026-05-10 00:46:35.558487337 +0000
@@ -169,11 +169,10 @@
 }
 
 //allocate and copy distribution info from uspace 
-static int get_dist_info(struct cdx_port_info *port_info)
+static int get_dist_info(struct cdx_port_info *port_info, void *uspace_info)
 {
 	uint32_t mem_size;
 	struct cdx_dist_info *dist_info;
-	void *uspace_info;
 
 #ifdef DPA_CFG_DEBUG
 	DPA_INFO("%s::port %s dist %d\n", __func__, 
@@ -187,7 +186,6 @@
 		return -ENOMEM;
 	}
 	memset(dist_info, 0, mem_size);
-	uspace_info = port_info->dist_info;
 	port_info->dist_info = dist_info;
 	if (copy_from_user(dist_info, uspace_info, 
 				mem_size)) {
@@ -273,6 +271,7 @@
 {	
 	struct cdx_port_info *port_info;
 	void *uspace_info;
+	void **uspace_dist_info;
 	uint32_t mem_size;
 	uint32_t ii;
 
@@ -289,13 +288,40 @@
 		return -ENOMEM;
 	}
 	memset(port_info, 0, mem_size);
+
+	uspace_dist_info = kcalloc(finfo->max_ports, sizeof(*uspace_dist_info),
+			GFP_KERNEL);
+	if (!uspace_dist_info) {
+		DPA_ERROR("%s::memalloc for uspace_dist_info failed\n",
+				__func__);
+		kfree(port_info);
+		return -ENOMEM;
+	}
+
 	uspace_info = finfo->portinfo;
 	finfo->portinfo = port_info;
 	if (copy_from_user(port_info, uspace_info, mem_size)) {
 		DPA_ERROR("%s::Read port_info failed\n",
 				__func__);
+		finfo->portinfo = NULL;
+		kfree(uspace_dist_info);
+		kfree(port_info);
 		return -EIO;
 	}
+
+	/*
+	 * port_info has just been copied from userspace, so each dist_info
+	 * member is still a userspace pointer. release_cfg_info() kfree()s
+	 * non-NULL dist_info members, therefore keeping those raw userspace
+	 * pointers in the kernel copy turns any later error path into an
+	 * invalid kfree(). Stash the userspace pointers separately and clear
+	 * the struct fields until get_dist_info() replaces them with real
+	 * kernel allocations.
+	 */
+	for (ii = 0; ii < finfo->max_ports; ii++) {
+		uspace_dist_info[ii] = port_info[ii].dist_info;
+		port_info[ii].dist_info = NULL;
+	}
 	//put the linux name for the port
 	for (ii = 0; ii < finfo->max_ports; ii++) {
 		struct net_device *dev;
@@ -311,6 +337,7 @@
 						__func__, port_info->name,
 						port_info->fm_index, port_info->index,
 						port_info->portid, port_info->type);
+				kfree(uspace_dist_info);
 				return -ENODEV;
 			}
 			DPA_INFO("%s::mapped user port %s to netdev %s\n",
@@ -330,11 +357,14 @@
 	for (ii = 0; ii < finfo->max_ports; ii++) {
 		int retval;
 		//get dist info for this port
-		retval = get_dist_info(port_info);
-		if (retval)
+		retval = get_dist_info(port_info, uspace_dist_info[ii]);
+		if (retval) {
+			kfree(uspace_dist_info);
 			return retval;
+		}
 		port_info++;
 	}
+	kfree(uspace_dist_info);
 	return 0;
 }