121 lines
3.8 KiB
Diff
121 lines
3.8 KiB
Diff
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;
|
|
}
|
|
|