summaryrefslogtreecommitdiff
path: root/net/openvswitch
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-27 13:53:43 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-27 13:53:43 -0700
commit1b78070aaef63512688aebfbc82365ef9d6660f1 (patch)
tree691c0aeaa3d92278ceeb6ace56bc8cd56a7f2ae8 /net/openvswitch
parent3ba13f5e7180c034b0a1ef7e052fb780856b134e (diff)
parent4a9d62a8774f130a5b8de26ca9f415e6050a9d51 (diff)
downloadlinux-stable-master.tar.gz
linux-stable-master.zip
Merge tag 'net-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/netHEADmaster
Pull networking fixes from Jakub Kicinski: "Including fixes from Bluetooth, IPSec and Netfilter. Current release - fix to a fix: - netfilter: ipset: remove need to allocate memory on delete operations Current release - regressions: - macb: drop CONFIG_OF #if block, fix build Previous releases - always broken: - stream of fixes for SCTP continues - inet: frags: strip GSO state from fragments before reassembly - virtio-net: ensure that TCP packets don't overflow gso_segs - tcp-ao: fix use-after-free of current_key on reconnect to another peer - page_pool: remove zone/policy GFP flags when allocating XArray entries - Bluetooth: L2CAP: reject accept queue add unless BT_LISTEN - tls: device: fix out-of-bounds write in tls_append_frag() - eth: bnxt: - ring the doorbell when SW USO exits early, avoid packets stuck in Tx - gate TPH enablement behind BNXT_SUPPORTS_QUEUE_API check, avoid users of older NICs seeing non-actionable warning messages - eth: qede: fix NULL pointer dereference in TPA fragment processing" * tag 'net-7.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net: (216 commits) inet: frags: strip GSO state from fragments before reassembly net/sched: sch_htb: limit htb_classify inner-class filter hops selftests/net: packetdrill: add tcp_urg_ptr_retransmit tcp: fix corruption of urgent data on multi-segment retransmit usb: atm: usbatm: fix invalid ci_range initialization net: fec: only stop PTP if it was initialized slip: remove slip_hangup() to fix use-after-free in slip_receive_buf() net: bridge: mcast: fix use-after-free of a master VLAN's multicast context net/sched: bound qdisc_pkt_len to prevent qdisc soft lockup net: dsa: mxl862xx: enable assisted learning on CPU port net: stmmac: restore NET_IP_ALIGN in the RX DMA offset net: stmmac: drop gso_enabled_types and rely on netdev features net: stmmac: selftests: Don't test flow control for small rx fifos net: stmmac: selftests: Account for the UC filter list for filtering tests net: stmmac: dwxgmac: Account for the primary MAC address for UC filtering net: stmmac: dwmac4: Account for the primary MAC address for UC filtering net: stmmac: dwmac1000: Account for the primary MAC address for UC filtering net: stmmac: selftests: Check multiple MMC counters selftests: net: Fix slow configurations in big_tcp_tunnels.sh selftests: net: Lower threshold with csum offload off in big_tcp_tunnels.sh ...
Diffstat (limited to 'net/openvswitch')
-rw-r--r--net/openvswitch/conntrack.c120
-rw-r--r--net/openvswitch/conntrack.h6
-rw-r--r--net/openvswitch/datapath.c16
-rw-r--r--net/openvswitch/datapath.h8
4 files changed, 102 insertions, 48 deletions
diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c
index 49eb2b0d234d..27115967e5d9 100644
--- a/net/openvswitch/conntrack.c
+++ b/net/openvswitch/conntrack.c
@@ -932,10 +932,14 @@ static int ovs_ct_check_limit(struct net *net,
const struct ovs_conntrack_info *info)
{
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
- const struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
+ const struct ovs_ct_limit_info *ct_limit_info;
u32 per_zone_limit, connections;
u32 conncount_key;
+ ct_limit_info = rcu_dereference(ovs_net->ct_limit_info);
+ if (!ct_limit_info)
+ return 0;
+
conncount_key = info->zone.id;
per_zone_limit = ct_limit_get(ct_limit_info, info->zone.id);
@@ -1579,40 +1583,55 @@ static void __ovs_ct_free_action(struct ovs_conntrack_info *ct_info)
#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
static int ovs_ct_limit_init(struct net *net, struct ovs_net *ovs_net)
{
+ struct ovs_ct_limit_info *info;
int i, err;
- ovs_net->ct_limit_info = kmalloc_obj(*ovs_net->ct_limit_info);
- if (!ovs_net->ct_limit_info)
+ info = kmalloc_obj(*info);
+ if (!info)
return -ENOMEM;
- ovs_net->ct_limit_info->default_limit = OVS_CT_LIMIT_DEFAULT;
- ovs_net->ct_limit_info->limits =
+ info->default_limit = OVS_CT_LIMIT_DEFAULT;
+ info->limits =
kmalloc_objs(struct hlist_head, CT_LIMIT_HASH_BUCKETS);
- if (!ovs_net->ct_limit_info->limits) {
- kfree(ovs_net->ct_limit_info);
+ if (!info->limits) {
+ kfree(info);
return -ENOMEM;
}
for (i = 0; i < CT_LIMIT_HASH_BUCKETS; i++)
- INIT_HLIST_HEAD(&ovs_net->ct_limit_info->limits[i]);
+ INIT_HLIST_HEAD(&info->limits[i]);
- ovs_net->ct_limit_info->data = nf_conncount_init(net, sizeof(u32));
+ info->data = nf_conncount_init(net, sizeof(u32));
- if (IS_ERR(ovs_net->ct_limit_info->data)) {
- err = PTR_ERR(ovs_net->ct_limit_info->data);
- kfree(ovs_net->ct_limit_info->limits);
- kfree(ovs_net->ct_limit_info);
+ if (IS_ERR(info->data)) {
+ err = PTR_ERR(info->data);
+ kfree(info->limits);
+ kfree(info);
pr_err("openvswitch: failed to init nf_conncount %d\n", err);
return err;
}
+ rcu_assign_pointer(ovs_net->ct_limit_info, info);
return 0;
}
-static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net)
+static void *ovs_ct_limit_exit_start(struct ovs_net *ovs_net)
+{
+ return rcu_replace_pointer(ovs_net->ct_limit_info, NULL,
+ lockdep_ovsl_is_held());
+}
+
+/* The CT limit state must be detached by ovs_ct_limit_exit_start() and an
+ * RCU grace period must elapse before this function runs. The pernet core
+ * guarantees the grace period between the .pre_exit and .exit callbacks.
+ */
+static void ovs_ct_limit_exit_finish(struct net *net, void *data)
{
- const struct ovs_ct_limit_info *info = ovs_net->ct_limit_info;
+ const struct ovs_ct_limit_info *info = data;
int i;
+ if (!info)
+ return;
+
nf_conncount_destroy(net, info->data);
for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) {
struct hlist_head *head = &info->limits[i];
@@ -1620,7 +1639,7 @@ static void ovs_ct_limit_exit(struct net *net, struct ovs_net *ovs_net)
struct hlist_node *next;
hlist_for_each_entry_safe(ct_limit, next, head, hlist_node)
- kfree_rcu(ct_limit, rcu);
+ kfree(ct_limit);
}
kfree(info->limits);
kfree(info);
@@ -1659,12 +1678,13 @@ static bool check_zone_id(int zone_id, u16 *pzone)
return false;
}
-static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
- struct ovs_ct_limit_info *info)
+static int ovs_ct_limit_set_zone_limit(struct ovs_net *ovs_net,
+ struct nlattr *nla_zone_limit)
{
struct ovs_zone_limit *zone_limit;
- int rem;
+ struct ovs_ct_limit_info *info;
u16 zone;
+ int rem;
rem = NLA_ALIGN(nla_len(nla_zone_limit));
zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit);
@@ -1673,6 +1693,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
if (unlikely(zone_limit->zone_id ==
OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
ovs_lock();
+ info = ovsl_dereference(ovs_net->ct_limit_info);
info->default_limit = zone_limit->limit;
ovs_unlock();
} else if (unlikely(!check_zone_id(
@@ -1689,6 +1710,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
ct_limit->limit = zone_limit->limit;
ovs_lock();
+ info = ovsl_dereference(ovs_net->ct_limit_info);
ct_limit_set(info, ct_limit);
ovs_unlock();
}
@@ -1703,12 +1725,13 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
return 0;
}
-static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit,
- struct ovs_ct_limit_info *info)
+static int ovs_ct_limit_del_zone_limit(struct ovs_net *ovs_net,
+ struct nlattr *nla_zone_limit)
{
struct ovs_zone_limit *zone_limit;
- int rem;
+ struct ovs_ct_limit_info *info;
u16 zone;
+ int rem;
rem = NLA_ALIGN(nla_len(nla_zone_limit));
zone_limit = (struct ovs_zone_limit *)nla_data(nla_zone_limit);
@@ -1717,6 +1740,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit,
if (unlikely(zone_limit->zone_id ==
OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
ovs_lock();
+ info = ovsl_dereference(ovs_net->ct_limit_info);
info->default_limit = OVS_CT_LIMIT_DEFAULT;
ovs_unlock();
} else if (unlikely(!check_zone_id(
@@ -1724,6 +1748,7 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit,
OVS_NLERR(true, "zone id is out of range");
} else {
ovs_lock();
+ info = ovsl_dereference(ovs_net->ct_limit_info);
ct_limit_del(info, zone);
ovs_unlock();
}
@@ -1767,6 +1792,7 @@ static int __ovs_ct_limit_get_zone_limit(struct net *net,
return nla_put_nohdr(reply, sizeof(zone_limit), &zone_limit);
}
+/* Called with RCU read lock held. */
static int ovs_ct_limit_get_zone_limit(struct net *net,
struct nlattr *nla_zone_limit,
struct ovs_ct_limit_info *info,
@@ -1790,12 +1816,10 @@ static int ovs_ct_limit_get_zone_limit(struct net *net,
&zone))) {
OVS_NLERR(true, "zone id is out of range");
} else {
- rcu_read_lock();
limit = ct_limit_get(info, zone);
err = __ovs_ct_limit_get_zone_limit(
net, info->data, zone, limit, reply);
- rcu_read_unlock();
if (err)
return err;
}
@@ -1810,6 +1834,7 @@ static int ovs_ct_limit_get_zone_limit(struct net *net,
return 0;
}
+/* Called with RCU read lock held. */
static int ovs_ct_limit_get_all_zone_limit(struct net *net,
struct ovs_ct_limit_info *info,
struct sk_buff *reply)
@@ -1822,19 +1847,16 @@ static int ovs_ct_limit_get_all_zone_limit(struct net *net,
if (err)
return err;
- rcu_read_lock();
for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) {
head = &info->limits[i];
hlist_for_each_entry_rcu(ct_limit, head, hlist_node) {
err = __ovs_ct_limit_get_zone_limit(net, info->data,
ct_limit->zone, ct_limit->limit, reply);
if (err)
- goto exit_err;
+ return err;
}
}
-exit_err:
- rcu_read_unlock();
return err;
}
@@ -1844,7 +1866,6 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info)
struct sk_buff *reply;
struct ovs_header *ovs_reply_header;
struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id);
- struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
int err;
reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_SET,
@@ -1857,8 +1878,8 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
- err = ovs_ct_limit_set_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
- ct_limit_info);
+ err = ovs_ct_limit_set_zone_limit(ovs_net,
+ a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]);
if (err)
goto exit_err;
@@ -1878,7 +1899,6 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info)
struct sk_buff *reply;
struct ovs_header *ovs_reply_header;
struct ovs_net *ovs_net = net_generic(sock_net(skb->sk), ovs_net_id);
- struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
int err;
reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_DEL,
@@ -1891,8 +1911,8 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
- err = ovs_ct_limit_del_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
- ct_limit_info);
+ err = ovs_ct_limit_del_zone_limit(ovs_net,
+ a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]);
if (err)
goto exit_err;
@@ -1912,7 +1932,7 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info)
struct ovs_header *ovs_reply_header;
struct net *net = sock_net(skb->sk);
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
- struct ovs_ct_limit_info *ct_limit_info = ovs_net->ct_limit_info;
+ struct ovs_ct_limit_info *ct_limit_info;
int err;
reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_GET,
@@ -1926,18 +1946,19 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
+ rcu_read_lock();
+ ct_limit_info = rcu_dereference(ovs_net->ct_limit_info);
if (a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT]) {
err = ovs_ct_limit_get_zone_limit(
net, a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT], ct_limit_info,
reply);
- if (err)
- goto exit_err;
} else {
err = ovs_ct_limit_get_all_zone_limit(net, ct_limit_info,
reply);
- if (err)
- goto exit_err;
}
+ rcu_read_unlock();
+ if (err)
+ goto exit_err;
nla_nest_end(reply, nla_reply);
genlmsg_end(reply, ovs_reply_header);
@@ -2012,12 +2033,29 @@ int ovs_ct_init(struct net *net)
return err;
}
-void ovs_ct_exit(struct net *net)
+/* Must be called with ovs_mutex held. Detaches the RCU-protected
+ * ct_limit_info and stores it in ovs_net->ct_limit_exit_data for
+ * ovs_ct_exit_finish() to complete the teardown after an RCU grace period.
+ */
+void ovs_ct_exit_start(struct net *net __maybe_unused)
+{
+#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
+ struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
+
+ ovs_net->ct_limit_exit_data = ovs_ct_limit_exit_start(ovs_net);
+#endif
+}
+
+/* Completes the CT limit teardown. The pernet core guarantees an RCU
+ * grace period between detaching the state in ovs_ct_exit_start() and
+ * this call, so no RCU readers remain.
+ */
+void ovs_ct_exit_finish(struct net *net)
{
struct ovs_net *ovs_net = net_generic(net, ovs_net_id);
#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
- ovs_ct_limit_exit(net, ovs_net);
+ ovs_ct_limit_exit_finish(net, ovs_net->ct_limit_exit_data);
#endif
if (ovs_net->xt_label)
diff --git a/net/openvswitch/conntrack.h b/net/openvswitch/conntrack.h
index 317e525c8a11..ab21d032fde3 100644
--- a/net/openvswitch/conntrack.h
+++ b/net/openvswitch/conntrack.h
@@ -14,7 +14,8 @@ enum ovs_key_attr;
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
int ovs_ct_init(struct net *);
-void ovs_ct_exit(struct net *);
+void ovs_ct_exit_start(struct net *net);
+void ovs_ct_exit_finish(struct net *net);
bool ovs_ct_verify(struct net *, enum ovs_key_attr attr);
int ovs_ct_copy_action(struct net *, const struct nlattr *,
const struct sw_flow_key *, struct sw_flow_actions **,
@@ -40,7 +41,8 @@ void ovs_ct_free_action(const struct nlattr *a);
static inline int ovs_ct_init(struct net *net) { return 0; }
-static inline void ovs_ct_exit(struct net *net) { }
+static inline void ovs_ct_exit_start(struct net *net) { }
+static inline void ovs_ct_exit_finish(struct net *net) { }
static inline bool ovs_ct_verify(struct net *net, int attr)
{
diff --git a/net/openvswitch/datapath.c b/net/openvswitch/datapath.c
index 69999f9cc44c..631a03136fa1 100644
--- a/net/openvswitch/datapath.c
+++ b/net/openvswitch/datapath.c
@@ -285,6 +285,7 @@ void ovs_dp_process_packet(struct sk_buff *skb, struct sw_flow_key *key)
consume_skb(skb);
break;
default:
+ skb_tx_error(skb);
kfree_skb(skb);
break;
}
@@ -467,6 +468,9 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
if (!dp_ifindex)
return -ENODEV;
+ if (!skb_frags_readable(skb))
+ return -EFAULT;
+
if (skb_vlan_tag_present(skb)) {
nskb = skb_clone(skb, GFP_ATOMIC);
if (!nskb)
@@ -601,8 +605,6 @@ static int queue_userspace_packet(struct datapath *dp, struct sk_buff *skb,
err = genlmsg_unicast(ovs_dp_get_net(dp), user_skb, upcall_info->portid);
user_skb = NULL;
out:
- if (err)
- skb_tx_error(skb);
consume_skb(user_skb);
consume_skb(nskb);
@@ -2739,6 +2741,13 @@ static void __net_exit list_vports_from_net(struct net *net, struct net *dnet,
}
}
+static void __net_exit ovs_pre_exit_net(struct net *dnet)
+{
+ ovs_lock();
+ ovs_ct_exit_start(dnet);
+ ovs_unlock();
+}
+
static void __net_exit ovs_exit_net(struct net *dnet)
{
struct datapath *dp, *dp_next;
@@ -2749,7 +2758,7 @@ static void __net_exit ovs_exit_net(struct net *dnet)
ovs_lock();
- ovs_ct_exit(dnet);
+ ovs_ct_exit_finish(dnet);
list_for_each_entry_safe(dp, dp_next, &ovs_net->dps, list_node)
__dp_destroy(dp);
@@ -2773,6 +2782,7 @@ static void __net_exit ovs_exit_net(struct net *dnet)
static struct pernet_operations ovs_net_ops = {
.init = ovs_init_net,
+ .pre_exit = ovs_pre_exit_net,
.exit = ovs_exit_net,
.id = &ovs_net_id,
.size = sizeof(struct ovs_net),
diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index 696640e88fa7..b2c2b8da12d4 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -164,7 +164,10 @@ struct dp_upcall_info {
* Protected by genl_mutex.
* @dp_notify_work: A work notifier to handle port unregistering.
* @masks_rebalance: A work to periodically optimize flow table caches.
- * @ct_limit_info: A hash table of conntrack zone connection limits.
+ * @ct_limit_info: Hash table of conntrack zone connection limits. Protected
+ * by RCU; updates and teardown are serialized by ovs_mutex. May be NULL during
+ * netns teardown.
+ * @ct_limit_exit_data: CT limit state detached at .pre_exit, freed at .exit.
* @xt_label: Whether connlables are configured for the network or not.
*/
struct ovs_net {
@@ -172,7 +175,8 @@ struct ovs_net {
struct work_struct dp_notify_work;
struct delayed_work masks_rebalance;
#if IS_ENABLED(CONFIG_NETFILTER_CONNCOUNT)
- struct ovs_ct_limit_info *ct_limit_info;
+ struct ovs_ct_limit_info __rcu *ct_limit_info;
+ struct ovs_ct_limit_info *ct_limit_exit_data;
#endif
bool xt_label;
};