Thread (26 messages) flat view 26 messages, 5 authors, 7d ago

Re: [PATCH net 07/10] ice: eswitch: fix use-after-free of metadata_dst in repr release

From: Jakub Kicinski <kuba@kernel.org>
Date: 2026-08-06 17:20:41
Also in: stable

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ice: eswitch: fix use-after-free of metadata_dst in repr release

This replaces metadata_dst_free() with dst_release() in
ice_eswitch_release_repr() so that the port representor metadata_dst is
freed only after the last dst_entry reference goes away, instead of being
kfree()d while in-flight skbs may still reference it.

(No Link: tags present in the commit header.)
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/ethernet/intel/ice/ice_eswitch.c b/drivers/net/ethernet/intel/ice/ice_eswitch.c
index b069e6c514fb1..6e7bba4738989 100644
--- a/drivers/net/ethernet/intel/ice/ice_eswitch.c
+++ b/drivers/net/ethernet/intel/ice/ice_eswitch.c
@@ -95,7 +95,7 @@ ice_eswitch_release_repr(struct ice_pf *pf, struct ice_repr *repr)
 		return;
 
 	ice_vsi_update_security(vsi, ice_vsi_ctx_set_antispoof);
-	metadata_dst_free(repr->dst);
+	dst_release(&repr->dst->dst);
 	repr->dst = NULL;
 	ice_fltr_add_mac_and_broadcast(vsi, repr->parent_mac,
 				       ICE_FWD_TO_VSI);
[Severity: High]

This isn't a bug introduced by this patch, but with the put side now fixed,
is the get side of repr->dst still unsynchronized?

ice_eswitch_port_start_xmit() does three separate plain loads of repr->dst
with no lock, no RCU section and no READ_ONCE():

	skb_dst_drop(skb);
	dst_hold((struct dst_entry *)repr->dst);
	skb_dst_set(skb, (struct dst_entry *)repr->dst);
	skb->dev = repr->dst->u.port_info.lower_dev;

while ice_eswitch_release_repr() drops the last reference and then stores
the NULL:

	dst_release(&repr->dst->dst);
	repr->dst = NULL;

In the normal teardown path the ordering appears to save this, because
ice_eswitch_detach() calls repr->ops.rem() (unregister_netdev(), which
quiesces the representor's ndo_start_xmit) before
ice_eswitch_release_repr().

The attach error path has the opposite order:

	err_xa_alloc:
		ice_eswitch_release_repr(pf, repr);
	err_setup_repr:
		repr->ops.rem(repr);

At err_xa_alloc the representor netdev is already registered, since
repr->ops.add() runs ice_repr_add_vf()/ice_repr_add_sf() ->
ice_repr_reg_netdev() -> register_netdev(), and the attach path holds
devl_lock but not RTNL, so userspace can open the interface and transmit.

Two outcomes seem possible in that window:

  - the xmit CPU loaded a non-NULL repr->dst just before dst_release()
    drove the rcuref to zero, so dst_hold() hits its
    WARN_ON(!rcuref_get(&dst->__rcuref)), takes no reference, and the skb
    carries a metadata_dst that dst_destroy_rcu() frees after the grace
    period, which then gets read by ice_eswitch_set_target_vsi() or by the
    skb free path;

  - the xmit CPU loads repr->dst after the NULL store, giving dst_hold(NULL)
    and a NULL dereference inside rcuref_get().

Would swapping the two error labels so repr->ops.rem() runs before
ice_eswitch_release_repr(), or clearing repr->dst followed by
synchronize_net() before dst_release(), close this window? Also, since
err_setup_repr is reached when metadata_dst_alloc() fails, does that path
leave a registered netdev whose repr->dst is NULL until repr->ops.rem()
runs?

[Severity: High]

This is a pre-existing issue as well, and not something this patch changes,
but does the deferred free help the LAG path at all?

ice_lag_config_eswitch() writes through repr->dst with no NULL check:

	xa_for_each(&lag->pf->eswitch.reprs, id, repr)
		repr->dst->u.port_info.lower_dev = netdev;

xa_for_each() expands to xa_find()/xa_find_after(), which take and drop
rcu_read_lock() internally, so repr is used outside any RCU read-side
section.

The caller ice_lag_process_event() is a work item and holds only
pf->lag_mutex, while the teardown side (ice_free_vfs() ->
ice_eswitch_detach_vf(), and the ice_reset_all_vfs() path) holds
vf->cfg_lock and devl_lock, so the locksets do not intersect.

That seems to allow:

	LAG work				detach thread
	repr = xa_find(...)
						xa_erase(...)
						ice_eswitch_release_repr()
						  repr->dst = NULL;
						ice_repr_destroy()
						  kfree(repr);
	repr->dst->u.port_info.lower_dev = netdev;

which is either a store through NULL or a store into freed memory, since
ice_repr_destroy() does a plain kfree(repr) with no grace period.

The change from metadata_dst_free() to dst_release() only defers freeing the
metadata_dst; the repr->dst = NULL store and the kfree(repr) are still
immediate. Should this path be serialized against the eswitch teardown, or
at least keep the repr lookup inside an RCU read-side section with an
RCU-deferred repr free?
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help