Commit a005fa5d authored by Kito Xu (veritas501)'s avatar Kito Xu (veritas501) Committed by Paolo Abeni
Browse files

net/sched: act_mirred: Fix blockcast recursion bypass leading to stack overflow



tcf_mirred_act() checks sched_mirred_nest against MIRRED_NEST_LIMIT (4)
to prevent deep recursion.  However, when the action uses blockcast
(tcfm_blockid != 0), the function returns at the tcf_blockcast() call
BEFORE reaching the counter increment.  As a result, the recursion
counter never advances and the limit check is entirely bypassed.

When two devices share a TC egress block with a mirred blockcast rule,
a packet egressing on device A is mirrored to device B via blockcast;
device B's egress TC re-enters tcf_mirred_act() via blockcast and
mirrors back to A, creating an unbounded recursion loop:

  tcf_mirred_act -> tcf_blockcast -> tcf_mirred_to_dev -> dev_queue_xmit
  -> sch_handle_egress -> tcf_classify -> tcf_mirred_act -> (repeat)

This recursion continues until the kernel stack overflows.

The bug is reachable from an unprivileged user via
unshare(CLONE_NEWUSER | CLONE_NEWNET): user namespaces grant
CAP_NET_ADMIN in the new network namespace, which is sufficient to
create dummy devices, attach clsact qdiscs with shared blocks, and
install mirred blockcast filters.

 BUG: TASK stack guard page was hit at ffffc90000b7fff8
 Oops: stack guard page: 0000 [#1] SMP KASAN NOPTI
 CPU: 2 UID: 1000 PID: 169 Comm: poc Not tainted 7.0.0-rc7-next-20260410
 RIP: 0010:xas_find+0x17/0x480
 Call Trace:
  xa_find+0x17b/0x1d0
  tcf_mirred_act+0x640/0x1060
  tcf_action_exec+0x400/0x530
  basic_classify+0x128/0x1d0
  tcf_classify+0xd83/0x1150
  tc_run+0x328/0x620
  __dev_queue_xmit+0x797/0x3100
  tcf_mirred_to_dev+0x7b1/0xf70
  tcf_mirred_act+0x68a/0x1060
  [repeating ~30+ times until stack overflow]
 Kernel panic - not syncing: Fatal exception in interrupt

Fix this by incrementing sched_mirred_nest before calling
tcf_blockcast() and decrementing it on return, mirroring the
non-blockcast path.  This ensures subsequent recursive entries see the
updated counter and are correctly limited by MIRRED_NEST_LIMIT.

Fixes: fe946a75 ("net/sched: act_mirred: add loop detection")
Signed-off-by: default avatarKito Xu (veritas501) <hxzene@gmail.com>
Link: https://patch.msgid.link/20260525122556.973584-7-jhs@mojatatu.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent db875221
Loading
Loading
Loading
Loading
+11 −7
Original line number Diff line number Diff line
@@ -396,14 +396,12 @@ static int tcf_blockcast_mirror(struct sk_buff *skb, struct tcf_mirred *m,

static int tcf_blockcast(struct sk_buff *skb, struct tcf_mirred *m,
			 const u32 blockid, struct tcf_result *res,
			 int retval)
			 int m_eaction, int retval)
{
	const u32 exception_ifindex = skb->dev->ifindex;
	struct tcf_block *block;
	bool is_redirect;
	int m_eaction;

	m_eaction = READ_ONCE(m->tcfm_eaction);
	is_redirect = tcf_mirred_is_act_redirect(m_eaction);

	/* we are already under rcu protection, so can call block lookup
@@ -453,8 +451,16 @@ TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
	tcf_action_update_bstats(&m->common, skb);

	blockid = READ_ONCE(m->tcfm_blockid);
	if (blockid)
		return tcf_blockcast(skb, m, blockid, res, retval);
	m_eaction = READ_ONCE(m->tcfm_eaction);
	want_ingress = tcf_mirred_act_wants_ingress(m_eaction);
	if (blockid) {
		if (!want_ingress)
			xmit->sched_mirred_dev[xmit->sched_mirred_nest++] = NULL;
		retval = tcf_blockcast(skb, m, blockid, res, m_eaction, retval);
		if (!want_ingress)
			xmit->sched_mirred_nest--;
		return retval;
	}

	dev = rcu_dereference_bh(m->tcfm_dev);
	if (unlikely(!dev)) {
@@ -463,8 +469,6 @@ TC_INDIRECT_SCOPE int tcf_mirred_act(struct sk_buff *skb,
		return retval;
	}

	m_eaction = READ_ONCE(m->tcfm_eaction);
	want_ingress = tcf_mirred_act_wants_ingress(m_eaction);
	if (!want_ingress) {
		for (i = 0; i < xmit->sched_mirred_nest; i++) {
			if (xmit->sched_mirred_dev[i] != dev)