mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Simon Horman <horms@kernel.org>
To: qingfang.deng@linux.dev
Cc: Simon Horman <horms@kernel.org>,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@kernel.org,
	kuba@kernel.org, pabeni@redhat.com, manizada@pm.me,
	ericwouds@gmail.com, nbd@nbd.name, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next v4 1/2] pppoe: pass bound packets directly to generic PPP
Date: Fri,  6 Nov 2026 18:12:29 +0000	[thread overview]
Message-ID: <20261106181228.1263020-2-horms@kernel.org> (raw)
In-Reply-To: <20261103111711.28202-1-qingfang.deng@linux.dev>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103111711.28202-1-qingfang.deng@linux.dev?part=1
---
pppoe: pass bound packets directly to generic PPP

This changes pppoe_rcv() to hand frames for PPPOX_BOUND sockets straight
to ppp_input(), instead of going through __sk_receive_skb(). It relies on
commit ec4215683e47 deferring the channel free to an RCU grace period. It
also clears skb->dev, matching what __sk_receive_skb() used to do.

> diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
> index 67c28ac763efd..5d5187e4976b3 100644
> --- a/drivers/net/ppp/pppoe.c
> +++ b/drivers/net/ppp/pppoe.c
> @@ -415,6 +415,12 @@ static int pppoe_rcv(struct sk_buff *skb, struct net_device *dev,
>  	if (!po)
>  		goto drop;
>  
> +	skb->dev = NULL;

[Severity: Low]
Should the comment in ppp_bridge_gso_segment() be updated as well? It
still says __sk_receive_skb() is what clears skb->dev:

drivers/net/ppp/ppp_generic.c:ppp_bridge_gso_segment() {
	/* skb->dev is NULL here (cleared by __sk_receive_skb() for PPPoE),
	 * and some GSO handlers (tunnels, IPv6 UFO) dereference it.
	 */
	...
}

Bound PPPoE frames are the ones that reach the bridge path, and they no
longer go through __sk_receive_skb(). The invariant now holds only
because of this assignment in pppoe_rcv(). Nothing in pppoe.c says that
the GSO path depends on it.

The follow-up patch "pppoe: queue unbound packets directly" does not
touch ppp_generic.c, so the comment stays stale after that patch too.

> +
> +	if (likely(po->sk.sk_state & PPPOX_BOUND)) {
> +		ppp_input(&po->chan, skb);
> +		return NET_RX_SUCCESS;
> +	}
>  	return __sk_receive_skb(&po->sk, skb, 0, 1, false);

[Severity: Medium]
The commit message says:

  "The direct bound receive path also skips the socket filter check in
  __sk_receive_skb()."

Does this path skip more than the socket filter? __sk_receive_skb()
calls sk_filter_trim_cap(), and that function runs the LSM hook before
the filter:

net/core/filter.c:sk_filter_trim_cap() {
	...
	err = security_sock_rcv_skb(sk, skb);
	if (err)
		return SKB_DROP_REASON_SECURITY_HOOK;
	...
}

apparmor_socket_sock_rcv_skb() mediates sockets of any family when
skb->secmark is set. BPF-LSM programs on socket_sock_rcv_skb also apply
to AF_PPPOX. After this change, neither runs for bound session frames
from the remote peer. A configured policy would therefore fail open.

Some other checks are skipped as well:

  - A filter attached with SO_ATTACH_FILTER or SO_ATTACH_BPF, even one
    locked with SO_LOCK_FILTER, is still accepted by setsockopt(), but it
    never runs on bound frames and never trims them.

  - The sk_rcvqueues_full() check and the sk_drops / drop reason
    accounting in __sk_receive_skb() no longer happen.

pppol2tp_recv() already calls ppp_input() directly for bound sockets, so
there is precedent. Could the commit message still describe the LSM
bypass and the other skipped checks?

This stays true after the follow-up patch "pppoe: queue unbound packets
directly". There, only the unbound path still reaches sk_filter, via
sock_queue_rcv_skb().

[Severity: Low]
Does include/linux/ppp_channel.h need an update along with this? Its SMP
locking notes say:

 * The channel code must ensure that when it calls ppp_unregister_channel,
 * nothing is executing in any of the procedures above, for that
 * channel.

PPPoE followed that rule before this patch. ppp_input() ran under
bh_lock_sock(), and every ppp_unregister_channel() caller (through
pppox_unbind_sock()) holds lock_sock().

Now pppoe_rcv() calls ppp_input() from softirq without the socket lock.
So ppp_input() and ppp_unregister_channel() can run at the same time on
the same channel.

That looks safe in practice:

  - struct channel is freed through call_rcu().
  - pch->ppp is cleared and then followed by synchronize_net().
  - pch->bridge is cleared and then followed by synchronize_rcu().

Even so, the documented contract no longer matches the rule PPPoE and
pppol2tp now rely on.

[Severity: Low]
Is chan->ppp now accessed locklessly without any annotation? ppp_input()
does a plain load:

drivers/net/ppp/ppp_generic.c:ppp_input() {
	struct channel *pch = chan->ppp;
	...
}

Meanwhile, ppp_unregister_channel() can do a plain store at the same
time, protected only by lock_sock():

pppox_unbind_sock()
  ppp_unregister_channel()
    chan->ppp = NULL;

For PPPoE, the socket BH lock used to serialize these two. Without it,
this looks like a data race that KCSAN would report.

There is a second ordering gap. ppp_register_net_channel() publishes
chan->ppp before it initializes pch:

	chan->ppp = pch;
	init_ppp_file(&pch->file, CHANNEL);

This store has no release ordering, and the sk_state test in pppoe_rcv()
gives no acquire ordering. In practice the gap between connect() and
PPPIOCGCHAN hides this.

Would READ_ONCE()/WRITE_ONCE() on chan->ppp be appropriate here? Should
chan->ppp also be published only after initialization, with release
semantics?

The sk_state access is volatile, so that side is not a KCSAN concern.
The chan->ppp race is still there after "pppoe: queue unbound packets
directly".

[Severity: Low]
Can frames reach generic PPP out of order during the PPPIOCGCHAN
handoff? pppox_ioctl() does:

		sk->sk_state |= PPPOX_BOUND;
		...
		synchronize_net();
		...
		while ((skb = skb_dequeue(&sk->sk_receive_queue))) {
			skb_orphan(skb);
			ppp_input(&po->chan, skb);

A frame that arrives after PPPOX_BOUND is set now takes the direct path.
It reaches ppp_input() during synchronize_net(), ahead of older frames
that are still in sk_receive_queue.

At this commit, frames that go to the backlog while the ioctl holds
lock_sock() arrive even later, from release_sock() -> pppoe_rcv_core().

Before the patch, the socket lock kept these frames in wire order. The
follow-up "pppoe: queue unbound packets directly" removes the backlog,
but the reordering against sk_receive_queue remains. pppol2tp already
behaves this way. The frames in this window are mostly LCP and other
control frames for pppd.

[Severity: High]
This isn't a bug introduced by this patch, but ppp_input() appears to
call ppp_receive_error() without holding ppp_recv_lock():

drivers/net/ppp/ppp_generic.c:ppp_input() {
	...
	rcu_read_lock_bh();
	ppp = rcu_dereference_bh(pch->ppp);
	if (!ppp_decompress_proto(skb)) {
		kfree_skb(skb);
		if (ppp) {
			DEV_STATS_INC(netdev_from_priv(ppp), rx_length_errors);
			ppp_receive_error(ppp);
		}
		goto done;
	}
	...
}

ppp_input_error() makes the same call inside ppp_recv_lock() /
ppp_recv_unlock(). ppp_receive_error() then calls slhc_toss(ppp->vj),
which does a non-atomic comp->flags |= SLF_TOSS.

A peer can reach this branch with a PPPoE session frame of length 0 or
1. pskb_trim_rcsum() trims the frame, and pskb_may_pull(skb, 2) then
fails in ppp_decompress_proto().

Can this race with PPPIOCSMAXCID in ppp_ioctl()?

		ppp_lock(ppp);
		if (ppp->vj)
			slhc_free(ppp->vj);
		ppp->vj = vj;
		ppp_unlock(ppp);

Without rlock, ppp_receive_error() could load the old ppp->vj and write
to the slcompress after it has been freed.

This patch adds one effect in the common single-channel PPPoE case. The
socket BH lock no longer serializes ppp_input() calls for one channel.
The lockless slhc_toss() can therefore run while another CPU is in
slhc_uncompress() / slhc_remember() under rlock, and a comp->flags
update can be lost.

Would taking ppp_recv_lock() around ppp_receive_error() in ppp_input(),
as ppp_input_error() already does, fix both cases?

  parent reply	other threads:[~2026-11-06 18:12 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 11:17 Qingfang Deng
2026-11-03 11:17 ` [PATCH net-next v4 2/2] pppoe: queue unbound packets directly Qingfang Deng
2026-11-06 18:12   ` Simon Horman
2026-11-06 18:12 ` Simon Horman [this message]
2026-11-07  2:24   ` [PATCH net-next v4 1/2] pppoe: pass bound packets directly to generic PPP Qingfang Deng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261106181228.1263020-2-horms@kernel.org \
    --to=horms@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=ericwouds@gmail.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=manizada@pm.me \
    --cc=nbd@nbd.name \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=qingfang.deng@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®