Files
Omar Ramadan afae89de73 amt: send the relay's General Query directly from the receive path
An skb queued in a qdisc can outlive the tunnel it references
through a raw pointer in skb->cb. For example, with igmp_qrv set
to 1 on the relay a tunnel lives for 135s, so a netem delay of
180s on the amt device outlives it; when the tunnel expires and is
freed, the subsequent dequeue triggers a use-after-free in
amt_dev_xmit().

  BUG: KASAN: slab-use-after-free in amt_dev_xmit+0x2763/0x2e20
  Call Trace:
   amt_dev_xmit+0x2763/0x2e20 [drivers/net/amt.c:1262]
   dev_hard_start_xmit+0x22f/0x620
   sch_direct_xmit+0x12e/0xac0
   netem_dequeue+0x333/0xc50
   net_tx_action+0x35c/0xa60

amt_send_igmp_gq() and amt_send_mld_gq() are only called from
amt_request_handler(), inside the rcu_read_lock_bh() section of
amt_rcv(). amt_request_handler() already has the tunnel the query is
for: it found or created it inside that section. Queuing the query with
dev_queue_xmit() only leads back into amt_dev_xmit(), which strips the
Ethernet header and calls amt_send_membership_query() for that tunnel.

Make that call directly from the two senders instead, the same way
amt_send_advertisement() transmits from the receive path. The query
never waits in a qdisc, the tunnel is only dereferenced inside the
RCU section that found or created it, and nothing is stored in
skb->cb, so no lookup or refcount is needed. Remove the query branch
of amt_dev_xmit(), amt_skb_cb() and struct amt_skb_cb, which have no
users left.

Behaviour changes:
 - The relay's own General Queries no longer pass through the amt
   device's egress path: its qdisc, tc egress (clsact/tcx), the
   netfilter egress hook and packet taps. They are still visible as
   UDP on the underlay.
 - A query that is sent successfully is no longer counted as
   tx_dropped. The old query branch left through the unlock label,
   which counted every sent query as dropped.
 - A query that reaches amt_dev_xmit() on a relay from elsewhere,
   such as a userspace querier, is now dropped at the IGMP/MLD type
   switch. Before, it trusted whatever skb->cb held, and a NULL
   tunnel hit the WARN_ON(1).

Fixes: cbc21dc1cf ("amt: add data plane of amt interface")
Reported-by: AutonomousCodeSecurity@microsoft.com
Reported-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Reported-by: Cen Zhang (Microsoft Security FORGE Labs) <cenzhang@linux.microsoft.com>
Signed-off-by: Omar Ramadan <omar@blockcast.net>
Link: https://patch.msgid.link/20260928202312.74574-2-omar@blockcast.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-10-01 18:11:09 -07:00

405 lines
8.3 KiB
C

