Skip to content

pimd: fix crash due to double free#21354

Merged
nabahr merged 1 commit intoFRRouting:masterfrom
Jafaral:fix-pim-crash
Mar 25, 2026
Merged

pimd: fix crash due to double free#21354
nabahr merged 1 commit intoFRRouting:masterfrom
Jafaral:fix-pim-crash

Conversation

@Jafaral
Copy link
Copy Markdown
Member

@Jafaral Jafaral commented Mar 25, 2026

local_membership_del may delete the ifchannel and last upstream, which runs pim_channel_oil_upstream_deref() and frees the channel_oil. IGMP still holds *oilp in that case; a second pim_channel_oil_del() corrupts the RB tree (typed_rb_remove on freed / zeroed links).

local_membership_del may delete the ifchannel and last upstream,
which runs pim_channel_oil_upstream_deref() and frees the channel_oil.
IGMP still holds *oilp in that case; a second pim_channel_oil_del()
corrupts the RB tree (typed_rb_remove on freed / zeroed links).

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
@Jafaral
Copy link
Copy Markdown
Member Author

Jafaral commented Mar 25, 2026

@Mergifyio backport stable/10.6 stable/10.5 stable/10.4

@mergify
Copy link
Copy Markdown

mergify Bot commented Mar 25, 2026

backport stable/10.6 stable/10.5 stable/10.4

✅ Backports have been created

Details

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 25, 2026

Greptile Summary

This PR fixes a double-free crash in tib_sg_gm_prune (pimd/pim_tib.c) that occurs when pim_ifchannel_local_membership_del deletes the last upstream referencing a channel_oil, causing pim_channel_oil_upstream_derefpim_channel_oil_del to free the oil and remove it from the RB tree — while the IGMP caller's *oilp still points to the now-freed memory. The old code then called pim_channel_oil_del(*oilp) a second time, corrupting the RB tree.

Changes:

  • After pim_ifchannel_local_membership_del, calls pim_find_channel_oil(pim, &sg) to look up whether the channel_oil is still present in the RB tree (i.e., was not freed by upstream_deref).
  • If live == *oilp, the oil is still alive and safe to delete normally via pim_channel_oil_del.
  • If live != *oilp (the oil was already freed), simply sets *oilp = NULL to avoid the double-free.
  • Adds a defensive if (!*oilp) return; guard before pim_channel_del_oif to handle any edge case where the caller passes a NULL oil pointer.

The fix correctly uses the RB tree as the source of truth for object liveness rather than touching freed memory, which is the right approach for this data structure.

Confidence Score: 5/5

  • This PR is safe to merge — it is a targeted, well-reasoned fix for a confirmed crash path with no functional regressions.
  • The change is minimal (one new local variable, one null guard, and a liveness check via the existing RB tree lookup API). The fix correctly uses pim_find_channel_oil as the authoritative check for whether the oil is still allocated, avoiding any access to potentially freed memory. The logic covers both the crash case (oil freed by upstream_deref → sets *oilp = NULL) and the normal case (oil still live → deletes it). The added !*oilp guard is a safe defensive improvement. No new code paths are introduced that could cause regressions.
  • No files require special attention.

Important Files Changed

Filename Overview
pimd/pim_tib.c Fixes a double-free / use-after-free crash in tib_sg_gm_prune by checking whether the channel_oil is still alive in the RB tree after local_membership_del may have freed it via pim_channel_oil_upstream_deref; adds a defensive NULL guard for *oilp before the del_oif call.

Sequence Diagram

sequenceDiagram
    participant IGMP
    participant tib_sg_gm_prune
    participant pim_channel_del_oif
    participant pim_ifchannel_local_membership_del
    participant pim_channel_oil_upstream_deref
    participant pim_channel_oil_del
    participant RB_Tree as RB Tree

    IGMP->>tib_sg_gm_prune: prune(pim, sg, oif, &oilp)
    Note over tib_sg_gm_prune: Guard: if (!*oilp) return (NEW)
    tib_sg_gm_prune->>pim_channel_del_oif: del_oif(*oilp, oif, PROTO_GM)
    tib_sg_gm_prune->>pim_ifchannel_local_membership_del: del(oif, &sg)
    pim_ifchannel_local_membership_del->>pim_channel_oil_upstream_deref: deref(c_oil) [last upstream]
    pim_channel_oil_upstream_deref->>pim_channel_oil_del: del(c_oil) [refcount → 0]
    pim_channel_oil_del->>RB_Tree: rb_pim_oil_del (removes entry)
    pim_channel_oil_del-->>pim_channel_oil_upstream_deref: freed, returns NULL
    Note over tib_sg_gm_prune: BUG (OLD): pim_channel_oil_del(*oilp) → double-free!
    Note over tib_sg_gm_prune: FIX (NEW): pim_find_channel_oil(pim, &sg)
    tib_sg_gm_prune->>RB_Tree: find_channel_oil(pim, &sg) → live
    alt live == *oilp  (oil still alive)
        tib_sg_gm_prune->>pim_channel_oil_del: del(live) — safe
        pim_channel_oil_del-->>tib_sg_gm_prune: NULL or c_oil
        tib_sg_gm_prune-->>IGMP: *oilp = result
    else live != *oilp  (already freed by upstream_deref)
        tib_sg_gm_prune-->>IGMP: *oilp = NULL  (no double-free)
    end
Loading

Reviews (1): Last reviewed commit: "pimd: fix crash due to double free" | Re-trigger Greptile

@nabahr nabahr merged commit d3a9111 into FRRouting:master Mar 25, 2026
26 checks passed
Jafaral added a commit that referenced this pull request Mar 26, 2026
Jafaral added a commit that referenced this pull request Mar 26, 2026
donaldsharp added a commit that referenced this pull request Mar 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants