From: Qingfang Deng <qingfang.deng@linux.dev>
To: Simon Horman <horms@kernel.org>
Cc: 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: Sat, 7 Nov 2026 10:24:15 +0800 [thread overview]
Message-ID: <be3166fd-596d-473a-8959-207492c06282@linux.dev> (raw)
In-Reply-To: <20261106181228.1263020-2-horms@kernel.org>
On 11/7/2026 2:12 AM, Simon Horman wrote:
> 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.
Ack.
>> +
>> + 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?
I can mention in the commit message that it make pppoe_rcv behave like
pppol2tp. This commit is intended to bypass the backlog queue, so no
need to mention sk_rcvqueues_full().
> 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.
Ack.
> [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?
Ack.
> 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.
The same concern was raised when reviewing my patch that introduced
synchronize_net() in pppox_ioctl(), commit 92c1bf630ab ("pppox: drain
queued packets on channel handoff"). Before that commit, unbound packets
were stranded in sk_receive_queue forever.
In pppd, the window between connect() and ioctl(PPPIOCGCHAN) is minimal.
If you prefer deliver order over best-effort deliver, should those
packets be dropped instead?
> [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.
pppd never uses any compression on PPPoE. Fix should be in a separate
commit.
> Would taking ppp_recv_lock() around ppp_receive_error() in ppp_input(),
> as ppp_input_error() already does, fix both cases?
prev parent reply other threads:[~2026-11-07 2:24 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 ` [PATCH net-next v4 1/2] pppoe: pass bound packets directly to generic PPP Simon Horman
2026-11-07 2:24 ` Qingfang Deng [this message]
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=be3166fd-596d-473a-8959-207492c06282@linux.dev \
--to=qingfang.deng@linux.dev \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@kernel.org \
--cc=ericwouds@gmail.com \
--cc=horms@kernel.org \
--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 \
/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®