All of lore.kernel.org
 help / color / mirror / Atom feed
From: Jakub Kicinski <kuba@kernel.org>
To: qingfang.deng@linux.dev
Cc: Jakub Kicinski <kuba@kernel.org>,
	linux-ppp@vger.kernel.org, andrew+netdev@lunn.ch,
	davem@davemloft.net, edumazet@google.com, pabeni@redhat.com,
	kees@kernel.org, ericwouds@gmail.com, manizada@pm.me,
	nbd@nbd.name, netdev@vger.kernel.org,
	linux-kernel@vger.kernel.org, norbert@doyensec.com,
	gnault@redhat.com
Subject: Re: [PATCH net-next] pppoe: pass bound packets directly to generic PPP
Date: Wed,  5 Aug 2026 18:28:21 -0700	[thread overview]
Message-ID: <20260806012820.1905519-2-kuba@kernel.org> (raw)
In-Reply-To: <20260804074452.75548-1-qingfang.deng@linux.dev>

This is (mostly) an AI-generated review of your patch.

One non-AI question - presumably you're doing this to improve performance?
Could you add some numbers to the cover letter?
---
pppoe: pass bound packets directly to generic PPP

This drops __sk_receive_skb() for PPPOX_BOUND PPPoE sockets and hands
session frames straight to ppp_input() from pppoe_rcv(), relying on the
RCU-deferred channel free added by ec4215683e47.  It also adds a
synchronize_net() in pppoe_connect() after the old session is unhashed,
before po->chan is cleared and reused.

No Link: tags are present in the commit header.

A few questions below.

