Netdev List
 help / color / mirror / Atom feed
* Re: [PATCH net] ipvs: fix PMTU for GUE/GRE tunnel ICMP errors
From: Julian Anastasov @ 2026-07-01 12:58 UTC (permalink / raw)
  To: Yizhou Zhao
  Cc: netdev, Simon Horman, Pablo Neira Ayuso, Florian Westphal,
	Phil Sutter, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, lvs-devel, netfilter-devel, coreteam, linux-kernel,
	stable, Yuxiang Yang, Ao Wang, Xuewei Feng, Qi Li, Ke Xu
In-Reply-To: <20260701065941.46249-1-zhaoyz24@mails.tsinghua.edu.cn>


	Hello,

On Wed, 1 Jul 2026, Yizhou Zhao wrote:

> When an ICMP Fragmentation Needed error is received for a tunneled IPVS
> connection, ip_vs_in_icmp() recomputes the MTU that the original packet
> can use by subtracting the tunnel overhead from the reported next-hop
> MTU.
> 
> The current code always subtracts sizeof(struct iphdr), which is only
> the IPIP overhead. For GUE and GRE tunnels, ipvs_udp_decap() and
> ipvs_gre_decap() already compute the additional tunnel header length,
> but that value is scoped to the decapsulation block and is lost before
> the ICMP_FRAG_NEEDED handling. As a result, the ICMP error sent back to
> the client advertises an MTU that is too large, so PMTUD can fail to
> converge for GUE/GRE-tunneled real servers.
> 
> With a reported next-hop MTU of 1400, a GUE tunnel currently returns
> 1380 to the client. The correct value is 1368:
> 
>   1400 - sizeof(struct iphdr) - sizeof(struct udphdr) -
>   sizeof(struct guehdr)
> 
> Hoist the tunnel header length into the main ip_vs_in_icmp() scope and
> subtract sizeof(struct iphdr) + ulen in the Fragmentation Needed path.
> The IPIP path keeps ulen as 0, so its existing 1400 - 20 = 1380 result
> is unchanged.
> 
> Fixes: 508f744c0de3 ("ipvs: strip udp tunnel headers from icmp errors")
> Cc: stable@vger.kernel.org
> Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
> Reported-by: Ao Wang <wangao@seu.edu.cn>
> Reported-by: Xuewei Feng <fengxw06@126.com>
> Reported-by: Qi Li <qli01@tsinghua.edu.cn>
> Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
> Assisted-by: Claude Code:GLM-5.2
> Signed-off-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
> ---
>  net/netfilter/ipvs/ip_vs_core.c | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
> index d40b404c1bf62..74c5bd8b5f48 100644
> --- a/net/netfilter/ipvs/ip_vs_core.c
> +++ b/net/netfilter/ipvs/ip_vs_core.c
> @@ -1765,8 +1765,9 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
>  	struct ip_vs_proto_data *pd;
>  	unsigned int offset, offset2, ihl, verdict;
>  	bool tunnel, new_cp = false;
>  	union nf_inet_addr *raddr;
>  	char *outer_proto = "IPIP";
> +	int ulen = 0;
>  
>  	*related = 1;
>  

	The change look correct but there is something to fix:

1. first hunk is bad, does not apply to the nf tree (-8+9 vs -6+7
lines):

# patch -p1 --dry-run < /tmp/file.patch
checking file net/netfilter/ipvs/ip_vs_core.c
Hunk #1 succeeded at 1765 with fuzz 2.

2. the first two words in Assisted-by line should not contain
white spaces:

# scripts/checkpatch.pl --strict /tmp/file.patch

WARNING: Assisted-by expects 'AGENT_NAME:MODEL_VERSION [TOOL1] [TOOL2]' format
#40: Assisted-by: Claude Code:GLM-5.2

	Please send v2 after solving the above problems. Thanks!

