From: Rosen Penev Date: Wed, 15 Jul 2026 22:58:06 +0000 (-0700) Subject: swconfig: fix portmap memory leak and hardening X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3978d4e5ee17e9ba0f2f6dd3fd8489a369fb4a14;p=thirdparty%2Fopenwrt.git swconfig: fix portmap memory leak and hardening Free the per-port segment strings on unregister to avoid leaking them across device rebind. Drop the dead (phys < 0) check and guard the kstrdup allocation against failure. Move swconfig_create_led_trigger() before list_add_tail() in register_switch so a failing registration never publishes the device on the global swdevs list with dangling portmap/portbuf pointers (a use-after-free for any swdevs iterator). Route both the LED trigger failure and the -ENFILE (swdev id exhaustion) failure paths through a shared cleanup that frees portbuf, portmap, and the per-port segment strings instead of leaking them. Reorder the frees in unregister_switch to run after list_del so the buffers are not freed while the device is still reachable from swdevs. Signed-off-by: Rosen Penev Link: https://github.com/openwrt/openwrt/pull/24246 Signed-off-by: Jonas Jelonek --- diff --git a/target/linux/generic/files/drivers/net/phy/swconfig.c b/target/linux/generic/files/drivers/net/phy/swconfig.c index d57c112802e..976bf78419f 100644 --- a/target/linux/generic/files/drivers/net/phy/swconfig.c +++ b/target/linux/generic/files/drivers/net/phy/swconfig.c @@ -1056,7 +1056,6 @@ static struct genl_family switch_fam = { .resv_start_op = SWITCH_CMD_SET_VLAN + 1, }; -#ifdef CONFIG_OF static void of_switch_load_portmap(struct switch_dev *dev) { @@ -1085,19 +1084,37 @@ of_switch_load_portmap(struct switch_dev *dev) } phys = be32_to_cpup(prop++); - if ((phys < 0) | (phys >= dev->ports)) { + if (phys >= dev->ports) { pr_err("%s: physical port index out of range\n", port->name); continue; } dev->portmap[phys].s = kstrdup(segment, GFP_KERNEL); + if (!dev->portmap[phys].s) + continue; dev->portmap[phys].virt = be32_to_cpup(prop); pr_debug("Found port: %s, physical: %d, virtual: %d\n", segment, phys, dev->portmap[phys].virt); } } -#endif + +static void +switch_free_portmap(struct switch_dev *dev) +{ + int i; + + if (dev->portmap) { + for (i = 0; i < dev->ports; i++) + kfree(dev->portmap[i].s); + + kfree(dev->portmap); + dev->portmap = NULL; + } + + kfree(dev->portbuf); + dev->portbuf = NULL; +} int register_switch(struct switch_dev *dev, struct net_device *netdev) @@ -1149,25 +1166,31 @@ register_switch(struct switch_dev *dev, struct net_device *netdev) if (i == max_switches) { swconfig_unlock(); + switch_free_portmap(dev); return -ENFILE; } -#ifdef CONFIG_OF - if (dev->ports) + if (IS_ENABLED(CONFIG_OF) && dev->ports) of_switch_load_portmap(dev); -#endif /* fill device name */ snprintf(dev->devname, IFNAMSIZ, SWCONFIG_DEVNAME, i); - list_add_tail(&dev->dev_list, &swdevs); swconfig_unlock(); err = swconfig_create_led_trigger(dev); if (err) - return err; + goto unlink; return 0; + +unlink: + swconfig_lock(); + list_del(&dev->dev_list); + swconfig_unlock(); + switch_free_portmap(dev); + + return err; } EXPORT_SYMBOL_GPL(register_switch); @@ -1175,12 +1198,12 @@ void unregister_switch(struct switch_dev *dev) { swconfig_destroy_led_trigger(dev); - kfree(dev->portbuf); mutex_lock(&dev->sw_mutex); swconfig_lock(); list_del(&dev->dev_list); swconfig_unlock(); mutex_unlock(&dev->sw_mutex); + switch_free_portmap(dev); } EXPORT_SYMBOL_GPL(unregister_switch);