> diff --git a/drivers/net/ppp/pppoe.c b/drivers/net/ppp/pppoe.c
> index 6874a1a8edaf1..0624116241823 100644
> --- a/drivers/net/ppp/pppoe.c
> +++ b/drivers/net/ppp/pppoe.c
> @@ -345,10 +345,10 @@ static struct notifier_block pppoe_notifier = {
>  
>  /************************************************************************
>   *
> - * Do the real work of receiving a PPPoE Session frame.
> + * Backlog receive a PPPoE Session frame and deliver to userspace.
>   *
>   ***********************************************************************/
> -static int pppoe_rcv_core(struct sock *sk, struct sk_buff *skb)
> +static int pppoe_backlog_rcv(struct sock *sk, struct sk_buff *skb)
>  {
>  	struct pppox_sock *po = pppox_sk(sk);

[Severity: Low]
Is the new banner comment accurate?  The function body still delivers to
the generic PPP layer, not to userspace:

drivers/net/ppp/pppoe.c:pppoe_backlog_rcv() {
	if (sk->sk_state & PPPOX_BOUND) {
		ppp_input(&po->chan, skb);
	} else {
		if (sock_queue_rcv_skb(sk, skb))
			goto abort_kfree;
	}
}

That branch still looks reachable: pppoe_rcv() only takes the new direct
path when it observes PPPOX_BOUND, and a frame observed while !BOUND is put
on the socket backlog by __sk_receive_skb() and only evaluated at
release_sock() time.  By then pppox_ioctl(PPPIOCGCHAN) may already have run
sk->sk_state |= PPPOX_BOUND under lock_sock().

Could the comment mention that the bound case still goes to ppp_input(), so
that a later reader does not remove the branch on the strength of the banner
text?

[ ... ]

> @@ -420,6 +420,10 @@ static int pppoe_rcv(struct sk_buff *skb, struct net_device *dev,
>  	if (!po)
>  		goto drop;
>  
> +	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]
Does this skip socket filters and the skb->dev clearing that
__sk_receive_skb() used to perform for bound sessions?

net/core/sock.c:__sk_receive_skb() {
	reason = sk_filter_trim_cap(sk, skb, trim_cap);
	if (reason)
		goto discard_and_relse;

	skb->dev = NULL;
	...
}

Two things in sk_filter_trim_cap() look relevant here.  A filter attached
with SO_ATTACH_FILTER/SO_ATTACH_BPF is now silently ignored for all bound
session traffic; do_sock_setsockopt() routes SOL_SOCKET options to
sock_setsockopt() for PF_PPPOX since pppoe does not set SOCK_CUSTOM_SOCKOPT,
so sk->sk_filter can be non-NULL.  The pfmemalloc guard is also bypassed:

net/core/filter.c:sk_filter_trim_cap() {
	if (skb_pfmemalloc(skb) && !sock_flag(sk, SOCK_MEMALLOC)) {
		NET_INC_STATS(sock_net(sk), LINUX_MIB_PFMEMALLOCDROP);
		return SKB_DROP_REASON_PFMEMALLOC;
	}
	...
}

The dropped skb->dev = NULL also matters for the frames ppp_input() parks on
pch->file.rq (proto >= 0xc000, PPP_CCPFRAG, or pch->ppp == NULL) -- those can
now sit queued indefinitely holding a non-refcounted net_device pointer.  Is
that intentional?  The cgroup-BPF and LSM parts of sk_filter_trim_cap() do
appear to be no-ops for PF_PPPOX, so those are not a concern, and the
sk_rcvqueues_full() check is not meaningful for the bound path.

Note the PPPOX_BOUND branch in pppoe_backlog_rcv() still runs under the old
contract, so the same class of frame is handled two different ways depending
on timing.  Could the changelog say something about the filtering and
skb->dev behaviour change?

[Severity: Low]
Should this read be annotated?  po->sk.sk_state is loaded here from softirq
with no lock held, while every writer stores it with a plain access under
lock_sock():

drivers/net/ppp/pppox.c:pppox_unbind_sock()	sk->sk_state = PPPOX_DEAD;
drivers/net/ppp/pppox.c:pppox_ioctl()		sk->sk_state |= PPPOX_BOUND;
drivers/net/ppp/pppoe.c:pppoe_connect()		sk->sk_state = PPPOX_CONNECTED;

Before the patch the test happened inside bh_lock_sock() in
__sk_receive_skb(), so it was serialized against those writers.  Would
KCSAN report this now, and would READ_ONCE()/WRITE_ONCE() be appropriate?

There is also a bare control dependency: pppoe_connect() fills in
po->chan.{hdrlen,mtu,private,ops}, calls ppp_register_net_channel() (which
sets chan->ppp), and only then publishes sk->sk_state, with no release
store to pair with.  The reader then loads po->chan.ppp inside ppp_input()
with no acquire.  As far as I can tell the worst outcome is ppp_input()
seeing !pch and dropping the frame, so this looks like an annotation
question rather than a correctness one.  pppol2tp_recv() has the same
unannotated pattern.

[Severity: Low]
Can frames reach ppp_input() out of arrival order now?  Two paths can feed
the same channel:

pppoe_rcv() -> ppp_input()			(softirq, no socket lock)
release_sock() -> pppoe_backlog_rcv() -> ppp_input()	(process context)

pppox_ioctl(PPPIOCGCHAN) sets sk->sk_state |= PPPOX_BOUND under lock_sock().
A frame that arrived just before that, while the socket was owned by the
ioctl caller, went to the socket backlog.  A frame arriving after the BOUND
store but before release_sock() takes the new fast path and is handed to
ppp_input() immediately, ahead of the older backlogged one.

ppp_input() serializes internally, and the window only exists once per
session while LCP/auth frames are in flight, so this may be acceptable --
but is it worth a note?

[Severity: Low]
Does this leave the documented channel contract stale?

include/linux/ppp_channel.h {
 * SMP locking notes:
 * The channel code must ensure that when it calls ppp_unregister_channel,
 * nothing is executing in any of the procedures above, for that
 * channel.
}

ppp_input() is one of the listed procedures.  After this change pppoe_rcv()
calls it from softirq with no serialization, while every pppoe caller that
reaches ppp_unregister_channel() via pppox_unbind_sock() -- pppoe_release(),
pppoe_flush_dev(), pppoe_unbind_sock_work(), pppoe_connect() -- does so
without draining in-flight ppp_input() callers.  In pppoe_connect() the new
synchronize_net() sits after pppox_unbind_sock(), so it fences the later
memset(&po->chan) and re-registration rather than the unregister itself.

The RCU-deferred struct channel free from ec4215683e47 does appear to close
the use-after-free, and pppol2tp_recv() already calls ppp_input() the same
lockless way, so this is about the text rather than a runtime failure.
Should include/linux/ppp_channel.h (and possibly
Documentation/networking/ppp_generic.rst) be updated to describe the
RCU-based rule that now makes this safe?

[ ... ]

> @@ -625,6 +629,13 @@ static int pppoe_connect(struct socket *sock, struct sockaddr_unsized *uservaddr
>  		pn = pppoe_pernet(sock_net(sk));
>  		delete_item(pn, po->pppoe_pa.sid,
>  			    po->pppoe_pa.remote, po->pppoe_ifindex);
> +
> +		/* pppoe_rcv() can call ppp_input() without taking the socket
> +		 * lock. Once the socket is unhashed, wait for any receive path
> +		 * that found it earlier before clearing and reusing po->chan.
> +		 */
> +		synchronize_net();
> +
>  		if (po->pppoe_dev) {
>  			dev_put(po->pppoe_dev);
>  			po->pppoe_dev = NULL;

[Severity: Low]
Is holding the socket lock across the grace period necessary here?
pppoe_connect() takes lock_sock(sk) at the top, so every disconnect/rebind
of a session now blocks other users of that socket for a full,
non-expedited RCU grace period.  The drain is only needed to protect the
following memset(&po->chan, 0, sizeof(po->chan)), so could it be arranged
without the socket lock held?

For the record, this does not look like it can stall RTNL:
delete_item() unhashes the socket before synchronize_net(), and
pppoe_flush_dev() only locks sockets it finds by walking pn->hash_table, so
the RTNL-holding notifier cannot find the socket sleeping in the grace
period.
-- 
pw-bot: cr

  reply	other threads:[~2026-08-06  1:29 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-04  7:44 [PATCH net-next] pppoe: pass bound packets directly to generic PPP Qingfang Deng
2026-08-06  1:28 ` Jakub Kicinski [this message]
2026-08-06  3:11   ` 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=20260806012820.1905519-2-kuba@kernel.org \
    --to=kuba@kernel.org \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=ericwouds@gmail.com \
    --cc=gnault@redhat.com \
    --cc=kees@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-ppp@vger.kernel.org \
    --cc=manizada@pm.me \
    --cc=nbd@nbd.name \
    --cc=netdev@vger.kernel.org \
    --cc=norbert@doyensec.com \
    --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.