From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pg1-f169.google.com (mail-pg1-f169.google.com [209.85.215.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 96804388E64 for ; Thu, 2 Apr 2026 20:21:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.215.169 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775161272; cv=none; b=ARwM2kPQ/gpp/acpf6aMWSw8/X6Lk82t6csC+0USbNI9wSHupcPEKZgJ39rjOSJ3RmPNGVjkroEEdoAxjc3lGQ1VhQtj1SS4QVjOnwVYO0eXjTJ18IdOwD/nNXmk9YCMoW7yn4fOF+zUaiS4fJHahRPmKnzJ747MHd3okSNxe58= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1775161272; c=relaxed/simple; bh=7TJYTLuF4sHA0EmD+U6zjmSvI+LNfwNGb257beaOCMY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=m2h8ReBdbYtoeWmiLkWnFCkQaypdk9ERgw+9aLAhOhAB7EONcAJ0kWp1Qqz1Eop+CB7rLXbyxN9yXkPvHf1TwuZLqYUUBsSezzjPfpQMoIuNK8ouY0XnWvqZZGYsSJ9cFjX/6ArkbRPJmocJoCJuU5dO67qYZc/KtxLnHkTu/CQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=networkplumber.org; spf=pass smtp.mailfrom=networkplumber.org; dkim=pass (2048-bit key) header.d=networkplumber-org.20230601.gappssmtp.com header.i=@networkplumber-org.20230601.gappssmtp.com header.b=tyssuoWu; arc=none smtp.client-ip=209.85.215.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=networkplumber.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=networkplumber.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=networkplumber-org.20230601.gappssmtp.com header.i=@networkplumber-org.20230601.gappssmtp.com header.b="tyssuoWu" Received: by mail-pg1-f169.google.com with SMTP id 41be03b00d2f7-c76bde70ec9so498016a12.2 for ; Thu, 02 Apr 2026 13:21:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=networkplumber-org.20230601.gappssmtp.com; s=20230601; t=1775161269; x=1775766069; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=9hBa0wPZWS88g78DRr5aUdb+T0deoLeGFO+Tb4NXLo4=; b=tyssuoWuP9EbDdumhyOqIwDVPnj0o5axLia6DWXxQ1A4CKNPJ9jHgvrFUyauoursZr hWVS6/2ke3KMQJiN5nNDq83moGiNS1k0lpnAZhU1G1LwmBYzE4SfPweJaCrbd+MgslnP FLNi4cElMKZAnb0TR2rxB//7aYjV783bx2xcoEHEDMIwbXP4dmJWrYIeAY1GEh7pyIWo kSPAu7+vmrN4Ly9otDdu2mxIGn+WYAsfWliHJWUbp+Al7/hvoVxVAf+Mn23sZdI0gs7h AvNHNCN36ObNidBUSjGWz53oN43XmZm4uCZK5ImdM4VzMiOMaYF90Bitt05Tl2Jv4Onx BfdQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775161269; x=1775766069; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=9hBa0wPZWS88g78DRr5aUdb+T0deoLeGFO+Tb4NXLo4=; b=mKQEFUn4AJwRWWwAnzPvXsZ3nwpALpHHjDlJ2ps6qWY2H544Tl+SwE5OsEMh8oiKd9 Bhx63LPAn5rekRICk1P5Hekyy3IxT5h/60B61jZ2IhVm6jcFCoZnv8kdJuUxCm4VDMtb v0BemUKgeuugO1hNglrjtdqxWJsgsuNtIBEYo6L0QoXL+4cYkC8VbIX0zy5ZhpdAIlPq x521F5R2UQ0yEY4l/FHJTUfNs4THp9HiA0GfPvZfh561RC4nw6EW276dFZp43x4ynZcX 5vo18ew+niq+zITID3ZcddVVO/Xmn6q/rVkKe29IOTmiqtdSRnU3QbPN2lvKmXUpv+ow t5nw== X-Forwarded-Encrypted: i=1; AJvYcCWWQS6oD4mX1KOdybl3JFN0HsIYxCwXJ0eRg5Mkqn8vUOgNzap1l7Sryk7Ttwt6SKGaAByGux30p9FxvSw=@vger.kernel.org X-Gm-Message-State: AOJu0YxsMLeI/unaMicz32KoADIf4w+M1Us+trHkYhD9ftYcfIkIW8Vo ElxIBBIp9IISJBqbwpyL8DWoZiQ3bsUHjdCxLVbpfGk8e9OVKh9LqM/1RG6U2c+PB1U= X-Gm-Gg: ATEYQzwJ9TYaUsLpKvgPPMxOM6GqItj6qn7vwlrAW9/HnTjmWsls9MtQQWBt72uevVb 0JUHuGlfUHqlV6pGPjgPxp/QLeWr+mtAj5ug8UNhFqneVFqWoPcYO/44r34csWd8kBjdMhrDJa7 basUWZL4vQomAgL3jv3p4D3098fOqij0d7Lmgs/N6njnI78+22uArvsulNqy3S3srFeVkhkQ6jv i+Qx+z/fgUHxCtfdXNsc2UY/6EXBsNbjv7hyFhN7An9KxbJCnfG1CjnbqdE6Mt9OLV4BfkCBUxk Iw2V3z2zDFBe+84oKgL9QTgLaWycE+rSygTcGJxR1PUlsVmoxb2ePyOI+phlVtA0nODGLjh0AWU yzDyr4JrYKOEMB7oaQmPcKqiSzr7clMtKHL3a8PyB/8LJTi9QsNceG2WKuObgwPs9D/JcWSYUcd z7s3UjiLLTOY7sDNVWSgViu0XyfdLLI2St X-Received: by 2002:a05:6a20:3d0a:b0:398:9c9e:996c with SMTP id adf61e73a8af0-39f2f3be49emr212093637.64.1775161268757; Thu, 02 Apr 2026 13:21:08 -0700 (PDT) Received: from phoenix.lan ([104.202.41.210]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-c76c657dfb7sm3345621a12.24.2026.04.02.13.21.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Apr 2026 13:21:08 -0700 (PDT) From: Stephen Hemminger To: netdev@vger.kernel.org Cc: Stephen Hemminger , Jamal Hadi Salim , Jiri Pirko , "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , linux-kernel@vger.kernel.org (open list) Subject: [PATCH net v3 4/7] net/sched: netem: restructure dequeue to avoid re-entrancy with child qdisc Date: Thu, 2 Apr 2026 13:19:32 -0700 Message-ID: <20260402202037.176299-5-stephen@networkplumber.org> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260402202037.176299-1-stephen@networkplumber.org> References: <20260402202037.176299-1-stephen@networkplumber.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit netem_dequeue() enqueues packets into its child qdisc while being called from the parent's dequeue path. This causes two problems: - HFSC tracks class active/inactive state on qlen transitions. A child enqueue during dequeue causes double-insertion into the eltree (CVE-2025-37890, CVE-2025-38001). - Non-work-conserving children like TBF may refuse to dequeue packets just enqueued, causing netem to return NULL despite having backlog. Parents like DRR then incorrectly deactivate the class. Split the dequeue into helpers: netem_pull_tfifo() - remove head packet from tfifo netem_slot_account() - update slot pacing counters netem_dequeue_child() - batch-transfer ready packets to the child, then dequeue from the child netem_dequeue_direct()- dequeue from tfifo when no child When a child qdisc is present, all time-ready packets are moved into the child before calling its dequeue. This separates the enqueue and dequeue phases so the parent sees consistent qlen transitions. Fixes: 50612537e9ab ("netem: fix classful handling") Signed-off-by: Stephen Hemminger --- net/sched/sch_netem.c | 201 +++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 73 deletions(-) diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index 556f9747f0e7..b93f0e886a2b 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c @@ -689,99 +689,154 @@ static struct sk_buff *netem_peek(struct netem_sched_data *q) return q->t_head; } -static void netem_erase_head(struct netem_sched_data *q, struct sk_buff *skb) +/* + * Pop the head packet from the tfifo and prepare it for delivery. + * skb->dev shares the rbnode area and must be restored after removal. + */ +static struct sk_buff *netem_pull_tfifo(struct netem_sched_data *q, + struct Qdisc *sch) { - if (skb == q->t_head) { + struct sk_buff *skb; + + if (q->t_head) { + skb = q->t_head; q->t_head = skb->next; if (!q->t_head) q->t_tail = NULL; } else { - rb_erase(&skb->rbnode, &q->t_root); + struct rb_node *p = rb_first(&q->t_root); + + if (!p) + return NULL; + skb = rb_to_skb(p); + rb_erase(p, &q->t_root); } + + q->t_len--; + skb->next = NULL; + skb->prev = NULL; + skb->dev = qdisc_dev(sch); + + return skb; } -static struct sk_buff *netem_dequeue(struct Qdisc *sch) +/* Update slot pacing counters after releasing a packet */ +static void netem_slot_account(struct netem_sched_data *q, + const struct sk_buff *skb, u64 now) +{ + if (!q->slot.slot_next) + return; + + q->slot.packets_left--; + q->slot.bytes_left -= qdisc_pkt_len(skb); + if (q->slot.packets_left <= 0 || q->slot.bytes_left <= 0) + get_slot_next(q, now); +} + +/* + * Transfer all time-ready packets from the tfifo into the child qdisc, + * then dequeue from the child. Batching the transfers avoids calling + * qdisc_enqueue() inside the parent's dequeue path, which confuses + * parents that track active/inactive state on qlen transitions (HFSC). + */ +static struct sk_buff *netem_dequeue_child(struct Qdisc *sch) { struct netem_sched_data *q = qdisc_priv(sch); + u64 now = ktime_get_ns(); struct sk_buff *skb; -tfifo_dequeue: - skb = __qdisc_dequeue_head(&sch->q); - if (skb) { -deliver: - qdisc_qstats_backlog_dec(sch, skb); - qdisc_bstats_update(sch, skb); - return skb; - } - skb = netem_peek(q); - if (skb) { - u64 time_to_send; - u64 now = ktime_get_ns(); - - /* if more time remaining? */ - time_to_send = netem_skb_cb(skb)->time_to_send; - if (q->slot.slot_next && q->slot.slot_next < time_to_send) - get_slot_next(q, now); - - if (time_to_send <= now && q->slot.slot_next <= now) { - netem_erase_head(q, skb); - q->t_len--; - skb->next = NULL; - skb->prev = NULL; - /* skb->dev shares skb->rbnode area, - * we need to restore its value. - */ - skb->dev = qdisc_dev(sch); - - if (q->slot.slot_next) { - q->slot.packets_left--; - q->slot.bytes_left -= qdisc_pkt_len(skb); - if (q->slot.packets_left <= 0 || - q->slot.bytes_left <= 0) - get_slot_next(q, now); - } + while ((skb = netem_peek(q)) != NULL) { + struct sk_buff *to_free = NULL; + unsigned int pkt_len; + int err; - if (q->qdisc) { - unsigned int pkt_len = qdisc_pkt_len(skb); - struct sk_buff *to_free = NULL; - int err; - - err = qdisc_enqueue(skb, q->qdisc, &to_free); - kfree_skb_list(to_free); - if (err != NET_XMIT_SUCCESS) { - if (net_xmit_drop_count(err)) - qdisc_qstats_drop(sch); - sch->qstats.backlog -= pkt_len; - sch->q.qlen--; - qdisc_tree_reduce_backlog(sch, 1, pkt_len); - } - goto tfifo_dequeue; - } + if (netem_skb_cb(skb)->time_to_send > now) + break; + if (q->slot.slot_next && q->slot.slot_next > now) + break; + + skb = netem_pull_tfifo(q, sch); + netem_slot_account(q, skb, now); + + pkt_len = qdisc_pkt_len(skb); + err = qdisc_enqueue(skb, q->qdisc, &to_free); + kfree_skb_list(to_free); + if (unlikely(err != NET_XMIT_SUCCESS)) { + if (net_xmit_drop_count(err)) + qdisc_qstats_drop(sch); + sch->qstats.backlog -= pkt_len; sch->q.qlen--; - goto deliver; + qdisc_tree_reduce_backlog(sch, 1, pkt_len); } + } - if (q->qdisc) { - skb = q->qdisc->ops->dequeue(q->qdisc); - if (skb) { - sch->q.qlen--; - goto deliver; - } - } + skb = q->qdisc->ops->dequeue(q->qdisc); + if (skb) + sch->q.qlen--; - qdisc_watchdog_schedule_ns(&q->watchdog, - max(time_to_send, - q->slot.slot_next)); - } + return skb; +} - if (q->qdisc) { - skb = q->qdisc->ops->dequeue(q->qdisc); - if (skb) { - sch->q.qlen--; - goto deliver; - } +/* Dequeue directly from the tfifo when no child qdisc is configured. */ +static struct sk_buff *netem_dequeue_direct(struct Qdisc *sch) +{ + struct netem_sched_data *q = qdisc_priv(sch); + struct sk_buff *skb; + u64 time_to_send; + u64 now; + + skb = netem_peek(q); + if (!skb) + return NULL; + + now = ktime_get_ns(); + time_to_send = netem_skb_cb(skb)->time_to_send; + + if (q->slot.slot_next && q->slot.slot_next < time_to_send) + get_slot_next(q, now); + + if (time_to_send > now || q->slot.slot_next > now) + return NULL; + + skb = netem_pull_tfifo(q, sch); + netem_slot_account(q, skb, now); + sch->q.qlen--; + + return skb; +} + +static struct sk_buff *netem_dequeue(struct Qdisc *sch) +{ + struct netem_sched_data *q = qdisc_priv(sch); + struct sk_buff *skb; + + /* First check the reorder queue */ + skb = __qdisc_dequeue_head(&sch->q); + if (skb) + goto deliver; + + if (q->qdisc) + skb = netem_dequeue_child(sch); + else + skb = netem_dequeue_direct(sch); + + if (skb) + goto deliver; + + /* Nothing ready — schedule watchdog for next packet */ + skb = netem_peek(q); + if (skb) { + u64 time_to_send = netem_skb_cb(skb)->time_to_send; + + qdisc_watchdog_schedule_ns(&q->watchdog, + max(time_to_send, q->slot.slot_next)); } return NULL; + +deliver: + qdisc_qstats_backlog_dec(sch, skb); + qdisc_bstats_update(sch, skb); + return skb; } static void netem_reset(struct Qdisc *sch) -- 2.53.0