> @@ -1831,7 +1832,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
>  		   /* Error for our tunnel must arrive at LOCAL_IN */
>  		   (skb_rtable(skb)->rt_flags & RTCF_LOCAL)) {
>  		__u8 iproto;
> -		int ulen;
>  
>  		/* Non-first fragment has no UDP/GRE header */
>  		if (unlikely(cih->frag_off & htons(IP_OFFSET)))
> @@ -1936,8 +1936,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
>  				if (dest_dst)
>  					mtu = dst_mtu(dest_dst->dst_cache);
>  			}
> -			if (mtu > 68 + sizeof(struct iphdr))
> -				mtu -= sizeof(struct iphdr);
> +			if (mtu > 68 + sizeof(struct iphdr) + ulen)
> +				mtu -= sizeof(struct iphdr) + ulen;
>  			info = htonl(mtu);
>  		}
>  		/* Strip outer IP, ICMP and IPIP/UDP/GRE, go to IP header of

Regards

--
Julian Anastasov <ja@ssi.bg>


^ permalink raw reply

* Re: [PATCH net-next V4 4/6] devlink: Apply eswitch mode boot defaults
From: Mark Bloch @ 2026-07-01 12:57 UTC (permalink / raw)
  To: Jiri Pirko
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <akThPmvUHvCMT2cp@FV6GYCPJ69>



On 01/07/2026 12:48, Jiri Pirko wrote:
> Mon, Jun 29, 2026 at 08:20:59PM +0200, mbloch@nvidia.com wrote:
>> Apply parsed devlink_eswitch_mode= defaults after devlink registration
>> and after successful reload.
>>
>> devl_register() may still be called before the device is ready for an
> 
> How so? I would assume that driver calls devl_register only after
> everything is up and running and ready. If not, isn't it a bug?
> 

You would think so :)

Some drivers, mlx5 included, call devl_register() while holding the
devlink instance lock and then finish setting up state before releasing
the lock.

In v3 I tried to enforce exactly that model, move devl_register() to
be the last thing the driver does. Jakub pushed back on making that a
general rule. So in v4 I changed the approach. devl_register() only
schedules the work, and the actual eswitch mode change can run only
after the driver releases the devlink lock.

Mark

> 
>> eswitch mode change, so keep a per-devlink delayed work item and pending
>> flag for the registration path. Registration queues the work, and the
>> worker tries to take the devlink instance lock.
>>
>> If the lock is busy, the worker requeues itself with a delay.
>>
>> For successful reloads that performed DRIVER_REINIT, devlink_reload()
>> already holds the devlink instance lock and the driver has completed
>> reload_up(). Clear pending work and apply the default directly from the
>> reload path instead of queueing work.
>>
>> If a user sets eswitch mode through netlink before the pending
>> registration work runs, clear the pending flag so the queued default does
>> not override that user request. Cancel pending default apply work when
>> freeing the devlink instance.
> 
> These AI generated code descriptive messages are generally not very
> useful :(
> 


^ permalink raw reply

* Re: [PATCH net-next V4 3/6] devlink: Parse eswitch mode boot defaults
From: Mark Bloch @ 2026-07-01 12:55 UTC (permalink / raw)
  To: Jiri Pirko
  Cc: Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
	Saeed Mahameed, Leon Romanovsky, Tariq Toukan, Andrew Lunn,
	Jonathan Corbet, Shuah Khan, netdev, linux-rdma, linux-doc
In-Reply-To: <akTencQhKSanuFeW@FV6GYCPJ69>



On 01/07/2026 12:38, Jiri Pirko wrote:
> Mon, Jun 29, 2026 at 08:20:58PM +0200, mbloch@nvidia.com wrote:
>> Add devlink_eswitch_mode= kernel command line parsing for a default
>> eswitch mode.
>>
>> The supported syntax selects either all devlink handles or one explicit
>> comma-separated handle list:
>>
>>  devlink_eswitch_mode=*=<mode>
>>
>>  devlink_eswitch_mode=<handle>[,<handle>...]=<mode>
>>
>> where <mode> is one of legacy, switchdev or switchdev_inactive. All
>> selected handles receive the same mode. Assigning different modes to
>> different handle lists in the same parameter value is not supported.
>>
>> Store the parsed selector and mode in devlink core so the default can be
>> applied by a downstream patch.
>>
>> Document the devlink_eswitch_mode= syntax and duplicate handle handling.
>>
>> Signed-off-by: Mark Bloch <mbloch@nvidia.com>
>> ---
>> .../admin-guide/kernel-parameters.txt         |  25 ++
>> .../networking/devlink/devlink-defaults.rst   |  78 ++++++
>> Documentation/networking/devlink/index.rst    |   1 +
>> net/devlink/core.c                            | 227 ++++++++++++++++++
>> 4 files changed, 331 insertions(+)
>> create mode 100644 Documentation/networking/devlink/devlink-defaults.rst
>>
>> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
>> index b5493a7f8f22..117300dd589c 100644
>> --- a/Documentation/admin-guide/kernel-parameters.txt
>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>> @@ -1249,6 +1249,31 @@ Kernel parameters
>> 	dell_smm_hwmon.fan_max=
>> 			[HW] Maximum configurable fan speed.
>>
>> +	devlink_eswitch_mode=
>> +			[NET]
>> +			Format:
>> +			<selector>=<mode>
>> +
>> +			<selector>:
>> +			* | <handle>[,<handle>...]
>> +
>> +			<handle>:
>> +			<bus-name>/<dev-name>
>> +
>> +			Configure default devlink eswitch mode for matching
>> +			devlink instances during device initialization.
>> +
>> +			<mode>:
>> +			legacy | switchdev | switchdev_inactive
>> +
>> +			Examples:
>> +			devlink_eswitch_mode=*=switchdev
>> +			devlink_eswitch_mode=pci/0000:08:00.0=switchdev
>> +			devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
>> +
>> +			See Documentation/networking/devlink/devlink-defaults.rst
>> +			for the full syntax.
>> +
>> 	dfltcc=		[HW,S390]
>> 			Format: { on | off | def_only | inf_only | always }
>> 			on:       s390 zlib hardware support for compression on
>> diff --git a/Documentation/networking/devlink/devlink-defaults.rst b/Documentation/networking/devlink/devlink-defaults.rst
>> new file mode 100644
>> index 000000000000..380c9e99210e
>> --- /dev/null
>> +++ b/Documentation/networking/devlink/devlink-defaults.rst
>> @@ -0,0 +1,78 @@
>> +.. SPDX-License-Identifier: GPL-2.0
>> +
>> +==============================
>> +Devlink Eswitch Mode Defaults
>> +==============================
>> +
>> +Devlink eswitch mode defaults allow the eswitch mode to be provided on the
>> +kernel command line and applied to matching devlink instances during device
>> +initialization.
>> +
>> +The devlink device is selected by its devlink handle. For PCI devices this is
>> +the same handle shown by ``devlink dev show``, for example
>> +``pci/0000:08:00.0``.
>> +
>> +Kernel command line syntax
>> +==========================
>> +
>> +Defaults are specified with the ``devlink_eswitch_mode=`` kernel command line
>> +parameter.
>> +
>> +The general syntax is::
>> +
>> +  devlink_eswitch_mode=<selector>=<mode>
>> +
>> +``<selector>`` is either ``*`` or one or more devlink handles::
>> +
>> +  * | <bus-name>/<dev-name>[,<bus-name>/<dev-name>...]
>> +
>> +``*`` applies the mode to every devlink instance. All handles in the same
>> +selector receive the same eswitch mode.
>> +
>> +``<mode>`` is one of ``legacy``, ``switchdev`` or ``switchdev_inactive``.
>> +
>> +Syntax rules
>> +------------
>> +
>> +The following syntax rules apply:
>> +
>> +* Specify the default in one ``devlink_eswitch_mode=`` parameter. Repeated
>> +  ``devlink_eswitch_mode=`` parameters are not accumulated.
>> +* The ``devlink_eswitch_mode=`` value is limited by the kernel command line
>> +  size.
>> +* Whitespace is not allowed within the parameter value.
>> +* ``<selector>`` must be either ``*`` or a handle list. ``*`` cannot be
>> +  combined with explicit handles.
>> +* ``<bus-name>`` and ``<dev-name>`` must not be empty.
>> +* ``<dev-name>`` may contain ``:``. This allows PCI names such as
>> +  ``0000:08:00.0``.
>> +* Handles must not contain whitespace, ``*``, ``=`` or more than one ``/``.
>> +* A comma separates handles.
>> +* Comma-separated default assignments are not supported.
>> +* Duplicate handles are rejected and the devlink eswitch mode default is
>> +  ignored.
>> +
>> +The eswitch mode default corresponds to the userspace command::
>> +
>> +  devlink dev eswitch set <handle> mode <value>
>> +
>> +
>> +Examples
>> +========
>> +
>> +Set all devlink instances to switchdev mode::
>> +
>> +  devlink_eswitch_mode=*=switchdev
>> +
>> +Set one PCI devlink instance to switchdev mode::
>> +
>> +  devlink_eswitch_mode=pci/0000:08:00.0=switchdev
>> +
>> +Set two PCI devlink instances to switchdev inactive mode::
>> +
>> +  devlink_eswitch_mode=pci/0000:08:00.0,pci/0000:09:00.1=switchdev_inactive
>> +
>> +The following is invalid because comma-separated default assignments are not
>> +supported::
>> +
>> +  devlink_eswitch_mode=pci/0000:08:00.0=switchdev,pci/0000:09:00.0=switchdev_inactive
> 
> Interesting. I would think that this is something user may want to set
> for some usecases, no?
> 
> 
>> diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst
>> index 32f70879ddd0..93f09cb18c44 100644
>> --- a/Documentation/networking/devlink/index.rst
>> +++ b/Documentation/networking/devlink/index.rst
>> @@ -56,6 +56,7 @@ general.
>>    :maxdepth: 1
>>
>>    devlink-dpipe
>> +   devlink-defaults
>>    devlink-eswitch-attr
>>    devlink-flash
>>    devlink-health
>> diff --git a/net/devlink/core.c b/net/devlink/core.c
> 
> Wanna have this in a separate file perhaps? "default.c"?
> 
> 
>> index fe9f6a0a67d5..5126509a9c4e 100644
>> --- a/net/devlink/core.c
>> +++ b/net/devlink/core.c
>> @@ -4,6 +4,10 @@
>>  * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
>>  */
>>
>> +#include <linux/init.h>
>> +#include <linux/list.h>
>> +#include <linux/slab.h>
>> +#include <linux/string.h>
>> #include <net/genetlink.h>
>> #define CREATE_TRACE_POINTS
>> #include <trace/events/devlink.h>
>> @@ -16,6 +20,193 @@ EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_trap_report);
>>
>> DEFINE_XARRAY_FLAGS(devlinks, XA_FLAGS_ALLOC);
>>
>> +static char *devlink_default_esw_mode_param;
>> +static bool devlink_default_esw_mode_match_all;
>> +static enum devlink_eswitch_mode devlink_default_esw_mode;
>> +static LIST_HEAD(devlink_default_esw_mode_nodes);
>> +
>> +struct devlink_default_esw_mode_node {
>> +	struct list_head list;
>> +	char *bus_name;
>> +	char *dev_name;
>> +};
>> +
>> +static int __init
>> +devlink_default_esw_mode_to_value(const char *str,
>> +				  enum devlink_eswitch_mode *mode)
>> +{
>> +	if (!strcmp(str, "legacy")) {
>> +		*mode = DEVLINK_ESWITCH_MODE_LEGACY;
>> +		return 0;
>> +	}
>> +	if (!strcmp(str, "switchdev")) {
>> +		*mode = DEVLINK_ESWITCH_MODE_SWITCHDEV;
>> +		return 0;
>> +	}
>> +	if (!strcmp(str, "switchdev_inactive")) {
>> +		*mode = DEVLINK_ESWITCH_MODE_SWITCHDEV_INACTIVE;
>> +		return 0;
>> +	}
>> +
>> +	return -EINVAL;
>> +}
>> +
>> +static int __init
>> +devlink_default_esw_mode_handle_parse(char *handle, char **bus_name,
>> +				      char **dev_name)
>> +{
>> +	char *slash;
>> +	char *p;
>> +
>> +	if (!*handle)
>> +		return -EINVAL;
>> +
>> +	for (p = handle; *p; p++) {
>> +		if (*p == '*' || *p == '=')
>> +			return -EINVAL;
>> +	}
>> +
>> +	slash = strchr(handle, '/');
>> +	if (!slash || slash == handle || !slash[1])
>> +		return -EINVAL;
>> +	if (strchr(slash + 1, '/'))
>> +		return -EINVAL;
>> +
>> +	*slash = '\0';
>> +
>> +	*bus_name = handle;
>> +	*dev_name = slash + 1;
>> +	return 0;
>> +}
>> +
>> +static struct devlink_default_esw_mode_node *
>> +devlink_default_esw_mode_node_find(const char *bus_name, const char *dev_name)
>> +{
>> +	struct devlink_default_esw_mode_node *node;
>> +
>> +	list_for_each_entry(node, &devlink_default_esw_mode_nodes, list) {
>> +		if (!strcmp(node->bus_name, bus_name) &&
>> +		    !strcmp(node->dev_name, dev_name))
>> +			return node;
>> +	}
>> +
>> +	return NULL;
>> +}
>> +
>> +static int __init
>> +devlink_default_esw_mode_node_add(const char *bus_name, const char *dev_name)
>> +{
>> +	struct devlink_default_esw_mode_node *node;
>> +
>> +	if (devlink_default_esw_mode_node_find(bus_name, dev_name))
>> +		return -EEXIST;
>> +
>> +	node = kzalloc_obj(*node);
>> +	if (!node)
>> +		return -ENOMEM;
>> +
>> +	INIT_LIST_HEAD(&node->list);
>> +	node->bus_name = kstrdup(bus_name, GFP_KERNEL);
>> +	node->dev_name = kstrdup(dev_name, GFP_KERNEL);
>> +	if (!node->bus_name || !node->dev_name) {
>> +		kfree(node->bus_name);
>> +		kfree(node->dev_name);
>> +		kfree(node);
>> +		return -ENOMEM;
>> +	}
>> +
>> +	list_add_tail(&node->list, &devlink_default_esw_mode_nodes);
>> +	return 0;
>> +}
>> +
>> +static int __init devlink_default_esw_mode_handles_parse(char *handles)
>> +{
>> +	char *handle;
>> +	int err;
>> +
>> +	if (!strcmp(handles, "*")) {
>> +		devlink_default_esw_mode_match_all = true;
>> +		return 0;
>> +	}
>> +
>> +	while ((handle = strsep(&handles, ",")) != NULL) {
>> +		char *bus_name;
>> +		char *dev_name;
>> +
>> +		err = devlink_default_esw_mode_handle_parse(handle, &bus_name,
>> +							    &dev_name);
>> +		if (err)
>> +			return err;
>> +
>> +		err = devlink_default_esw_mode_node_add(bus_name, dev_name);
>> +		if (err)
>> +			return err;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void __init
>> +devlink_default_esw_mode_node_free(struct devlink_default_esw_mode_node *node)
>> +{
>> +	kfree(node->bus_name);
>> +	kfree(node->dev_name);
>> +	kfree(node);
>> +}
>> +
>> +static void __init devlink_default_esw_mode_nodes_clear(void)
>> +{
>> +	struct devlink_default_esw_mode_node *node;
>> +	struct devlink_default_esw_mode_node *node_tmp;
>> +
>> +	list_for_each_entry_safe(node, node_tmp,
>> +				 &devlink_default_esw_mode_nodes, list) {
>> +		list_del(&node->list);
>> +		devlink_default_esw_mode_node_free(node);
>> +	}
>> +
>> +	devlink_default_esw_mode_match_all = false;
>> +}
>> +
>> +static int __init devlink_default_esw_mode_parse(char *str)
>> +{
>> +	char *handles;
>> +	char *separator;
>> +	char *mode;
>> +	enum devlink_eswitch_mode esw_mode;
>> +	int err;
>> +
>> +	if (!*str)
>> +		return -EINVAL;
>> +
>> +	separator = strrchr(str, '=');
>> +	if (!separator || separator == str || !separator[1])
>> +		return -EINVAL;
>> +
>> +	*separator = '\0';
>> +	handles = str;
>> +	mode = separator + 1;
>> +
>> +	err = devlink_default_esw_mode_to_value(mode, &esw_mode);
>> +	if (err)
>> +		return err;
>> +
>> +	err = devlink_default_esw_mode_handles_parse(handles);
>> +	if (err)
>> +		devlink_default_esw_mode_nodes_clear();
>> +	else
>> +		devlink_default_esw_mode = esw_mode;
>> +
>> +	return err;
>> +}
>> +
>> +static int __init devlink_default_esw_mode_setup(char *str)
>> +{
>> +	devlink_default_esw_mode_param = str;
>> +	return 1;
>> +}
>> +__setup("devlink_eswitch_mode=", devlink_default_esw_mode_setup);
>> +
>> static struct devlink *devlinks_xa_get(unsigned long index)
>> {
>> 	struct devlink *devlink;
>> @@ -382,6 +573,14 @@ struct devlink *devlinks_xa_lookup_get(struct net *net, unsigned long index)
>> /**
>>  * devl_register - Register devlink instance
>>  * @devlink: devlink
>> + *
>> + * Make @devlink visible to userspace. Drivers must call this only after the
>> + * instance is fully initialized and its devlink operations can be called.
>> + *
>> + * Context: Caller must hold the devlink instance lock. Use devlink_register()
>> + * when the lock is not already held.
>> + *
>> + * Return: 0 on success.
>>  */
>> int devl_register(struct devlink *devlink)
>> {
>> @@ -580,6 +779,31 @@ static int __init devlink_init(void)
>> {
>> 	int err;
>>
>> +	if (devlink_default_esw_mode_param) {
>> +		char *def;
>> +
>> +		def = kstrdup(devlink_default_esw_mode_param, GFP_KERNEL);
>> +		if (!def) {
>> +			devlink_default_esw_mode_param = NULL;
>> +			pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
>> +		} else {
>> +			err = devlink_default_esw_mode_parse(def);
>> +			kfree(def);
>> +			if (err == -EEXIST) {
>> +				devlink_default_esw_mode_param = NULL;
>> +				pr_warn("devlink: duplicate eswitch mode handles ignored\n");
>> +			} else if (err == -EINVAL) {
>> +				devlink_default_esw_mode_param = NULL;
>> +				pr_warn("devlink: invalid devlink_eswitch_mode parameter ignored\n");
>> +			} else if (err == -ENOMEM) {
>> +				devlink_default_esw_mode_param = NULL;
>> +				pr_warn("devlink: devlink_eswitch_mode parameter ignored, failed to allocate memory\n");
>> +			} else if (err) {
>> +				goto out;
>> +			}
> 
> Move this to a separate helper alongside the other "default" functions?

I did that in the following patch. If I respin I'll do it in this one.

Mark

> 
> 
>> +		}
>> +	}
>> +
>> 	err = register_pernet_subsys(&devlink_pernet_ops);
>> 	if (err)
>> 		goto out;
>> @@ -595,7 +819,10 @@ static int __init devlink_init(void)
>> out_unreg_pernet_subsys:
>> 	unregister_pernet_subsys(&devlink_pernet_ops);
>> out:
>> +	if (err)
>> +		devlink_default_esw_mode_nodes_clear();
>> 	WARN_ON(err);
>> +
>> 	return err;
>> }
>>
>> -- 
>> 2.43.0
>>


^ permalink raw reply

* [PATCH net-next] net: rmnet: annotate endpoint lookup under RTNL
From: Runyu Xiao @ 2026-07-01 12:40 UTC (permalink / raw)
  To: subash.a.kasiviswanathan, sean.tranchetti
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, netdev,
	linux-kernel, runyu.xiao, jianhao.xu

rmnet_get_endpoint() is shared by packet receive paths and
RTNL-protected control paths. The receive paths already run under RCU/BH
context through the RX handler, while the control paths reach
rmnet_get_endpoint() after obtaining the rmnet port with
rmnet_get_port_rtnl().

The helper walks port->muxed_ep[] with hlist_for_each_entry_rcu(). Pass
lockdep_rtnl_is_held() as the non-RCU protection condition so
CONFIG_PROVE_RCU_LIST can see the RTNL-protected control-path calls
while preserving the existing RCU-reader behavior for data paths.

This was found by our static analysis tool and then manually reviewed
against the current tree. The dynamic triage evidence is a
target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
to documenting the existing protection contract.

This is a lockdep annotation cleanup. It does not change endpoint
lifetime or hash updates.

Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
---
 drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
index ba8763cac9d9..977fb80d8bf8 100644
--- a/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
+++ b/drivers/net/ethernet/qualcomm/rmnet/rmnet_config.c
@@ -423,7 +423,8 @@ struct rmnet_endpoint *rmnet_get_endpoint(struct rmnet_port *port, u8 mux_id)
 {
 	struct rmnet_endpoint *ep;
 
-	hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode) {
+	hlist_for_each_entry_rcu(ep, &port->muxed_ep[mux_id], hlnode,
+				 lockdep_rtnl_is_held()) {
 		if (ep->mux_id == mux_id)
 			return ep;
 	}
-- 
2.34.1


^ permalink raw reply related

* Re: [PATCH net-next v7 4/4] net: phy: realtek: load firmware for RTL8261C_CG
From: Andrew Lunn @ 2026-07-01 12:52 UTC (permalink / raw)
  To: Javen
  Cc: hkallweit1@gmail.com, linux@armlinux.org.uk, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	顾晓军, nb@tipi-net.de, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, daniel@makrotopia.org,
	vladimir.oltean@nxp.com, nic_swsd@realtek.com
In-Reply-To: <00ed7993f38d4597a8c20f71bc7da58e@realsil.com.cn>

On Wed, Jul 01, 2026 at 03:12:34AM +0000, Javen wrote:
> >
> >On Mon, Jun 29, 2026 at 02:47:18PM +0800, javen wrote:
> >> From: Javen Xu <javen_xu@realsil.com.cn>
> >>
> >> This patch adds support for loading firmware. Download some parameters
> >> for RTL8261C_CG.
> >>
> >> Signed-off-by: Javen Xu <javen_xu@realsil.com.cn>
> >> ---
> >> Changes in v2:
> >>  - remove __pack, struct rtl8261x_fw_header and rtl8261x_fw_entry will
> >> not pad
> >>  - reverse xmas tree for some definition
> >>  - add explanation on rtl_phy_write_mmd_bits()
> >>
> >> Changes in v3:
> >>  - add struct rtl8261x_priv
> >>
> >> Changes in v4:
> >>  - add struct device *dev
> >>
> >> Changes in v5:
> >>  - no changes
> >>
> >> Changes in v6:
> >>  - replace rtl_phy_write_mmd_bits with phy_modify_mmd, keep mdio lock
> >>  - check msb and lsb at the beginning of rtl8261x_fw_execute_entry()
> >>  - add comments on rtl8261x_config_init()
> >>
> >> Changes in v7:
> >>  - no changes
> >> ---
> >>  drivers/net/phy/realtek/realtek_main.c | 220
> >> +++++++++++++++++++++++++
> >>  1 file changed, 220 insertions(+)
> >>
> >> diff --git a/drivers/net/phy/realtek/realtek_main.c
> >> b/drivers/net/phy/realtek/realtek_main.c
> >> index ef3700894ebf..bf7bc19fb44c 100644
> >> --- a/drivers/net/phy/realtek/realtek_main.c
> >> +++ b/drivers/net/phy/realtek/realtek_main.c
> >> @@ -8,7 +8,9 @@
> >>   * Copyright (c) 2004 Freescale Semiconductor, Inc.
> >>   */
> >>  #include <linux/bitops.h>
> >> +#include <linux/crc32.h>
> >>  #include <linux/ethtool_netlink.h>
> >> +#include <linux/firmware.h>
> >>  #include <linux/of.h>
> >>  #include <linux/phy.h>
> >>  #include <linux/pm_wakeirq.h>
> >> @@ -281,6 +283,42 @@
> >>                                        RTL8261X_INT_ALDPS_CHG | \
> >>                                        RTL8261X_INT_JABBER)
> >>
> >> +#define FW_MAIN_MAGIC                        0x52544C38
> >> +#define FW_SUB_MAGIC_8261C           0x32363143
> >> +#define RTL8261X_POLL_TIMEOUT_MS     100
> >> +
> >> +#define RTL8261C_CE_FW_NAME  "rtl_nic/rtl8261c.bin"
> >> +MODULE_FIRMWARE(RTL8261C_CE_FW_NAME);
> >> +
> >> +enum rtl8261x_fw_op {
> >> +     OP_WRITE = 0x00,        /* Write */
> >> +     OP_POLL  = 0x02,        /* Polling */
> >> +};
> >> +
> >> +struct rtl8261x_fw_header {
> >> +     __le32 main_magic;      /* Main magic number 0x52544C38 ("RTL8") */
> >> +     __le32 sub_magic;       /* Sub magic number */
> >> +     __le16 version_major;   /* Major version */
> >> +     __le16 version_minor;   /* Minor version */
> >> +     __le16 num_entries;     /* Number of entries */
> >> +     __le16 reserved;        /* Reserved */
> >> +     __le32 crc32;           /* CRC32 checksum */
> >> +};
> >> +
> >> +struct rtl8261x_fw_entry {
> >> +     __u8  type;             /* Operation type (OP_*) */
> >> +     __u8  dev;              /* MMD device */
> >> +     __le16 addr;            /* Register address */
> >> +     __u8  msb;              /* MSB bit position */
> >> +     __u8  lsb;              /* LSB bit position */
> >> +     __le16 value;           /* Value to write/compare */
> >> +     __le16 timeout_ms;      /* Poll timeout in milliseconds */
> >> +     __u8  poll_set;         /* Poll for set (1) or clear (0) */
> >> +     __u8  reserved;         /* Reserved */
> >> +};
> >
> >Are there other devices which need firmware download? Do they use the
> >same header? I'm just wondering if this will be reused by other devices?
> >
> Hi Andrew,
> 
> Currently, RTL8261C is the only device which needs this firmware download flow.
> 
> Future Realtek PHY ICs which require firmware download are expected to use the same firmware format, so
> the current header/entry definition is intended to be reusable.

If this is going to be re-used, then O.K. I was asking because it is
more complex than it needs to be for just one PHY.

     Andrew

^ permalink raw reply

* [PATCH net-next] tun: no longer rely on RTNL in tun_fill_info()
From: Eric Dumazet @ 2026-07-01 12:51 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
	eric.dumazet, Eric Dumazet

Update tun_fill_info() to read device configuration fields (flags, owner,
group, numqueues, numdisabled) locklessly using READ_ONCE().

Annotate all writes to these fields in the control paths with WRITE_ONCE()
to prevent data races, as these fields can be modified concurrently via
ioctls (TUNSETPERSIST, TUNSETOWNER, TUNSETGROUP, TUNSETIFF) or queue
attaching/detaching.

Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 drivers/net/tun.c | 64 +++++++++++++++++++++++++----------------------
 1 file changed, 34 insertions(+), 30 deletions(-)

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index ffbe6f13fb1fa2333227a6b085806c82a362c0a4..fc8ad6a35708825006f7d199a8f220f30e43df9e 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -532,7 +532,7 @@ static void tun_disable_queue(struct tun_struct *tun, struct tun_file *tfile)
 {
 	tfile->detached = tun;
 	list_add_tail(&tfile->next, &tun->disabled);
-	++tun->numdisabled;
+	WRITE_ONCE(tun->numdisabled, tun->numdisabled + 1);
 }
 
 static struct tun_struct *tun_enable_queue(struct tun_file *tfile)
@@ -541,7 +541,7 @@ static struct tun_struct *tun_enable_queue(struct tun_file *tfile)
 
 	tfile->detached = NULL;
 	list_del_init(&tfile->next);
-	--tun->numdisabled;
+	WRITE_ONCE(tun->numdisabled, tun->numdisabled - 1);
 	return tun;
 }
 
@@ -600,7 +600,7 @@ static void __tun_detach(struct tun_file *tfile, bool clean)
 		rcu_assign_pointer(tun->tfiles[tun->numqueues - 1],
 				   NULL);
 
-		--tun->numqueues;
+		WRITE_ONCE(tun->numqueues, tun->numqueues - 1);
 		if (clean) {
 			RCU_INIT_POINTER(tfile->tun, NULL);
 			sock_put(&tfile->sk);
@@ -663,7 +663,7 @@ static void tun_detach_all(struct net_device *dev)
 		tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN;
 		tfile->socket.sk->sk_data_ready(tfile->socket.sk);
 		RCU_INIT_POINTER(tfile->tun, NULL);
-		--tun->numqueues;
+		WRITE_ONCE(tun->numqueues, tun->numqueues - 1);
 	}
 	list_for_each_entry(tfile, &tun->disabled, next) {
 		tfile->socket.sk->sk_shutdown = RCV_SHUTDOWN;
@@ -786,7 +786,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
 	if (publish_tun)
 		rcu_assign_pointer(tfile->tun, tun);
 	rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
-	tun->numqueues++;
+	WRITE_ONCE(tun->numqueues, tun->numqueues + 1);
 	tun_set_real_num_queues(tun);
 out:
 	return err;
@@ -943,8 +943,8 @@ static int tun_net_init(struct net_device *dev)
 			       NETIF_F_HW_VLAN_STAG_TX);
 	dev->lltx = true;
 
-	tun->flags = (tun->flags & ~TUN_FEATURES) |
-		      (ifr->ifr_flags & TUN_FEATURES);
+	WRITE_ONCE(tun->flags, (tun->flags & ~TUN_FEATURES) |
+			       (ifr->ifr_flags & TUN_FEATURES));
 
 	INIT_LIST_HEAD(&tun->disabled);
 	err = tun_attach(tun, tun->file, false, ifr->ifr_flags & IFF_NAPI,
@@ -2370,32 +2370,36 @@ static size_t tun_get_size(const struct net_device *dev)
 
 static int tun_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
-	struct tun_struct *tun = netdev_priv(dev);
+	const struct tun_struct *tun = netdev_priv(dev);
+	unsigned int flags = READ_ONCE(tun->flags);
+	kuid_t owner = READ_ONCE(tun->owner);
+	kgid_t group = READ_ONCE(tun->group);
 
-	if (nla_put_u8(skb, IFLA_TUN_TYPE, tun->flags & TUN_TYPE_MASK))
+	if (nla_put_u8(skb, IFLA_TUN_TYPE, flags & TUN_TYPE_MASK))
 		goto nla_put_failure;
-	if (uid_valid(tun->owner) &&
+	if (uid_valid(owner) &&
 	    nla_put_u32(skb, IFLA_TUN_OWNER,
-			from_kuid_munged(current_user_ns(), tun->owner)))
+			from_kuid_munged(current_user_ns(), owner)))
 		goto nla_put_failure;
-	if (gid_valid(tun->group) &&
+	if (gid_valid(group) &&
 	    nla_put_u32(skb, IFLA_TUN_GROUP,
-			from_kgid_munged(current_user_ns(), tun->group)))
+			from_kgid_munged(current_user_ns(), group)))
 		goto nla_put_failure;
-	if (nla_put_u8(skb, IFLA_TUN_PI, !(tun->flags & IFF_NO_PI)))
+	if (nla_put_u8(skb, IFLA_TUN_PI, !(flags & IFF_NO_PI)))
 		goto nla_put_failure;
-	if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(tun->flags & IFF_VNET_HDR)))
+	if (nla_put_u8(skb, IFLA_TUN_VNET_HDR, !!(flags & IFF_VNET_HDR)))
 		goto nla_put_failure;
