Merge branch 'net-fix-hard_header_len-races-in-packet-send-paths'

Qihang Tang says:

====================
net: fix hard_header_len races in packet send paths

The packet socket TX paths read dev->hard_header_len independently for
skb allocation and header construction. Concurrent netdevice
reconfiguration (e.g. bonding device type changes) can change this value
in between, leading to mismatched headroom and copy length, and in the
SOCK_RAW case to out-of-bounds writes.

Patch 1 removes the CAP_SYS_RAWIO zero-padding branch in
dev_validate_header(). That branch sizes a memset against the live
dev->hard_header_len while operating on an skb whose headroom was
allocated from an earlier hard_header_len read, so a concurrent increase
can write past the reserved buffer. Removing it first keeps the later
snapshot fixes bisect-safe: they do not replace an earlier skb_under_panic
with a silent overwrite.

Patches 2 and 3 snapshot hard_header_len once per send and use it
consistently for allocation and construction, in the non-ring and TX_RING
paths respectively. The separate SOCK_DGRAM consistency problem between
hard_header_len and header_ops->create remains out of scope, as noted in
the commit messages.
====================

Link: https://patch.msgid.link/20260805125729.19220-1-q.h.hack.winter@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-08-06 09:27:24 -07:00
2 changed files with 31 additions and 25 deletions

View File

