Commit a18f1b396c for frr
commit a18f1b396ccc0b025035bdf9a34afea6aa63038a
Author: Harini Nattamai Subramanian <hnattamaisub@nvidia.com>
Date: Tue Oct 6 03:24:49 2026 -0700
pimd: balance non-DR upstream references to prevent MSDP crash
Failure sequence
1. `pim_mroute_msg_wholepkt()` or `pim_mroute_msg_wrvifwhole()` creates or
updates the upstream and marks it as a source stream.
2. MSDP also holds a reference through `sa->up`.
3. The keepalive timer expires and releases one reference for each owner flag.
4. Two references are released even though only one corresponding reference
was acquired by the mroute path.
5. The MSDP-held reference is consumed, the upstream is freed, and `sa->up`
becomes a dangling pointer.
6. A later MSDP access follows the freed pointer and `pimd`
aborts/crashes.
Without fix:
DR path acquires one reference and sets FHR + SRC_STREAM;
this is balanced because only SRC_STREAM owns a reference and expiry
also releases one reference.
Non-DR path also acquires one reference but sets SRC_NOCACHE + SRC_STREAM,
so keepalive expiry releases two references and can prematurely free the upstream.
With fix:
DR path continues setting SRC_STREAM without an extra increment.
Non-DR path increments reference for SRC_STREAM for balancing.
The fix is applied in both `pim_mroute_msg_wholepkt()` and
`pim_mroute_msg_wrvifwhole()`.
During transition, we handle cleanup accordingly.
Backtrace flow:
igmp_source_timer
igmp_source_forward_stop
tib_sg_gm_prune
pim_ifchannel_local_membership_del
ifmembership_set
pim_upstream_switch
pim_msdp_up_join_state_changed
pim_msdp_sa_upstream_update
pim_msdp_sa_upstream_del
pim_upstream_del
assert(up->ref_count > 0)
SIGABRT
Note: Crash is rarely seen
Signed-off-by: Harini Nattamai Subramanian <hnattamaisub@nvidia.com>
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
index 6131287305..5ea3bd4193 100644
--- a/pimd/pim_mroute.c
+++ b/pimd/pim_mroute.c
@@ -614,7 +614,11 @@ int pim_mroute_msg_wholepkt(int fd, struct interface *ifp, const char *buf,
up = pim_upstream_add(pim_ifp->pim, &sg, src_conn->ifp, up_flags,
__func__, NULL);
- PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
+ if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE)
+ pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM,
+ __func__);
+ else
+ PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
pim_upstream_keep_alive_timer_start(up,
pim_ifp->pim->keep_alive_time);
pim_upstream_inherited_olist(pim_ifp->pim, up);
@@ -1307,7 +1311,10 @@ int pim_mroute_msg_wrvifwhole(int fd, struct interface *ifp, const char *buf,
&sg, ifp->name);
return -2;
}
- PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
+ if (up_flags == PIM_UPSTREAM_FLAG_MASK_SRC_NOCACHE)
+ pim_upstream_ref(up, PIM_UPSTREAM_FLAG_MASK_SRC_STREAM, __func__);
+ else
+ PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
pim_upstream_keep_alive_timer_start(
up, pim_ifp->pim->keep_alive_time);
up->channel_oil->cc.pktcnt++;
diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c
index a9f13ae848..a4d6fea652 100644
--- a/pimd/pim_oil.c
+++ b/pimd/pim_oil.c
@@ -166,18 +166,30 @@ void pim_clear_nocache_state(struct pim_interface *pim_ifp)
struct channel_oil *c_oil;
frr_each_safe (rb_pim_oil, &pim_ifp->pim->channel_oil_head, c_oil) {
+ struct pim_upstream *up = c_oil->up;
- if ((!c_oil->up) ||
- !(PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(c_oil->up->flags)))
+ if ((!up) || !(PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)))
continue;
if (c_oil->iif.index != pim_ifp->mroute_vif_index)
continue;
- event_cancel(&c_oil->up->t_ka_timer);
- PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(c_oil->up->flags);
- PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(c_oil->up->flags);
- pim_upstream_del(pim_ifp->pim, c_oil->up, __func__);
+ event_cancel(&up->t_ka_timer);
+
+ /* So we release reference for each owner flag exactly.
+ * SRC_STREAM is only present when the creator took a
+ * reference for it, so it must be tested rather than
+ * cleared unconditionally.
+ */
+ if (PIM_UPSTREAM_FLAG_TEST_SRC_STREAM(up->flags)) {
+ PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(up->flags);
+ up = pim_upstream_del(pim_ifp->pim, up, __func__);
+ }
+
+ if (up && PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(up->flags)) {
+ PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(up->flags);
+ pim_upstream_del(pim_ifp->pim, up, __func__);
+ }
}
}