-	if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(tun->flags & IFF_PERSIST)))
+	if (nla_put_u8(skb, IFLA_TUN_PERSIST, !!(flags & IFF_PERSIST)))
 		goto nla_put_failure;
 	if (nla_put_u8(skb, IFLA_TUN_MULTI_QUEUE,
-		       !!(tun->flags & IFF_MULTI_QUEUE)))
+		       !!(flags & IFF_MULTI_QUEUE)))
 		goto nla_put_failure;
-	if (tun->flags & IFF_MULTI_QUEUE) {
-		if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES, tun->numqueues))
+	if (flags & IFF_MULTI_QUEUE) {
+		if (nla_put_u32(skb, IFLA_TUN_NUM_QUEUES,
+				READ_ONCE(tun->numqueues)))
 			goto nla_put_failure;
 		if (nla_put_u32(skb, IFLA_TUN_NUM_DISABLED_QUEUES,
-				tun->numdisabled))
+				READ_ONCE(tun->numdisabled)))
 			goto nla_put_failure;
 	}
 
@@ -2814,8 +2818,8 @@ static int tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
 			return 0;
 		}
 
-		tun->flags = (tun->flags & ~TUN_FEATURES) |
-			      (ifr->ifr_flags & TUN_FEATURES);
+		WRITE_ONCE(tun->flags, (READ_ONCE(tun->flags) & ~TUN_FEATURES) |
+				       (ifr->ifr_flags & TUN_FEATURES));
 
 		netdev_state_change(dev);
 	} else {
@@ -3213,13 +3217,13 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 		/* Disable/Enable persist mode. Keep an extra reference to the
 		 * module to prevent the module being unprobed.
 		 */
-		if (arg && !(tun->flags & IFF_PERSIST)) {
-			tun->flags |= IFF_PERSIST;
+		if (arg && !(READ_ONCE(tun->flags) & IFF_PERSIST)) {
+			WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) | IFF_PERSIST);
 			__module_get(THIS_MODULE);
 			do_notify = true;
 		}
-		if (!arg && (tun->flags & IFF_PERSIST)) {
-			tun->flags &= ~IFF_PERSIST;
+		if (!arg && (READ_ONCE(tun->flags) & IFF_PERSIST)) {
+			WRITE_ONCE(tun->flags, READ_ONCE(tun->flags) & ~IFF_PERSIST);
 			module_put(THIS_MODULE);
 			do_notify = true;
 		}
@@ -3235,10 +3239,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 			ret = -EINVAL;
 			break;
 		}
-		tun->owner = owner;
+		WRITE_ONCE(tun->owner, owner);
 		do_notify = true;
 		netif_info(tun, drv, tun->dev, "owner set to %u\n",
-			   from_kuid(&init_user_ns, tun->owner));
+			   from_kuid(&init_user_ns, owner));
 		break;
 
 	case TUNSETGROUP:
@@ -3248,10 +3252,10 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
 			ret = -EINVAL;
 			break;
 		}
-		tun->group = group;
+		WRITE_ONCE(tun->group, group);
 		do_notify = true;
 		netif_info(tun, drv, tun->dev, "group set to %u\n",
-			   from_kgid(&init_user_ns, tun->group));
+			   from_kgid(&init_user_ns, group));
 		break;
 
 	case TUNSETLINK:
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* [PATCH net-next] amt: no longer rely on RTNL in amt_fill_info()
From: Eric Dumazet @ 2026-07-01 12:50 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
	eric.dumazet, Eric Dumazet

Update amt_fill_info() to run under RCU read lock instead of RTNL.

The AMT device configuration fields (mode, relay_port, gw_port, local_ip,
discovery_ip, max_tunnels) and stream_dev pointer are initialized during
device creation (amt_newlink) and are immutable. Accessing them locklessly
is safe. The stream_dev net_device structure is protected from being freed
by RCU.

The only field that can change concurrently is amt->remote_ip, which is
updated in the packet receive path (amt_advertisement_handler) and
workqueue (amt_req_work). Add READ_ONCE()/WRITE_ONCE() annotations
around amt->remote_ip to prevent data races.

Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 drivers/net/amt.c | 43 +++++++++++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 16 deletions(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 724a8163a5142a6835950abb63d80f29417b2654..5cf97c65576fc23b6faac1d686b0fd90b0e05433 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -708,11 +708,13 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 	struct iphdr *iph;
 	struct rtable *rt;
 	struct flowi4 fl4;
+	__be32 remote_ip;
 	struct sock *sk;
 	u32 len;
 	int err;
 
 	rcu_read_lock();
+	remote_ip = READ_ONCE(amt->remote_ip);
 	sk = rcu_dereference(amt->sk);
 	if (!sk)
 		goto out;
@@ -721,7 +723,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 		goto out;
 
 	rt = ip_route_output_ports(amt->net, &fl4, sk,
-				   amt->remote_ip, amt->local_ip,
+				   remote_ip, amt->local_ip,
 				   amt->gw_port, amt->relay_port,
 				   IPPROTO_UDP, 0,
 				   amt->stream_dev->ifindex);
@@ -762,7 +764,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 	udph->check	= 0;
 	offset = skb_transport_offset(skb);
 	skb->csum = skb_checksum(skb, offset, skb->len - offset, 0);
-	udph->check = csum_tcpudp_magic(amt->local_ip, amt->remote_ip,
+	udph->check = csum_tcpudp_magic(amt->local_ip, remote_ip,
 					sizeof(*udph) + sizeof(*amtrh),
 					IPPROTO_UDP, skb->csum);
 
@@ -773,7 +775,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6)
 	iph->tos	= AMT_TOS;
 	iph->frag_off	= 0;
 	iph->ttl	= ip4_dst_hoplimit(&rt->dst);
-	iph->daddr	= amt->remote_ip;
+	iph->daddr	= remote_ip;
 	iph->saddr	= amt->local_ip;
 	iph->protocol	= IPPROTO_UDP;
 	iph->tot_len	= htons(len);
@@ -962,7 +964,7 @@ static void amt_event_send_request(struct amt_dev *amt)
 		amt->qi = AMT_INIT_REQ_TIMEOUT;
 		WRITE_ONCE(amt->ready4, false);
 		WRITE_ONCE(amt->ready6, false);
-		amt->remote_ip = 0;
+		WRITE_ONCE(amt->remote_ip, 0);
 		amt_update_gw_status(amt, AMT_STATUS_INIT, false);
 		amt->req_cnt = 0;
 		amt->nonce = 0;
@@ -999,6 +1001,7 @@ static bool amt_send_membership_update(struct amt_dev *amt,
 				       struct sk_buff *skb,
 				       bool v6)
 {
+	__be32 remote_ip = READ_ONCE(amt->remote_ip);
 	struct amt_header_membership_update *amtmu;
 	struct iphdr *iph;
 	struct flowi4 fl4;
@@ -1018,13 +1021,13 @@ static bool amt_send_membership_update(struct amt_dev *amt,
 	skb_reset_inner_headers(skb);
 	memset(&fl4, 0, sizeof(struct flowi4));
 	fl4.flowi4_oif         = amt->stream_dev->ifindex;
-	fl4.daddr              = amt->remote_ip;
+	fl4.daddr              = remote_ip;
 	fl4.saddr              = amt->local_ip;
 	fl4.flowi4_dscp        = inet_dsfield_to_dscp(AMT_TOS);
 	fl4.flowi4_proto       = IPPROTO_UDP;
 	rt = ip_route_output_key(amt->net, &fl4);
 	if (IS_ERR(rt)) {
-		netdev_dbg(amt->dev, "no route to %pI4\n", &amt->remote_ip);
+		netdev_dbg(amt->dev, "no route to %pI4\n", &remote_ip);
 		return true;
 	}
 
@@ -2272,8 +2275,8 @@ static bool amt_advertisement_handler(struct amt_dev *amt, struct sk_buff *skb)
 	    amt->nonce != amta->nonce)
 		return true;
 
-	amt->remote_ip = amta->ip4;
-	netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amt->remote_ip);
+	WRITE_ONCE(amt->remote_ip, amta->ip4);
+	netdev_dbg(amt->dev, "advertised remote ip = %pI4\n", &amta->ip4);
 	mod_delayed_work(amt_wq, &amt->req_wq, 0);
 
 	amt_update_gw_status(amt, AMT_STATUS_RECEIVED_ADVERTISEMENT, true);
@@ -2773,6 +2776,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 {
 	struct amt_dev *amt;
 	struct iphdr *iph;
+	__be32 remote_ip;
 	int type;
 	bool err;
 
@@ -2783,6 +2787,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 		kfree_skb(skb);
 		goto out;
 	}
+	remote_ip = READ_ONCE(amt->remote_ip);
 
 	skb->dev = amt->dev;
 	iph = ip_hdr(skb);
@@ -2807,7 +2812,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 			}
 			goto out;
 		case AMT_MSG_MULTICAST_DATA:
