Re: [PATCH net-next] ppp: consolidate RX skb queueing

Qingfang Deng <[email protected]> Wed, 6 May 2026 10:54:50 +0800
Newsgroups org.kernel.vger.linux-ppp,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
On 2026/4/30 16:54, Paolo Abeni wrote:
>
> On 4/28/26 4:44 AM, Qingfang Deng wrote:
>> In ppp_input() and ppp_receive_nonmp_frame(), received skbs are queued
>> for userspace delivery using the same open-coded pattern:
>>
>> 	skb_queue_tail(&pf->rq, skb);
>> 	while (pf->rq.qlen > PPP_MAX_RQLEN &&
>> 	       (skb = skb_dequeue(&pf->rq)))
>> 		kfree_skb(skb);
>> 	wake_up_interruptible(&pf->rwait);
>>
>> This has a potential race: skb_queue_tail() releases the queue lock,
>> then qlen is read locklessly before skb_dequeue() re-acquires it.
>> Another CPU enqueueing concurrently could cause the length check to see
>> stale data. This race is benign, as it only causes extra skbs to be
>> freed in the worst case.
>>
>> Introduce ppp_file_queue_rx_skb() to perform the enqueue, length check,
>> and trim atomically under a single pf->rq.lock critical section. As both
>> callers have softirq disabled, plain spin_lock() can be used instead of
>> _bh()/_irqsave() variants. Since only one skb is enqueued at a time, the
>> queue can exceed PPP_MAX_RQLEN by at most one frame, so replace the
>> while-loop with an if-statement. While at it, use skb_queue_len()
>> instead of open-coding the qlen access.
>>
>> Signed-off-by: Qingfang Deng <[email protected]>
>> ---
>>   drivers/net/ppp/ppp_generic.c | 37 ++++++++++++++++++++++-------------
>>   1 file changed, 23 insertions(+), 14 deletions(-)
>>
>> diff --git a/drivers/net/ppp/ppp_generic.c b/drivers/net/ppp/ppp_generic.c
>> index 57c68efa5ff8..6ab5011540a0 100644
>> --- a/drivers/net/ppp/ppp_generic.c
>> +++ b/drivers/net/ppp/ppp_generic.c
>> @@ -2307,6 +2307,27 @@ static bool ppp_channel_bridge_input(struct channel *pch, struct sk_buff *skb)
>>   	return !!pchb;
>>   }
>>   
>> +/* Queue up and deliver a received skb to userspace.
>> + * Must be called in softirq.
>> + */
>> +static void ppp_file_queue_rx_skb(struct ppp_file *pf, struct sk_buff *skb)
>> +{
>> +	spin_lock(&pf->rq.lock);
>> +	__skb_queue_tail(&pf->rq, skb);
>> +	/* limit queue length by dropping old frames */
>> +	if (unlikely(skb_queue_len(&pf->rq) > PPP_MAX_RQLEN)) {
>> +		struct sk_buff *old = __skb_peek(&pf->rq);
>> +
>> +		__skb_unlink(old, &pf->rq);
>> +		spin_unlock(&pf->rq.lock);
>> +		kfree_skb(old);
>> +	} else {
>> +		spin_unlock(&pf->rq.lock);
> Note that after __skb_queue_tail(), skb_queue_len(&pf->rq) could be ==
> PPP_MAX_RQLEN + 2, due to the slightly different check in
> ppp_prepare_tx_skb().

The check in ppp_prepare_tx_skb() is for demand dialing mode. As the 
name and comment suggest, when waiting for traffic a ppp interface is 
not able to receive packets, until we see a tx packet and then do the 
actual dial-up to resume normal operation, so that can't happen.

>
> I think the above it could/should be simplified to:
> 	while (unlikely(skb_queue_len(&pf->rq) > PPP_MAX_RQLEN))
> 		kfree_skb(__skb_dequeue(&pf->rq));
> 	spin_unlock(&pf->rq.lock);
>
> And possibly it would make sense to consolidate the test in
> ppp_prepare_tx_skb(), too for consistency - in that case an `if`
> statement should become enough.
I could consolidate this, but it tail-drops the skb instead of 
head-dropping.