@@ -300,9 +300,11 @@ struct hh_cache {
* We could use other alignment values, but we must maintain the
* relationship HH alignment <= LL alignment.
*/
#define LL_RESERVED_SPACE(dev) \
((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom)) \
#define LL_RESERVED_SPACE_EX(dev, hlen) \
((((hlen) + READ_ONCE((dev)->needed_headroom)) \
& ~(HH_DATA_MOD - 1)) + HH_DATA_MOD)
#define LL_RESERVED_SPACE(dev) \
LL_RESERVED_SPACE_EX(dev, (dev)->hard_header_len)
#define LL_RESERVED_SPACE_EXTRA(dev,extra) \
((((dev)->hard_header_len + READ_ONCE((dev)->needed_headroom) + (extra)) \
& ~(HH_DATA_MOD - 1)) + HH_DATA_MOD)
@@ -3531,11 +3533,6 @@ static inline bool dev_validate_header(const struct net_device *dev,
if (len < dev->min_header_len)
return false;
if (capable(CAP_SYS_RAWIO)) {
memset(ll_header + len, 0, dev->hard_header_len - len);
return true;
}
if (dev->header_ops && dev->header_ops->validate)
return dev->header_ops->validate(ll_header, len);

View File

@@ -1966,8 +1966,9 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg,
struct net_device *dev;
struct sockcm_cookie sockc;
__be16 proto = 0;
int err;
int hard_header_len;
int extra_len = 0;
int err;
/*
* Get and verify the address.
@@ -2010,14 +2011,18 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg,
extra_len = 4; /* We're doing our own CRC */
}
/* Keep the allocation-time header length across retry. */
if (!skb)
hard_header_len = READ_ONCE(dev->hard_header_len);
err = -EMSGSIZE;
if (len > dev->mtu + dev->hard_header_len + VLAN_HLEN + extra_len)
if (len > dev->mtu + hard_header_len + VLAN_HLEN + extra_len)
goto out_unlock;
if (!skb) {
size_t reserved = LL_RESERVED_SPACE(dev);
size_t reserved = LL_RESERVED_SPACE_EX(dev, hard_header_len);
int tlen = dev->needed_tailroom;
unsigned int hhlen = dev->header_ops ? dev->hard_header_len : 0;
unsigned int hhlen = dev->header_ops ? hard_header_len : 0;
rcu_read_unlock();
skb = sock_wmalloc(sk, len + reserved + tlen, 0, GFP_KERNEL);
@@ -2047,7 +2052,7 @@ static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg,
err = -EINVAL;
goto out_unlock;
}
if (len > (dev->mtu + dev->hard_header_len + extra_len) &&
if (len > (dev->mtu + hard_header_len + extra_len) &&
!packet_extra_vlan_len_allowed(dev, skb)) {
err = -EMSGSIZE;
goto out_unlock;
@@ -2582,6 +2587,7 @@ static int packet_snd_vnet_parse(struct msghdr *msg, size_t *len,
static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb,
void *frame, struct net_device *dev, void *data, int tp_len,
__be16 proto, unsigned char *addr, int hlen, int copylen,
int hard_header_len,
const struct sockcm_cookie *sockc)
{
union tpacket_uhdr ph;
@@ -2613,8 +2619,8 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb,
} else if (copylen) {
int hdrlen = min_t(int, copylen, tp_len);
skb_push(skb, dev->hard_header_len);
skb_put(skb, copylen - dev->hard_header_len);
skb_push(skb, hard_header_len);
skb_put(skb, copylen - hard_header_len);
err = skb_store_bits(skb, 0, data, hdrlen);
if (unlikely(err))
return err;
@@ -2745,7 +2751,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
void *data;
int len_sum = 0;
int status = TP_STATUS_AVAILABLE;
int hlen, tlen, copylen = 0;
int hard_header_len, hlen, tlen, copylen = 0;
long timeo;
mutex_lock(&po->pg_vec_lock);
@@ -2792,8 +2798,9 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
goto out_put;
}
hard_header_len = READ_ONCE(dev->hard_header_len);
if (po->sk.sk_socket->type == SOCK_RAW)
reserve = dev->hard_header_len;
reserve = hard_header_len;
size_max = po->tx_ring.frame_size
- (po->tp_hdrlen - sizeof(struct sockaddr_ll));
@@ -2830,7 +2837,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
goto tpacket_error;
status = TP_STATUS_SEND_REQUEST;
hlen = LL_RESERVED_SPACE(dev);
hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len);
tlen = dev->needed_tailroom;
if (vnet_hdr_sz) {
data += vnet_hdr_sz;
@@ -2848,10 +2855,10 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
vnet_hdr.hdr_len);
has_vnet_hdr = true;
}
copylen = max_t(int, copylen, dev->hard_header_len);
copylen = max_t(int, copylen, hard_header_len);
skb = sock_alloc_send_skb(&po->sk,
hlen + tlen + sizeof(struct sockaddr_ll) +
(copylen - dev->hard_header_len),
(copylen - hard_header_len),
!need_wait, &err);
if (unlikely(skb == NULL)) {
@@ -2861,7 +2868,8 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg)
goto out_status;
}
tp_len = tpacket_fill_skb(po, skb, ph, dev, data, tp_len, proto,
addr, hlen, copylen, &sockc);
addr, hlen, copylen, hard_header_len,
&sockc);
if (likely(tp_len >= 0) &&
tp_len > dev->mtu + reserve &&
!vnet_hdr_sz &&
@@ -2969,7 +2977,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
int offset = 0;
struct packet_sock *po = pkt_sk(sk);
int vnet_hdr_sz = READ_ONCE(po->vnet_hdr_sz);
int hlen, tlen, linear;
int hard_header_len, hlen, tlen, linear;
int extra_len = 0;
/*
@@ -3009,8 +3017,9 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
goto out_unlock;
}
hard_header_len = READ_ONCE(dev->hard_header_len);
if (sock->type == SOCK_RAW)
reserve = dev->hard_header_len;
reserve = hard_header_len;
if (vnet_hdr_sz) {
err = packet_snd_vnet_parse(msg, &len, &vnet_hdr, vnet_hdr_sz);
if (err)
@@ -3031,10 +3040,10 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
goto out_unlock;
err = -ENOBUFS;
hlen = LL_RESERVED_SPACE(dev);
hlen = LL_RESERVED_SPACE_EX(dev, hard_header_len);
tlen = dev->needed_tailroom;
linear = __virtio16_to_cpu(vio_le(), vnet_hdr.hdr_len);
linear = max(linear, min_t(int, len, dev->hard_header_len));
linear = max(linear, min_t(int, len, hard_header_len));
skb = packet_alloc_skb(sk, hlen + tlen, hlen, len, linear,
msg->msg_flags & MSG_DONTWAIT, &err);
if (skb == NULL)
@@ -3050,7 +3059,7 @@ static int packet_snd(struct socket *sock, struct msghdr *msg, size_t len)
} else if (reserve) {
skb_reserve(skb, -reserve);
if (len < reserve + sizeof(struct ipv6hdr) &&
dev->min_header_len != dev->hard_header_len)
dev->min_header_len != hard_header_len)
skb_reset_network_header(skb);
}