/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (c) 2021 Taehee Yoo <ap420073@gmail.com>
*/
#ifndef _NET_AMT_H_
#define _NET_AMT_H_
#include <linux/siphash.h>
#include <linux/jhash.h>
#include <linux/netdevice.h>
#include <net/gro_cells.h>
#include <net/rtnetlink.h>
enum amt_msg_type {
AMT_MSG_DISCOVERY = 1,
AMT_MSG_ADVERTISEMENT,
AMT_MSG_REQUEST,
AMT_MSG_MEMBERSHIP_QUERY,
AMT_MSG_MEMBERSHIP_UPDATE,
AMT_MSG_MULTICAST_DATA,
AMT_MSG_TEARDOWN,
__AMT_MSG_MAX,
};
#define AMT_MSG_MAX (__AMT_MSG_MAX - 1)
enum amt_ops {
/* A*B */
AMT_OPS_INT,
/* A+B */
AMT_OPS_UNI,
/* A-B */
AMT_OPS_SUB,
/* B-A */
AMT_OPS_SUB_REV,
__AMT_OPS_MAX,
};
#define AMT_OPS_MAX (__AMT_OPS_MAX - 1)
enum amt_filter {
AMT_FILTER_FWD,
AMT_FILTER_D_FWD,
AMT_FILTER_FWD_NEW,
AMT_FILTER_D_FWD_NEW,
AMT_FILTER_ALL,
AMT_FILTER_NONE_NEW,
AMT_FILTER_BOTH,
AMT_FILTER_BOTH_NEW,
__AMT_FILTER_MAX,
};
#define AMT_FILTER_MAX (__AMT_FILTER_MAX - 1)
enum amt_act {
AMT_ACT_GMI,
AMT_ACT_GMI_ZERO,
AMT_ACT_GT,
AMT_ACT_STATUS_FWD_NEW,
AMT_ACT_STATUS_D_FWD_NEW,
AMT_ACT_STATUS_NONE_NEW,
__AMT_ACT_MAX,
};
#define AMT_ACT_MAX (__AMT_ACT_MAX - 1)
enum amt_status {
AMT_STATUS_INIT,
AMT_STATUS_SENT_DISCOVERY,
AMT_STATUS_RECEIVED_DISCOVERY,
AMT_STATUS_SENT_ADVERTISEMENT,
AMT_STATUS_RECEIVED_ADVERTISEMENT,
AMT_STATUS_SENT_REQUEST,
AMT_STATUS_RECEIVED_REQUEST,
AMT_STATUS_SENT_QUERY,
AMT_STATUS_RECEIVED_QUERY,
AMT_STATUS_SENT_UPDATE,
AMT_STATUS_RECEIVED_UPDATE,
__AMT_STATUS_MAX,
};
#define AMT_STATUS_MAX (__AMT_STATUS_MAX - 1)
/* Gateway events only */
enum amt_event {
AMT_EVENT_NONE,
AMT_EVENT_RECEIVE,
AMT_EVENT_SEND_DISCOVERY,
AMT_EVENT_SEND_REQUEST,
__AMT_EVENT_MAX,
};
struct amt_header {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u8 type:4,
version:4;
#elif defined(__BIG_ENDIAN_BITFIELD)
u8 version:4,
type:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
} __packed;
struct amt_header_discovery {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u32 type:4,
version:4,
reserved:24;
#elif defined(__BIG_ENDIAN_BITFIELD)
u32 version:4,
type:4,
reserved:24;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__be32 nonce;
} __packed;
struct amt_header_advertisement {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u32 type:4,
version:4,
reserved:24;
#elif defined(__BIG_ENDIAN_BITFIELD)
u32 version:4,
type:4,
reserved:24;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__be32 nonce;
__be32 ip4;
} __packed;
struct amt_header_request {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u32 type:4,
version:4,
reserved1:7,
p:1,
reserved2:16;
#elif defined(__BIG_ENDIAN_BITFIELD)
u32 version:4,
type:4,
p:1,
reserved1:7,
reserved2:16;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__be32 nonce;
} __packed;
struct amt_header_membership_query {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u64 type:4,
version:4,
reserved:6,
l:1,
g:1,
response_mac:48;
#elif defined(__BIG_ENDIAN_BITFIELD)
u64 version:4,
type:4,
g:1,
l:1,
reserved:6,
response_mac:48;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__be32 nonce;
} __packed;
struct amt_header_membership_update {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u64 type:4,
version:4,
reserved:8,
response_mac:48;
#elif defined(__BIG_ENDIAN_BITFIELD)
u64 version:4,
type:4,
reserved:8,
response_mac:48;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__be32 nonce;
} __packed;
struct amt_header_mcast_data {
#if defined(__LITTLE_ENDIAN_BITFIELD)
u16 type:4,
version:4,
reserved:8;
#elif defined(__BIG_ENDIAN_BITFIELD)
u16 version:4,
type:4,
reserved:8;
#else
#error "Please fix <asm/byteorder.h>"
#endif
} __packed;
struct amt_headers {
union {
struct amt_header_discovery discovery;
struct amt_header_advertisement advertisement;
struct amt_header_request request;
struct amt_header_membership_query query;
struct amt_header_membership_update update;
struct amt_header_mcast_data data;
};
} __packed;
struct amt_gw_headers {
union {
struct amt_header_discovery discovery;
struct amt_header_request request;
struct amt_header_membership_update update;
};
} __packed;
struct amt_relay_headers {
union {
struct amt_header_advertisement advertisement;
struct amt_header_membership_query query;
struct amt_header_mcast_data data;
};
} __packed;
struct amt_tunnel_list {
struct list_head list;
/* Protect All resources under an amt_tunne_list */
spinlock_t lock;
struct amt_dev *amt;
u32 nr_groups;
u32 nr_sources;
enum amt_status status;
struct delayed_work gc_wq;
__be16 source_port;
__be32 ip4;
__be32 nonce;
siphash_key_t key;
u64 mac:48,
reserved:16;
struct rcu_head rcu;
struct hlist_head groups[];
};
union amt_addr {
__be32 ip4;
#if IS_ENABLED(CONFIG_IPV6)
struct in6_addr ip6;
#endif
};
/* RFC 3810
*
* When the router is in EXCLUDE mode, the router state is represented
* by the notation EXCLUDE (X,Y), where X is called the "Requested List"
* and Y is called the "Exclude List". All sources, except those from
* the Exclude List, will be forwarded by the router
*/
enum amt_source_status {
AMT_SOURCE_STATUS_NONE,
/* Node of Requested List */
AMT_SOURCE_STATUS_FWD,
/* Node of Exclude List */
AMT_SOURCE_STATUS_D_FWD,
};
/* protected by gnode->lock */
struct amt_source_node {
struct hlist_node node;
struct amt_group_node *gnode;
struct delayed_work source_timer;
union amt_addr source_addr;
enum amt_source_status status;
#define AMT_SOURCE_OLD 0
#define AMT_SOURCE_NEW 1
u8 flags;
struct rcu_head rcu;
};
/* Protected by amt_tunnel_list->lock */
struct amt_group_node {
struct amt_dev *amt;
union amt_addr group_addr;
union amt_addr host_addr;
bool v6;
u8 filter_mode;
u32 nr_sources;
struct amt_tunnel_list *tunnel_list;
struct hlist_node node;
struct delayed_work group_timer;
struct rcu_head rcu;
struct hlist_head sources[];
};
#define AMT_MAX_EVENTS 16
struct amt_events {
enum amt_event event;
struct sk_buff *skb;
};
struct amt_dev {
struct net_device *dev;
struct net_device *stream_dev;
struct net *net;
/* Global lock for amt device */
spinlock_t lock;
/* Used only in relay mode */
struct list_head tunnel_list;
struct gro_cells gro_cells;
/* Protected by RTNL */
struct delayed_work discovery_wq;
/* Protected by RTNL */
struct delayed_work req_wq;
/* Protected by RTNL */
struct delayed_work secret_wq;
struct work_struct event_wq;
/* AMT status */
enum amt_status status;
/* Generated key */
siphash_key_t key;
struct sock __rcu *sk;
u32 max_groups;
u32 max_sources;
u32 hash_buckets;
u32 hash_seed;
/* Default 128 */
u32 max_tunnels;
/* Default 128 */
u32 nr_tunnels;
/* Gateway or Relay mode */
u32 mode;
/* Default 2268 */
__be16 relay_port;
/* Default 2268 */
__be16 gw_port;
/* Outer local ip */
__be32 local_ip;
/* Outer remote ip */
__be32 remote_ip;
/* Outer discovery ip */
__be32 discovery_ip;
/* Only used in gateway mode */
__be32 nonce;
/* Gateway sent request and received query */
bool ready4;
bool ready6;
u8 req_cnt;
u8 qi;
u64 qrv;
u64 qri;
/* Used only in gateway mode */
u64 mac:48,
reserved:16;
/* AMT gateway side message handler queue */
struct amt_events events[AMT_MAX_EVENTS];
u8 event_idx;
u8 nr_events;
};
#define AMT_TOS 0xc0
#define AMT_IPHDR_OPTS 4
#define AMT_IP6HDR_OPTS 8
#define AMT_GC_INTERVAL (30 * 1000)
#define AMT_MAX_GROUP 32
#define AMT_MAX_SOURCE 128
#define AMT_HSIZE_SHIFT 8
#define AMT_HSIZE (1 << AMT_HSIZE_SHIFT)
#define AMT_DISCOVERY_TIMEOUT 5000
#define AMT_INIT_REQ_TIMEOUT 1
#define AMT_INIT_QUERY_INTERVAL 125
#define AMT_MAX_REQ_TIMEOUT 120
#define AMT_MAX_REQ_COUNT 3
#define AMT_SECRET_TIMEOUT 60000
#define IANA_AMT_UDP_PORT 2268
#define AMT_MAX_TUNNELS 128
#define AMT_MAX_REQS 128
#define AMT_GW_HLEN (sizeof(struct iphdr) + \
sizeof(struct udphdr) + \
sizeof(struct amt_gw_headers))
#define AMT_RELAY_HLEN (sizeof(struct iphdr) + \
sizeof(struct udphdr) + \
sizeof(struct amt_relay_headers))
static inline bool netif_is_amt(const struct net_device *dev)
{
return dev->rtnl_link_ops && !strcmp(dev->rtnl_link_ops->kind, "amt");
}
static inline u64 amt_gmi(const struct amt_dev *amt)
{
return ((amt->qrv * amt->qi) + amt->qri) * 1000;
}
#endif /* _NET_AMT_H_ */