From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: monok8s 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; }