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__);
+		}
 	}
 }