All of lore.kernel.org
 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 [PATCH net-next v4 1/2] pppoe: pass bound packets directly to generic PPP 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 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.