* [PATCH net v4 0/2] packet: use consistent hard_header_len in send paths
@ 2026-07-29 3:01 Qihang
2026-07-29 3:01 ` [PATCH net v4 1/2] packet: use consistent hard_header_len in non-ring " Qihang
2026-07-29 3:01 ` [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path Qihang
0 siblings, 2 replies; 4+ messages in thread
From: Qihang @ 2026-07-29 3:01 UTC (permalink / raw)
To: netdev
Cc: willemdebruijn.kernel, daniel.zahka, davem, edumazet, kuba,
pabeni, horms, stable, Qihang
AF_PACKET send paths read dev->hard_header_len at several stages of skb
allocation and construction. Concurrent netdevice reconfiguration can
make those reads inconsistent and cause skb headroom underflow.
Split the regular and TX_RING paths so each patch has one Fixes tag.
The separate SOCK_DGRAM consistency issue between hard_header_len and
header_ops->create remains outside this series.
Changes in v4:
- Use one hard_header_len snapshot throughout tpacket_snd(), including
reserve and per-frame skb construction.
- Carry Willem's Reviewed-by on patch 1.
Link to v3: https://lore.kernel.org/netdev/20260728031345.49562-1-q.h.hack.winter@gmail.com/
Qihang (2):
packet: use consistent hard_header_len in non-ring send paths
packet: use consistent hard_header_len in TX_RING send path
include/linux/netdevice.h | 6 +++--
net/packet/af_packet.c | 46 ++++++++++++++++++++++++---------------
2 files changed, 32 insertions(+), 20 deletions(-)
--
2.50.1 (Apple Git-155)
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH net v4 1/2] packet: use consistent hard_header_len in non-ring send paths
2026-07-29 3:01 [PATCH net v4 0/2] packet: use consistent hard_header_len in send paths Qihang
@ 2026-07-29 3:01 ` Qihang
2026-07-29 3:01 ` [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path Qihang
1 sibling, 0 replies; 4+ messages in thread
From: Qihang @ 2026-07-29 3:01 UTC (permalink / raw)
To: netdev
Cc: willemdebruijn.kernel, daniel.zahka, davem, edumazet, kuba,
pabeni, horms, stable, Qihang, Willem de Bruijn
packet_snd() reads dev->hard_header_len multiple times while allocating
and constructing an skb. Device reconfiguration can change this value
concurrently, for example through bonding device type changes.
For SOCK_RAW, packet_snd() can save a larger value in reserve and later
allocate headroom using a smaller value. Moving skb->data back by reserve
then places it before skb->head, and the following copy from userspace can
attempt an out-of-bounds write.
packet_sendmsg_spkt() has the same issue because it calculates its
reservation and header offset from separate reads before dropping the RCU
read lock to allocate the skb.
Add LL_RESERVED_SPACE_EX() for callers that already saved a header length.
Read hard_header_len once in packet_snd() and use it for allocation and
construction. In packet_sendmsg_spkt(), preserve the allocation-time value
through the device lookup retry.
The separate SOCK_DGRAM consistency problem between hard_header_len and
header_ops->create is not addressed here.
Fixes: b84bbaf7a6c8 ("packet: in packet_snd start writing at link layer allocation")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
---
include/linux/netdevice.h | 6 ++++--
net/packet/af_packet.c | 26 ++++++++++++++++----------
2 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 9981d637f8b5..e50e8abc1392 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -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)
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index e75d2932475a..88674a6e868c 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -1953,8 +1953,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.
@@ -1997,14 +1998,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);
@@ -2034,7 +2039,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;
@@ -2956,7 +2961,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;
/*
@@ -2996,8 +3001,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)
@@ -3018,10 +3024,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)
@@ -3037,7 +3043,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);
}
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path
2026-07-29 3:01 [PATCH net v4 0/2] packet: use consistent hard_header_len in send paths Qihang
2026-07-29 3:01 ` [PATCH net v4 1/2] packet: use consistent hard_header_len in non-ring " Qihang
@ 2026-07-29 3:01 ` Qihang
2026-07-29 7:46 ` Willem de Bruijn
1 sibling, 1 reply; 4+ messages in thread
From: Qihang @ 2026-07-29 3:01 UTC (permalink / raw)
To: netdev
Cc: willemdebruijn.kernel, daniel.zahka, davem, edumazet, kuba,
pabeni, horms, stable, Qihang
tpacket_snd() reads dev->hard_header_len independently for skb
allocation and header construction in tpacket_fill_skb(). Concurrent
netdevice reconfiguration can therefore make the reserved headroom
smaller than the amount later pushed, or make copylen - hard_header_len
negative.
Snapshot hard_header_len once before processing ring frames and use it
for the frame limit, headroom allocation, copy length, and skb
construction. Pass the snapshot to tpacket_fill_skb(). Reset copylen for
each frame so a previous frame cannot retain a larger construction
length.
The separate SOCK_DGRAM consistency problem between hard_header_len and
header_ops->create is not addressed here.
Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap")
Cc: stable@vger.kernel.org
Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
---
net/packet/af_packet.c | 20 ++++++++++++--------
1 file changed, 12 insertions(+), 8 deletions(-)
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 88674a6e868c..8ee0a80fdcea 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -2574,6 +2574,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;
@@ -2605,8 +2606,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;
@@ -2737,7 +2738,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);
@@ -2784,8 +2785,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));
@@ -2822,8 +2824,9 @@ 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;
+ copylen = 0;
if (vnet_hdr_sz) {
data += vnet_hdr_sz;
tp_len -= vnet_hdr_sz;
@@ -2840,10 +2843,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)) {
@@ -2853,7 +2856,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 &&
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path
2026-07-29 3:01 ` [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path Qihang
@ 2026-07-29 7:46 ` Willem de Bruijn
0 siblings, 0 replies; 4+ messages in thread
From: Willem de Bruijn @ 2026-07-29 7:46 UTC (permalink / raw)
To: Qihang, netdev
Cc: willemdebruijn.kernel, daniel.zahka, davem, edumazet, kuba,
pabeni, horms, stable, Qihang
Qihang wrote:
> tpacket_snd() reads dev->hard_header_len independently for skb
> allocation and header construction in tpacket_fill_skb(). Concurrent
> netdevice reconfiguration can therefore make the reserved headroom
> smaller than the amount later pushed, or make copylen - hard_header_len
> negative.
>
> Snapshot hard_header_len once before processing ring frames and use it
> for the frame limit, headroom allocation, copy length, and skb
> construction. Pass the snapshot to tpacket_fill_skb(). Reset copylen for
> each frame so a previous frame cannot retain a larger construction
> length.
>
> The separate SOCK_DGRAM consistency problem between hard_header_len and
> header_ops->create is not addressed here.
>
> Fixes: 69e3c75f4d54 ("net: TX_RING and packet mmap")
> Cc: stable@vger.kernel.org
> Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
> ---
> net/packet/af_packet.c | 20 ++++++++++++--------
> 1 file changed, 12 insertions(+), 8 deletions(-)
>
> diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
> index 88674a6e868c..8ee0a80fdcea 100644
> --- a/net/packet/af_packet.c
> +++ b/net/packet/af_packet.c
> @@ -2574,6 +2574,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;
> @@ -2605,8 +2606,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;
> @@ -2737,7 +2738,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);
> @@ -2784,8 +2785,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));
>
> @@ -2822,8 +2824,9 @@ 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;
> + copylen = 0;
Why is this introduced?
> if (vnet_hdr_sz) {
> data += vnet_hdr_sz;
> tp_len -= vnet_hdr_sz;
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-29 7:46 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 3:01 [PATCH net v4 0/2] packet: use consistent hard_header_len in send paths Qihang
2026-07-29 3:01 ` [PATCH net v4 1/2] packet: use consistent hard_header_len in non-ring " Qihang
2026-07-29 3:01 ` [PATCH net v4 2/2] packet: use consistent hard_header_len in TX_RING send path Qihang
2026-07-29 7:46 ` Willem de Bruijn
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.