-			if (iph->saddr != amt->remote_ip) {
+			if (iph->saddr != remote_ip) {
 				netdev_dbg(amt->dev, "Invalid Relay IP\n");
 				err = true;
 				goto drop;
@@ -2818,7 +2823,7 @@ static int amt_rcv(struct sock *sk, struct sk_buff *skb)
 			else
 				goto out;
 		case AMT_MSG_MEMBERSHIP_QUERY:
-			if (iph->saddr != amt->remote_ip) {
+			if (iph->saddr != remote_ip) {
 				netdev_dbg(amt->dev, "Invalid Relay IP\n");
 				err = true;
 				goto drop;
@@ -3000,7 +3005,7 @@ static int amt_dev_open(struct net_device *dev)
 		return err;
 
 	amt->req_cnt = 0;
-	amt->remote_ip = 0;
+	WRITE_ONCE(amt->remote_ip, 0);
 	amt->nonce = 0;
 	get_random_bytes(&amt->key, sizeof(siphash_key_t));
 
@@ -3045,7 +3050,7 @@ static int amt_dev_stop(struct net_device *dev)
 	amt->ready4 = false;
 	amt->ready6 = false;
 	amt->req_cnt = 0;
-	amt->remote_ip = 0;
+	WRITE_ONCE(amt->remote_ip, 0);
 
 	list_for_each_entry_safe(tunnel, tmp, &amt->tunnel_list, list) {
 		list_del_rcu(&tunnel->list);
@@ -3244,7 +3249,7 @@ static int amt_newlink(struct net_device *dev,
 					    "gateway port must not be 0");
 			goto err;
 		}
-		amt->remote_ip = 0;
+		WRITE_ONCE(amt->remote_ip, 0);
 		amt->discovery_ip = nla_get_in_addr(data[IFLA_AMT_DISCOVERY_IP]);
 		if (ipv4_is_loopback(amt->discovery_ip) ||
 		    ipv4_is_zeronet(amt->discovery_ip) ||
@@ -3308,8 +3313,10 @@ static size_t amt_get_size(const struct net_device *dev)
 
 static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)
 {
-	struct amt_dev *amt = netdev_priv(dev);
+	const struct amt_dev *amt = netdev_priv(dev);
+	__be32 remote_ip;
 
+	rcu_read_lock();
 	if (nla_put_u32(skb, IFLA_AMT_MODE, amt->mode))
 		goto nla_put_failure;
 	if (nla_put_be16(skb, IFLA_AMT_RELAY_PORT, amt->relay_port))
@@ -3322,15 +3329,19 @@ static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)
 		goto nla_put_failure;
 	if (nla_put_in_addr(skb, IFLA_AMT_DISCOVERY_IP, amt->discovery_ip))
 		goto nla_put_failure;
-	if (amt->remote_ip)
-		if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, amt->remote_ip))
+
+	remote_ip = READ_ONCE(amt->remote_ip);
+	if (remote_ip)
+		if (nla_put_in_addr(skb, IFLA_AMT_REMOTE_IP, remote_ip))
 			goto nla_put_failure;
 	if (nla_put_u32(skb, IFLA_AMT_MAX_TUNNELS, amt->max_tunnels))
 		goto nla_put_failure;
 
+	rcu_read_unlock();
 	return 0;
 
 nla_put_failure:
+	rcu_read_unlock();
 	return -EMSGSIZE;
 }
 
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* Re: [PATCH 2/3] arm64: dts: socfpga: agilex5: Add SoCDK TSN Config2 board
From: Andrew Lunn @ 2026-07-01 12:47 UTC (permalink / raw)
  To: Nazle Asmade, Muhammad Nazim Amirul
  Cc: dinguyen@kernel.org, maxime.chevallier@bootlin.com,
	rmk+kernel@armlinux.org.uk, krzk+dt@kernel.org,
	conor+dt@kernel.org, robh@kernel.org, davem@davemloft.net,
	edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
	andrew+netdev@lunn.ch, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
In-Reply-To: <5a0c962e-1af0-4d6a-b871-d8a0b0197ff5@altera.com>

> > # There are a small number of cases where the MAC has hard coded
> > # delays which cannot be disabled. The 'phy-mode' only describes the
> > # PCB.  The inability to disable the delays in the MAC does not change
> > # the meaning of 'phy-mode'. It does however mean that a 'phy-mode' of
> > # 'rgmii' is now invalid, it cannot be supported, since both the PCB
> > # and the MAC and PHY adding delays cannot result in a functional
> > # link. Thus the MAC should report a fatal error for any modes which
> > # cannot be supported. When the MAC implements the delay, it must
> > # ensure that the PHY does not also implement the same delay. So it
> > # must modify the phy-mode it passes to the PHY, removing the delay it
> > # has added. Failure to remove the delay will result in a
> > # non-functioning link.
> >
> >      Andrew
> >
> > ---
> > pw-bot: cr
> Hi Andrew,
> 
> The delays are provided by the FPGA GMII-to-RGMII converter soft IP,
> which is hardcoded in the FPGA bitstream and cannot be disabled or
> modified from the driver side.
> 
> Using phy-mode = "rgmii" is intentional here — it prevents the PHY from
> adding its own internal delays on top, since the FPGA converter already
> provides the full required delay. This is consistent with how all other
> Agilex5 SoCDK board variants are described, as seen in commit
> c5637e5ceb4b ("arm64: dts: socfpga: agilex5: Fix phy-mode to rgmii as HW
> provides clock delay") already in Dinh Nguyen's tree, which applies the
> same rationale across all Agilex5 boards.

I've become more insistent that designs get this correct. So i don't
care too much about past systems. Many vendors are having to fix up
their drivers and DT in order to make new boards consistent.

You can look at your system as the FPGA being the MAC, and the PHY is
the PHY. The PCB is not providing the delay, the MAC is. This exactly
fits the description above.

     Andrew

^ permalink raw reply

* [PATCH net-next] net/mlx5e: MACsec: annotate context list traversals
From: Runyu Xiao @ 2026-07-01 12:40 UTC (permalink / raw)
  To: borisp, saeedm, leon, tariqt, mbloch
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, sd, dtatulea,
	cjubran, horms, jianbol, netdev, linux-rdma, linux-kernel,
	runyu.xiao, jianhao.xu

The MACsec offload control paths take macsec->lock before looking up
MACsec device and RX SC contexts. The lookup helpers walk RCU lists, but
the iterators do not currently pass the mutex condition, so
CONFIG_PROVE_RCU_LIST cannot see the existing writer/control-path
protection.

Pass lockdep_is_held(&macsec->lock) to the list iterators in the MACsec
lookup helpers. The RX SC helper does not otherwise need the MACsec
context, so pass it in only to express the existing lockdep condition.

This was found by our static analysis tool and then manually reviewed
against the current tree. The dynamic triage evidence is a
target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
to documenting the existing protection contract.

This is a lockdep annotation cleanup. It does not change MACsec context
lifetime or list updates.

Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
---
 .../mellanox/mlx5/core/en_accel/macsec.c      | 23 +++++++++++--------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c
index 528b04d4de41..3028e327e36d 100644
--- a/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c
+++ b/drivers/net/ethernet/mellanox/mlx5/core/en_accel/macsec.c
@@ -405,11 +405,13 @@ static int mlx5e_macsec_init_sa(struct macsec_context *ctx,
 }
 
 static struct mlx5e_macsec_rx_sc *
-mlx5e_macsec_get_rx_sc_from_sc_list(const struct list_head *list, sci_t sci)
+mlx5e_macsec_get_rx_sc_from_sc_list(struct mlx5e_macsec *macsec,
+				    const struct list_head *list, sci_t sci)
 {
 	struct mlx5e_macsec_rx_sc *iter;
 
-	list_for_each_entry_rcu(iter, list, rx_sc_list_element) {
+	list_for_each_entry_rcu(iter, list, rx_sc_list_element,
+				lockdep_is_held(&macsec->lock)) {
 		if (iter->sci == sci)
 			return iter;
 	}
@@ -473,14 +475,15 @@ static bool mlx5e_macsec_secy_features_validate(struct macsec_context *ctx)
 }
 
 static struct mlx5e_macsec_device *
-mlx5e_macsec_get_macsec_device_context(const struct mlx5e_macsec *macsec,
+mlx5e_macsec_get_macsec_device_context(struct mlx5e_macsec *macsec,
 				       const struct macsec_context *ctx)
 {
 	struct mlx5e_macsec_device *iter;
 	const struct list_head *list;
 
 	list = &macsec->macsec_device_list_head;
-	list_for_each_entry_rcu(iter, list, macsec_device_list_element) {
+	list_for_each_entry_rcu(iter, list, macsec_device_list_element,
+				lockdep_is_held(&macsec->lock)) {
 		if (iter->netdev == ctx->secy->netdev)
 			return iter;
 	}
@@ -692,7 +695,7 @@ static int mlx5e_macsec_add_rxsc(struct macsec_context *ctx)
 	}
 
 	rx_sc_list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(rx_sc_list, ctx_rx_sc->sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, rx_sc_list, ctx_rx_sc->sci);
 	if (rx_sc) {
 		netdev_err(ctx->netdev, "MACsec offload: rx_sc (sci %lld) already exists\n",
 			   ctx_rx_sc->sci);
@@ -775,7 +778,7 @@ static int mlx5e_macsec_upd_rxsc(struct macsec_context *ctx)
 	}
 
 	list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(list, ctx_rx_sc->sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, list, ctx_rx_sc->sci);
 	if (!rx_sc) {
 		err = -EINVAL;
 		goto out;
@@ -853,7 +856,7 @@ static int mlx5e_macsec_del_rxsc(struct macsec_context *ctx)
 	}
 
 	list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(list, ctx->rx_sc->sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, list, ctx->rx_sc->sci);
 	if (!rx_sc) {
 		netdev_err(ctx->netdev,
 			   "MACsec offload rx_sc sci %lld doesn't exist\n",
@@ -894,7 +897,7 @@ static int mlx5e_macsec_add_rxsa(struct macsec_context *ctx)
 	}
 
 	list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(list, sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, list, sci);
 	if (!rx_sc) {
 		netdev_err(ctx->netdev,
 			   "MACsec offload rx_sc sci %lld doesn't exist\n",
@@ -978,7 +981,7 @@ static int mlx5e_macsec_upd_rxsa(struct macsec_context *ctx)
 	}
 
 	list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(list, sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, list, sci);
 	if (!rx_sc) {
 		netdev_err(ctx->netdev,
 			   "MACsec offload rx_sc sci %lld doesn't exist\n",
@@ -1035,7 +1038,7 @@ static int mlx5e_macsec_del_rxsa(struct macsec_context *ctx)
 	}
 
 	list = &macsec_device->macsec_rx_sc_list_head;
-	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(list, sci);
+	rx_sc = mlx5e_macsec_get_rx_sc_from_sc_list(macsec, list, sci);
 	if (!rx_sc) {
 		netdev_err(ctx->netdev,
 			   "MACsec offload rx_sc sci %lld doesn't exist\n",
-- 
2.34.1


^ permalink raw reply related

* [PATCH v5 net 7/7] i40e: keep q_vectors array in sync with channel count changes
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

For the main VSI, i40e_set_num_rings_in_vsi() always derives
num_q_vectors from pf->num_lan_msix. At the same time, ethtool -L stores
the user requested channel count in vsi->req_queue_pairs and the queue
setup path uses that value for the effective number of queue pairs.

This leaves queue and vector counts out of sync after shrinking channel
count via ethtool -L. The active queue configuration is reduced, but the
VSI still keeps the full PF-sized q_vector topology.

That mismatch breaks reconfiguration flows which rely on vector/NAPI
state matching the effective channel configuration. In particular,
toggling /sys/class/net/<dev>/threaded after reducing the channel count
can hang, and later channel-count changes can fail because VSI reinit
does not rebuild q_vectors to match the new vector count.

Fix this by making the main VSI num_q_vectors follow the effective
requested channel count, capped by the available MSI-X vectors. Update
i40e_vsi_reinit_setup() to rebuild q_vectors during VSI reinit so the
vector topology is refreshed together with the ring arrays when channel
count changes.

Keep alloc_queue_pairs unchanged and based on pf->num_lan_qps so the VSI
retains its full queue capacity. Also do not touch irq_pile when
rebuilding vectors.

Selftest napi_threaded.py was originally used when Jakub reported hang
on /sys/class/net/<dev>/threaded toggle. In order to make it pass on
i40e, use persistent NAPI configuration for q_vector NAPIs so NAPI
identity and threaded settings survive q_vector reallocation across
channel-count changes. This is achieved by using netif_napi_add_config()
when configuring q_vectors.

$ export NETIF=ens259f1np1
$ sudo -E env PATH="$PATH" ./tools/testing/selftests/drivers/net/napi_threaded.py
TAP version 13
1..3
ok 1 napi_threaded.napi_init
ok 2 napi_threaded.change_num_queues
ok 3 napi_threaded.enable_dev_threaded_disable_napi_threaded
Totals: pass:3 fail:0 xfail:0 xpass:0 skip:0 error:0

Reported-by: Jakub Kicinski <kuba@kernel.org>
Closes: https://lore.kernel.org/intel-wired-lan/20260316133100.6054a11f@kernel.org/
Fixes: d2a69fefd756 ("i40e: Fix changing previously set num_queue_pairs for PFs")
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 69 +++++++++++++--------
 1 file changed, 44 insertions(+), 25 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index a9ec53cfd905..8a23bd99bd12 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -11406,10 +11406,14 @@ static void i40e_service_timer(struct timer_list *t)
 static int i40e_set_num_rings_in_vsi(struct i40e_vsi *vsi)
 {
 	struct i40e_pf *pf = vsi->back;
+	u16 qps;
 
 	switch (vsi->type) {
 	case I40E_VSI_MAIN:
 		vsi->alloc_queue_pairs = pf->num_lan_qps;
+		qps = vsi->req_queue_pairs ?
+		      min(vsi->req_queue_pairs, pf->num_lan_qps) :
+		      pf->num_lan_qps;
 		if (!vsi->num_tx_desc)
 			vsi->num_tx_desc = ALIGN(I40E_DEFAULT_NUM_DESCRIPTORS,
 						 I40E_REQ_DESCRIPTOR_MULTIPLE);
@@ -11417,7 +11421,7 @@ static int i40e_set_num_rings_in_vsi(struct i40e_vsi *vsi)
 			vsi->num_rx_desc = ALIGN(I40E_DEFAULT_NUM_DESCRIPTORS,
 						 I40E_REQ_DESCRIPTOR_MULTIPLE);
 		if (test_bit(I40E_FLAG_MSIX_ENA, pf->flags))
-			vsi->num_q_vectors = pf->num_lan_msix;
+			vsi->num_q_vectors = clamp(qps, 1, pf->num_lan_msix);
 		else
 			vsi->num_q_vectors = 1;
 
@@ -11469,12 +11473,11 @@ static int i40e_set_num_rings_in_vsi(struct i40e_vsi *vsi)
 /**
  * i40e_vsi_alloc_arrays - Allocate queue and vector pointer arrays for the vsi
  * @vsi: VSI pointer
- * @alloc_qvectors: a bool to specify if q_vectors need to be allocated.
  *
  * On error: returns error code (negative)
  * On success: returns 0
  **/
-static int i40e_vsi_alloc_arrays(struct i40e_vsi *vsi, bool alloc_qvectors)
+static int i40e_vsi_alloc_arrays(struct i40e_vsi *vsi)
 {
 	struct i40e_ring **next_rings;
 	int size;
@@ -11493,19 +11496,20 @@ static int i40e_vsi_alloc_arrays(struct i40e_vsi *vsi, bool alloc_qvectors)
 	}
 	vsi->rx_rings = next_rings;
 
-	if (alloc_qvectors) {
-		/* allocate memory for q_vector pointers */
-		size = sizeof(struct i40e_q_vector *) * vsi->num_q_vectors;
-		vsi->q_vectors = kzalloc(size, GFP_KERNEL);
-		if (!vsi->q_vectors) {
-			ret = -ENOMEM;
-			goto err_vectors;
-		}
+	/* allocate memory for q_vector pointers */
+	size = sizeof(struct i40e_q_vector *) * vsi->num_q_vectors;
+	vsi->q_vectors = kzalloc(size, GFP_KERNEL);
+	if (!vsi->q_vectors) {
+		ret = -ENOMEM;
+		goto err_vectors;
 	}
 	return ret;
 
 err_vectors:
 	kfree(vsi->tx_rings);
+	vsi->tx_rings = NULL;
+	vsi->rx_rings = NULL;
+	vsi->xdp_rings = NULL;
 	return ret;
 }
 
@@ -11578,7 +11582,7 @@ static int i40e_vsi_mem_alloc(struct i40e_pf *pf, enum i40e_vsi_type type)
 	if (ret)
 		goto err_rings;
 
-	ret = i40e_vsi_alloc_arrays(vsi, true);
+	ret = i40e_vsi_alloc_arrays(vsi);
 	if (ret)
 		goto err_rings;
 
@@ -11603,18 +11607,15 @@ static int i40e_vsi_mem_alloc(struct i40e_pf *pf, enum i40e_vsi_type type)
 /**
  * i40e_vsi_free_arrays - Free queue and vector pointer arrays for the VSI
  * @vsi: VSI pointer
- * @free_qvectors: a bool to specify if q_vectors need to be freed.
  *
  * On error: returns error code (negative)
  * On success: returns 0
  **/
-static void i40e_vsi_free_arrays(struct i40e_vsi *vsi, bool free_qvectors)
+static void i40e_vsi_free_arrays(struct i40e_vsi *vsi)
 {
 	/* free the ring and vector containers */
-	if (free_qvectors) {
-		kfree(vsi->q_vectors);
-		vsi->q_vectors = NULL;
-	}
+	kfree(vsi->q_vectors);
+	vsi->q_vectors = NULL;
 	kfree(vsi->tx_rings);
 	vsi->tx_rings = NULL;
 	vsi->rx_rings = NULL;
@@ -11674,7 +11675,7 @@ static int i40e_vsi_clear(struct i40e_vsi *vsi)
 	i40e_put_lump(pf->irq_pile, vsi->base_vector, vsi->idx);
 
 	bitmap_free(vsi->af_xdp_zc_qps);
-	i40e_vsi_free_arrays(vsi, true);
+	i40e_vsi_free_arrays(vsi);
 	i40e_clear_rss_config_user(vsi);
 
 	pf->vsi[vsi->idx] = NULL;
@@ -12048,7 +12049,8 @@ static int i40e_vsi_alloc_q_vector(struct i40e_vsi *vsi, int v_idx)
 	cpumask_copy(&q_vector->affinity_mask, cpu_possible_mask);
 
 	if (vsi->netdev)
-		netif_napi_add(vsi->netdev, &q_vector->napi, i40e_napi_poll);
+		netif_napi_add_config(vsi->netdev, &q_vector->napi,
+				      i40e_napi_poll, v_idx);
 
 	/* tie q_vector and vsi together */
 	vsi->q_vectors[v_idx] = q_vector;
@@ -14203,8 +14205,9 @@ int i40e_vsi_release(struct i40e_vsi *vsi)
  **/
 static int i40e_vsi_setup_vectors(struct i40e_vsi *vsi)
 {
-	int ret = -ENOENT;
 	struct i40e_pf *pf = vsi->back;
+	bool reuse_irq_lump = false;
+	int ret = -ENOENT;
 
 	if (vsi->q_vectors[0]) {
 		dev_info(&pf->pdev->dev, "VSI %d has existing q_vectors\n",
@@ -14212,7 +14215,10 @@ static int i40e_vsi_setup_vectors(struct i40e_vsi *vsi)
 		return -EEXIST;
 	}
 
-	if (vsi->base_vector) {
+	if (vsi->type == I40E_VSI_MAIN && vsi->base_vector)
+		reuse_irq_lump = true;
+
+	if (vsi->base_vector && !reuse_irq_lump) {
 		dev_info(&pf->pdev->dev, "VSI %d has non-zero base vector %d\n",
 			 vsi->seid, vsi->base_vector);
 		return -EEXIST;
@@ -14232,6 +14238,10 @@ static int i40e_vsi_setup_vectors(struct i40e_vsi *vsi)
 	*/
 	if (!test_bit(I40E_FLAG_MSIX_ENA, pf->flags))
 		return ret;
+
+	if (reuse_irq_lump)
+		return ret;
+
 	if (vsi->num_q_vectors)
 		vsi->base_vector = i40e_get_lump(pf, pf->irq_pile,
 						 vsi->num_q_vectors, vsi->idx);
@@ -14271,11 +14281,20 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi,
 	pf = vsi->back;
 
 	i40e_put_lump(pf->qp_pile, vsi->base_queue, vsi->idx);
+	i40e_vsi_free_q_vectors(vsi);
 	i40e_vsi_clear_rings(vsi);
+	i40e_vsi_free_arrays(vsi);
 
-	i40e_vsi_free_arrays(vsi, false);
 	i40e_set_num_rings_in_vsi(vsi);
-	ret = i40e_vsi_alloc_arrays(vsi, false);
+	ret = i40e_vsi_alloc_arrays(vsi);
+	if (ret)
+		goto err_netdev;
+
+	/* Rebuild q_vectors during VSI reinit because the effective channel
+	 * count may change num_q_vectors. Keep vector topology aligned with the
+	 * queue configuration after ethtool's .set_channels() callback.
+	 */
+	ret = i40e_vsi_setup_vectors(vsi);
 	if (ret)
 		goto err_netdev;
 
@@ -14287,7 +14306,7 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi,
 		dev_info(&pf->pdev->dev,
 			 "failed to get tracking for %d queues for VSI %d err %d\n",
 			 alloc_queue_pairs, vsi->seid, ret);
-		goto err_netdev;
+		goto err_rings;
 	}
 	vsi->base_queue = ret;
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 6/7] i40e: do not expose netdev too early
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski, Sashiko AI Review
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

i40e_vsi_setup() registers the netdev before rings and q_vectors are fully
allocated and mapped. Once register_netdev() returns, userspace can reach
netdev callbacks such as ndo_open(), so the VSI backing state must already
be ready.

Move register_netdev() to the end of the setup path, after ring/q_vector
allocation, ring mapping and RSS configuration. Keep freeing an allocated
but not registered netdev on the error path.

Fixes: 41c445ff0f48 ("i40e: main driver core")
Reported-by: Sashiko AI Review <sashiko-bot@kernel.org>
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 29 ++++++++++++---------
 1 file changed, 17 insertions(+), 12 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index fcdd13af08ea..a9ec53cfd905 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -14454,15 +14454,6 @@ struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf, u8 type,
 				goto err_netdev;
 			SET_NETDEV_DEVLINK_PORT(vsi->netdev, &pf->devlink_port);
 		}
-		ret = register_netdev(vsi->netdev);
-		if (ret)
-			goto err_dl_port;
-		vsi->netdev_registered = true;
-		netif_carrier_off(vsi->netdev);
-#ifdef CONFIG_I40E_DCB
-		/* Setup DCB netlink interface */
-		i40e_dcbnl_setup(vsi);
-#endif /* CONFIG_I40E_DCB */
 		fallthrough;
 	case I40E_VSI_FDIR:
 		/* set up vectors and rings if needed */
@@ -14490,6 +14481,19 @@ struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf, u8 type,
 		if (ret)
 			goto err_config;
 	}
+
+	if (vsi->netdev) {
+		ret = register_netdev(vsi->netdev);
+		if (ret)
+			goto err_config;
+		vsi->netdev_registered = true;
+		netif_carrier_off(vsi->netdev);
+#ifdef CONFIG_I40E_DCB
+		/* Setup DCB netlink interface */
+		i40e_dcbnl_setup(vsi);
+#endif /* CONFIG_I40E_DCB */
+	}
+
 	return vsi;
 
 err_config:
@@ -14500,13 +14504,14 @@ struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf, u8 type,
 	if (vsi->netdev_registered) {
 		vsi->netdev_registered = false;
 		unregister_netdev(vsi->netdev);
-		free_netdev(vsi->netdev);
-		vsi->netdev = NULL;
 	}
-err_dl_port:
 	if (vsi->type == I40E_VSI_MAIN)
 		i40e_devlink_destroy_port(pf);
 err_netdev:
+	if (vsi->netdev) {
+		free_netdev(vsi->netdev);
+		vsi->netdev = NULL;
+	}
 	i40e_aq_delete_element(&pf->hw, vsi->seid, NULL);
 err_vsi:
 	i40e_vsi_clear(vsi);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 5/7] i40e: fix potential UAF in i40e_vsi_setup()'s error path
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

Sashiko pointed out an issue where error path in i40e_vsi_reinit_setup()
released ring memory but then when freeing q_vectors, the rings mapped
to q_vectors where touched which implies a regular use-after-free bug.

Apparently i40e_vsi_setup() has the same problem, so swap the allocation
and freeing order and fix the 13 year old bug.

Fixes: 41c445ff0f48 ("i40e: main driver core")
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index e88cf7cfbd84..fcdd13af08ea 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -14466,14 +14466,14 @@ struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf, u8 type,
 		fallthrough;
 	case I40E_VSI_FDIR:
 		/* set up vectors and rings if needed */
-		ret = i40e_vsi_setup_vectors(vsi);
-		if (ret)
-			goto err_msix;
-
 		ret = i40e_alloc_rings(vsi);
 		if (ret)
 			goto err_rings;
 
+		ret = i40e_vsi_setup_vectors(vsi);
+		if (ret)
+			goto err_qvec;
+
 		/* map all of the rings to the q_vectors */
 		i40e_vsi_map_rings_to_vectors(vsi);
 
@@ -14493,10 +14493,10 @@ struct i40e_vsi *i40e_vsi_setup(struct i40e_pf *pf, u8 type,
 	return vsi;
 
 err_config:
+	i40e_vsi_free_q_vectors(vsi);
+err_qvec:
 	i40e_vsi_clear_rings(vsi);
 err_rings:
-	i40e_vsi_free_q_vectors(vsi);
-err_msix:
 	if (vsi->netdev_registered) {
 		vsi->netdev_registered = false;
 		unregister_netdev(vsi->netdev);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 4/7] i40e: avoid deadlock when calling unregister_netdev()
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski, Sashiko AI Review
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

Sashiko reports:
***
> +err_netdev:
>  	if (vsi->netdev_registered) {
>  		vsi->netdev_registered = false;
>  		unregister_netdev(vsi->netdev);
Could this result in a deadlock when called during a device rebuild?
Looking at i40e_rebuild(), it explicitly acquires the RTNL lock before
proceeding:
drivers/net/ethernet/intel/i40e/i40e_main.c:i40e_rebuild() {
    ...
	if (!lock_acquired)
		rtnl_lock();
	ret = i40e_setup_pf_switch(pf, reinit, true);
    ...
}
If i40e_setup_pf_switch() calls i40e_vsi_reinit_setup() and takes this new
err_netdev path, unregister_netdev() will unconditionally attempt to acquire
rtnl_lock(), leading to a deadlock on the non-recursive mutex.
***

Use unregister_netdevice() when the rebuild path already holds RTNL, and
keep unregister_netdev() for callers that do not. This avoids both
recursive RTNL locking and dropping RTNL in the middle of the VSI unwind
path.

Fixes: bc7d338fbb3f ("i40e: reinit flow for the main VSI")
Reported-by: Sashiko AI Review <sashiko-bot@kernel.org>
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index a29a89192a7a..e88cf7cfbd84 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -14257,7 +14257,8 @@ static int i40e_vsi_setup_vectors(struct i40e_vsi *vsi)
  * Returns pointer to the successfully allocated and configured VSI sw struct
  * on success, otherwise returns NULL on failure.
  **/
-static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
+static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi,
+					      bool lock_acquired)
 {
 	struct i40e_vsi *main_vsi;
 	u16 alloc_queue_pairs;
@@ -14314,7 +14315,10 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
 err_netdev:
 	if (vsi->netdev_registered) {
 		vsi->netdev_registered = false;
-		unregister_netdev(vsi->netdev);
+		if (lock_acquired)
+			unregister_netdevice(vsi->netdev);
+		else
+			unregister_netdev(vsi->netdev);
 		free_netdev(vsi->netdev);
 		vsi->netdev = NULL;
 	}
@@ -15036,7 +15040,7 @@ static int i40e_setup_pf_switch(struct i40e_pf *pf, bool reinit, bool lock_acqui
 			main_vsi = i40e_vsi_setup(pf, I40E_VSI_MAIN,
 						  uplink_seid, 0);
 		else if (reinit)
-			main_vsi = i40e_vsi_reinit_setup(main_vsi);
+			main_vsi = i40e_vsi_reinit_setup(main_vsi, lock_acquired);
 		if (!main_vsi) {
 			dev_info(&pf->pdev->dev, "setup of MAIN VSI failed\n");
 			i40e_cloud_filter_exit(pf);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 3/7] i40e: make ring pointers unreachable before freeing via rcu
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski, Sashiko AI Review
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

Sashiko reports:
***
>  err_config:
> +	i40e_vsi_free_q_vectors(vsi);
> +err_qvec:
>  	i40e_vsi_clear_rings(vsi);
This is a pre-existing issue, but can the sequence in i40e_vsi_clear_rings()
lead to an RCU ordering violation?
In i40e_vsi_clear_rings(), the rings are freed before the array pointers are
nullified:
	kfree_rcu(vsi->tx_rings[i], rcu);
	WRITE_ONCE(vsi->tx_rings[i], NULL);
Under RCU rules, a pointer must be made unreachable to new readers before it
is handed off to kfree_rcu(). Could a new RCU reader (like
i40e_get_netdev_stats_struct_tx()) fetch the pointer after kfree_rcu() is
invoked, and access freed memory if the grace period expires while the
reader is still active?
***

Save the Tx ring pointer before clearing the published ring array slots
and pass the saved pointer to kfree_rcu(). This preserves the intended
RCU ordering, where new readers can no longer discover the ring through
vsi->tx_rings/rx_rings/xdp_rings before the object is queued for
deferred freeing, while avoiding a NULL kfree_rcu() argument after the
slot has already been cleared. Since the Tx pointer is the base of the
per-queue-pair allocation block, re-reading vsi->tx_rings[i] after
WRITE_ONCE(..., NULL) would otherwise turn the free into a no-op and
leak the whole ring block.

Fixes: 9f65e15b4f98 ("i40e: Move rings from pointer to array to array of pointers")
Reported-by: Sashiko AI Review <sashiko-bot@kernel.org>
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index 471fa7f7b643..a29a89192a7a 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -11699,11 +11699,13 @@ static void i40e_vsi_clear_rings(struct i40e_vsi *vsi)
 
 	if (vsi->tx_rings && vsi->tx_rings[0]) {
 		for (i = 0; i < vsi->alloc_queue_pairs; i++) {
-			kfree_rcu(vsi->tx_rings[i], rcu);
+			struct i40e_ring *tx_ring = vsi->tx_rings[i];
+
 			WRITE_ONCE(vsi->tx_rings[i], NULL);
 			WRITE_ONCE(vsi->rx_rings[i], NULL);
 			if (vsi->xdp_rings)
 				WRITE_ONCE(vsi->xdp_rings[i], NULL);
+			kfree_rcu(tx_ring, rcu);
 		}
 	}
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 2/7] i40e: avoid null ptr dereference in i40e_ptp_stop()
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski, Sashiko AI Review
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

Sashiko reports:
***
If an allocation fails here during i40e_rebuild(), i40e_vsi_clear()
frees the
main VSI and sets pf->vsi[vsi->idx] = NULL, and the rebuild will abort
without
stopping the PTP clock.
Later, if the device is removed or unbound, i40e_remove()
unconditionally
calls i40e_ptp_stop(), which does:
drivers/net/ethernet/intel/i40e/i40e_ptp.c:i40e_ptp_stop() {
    ...
	struct i40e_vsi *main_vsi = i40e_pf_get_main_vsi(pf);
    ...
	dev_info(&pf->pdev->dev, "%s: removed PHC on %s\n", __func__,
		 main_vsi->netdev->name);
    ...
}
Would this cause a NULL pointer dereference since main_vsi is now NULL?
***

Check if main_vsi is not null before calling dev_info().

Fixes: beb0dff1251d ("i40e: enable PTP")
Reported-by: Sashiko AI Review <sashiko-bot@kernel.org>
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_ptp.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_ptp.c b/drivers/net/ethernet/intel/i40e/i40e_ptp.c
index ff62b5f2c815..ca93df4d6785 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_ptp.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_ptp.c
@@ -1556,8 +1556,9 @@ void i40e_ptp_stop(struct i40e_pf *pf)
 	if (pf->ptp_clock) {
 		ptp_clock_unregister(pf->ptp_clock);
 		pf->ptp_clock = NULL;
-		dev_info(&pf->pdev->dev, "%s: removed PHC on %s\n", __func__,
-			 main_vsi->netdev->name);
+		if (main_vsi)
+			dev_info(&pf->pdev->dev, "%s: removed PHC on %s\n", __func__,
+				 main_vsi->netdev->name);
 	}
 
 	if (i40e_is_ptp_pin_dev(&pf->hw)) {
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 1/7] i40e: unregister netdev before clearing VSI on reinit failure
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski
In-Reply-To: <20260701124524.13644-1-maciej.fijalkowski@intel.com>

i40e_vsi_reinit_setup() tears down the existing VSI queue/ring backing
state before allocating replacement arrays and queue tracking. If one of
these early allocations fails, the function jumps directly to err_vsi
and calls i40e_vsi_clear().

For a registered netdev, this frees the VSI while
netdev_priv(netdev)->vsi can still point at it, leaving the registered
netdev with dangling private driver state.

Split the error path so failures after destructive reinit teardown first
unregister and free the netdev before clearing the VSI.

Fixes: d2a69fefd756 ("i40e: Fix changing previously set num_queue_pairs for PFs")
Signed-off-by: Maciej Fijalkowski <maciej.fijalkowski@intel.com>
---
 drivers/net/ethernet/intel/i40e/i40e_main.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/intel/i40e/i40e_main.c b/drivers/net/ethernet/intel/i40e/i40e_main.c
index a04683004a56..471fa7f7b643 100644
--- a/drivers/net/ethernet/intel/i40e/i40e_main.c
+++ b/drivers/net/ethernet/intel/i40e/i40e_main.c
@@ -14274,7 +14274,7 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
 	i40e_set_num_rings_in_vsi(vsi);
 	ret = i40e_vsi_alloc_arrays(vsi, false);
 	if (ret)
-		goto err_vsi;
+		goto err_netdev;
 
 	alloc_queue_pairs = vsi->alloc_queue_pairs *
 			    (i40e_enabled_xdp_vsi(vsi) ? 2 : 1);
@@ -14284,7 +14284,7 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
 		dev_info(&pf->pdev->dev,
 			 "failed to get tracking for %d queues for VSI %d err %d\n",
 			 alloc_queue_pairs, vsi->seid, ret);
-		goto err_vsi;
+		goto err_netdev;
 	}
 	vsi->base_queue = ret;
 
@@ -14309,6 +14309,7 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
 
 err_rings:
 	i40e_vsi_free_q_vectors(vsi);
+err_netdev:
 	if (vsi->netdev_registered) {
 		vsi->netdev_registered = false;
 		unregister_netdev(vsi->netdev);
@@ -14318,7 +14319,6 @@ static struct i40e_vsi *i40e_vsi_reinit_setup(struct i40e_vsi *vsi)
 	if (vsi->type == I40E_VSI_MAIN)
 		i40e_devlink_destroy_port(pf);
 	i40e_aq_delete_element(&pf->hw, vsi->seid, NULL);
-err_vsi:
 	i40e_vsi_clear(vsi);
 	return NULL;
 }
-- 
2.43.0


^ permalink raw reply related

* [PATCH v5 net 0/7] i40e: re-init and UAF fixes
From: Maciej Fijalkowski @ 2026-07-01 12:45 UTC (permalink / raw)
  To: intel-wired-lan
  Cc: netdev, magnus.karlsson, kuba, pabeni, horms, przemyslaw.kitszel,
	jacob.e.keller, Maciej Fijalkowski

v5:
- include three new patches to address last Sashiko review
  *
- do not release the irq lump in rebuild path in patch 7
- clear dangling pointers from rx and xdp rings arrays
v4:
- add preceding patch that fixes a case when some of re-init allocations
  failed and we missed de-registering netdev at failure path
- pull out i40e_vsi_setup() changes onto separate patch
v3:
- address UAF when ring arrays were freed before q_vector's ring
  containers (Sashiko, Jacob)
- remove bool params from alloc/free array routines (Simon)
v2:
- NULL vsi->tx_rings in i40e_vsi_alloc_arrays() (Sashiko)


Maciej Fijalkowski (7):
  i40e: unregister netdev before clearing VSI on reinit failure
  i40e: avoid null ptr dereference in i40e_ptp_stop()
  i40e: make ring pointers unreachable before freeing via rcu
  i40e: avoid deadlock when calling unregister_netdev()
  i40e: fix potential UAF in i40e_vsi_setup()'s error path
  i40e: do not expose netdev too early
  i40e: keep q_vectors array in sync with channel count changes

 drivers/net/ethernet/intel/i40e/i40e_main.c | 128 ++++++++++++--------
 drivers/net/ethernet/intel/i40e/i40e_ptp.c  |   5 +-
 2 files changed, 82 insertions(+), 51 deletions(-)

-- 
2.43.0


^ permalink raw reply

* [PATCH net-next] gtp: annotate PDP lookups under RTNL
From: Runyu Xiao @ 2026-07-01 12:39 UTC (permalink / raw)
  To: pablo, laforge
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, osmocom-net-gprs,
	netdev, linux-kernel, runyu.xiao, jianhao.xu

The GTP PDP lookup helpers are shared by RCU-protected data and report
paths and RTNL-protected control paths such as gtp_genl_new_pdp(). The
helpers walk RCU hlists, but they do not currently pass the RTNL
condition for the control-path lookups.

Pass lockdep_rtnl_is_held() to the PDP hlist iterators. Existing
RCU-reader callers remain valid because the RCU-list macros also accept
an active RCU read-side section; the added condition only documents the
non-RCU protection already used by RTNL control paths.

This was found by our static analysis tool and then manually reviewed
against the current tree. The dynamic triage evidence is a
target-matched CONFIG_PROVE_RCU_LIST warning; the change is limited
to documenting the existing protection contract.

This is a lockdep annotation cleanup. It does not change PDP lifetime or
hash updates.

Signed-off-by: Runyu Xiao <runyu.xiao@seu.edu.cn>
---
 drivers/net/gtp.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c
index 5cb59d72bc82..b94073c55f17 100644
--- a/drivers/net/gtp.c
+++ b/drivers/net/gtp.c
@@ -151,7 +151,8 @@ static struct pdp_ctx *gtp0_pdp_find(struct gtp_dev *gtp, u64 tid, u16 family)
 
 	head = &gtp->tid_hash[gtp0_hashfn(tid) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_tid) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_tid,
+				 lockdep_rtnl_is_held()) {
 		if (pdp->af == family &&
 		    pdp->gtp_version == GTP_V0 &&
 		    pdp->u.v0.tid == tid)
@@ -168,7 +169,8 @@ static struct pdp_ctx *gtp1_pdp_find(struct gtp_dev *gtp, u32 tid, u16 family)
 
 	head = &gtp->tid_hash[gtp1u_hashfn(tid) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_tid) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_tid,
+				 lockdep_rtnl_is_held()) {
 		if (pdp->af == family &&
 		    pdp->gtp_version == GTP_V1 &&
 		    pdp->u.v1.i_tei == tid)
@@ -185,7 +187,8 @@ static struct pdp_ctx *ipv4_pdp_find(struct gtp_dev *gtp, __be32 ms_addr)
 
 	head = &gtp->addr_hash[ipv4_hashfn(ms_addr) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_addr) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_addr,
+				 lockdep_rtnl_is_held()) {
 		if (pdp->af == AF_INET &&
 		    pdp->ms.addr.s_addr == ms_addr)
 			return pdp;
@@ -220,7 +223,8 @@ static struct pdp_ctx *ipv6_pdp_find(struct gtp_dev *gtp,
 
 	head = &gtp->addr_hash[ipv6_hashfn(ms_addr) % gtp->hash_size];
 
-	hlist_for_each_entry_rcu(pdp, head, hlist_addr) {
+	hlist_for_each_entry_rcu(pdp, head, hlist_addr,
+				 lockdep_rtnl_is_held()) {
 		if (pdp->af == AF_INET6 &&
 		    ipv6_pdp_addr_equal(&pdp->ms.addr6, ms_addr))
 			return pdp;
-- 
2.34.1


^ permalink raw reply related

* [syzbot] Monthly netfilter report (Jul 2026)
From: syzbot @ 2026-07-01 12:32 UTC (permalink / raw)
  To: fw, linux-kernel, netdev, netfilter-devel, pablo, syzkaller-bugs

Hello netfilter maintainers/developers,

This is a 31-day syzbot report for the netfilter subsystem.
All related reports/information can be found at:
https://syzkaller.appspot.com/upstream/s/netfilter

During the period, 0 new issues were detected and 0 were fixed.
In total, 11 issues are still open and 198 have already been fixed.
There are also 3 low-priority issues.

Some of the still happening issues:

Ref Crashes Repro Title
<1> 1759    Yes   INFO: task hung in addrconf_dad_work (5)
                  https://syzkaller.appspot.com/bug?extid=82ccd564344eeaa5427d
<2> 703     Yes   INFO: rcu detected stall in addrconf_rs_timer (6)
                  https://syzkaller.appspot.com/bug?extid=fecf8bd19c1f78edb255
<3> 137     Yes   INFO: rcu detected stall in NF_HOOK (2)
                  https://syzkaller.appspot.com/bug?extid=34c2df040c6cfa15fdfe
<4> 124     Yes   INFO: rcu detected stall in br_handle_frame (6)
                  https://syzkaller.appspot.com/bug?extid=f8850bc3986562f79619
<5> 78      No    INFO: task hung in nfnetlink_rcv_msg (5)
                  https://syzkaller.appspot.com/bug?extid=c4b20b80ee6a7a2f5012
<6> 2       No    WARNING in nf_conntrack_cleanup_net_list (2)
                  https://syzkaller.appspot.com/bug?extid=122256c3e2bf6ec9f928

---
This report is generated by a bot. It may contain errors.
See https://goo.gl/tpsmEJ for more information about syzbot.
syzbot engineers can be reached at syzkaller@googlegroups.com.

To disable reminders for individual bugs, reply with the following command:
#syz set <Ref> no-reminders

To change bug's subsystems, reply with:
#syz set <Ref> subsystems: new-subsystem

You may send multiple commands in a single email message.

^ permalink raw reply

* Re: [Intel-wired-lan] [PATCH iwl-next v1] ixgbe: E610: force phy link to get down when interface is down
From: Paul Menzel @ 2026-07-01 12:27 UTC (permalink / raw)
  To: Jedrzej Jagielski
  Cc: intel-wired-lan, anthony.l.nguyen, netdev, Aleksandr Loktionov
In-Reply-To: <20260701113519.49859-1-jedrzej.jagielski@intel.com>

Dear Jedrzej,


Thank you for your patch.

Am 01.07.26 um 13:35 schrieb Jedrzej Jagielski:
> For the E610 family, similarly to the E8xx adapters, the default behavior
> is for the PHY link to remain up even when the corresponding OS interface
> is down.
> 
> Add function setting down the PHY config IXGBE_ACI_PHY_ENA_LINK bit
> what leads to disabling PHY link.

It’d extend it a little:

… by factoring the code out into ixgbe_handle_link_down(), and call it 
in ixgbe_close().

> Align functionality with the implementation of the ice driver.

Please add a paragraph detailing regression potential. Are there users 
that might depend on the current default, as uncommon it might be?

> Let user to configure link-down-on-close enablement through ethtool.

Please provide examples, and how to test your change. Doing this you can 
also paste the new log messages.

> Reviewed-by: Aleksandr Loktionov <aleksandr.loktionov@intel.com>
> Signed-off-by: Jedrzej Jagielski <jedrzej.jagielski@intel.com>
> ---
>   drivers/net/ethernet/intel/ixgbe/ixgbe.h      |  1 +
>   drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c | 35 ++++++++++++++++++-
>   drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h |  1 +
>   .../net/ethernet/intel/ixgbe/ixgbe_ethtool.c  | 15 ++++++++
>   drivers/net/ethernet/intel/ixgbe/ixgbe_main.c | 27 +++++++++++---
>   5 files changed, 73 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe.h b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
> index 30f62174acf2..7bbb82dd962c 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe.h
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe.h
> @@ -685,6 +685,7 @@ struct ixgbe_adapter {
>   #define IXGBE_FLAG2_MOD_POWER_UNSUPPORTED	BIT(22)
>   #define IXGBE_FLAG2_API_MISMATCH		BIT(23)
>   #define IXGBE_FLAG2_FW_ROLLBACK			BIT(24)
> +#define IXGBE_FLAG2_LINK_DOWN_ON_CLOSE		BIT(25)
>   
>   	/* Tx fast path data */
>   	int num_tx_queues;
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> index da445fb673fc..46d8a3ea86b8 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c
> @@ -1923,6 +1923,33 @@ void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw)
>   	hw->fc.current_mode = hw->fc.requested_mode;
>   }
>   
> +/**
> + * ixgbe_disable_phy_link - force phy link to get down
> + * @hw: pointer to hardware structure
> + *
> + * Send 0x0601 with the IXGBE_ACI_PHY_ENA_LINK bit set down.
> + *
> + * Return: the exit code of the operation.

At least for me it’s not that helpful. Shouldn’t the return values be 
listed? What is success? What is failure?

> + */
> +int ixgbe_disable_phy_link(struct ixgbe_hw *hw)
> +{
> +	struct ixgbe_aci_cmd_get_phy_caps_data pcaps = {};
> +	struct ixgbe_aci_cmd_set_phy_cfg_data pcfg = {};
> +	int err;
> +
> +	err = ixgbe_aci_get_phy_caps(hw, false, IXGBE_ACI_REPORT_ACTIVE_CFG,
> +				     &pcaps);
> +	if (err)
> +		return err;
> +
> +	ixgbe_copy_phy_caps_to_cfg(&pcaps, &pcfg);
> +
> +	pcfg.caps &= ~IXGBE_ACI_PHY_ENA_LINK;
> +	pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
> +
> +	return ixgbe_aci_set_phy_cfg(hw, &pcfg);
> +}
> +
>   /**
>    * ixgbe_disable_rx_e610 - Disable RX unit
>    * @hw: pointer to hardware structure
> @@ -2207,6 +2234,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
>   	u8 rmode = IXGBE_ACI_REPORT_TOPO_CAP_MEDIA;
>   	u64 sup_phy_type_low, sup_phy_type_high;
>   	u64 phy_type_low = 0, phy_type_high = 0;
> +	bool force_on_required;
>   	int err;
>   
>   	err = ixgbe_aci_get_link_info(hw, false, NULL);
> @@ -2272,6 +2300,11 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
>   		phy_type_high |= IXGBE_PHY_TYPE_HIGH_10G_USXGMII;
>   	}
>   
> +	/* If IXGBE_ACI_PHY_ENA_LINK has been explicitly disabled that means
> +	 * we need to force interface enablement after reaching that point

It’d be great, if you rephrased “that point”.

> +	 */
> +	force_on_required = !(pcfg.caps & IXGBE_ACI_PHY_ENA_LINK);
> +
>   	/* Mask the set values to avoid requesting unsupported link types. */
>   	phy_type_low &= sup_phy_type_low;
>   	pcfg.phy_type_low = cpu_to_le64(phy_type_low);
> @@ -2280,7 +2313,7 @@ int ixgbe_setup_phy_link_e610(struct ixgbe_hw *hw)
>   
>   	if (pcfg.phy_type_high != pcaps.phy_type_high ||
>   	    pcfg.phy_type_low != pcaps.phy_type_low ||
> -	    pcfg.caps != pcaps.caps) {
> +	    pcfg.caps != pcaps.caps || force_on_required) {
>   		pcfg.caps |= IXGBE_ACI_PHY_ENA_LINK;
>   		pcfg.caps |= IXGBE_ACI_PHY_ENA_AUTO_LINK_UPDT;
>   
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
> index 2cb76a3d30ae..59044d67ebeb 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.h
> @@ -50,6 +50,7 @@ int ixgbe_cfg_phy_fc(struct ixgbe_hw *hw,
>   		     enum ixgbe_fc_mode req_mode);
>   int ixgbe_setup_fc_e610(struct ixgbe_hw *hw);
>   void ixgbe_fc_autoneg_e610(struct ixgbe_hw *hw);
> +int ixgbe_disable_phy_link(struct ixgbe_hw *hw);
>   void ixgbe_disable_rx_e610(struct ixgbe_hw *hw);
>   int ixgbe_init_phy_ops_e610(struct ixgbe_hw *hw);
>   int ixgbe_identify_phy_e610(struct ixgbe_hw *hw);
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
> index 4dfae53b4ea1..0fcb9d738984 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_ethtool.c
> @@ -139,6 +139,8 @@ static const char ixgbe_priv_flags_strings[][ETH_GSTRING_LEN] = {
>   	"vf-ipsec",
>   #define IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF	BIT(2)
>   	"mdd-disable-vf",
> +#define IXGBE_PRIV_LINK_DOWN_ON_CLOSE	BIT(3)
> +	"link-down-on-close",
>   };
>   
>   #define IXGBE_PRIV_FLAGS_STR_LEN ARRAY_SIZE(ixgbe_priv_flags_strings)
> @@ -3842,6 +3844,9 @@ static u32 ixgbe_get_priv_flags(struct net_device *netdev)
>   	if (adapter->flags2 & IXGBE_FLAG2_AUTO_DISABLE_VF)
>   		priv_flags |= IXGBE_PRIV_FLAGS_AUTO_DISABLE_VF;
>   
> +	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE)
> +		priv_flags |= IXGBE_PRIV_LINK_DOWN_ON_CLOSE;
> +
>   	return priv_flags;
>   }
>   
> @@ -3879,6 +3884,16 @@ static int ixgbe_set_priv_flags(struct net_device *netdev, u32 priv_flags)
>   		}
>   	}
>   
> +	flags2 &= ~IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
> +	if (priv_flags & IXGBE_PRIV_LINK_DOWN_ON_CLOSE) {
> +		if (adapter->hw.mac.type == ixgbe_mac_e610) {
> +			flags2 |= IXGBE_FLAG2_LINK_DOWN_ON_CLOSE;
> +		} else {
> +			e_info(probe, "Cannot set private flags: Unsupported hardware\n");

Please print hw.mac.type, and mention, that it’s only supported on E610.

> +			return -EOPNOTSUPP;
> +		}
> +	}
> +
>   	if (flags2 != adapter->flags2) {
>   		adapter->flags2 = flags2;
>   
> diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
> index 62c2d83e1577..58ee4a186039 100644
> --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
> +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
> @@ -7544,6 +7544,17 @@ static void ixgbe_close_suspend(struct ixgbe_adapter *adapter)
>   	ixgbe_free_all_rx_resources(adapter);
>   }
>   
> +static void ixgbe_handle_link_down(struct ixgbe_adapter *adapter)
> +{
> +	struct net_device *netdev = adapter->netdev;
> +
> +	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
> +		ixgbe_ptp_start_cyclecounter(adapter);
> +
> +	e_info(drv, "NIC Link is Down\n");
> +	netif_carrier_off(netdev);
> +}
> +
>   /**
>    * ixgbe_close - Disables a network interface
>    * @netdev: network interface device structure
> @@ -7566,6 +7577,16 @@ int ixgbe_close(struct net_device *netdev)
>   
>   	ixgbe_fdir_filter_exit(adapter);
>   
> +	if (adapter->flags2 & IXGBE_FLAG2_LINK_DOWN_ON_CLOSE) {
> +		int err;
> +
> +		err = ixgbe_disable_phy_link(&adapter->hw);
> +		if (err)
> +			e_warn(drv, "Cannot set PHY link down\n");

Log the error?

> +
> +		ixgbe_handle_link_down(adapter);
> +	}
> +
>   	ixgbe_release_hw_control(adapter);
>   
>   	return 0;
> @@ -8244,11 +8265,7 @@ static void ixgbe_watchdog_link_is_down(struct ixgbe_adapter *adapter)
>   	if (ixgbe_is_sfp(hw) && hw->mac.type == ixgbe_mac_82598EB)
>   		adapter->flags2 |= IXGBE_FLAG2_SEARCH_FOR_SFP;
>   
> -	if (test_bit(__IXGBE_PTP_RUNNING, &adapter->state))
> -		ixgbe_ptp_start_cyclecounter(adapter);
> -
> -	e_info(drv, "NIC Link is Down\n");
> -	netif_carrier_off(netdev);
> +	ixgbe_handle_link_down(adapter);
>   }
>   
>   static bool ixgbe_ring_tx_pending(struct ixgbe_adapter *adapter)


Kind regards,

Paul

^ permalink raw reply

* Re: [PATCH net 4/9] netfilter: nf_conntrack_sip: validate skb_dst() before accessing it
From: Pablo Neira Ayuso @ 2026-07-01 12:23 UTC (permalink / raw)
  To: Florian Westphal
  Cc: netdev, Paolo Abeni, David S. Miller, Eric Dumazet,
	Jakub Kicinski, netfilter-devel
In-Reply-To: <akTzomC4Qz8u8teJ@chamomile>

On Wed, Jul 01, 2026 at 01:01:58PM +0200, Pablo Neira Ayuso wrote:
> On Wed, Jul 01, 2026 at 08:36:11AM +0200, Florian Westphal wrote:
> > Florian Westphal <fw@strlen.de> wrote:
> > > From: Pablo Neira Ayuso <pablo@netfilter.org>
> > > 
> > > tc ingress and openvswitch do not guarantee routing information to be
> > > available. These subsystems use the conntrack helper infrastructure, and
> > > the SIP helper relies on the skb_dst() to be present if
> > > sip_external_media is set to 1 (which is disabled by default as a module
> > > parameter).
> > 
> > The sashiko drive-by appears real, I submitted a patch for it.
> > Its not a regression added by this patch but a unrelated issue.
> > 
> > https://patchwork.ozlabs.org/project/netfilter-devel/patch/20260701062922.9660-1-fw@strlen.de/
> 
> Is skb_ensure_writable() bogus here?
> 
> As you said, skb is already linearized. As for clones, they should
> only happen in br_netfilter? In such case, it should be br_netfilter
> that should be audited not to pass cloned skbuffs before calling the
> inet hooks.

Forget this, this skb_ensure_writable() is really needed here.

^ permalink raw reply

* [PATCH net] amt: fix size calculation in amt_get_size()
From: Eric Dumazet @ 2026-07-01 12:23 UTC (permalink / raw)
  To: David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Kuniyuki Iwashima, Andrew Lunn, netdev,
	eric.dumazet, Eric Dumazet

amt_get_size() incorrectly used sizeof(struct iphdr) for the sizes of
IFLA_AMT_DISCOVERY_IP, IFLA_AMT_REMOTE_IP, and IFLA_AMT_LOCAL_IP.
These attributes contain IPv4 addresses (__be32), not full IP headers.

Replace sizeof(struct iphdr) with sizeof(__be32) to avoid over-allocating
netlink message space.

Fixes: b9022b53adad ("amt: add control plane of amt interface")
Signed-off-by: Eric Dumazet <edumazet@google.com>
---
 drivers/net/amt.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/amt.c b/drivers/net/amt.c
index 724a8163a5142a6835950abb63d80f29417b2654..951dd10e192b7924f9d3f05065a298ddcf8f4b25 100644
--- a/drivers/net/amt.c
+++ b/drivers/net/amt.c
@@ -3301,9 +3301,9 @@ static size_t amt_get_size(const struct net_device *dev)
 	       nla_total_size(sizeof(__u16)) + /* IFLA_AMT_GATEWAY_PORT */
 	       nla_total_size(sizeof(__u32)) + /* IFLA_AMT_LINK */
 	       nla_total_size(sizeof(__u32)) + /* IFLA_MAX_TUNNELS */
-	       nla_total_size(sizeof(struct iphdr)) + /* IFLA_AMT_DISCOVERY_IP */
-	       nla_total_size(sizeof(struct iphdr)) + /* IFLA_AMT_REMOTE_IP */
-	       nla_total_size(sizeof(struct iphdr)); /* IFLA_AMT_LOCAL_IP */
+	       nla_total_size(sizeof(__be32)) + /* IFLA_AMT_DISCOVERY_IP */
+	       nla_total_size(sizeof(__be32)) + /* IFLA_AMT_REMOTE_IP */
+	       nla_total_size(sizeof(__be32)); /* IFLA_AMT_LOCAL_IP */
 }
 
 static int amt_fill_info(struct sk_buff *skb, const struct net_device *dev)
-- 
2.55.0.rc0.799.gd6f94ed593-goog


^ permalink raw reply related

* Re: [PATCH v8 10/14] media: qcom: Pass proper PAS ID to set_remote_state API
From: Sumit Garg @ 2026-07-01 12:19 UTC (permalink / raw)
  To: Konrad Dybcio
  Cc: andersson, linux-arm-msm, dri-devel, freedreno, linux-media,
	netdev, linux-wireless, ath12k, linux-remoteproc, konradybcio,
	robh, krzk+dt, conor+dt, robin.clark, sean, akhilpo, lumag,
	abhinav.kumar, jesszhan0024, marijn.suijten, airlied, simona,
	vikash.garodia, bod, mchehab, elder, andrew+netdev, davem,
	edumazet, kuba, pabeni, jjohnson, mathieu.poirier,
	trilokkumar.soni, mukesh.ojha, pavan.kondeti, jorge.ramirez,
	tonyh, vignesh.viswanathan, srinivas.kandagatla, amirreza.zarrabi,
	jens.wiklander, op-tee, apurupa, skare, linux-kernel, Sumit Garg
In-Reply-To: <c93b47af-e291-4a6a-ae4b-cc46f25c422b@oss.qualcomm.com>

On Wed, Jul 01, 2026 at 01:01:52PM +0200, Konrad Dybcio wrote:
> On 7/1/26 9:44 AM, Sumit Garg wrote:
> > On Tue, Jun 30, 2026 at 02:42:25PM +0200, Konrad Dybcio wrote:
> >> On 6/26/26 3:34 PM, Sumit Garg wrote:
> >>> From: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >>>
> >>> As per testing the SCM backend just ignores it while OP-TEE makes
> >>> use of it to for proper book keeping purpose.
> >>>
> >>> Reviewed-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com>
> >>> Tested-by: Mukesh Ojha <mukesh.ojha@oss.qualcomm.com> # Lemans
> >>> Reviewed-by: Vikash Garodia <vikash.garodia@oss.qualcomm.com>
> >>> Signed-off-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
> >>> ---
> >>>  drivers/media/platform/qcom/iris/iris_firmware.c | 2 +-
> >>>  drivers/media/platform/qcom/venus/firmware.c     | 2 +-
> >>>  2 files changed, 2 insertions(+), 2 deletions(-)
> >>>
> >>> diff --git a/drivers/media/platform/qcom/iris/iris_firmware.c b/drivers/media/platform/qcom/iris/iris_firmware.c
> >>> index ea9654dd679e..d2e7ba4f37e3 100644
> >>> --- a/drivers/media/platform/qcom/iris/iris_firmware.c
> >>> +++ b/drivers/media/platform/qcom/iris/iris_firmware.c
> >>> @@ -110,5 +110,5 @@ int iris_fw_unload(struct iris_core *core)
> >>>  
> >>>  int iris_set_hw_state(struct iris_core *core, bool resume)
> >>>  {
> >>> -	return qcom_pas_set_remote_state(resume, 0);
> >>> +	return qcom_pas_set_remote_state(resume, IRIS_PAS_ID);
> >>>  }
> >>> diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c
> >>> index 3a38ff985822..3c0727ea137d 100644
> >>> --- a/drivers/media/platform/qcom/venus/firmware.c
> >>> +++ b/drivers/media/platform/qcom/venus/firmware.c
> >>> @@ -59,7 +59,7 @@ int venus_set_hw_state(struct venus_core *core, bool resume)
> >>>  	int ret;
> >>>  
> >>>  	if (core->use_tz) {
> >>> -		ret = qcom_pas_set_remote_state(resume, 0);
> >>> +		ret = qcom_pas_set_remote_state(resume, VENUS_PAS_ID);
> >>
> >> This should not be in the middle of a mildly related series..
> >> The PAS IDs should be centralized into a single header. And the
> >> name of the driver shouldn't be part of the define. I would guesstimate
> >> that on the secure side it's probably called VPU or VIDEO
> > 
> > I agree with your comments, this is something I would also like to
> > consolidate on OP-TEE side as well: see discussion here [1].
> > 
> > However, the patch itself was needed to do book keeping on OP-TEE side
> > but I can drop it since anyhow the video isn't functional yet in
> > upstream dependent on the proper IOMMU support.
> 
> For this patch.. I think QCTZ may be ignoring the argument so it
> may not matter.. on a second thought you already have it reviewed
> and it's already a cross-subsys merge so might as well pull it in,
> worst case scenario it'll revert cleanly

Thanks, I will keep it then.

> 
> Once this lands, please move all PAS defines to.. hmm.. qcom_pas.h
> sounds like a good candidate?

Sure, I will propose that as a follow-up change. We have to agree on
common naming there.

-Sumit

^ permalink raw reply

* [PATCH net] ppp: defer channel free to an RCU grace period to fix pppol2tp RX UAF
From: Norbert Szetei @ 2026-07-01 12:14 UTC (permalink / raw)
  To: netdev
  Cc: Andrew Lunn, David S. Miller, Eric Dumazet, Jakub Kicinski,
	Paolo Abeni, Qingfang Deng, Taegu Ha, Yue Haibing,
	Sebastian Andrzej Siewior, Kees Cook, linux-ppp, linux-kernel

pppol2tp_recv() runs in the L2TP UDP-encap softirq RX path:

 l2tp_udp_encap_recv() -> l2tp_recv_common() -> pppol2tp_recv()
   -> ppp_input(&po->chan)

It runs under rcu_read_lock() holding only an l2tp_session reference and
takes NO reference on the internal PPP channel (struct channel,
chan->ppp) that ppp_input() dereferences.

The pppox socket is SOCK_RCU_FREE, so 'po' and the embedded ppp_channel
are RCU-safe.  But the internal struct channel is a separate allocation
that ppp_release_channel() frees with a plain kfree():

 close(data socket) -> pppol2tp_release() -> pppox_unbind_sock()
   -> ppp_unregister_channel() -> ppp_release_channel() -> kfree(pch)

For a channel that is bound (PPPIOCGCHAN) but not attached to a ppp unit
(no PPPIOCCONNECT, pch->ppp == NULL) and not bridged, teardown skips
both ppp_disconnect_channel()'s synchronize_net() and
ppp_unbridge_channels()'s synchronize_rcu(), so the kfree() has no grace
period.  rcu_read_lock() in pppol2tp_recv() does not protect against a
plain kfree(), so an in-flight ppp_input() on one CPU can dereference
the channel just freed by close() on another CPU.

The bug is reachable by an unprivileged user.

Free the channel with kfree_rcu() instead of kfree() so the grace period
fences any in-flight ppp_input(). Done in ppp_release_channel(), this
covers all callers in one place.

Fixes: ee40fb2e1eb5 ("l2tp: protect sock pointer of struct pppol2tp_session with RCU")
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Norbert Szetei <norbert@doyensec.com>
---
 drivers/net/ppp/ppp_generic.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
index 57c68efa5ff8..cb8fe37170d3 100644
--- a/drivers/net/ppp/ppp_generic.c
+++ b/drivers/net/ppp/ppp_generic.c
@@ -184,6 +184,7 @@ struct channel {
 	struct list_head clist;		/* link in list of channels per unit */
 	spinlock_t	upl;		/* protects `ppp' and 'bridge' */
 	struct channel __rcu *bridge;	/* "bridged" ppp channel */
+	struct rcu_head	rcu;		/* for RCU-deferred free of the channel */
 #ifdef CONFIG_PPP_MULTILINK
 	u8		avail;		/* flag used in multilink stuff */
 	u8		had_frag;	/* >= 1 fragments have been sent */
@@ -3583,7 +3584,7 @@ static void ppp_release_channel(struct channel *pch)
 	}
 	skb_queue_purge(&pch->file.xq);
 	skb_queue_purge(&pch->file.rq);
-	kfree(pch);
+	kfree_rcu(pch, rcu);
 }
 
 static void __exit ppp_cleanup(void)
-- 
2.54.0

^ permalink raw reply related

* Re: [PATCH net-next 2/2] macvlan: no longer rely on RTNL in macvlan_fill_info()
From: Nikolay Aleksandrov @ 2026-07-01 12:11 UTC (permalink / raw)
  To: Eric Dumazet, David S . Miller, Jakub Kicinski, Paolo Abeni
  Cc: Simon Horman, Kuniyuki Iwashima, netdev, eric.dumazet
In-Reply-To: <20260701082214.2974946-3-edumazet@google.com>

On 01/07/2026 11:22, Eric Dumazet wrote:
> Add READ_ONCE()/WRITE_ONCE() annotations on vlan->mode, vlan->flags,
> vlan->bc_queue_len_req and port->bc_cutoff.
> 
> Fill IFLA_MACVLAN_MACADDR_DATA nested attribute and compute
> on the fly the precise number of elements we put in it,
> to fill an accurate IFLA_MACVLAN_MACADDR_COUNT attribute
> as some user space applications could depend on its value
> and the attributes order.
> 
> Signed-off-by: Eric Dumazet <edumazet@google.com>
> ---
>   drivers/net/macvlan.c | 71 +++++++++++++++++++++++++++++--------------
>   1 file changed, 48 insertions(+), 23 deletions(-)
> 

The snippet that sets macaddr_count gave me pause. :)
Reviewed-by: Nikolay Aleksandrov <razor@blackwall.org>

[snip]
> +
> +	if (READ_ONCE(vlan->macaddr_count) > 0) {
>   		nest = nla_nest_start_noflag(skb, IFLA_MACVLAN_MACADDR_DATA);
>   		if (nest == NULL)
>   			goto nla_put_failure;
>   
>   		for (i = 0; i < MACVLAN_HASH_SIZE; i++) {
> -			if (macvlan_fill_info_macaddr(skb, vlan, i))
> +			cnt = macvlan_fill_info_macaddr(skb, vlan, i);
> +			if (cnt < 0)
>   				goto nla_put_failure;
> +			macaddr_count += cnt;
>   		}
> -		nla_nest_end(skb, nest);
> +		if (!macaddr_count)
> +			nla_nest_cancel(skb, nest);
> +		else if (nla_nest_end_safe(skb, nest) < 0)
> +			goto nla_put_failure;
>   	}
> -	if (nla_put_u32(skb, IFLA_MACVLAN_BC_QUEUE_LEN, vlan->bc_queue_len_req))
> +	*(u32 *)nla_data(attr) = macaddr_count;
> +

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox