From 0de4e69cd021c99d0aa13e1f30aafd13ad74a2a5 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 26 Oct 2026 16:59:27 +0100 Subject: ppp: linearize and segment bridged packets when required PPP channel bridging hands packets received on one channel directly to the start_xmit() of another. Packets received by PPPoE can be non-linear, GSO since commit 55a5d8fca836 ("net: pppoe: implement GRO/GSO support"), or CHECKSUM_PARTIAL when injected with a virtio_net_hdr. The regular transmit path never sees such packets, and only PPPoE, which uses dev_queue_xmit(), handles them: - PPP Async reads the whole packet through skb->data, transmitting unrelated memory for non-linear packets. - PPP Async and Sync send GSO packets as single frames of up to 64KB, and CHECKSUM_PARTIAL packets with a bad L4 checksum. - PPTP and L2TP cannot segment a TCP GSO packet once their GRE or UDP header is pushed, and do not finish pending checksums. Add a xmit_offload flag to struct ppp_channel, set for PPPoE. For other channels, segment GSO packets in software, using the PPP protocol field as the L2 header, finish CHECKSUM_PARTIAL checksums (including SCTP CRC32c), and linearize packets for channels without direct_xmit, as ppp_push() does. Packets are dropped if any step fails, or if the channel is busy, as before. skb->dev is NULL at this point for PPPoE (cleared by __sk_receive_skb()), and tunnel and IPv6 UFO GSO handlers dereference it: only TCP, UDP L4 and fraglist GSO packets without encapsulation are segmented, others are dropped. Receive paths like L2TP pull headers without updating the checksum state, possibly leaving a CHECKSUM_PARTIAL state with csum_start before skb->data. Reset it to CHECKSUM_NONE, as skb_postpull_rcsum() does, so that skb_checksum_help() does not warn, and so that PPPoE does not offload a checksum at a wrong offset. Tested in a VM by bridging PPPoE (on a TAP with IFF_NAPI_FRAGS, or on veth) to PPP Async on a PTY, PPPoL2TP and PPPoE: non-linear packets no longer leak garbage, GSO packets (including fraglist GRO) are sent as valid segments for IPv4 and IPv6, CHECKSUM_PARTIAL packets get a valid checksum, and PPPoE still receives GSO packets unchanged. Fixes: 4cf476ced45d ("ppp: add PPPIOCBRIDGECHAN and PPPIOCUNBRIDGECHAN ioctls") Fixes: 55a5d8fca836 ("net: pppoe: implement GRO/GSO support") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/20260908072135.877364-1-qingfang.deng@linux.dev Co-developed-by: Qingfang Deng Signed-off-by: Qingfang Deng Signed-off-by: Eric Dumazet Cc: Felix Fietkau Cc: Pablo Neira Ayuso Reviewed-by: Simon Horman Link: https://patch.msgid.link/20261026155927.707655-1-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ppp_channel.h | 1 + 1 file changed, 1 insertion(+) (limited to 'include/linux') diff --git a/include/linux/ppp_channel.h b/include/linux/ppp_channel.h index 2f63e9a6cc88..15bc3aa4f171 100644 --- a/include/linux/ppp_channel.h +++ b/include/linux/ppp_channel.h @@ -43,6 +43,7 @@ struct ppp_channel { void *ppp; /* opaque to channel */ int speed; /* transfer rate (bytes/second) */ bool direct_xmit; /* no qdisc, xmit directly */ + bool xmit_offload; /* accepts GSO and CHECKSUM_PARTIAL skbs */ }; #ifdef __KERNEL__ -- cgit v1.2.3 From 2519a6fe7a8bba57a961eb804e43e7df35ac40fe Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 28 Oct 2026 13:26:59 +0100 Subject: net: add eth_type_trans_careful() eth_type_trans() assumes ETH_HLEN bytes are present in skb->head. NIC drivers always provide this, and they should keep using the fast version. Devices looping skbs from their transmit path back to their receive path get skbs built by the stack instead. Their link-layer header might have been popped by tc actions (act_vlan pop_eth, act_ife decode) or OVS actions (pop_eth + pop_mpls / pop_nsh), leaving fewer than ETH_HLEN bytes in the linear part. eth_type_trans() then hits the BUG() in __skb_pull(). Add eth_type_trans_careful(), which first pulls the Ethernet header, and returns a drop reason on failure, so that the few devices exposed to this can drop such packets. Signed-off-by: Eric Dumazet Reviewed-by: Aaron Conole Reviewed-by: Willem de Bruijn Reviewed-by: Jamal Hadi Salim Tested-by: Jamal Hadi Salim Acked-by: Alexei Starovoitov Link: https://patch.msgid.link/20261028122703.2093730-2-edumazet@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/etherdevice.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'include/linux') diff --git a/include/linux/etherdevice.h b/include/linux/etherdevice.h index df8f88f63a70..1f833ebd71ed 100644 --- a/include/linux/etherdevice.h +++ b/include/linux/etherdevice.h @@ -38,6 +38,34 @@ int fwnode_get_mac_address(struct fwnode_handle *fwnode, char *addr); u32 eth_get_headlen(const struct net_device *dev, const void *data, u32 len); __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev); + +/** + * eth_type_trans_careful - eth_type_trans() for skbs coming from the stack + * @skb: buffer, possibly with less than ETH_HLEN bytes in its linear area + * @dev: receiving network device + * + * eth_type_trans() expects ETH_HLEN bytes in skb->head, which NIC drivers + * provide. Devices looping skbs from their transmit path back to the + * receive path (loopback, OVS internal ports) get skbs built by the stack, + * whose link-layer header might have been popped by tc or OVS actions. + * + * Sets skb->protocol on success. + * + * Return: SKB_NOT_DROPPED_YET on success, or a drop reason. In the latter + * case, the caller must free @skb. + */ +static inline enum skb_drop_reason +eth_type_trans_careful(struct sk_buff *skb, struct net_device *dev) +{ + enum skb_drop_reason reason = pskb_may_pull_reason(skb, ETH_HLEN); + + if (unlikely(reason)) + return reason; + + skb->protocol = eth_type_trans(skb, dev); + return SKB_NOT_DROPPED_YET; +} + extern const struct header_ops eth_header_ops; int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, -- cgit v1.2.3