From e06ff6c64741cc1675fd3296615910f532f6b1a1 Mon Sep 17 00:00:00 2001
From: Nanang Izzuddin <nanang@teluu.com>
Date: Thu, 5 Mar 2026 07:34:29 +0700
Subject: [PATCH] Merge commit from fork

During Expires=0 unsubscription, on_tsx_state_uas() calls
set_state(TERMINATED) which triggers pres_on_evsub_state() and frees
status_pool/tmp_pool. Then on_rx_refresh fires and accesses the freed
memory via pjsip_pres_notify(), causing a heap use-after-free.

Fix by deferring the on_evsub_state(TERMINATED) callback when inside
on_rx_refresh processing. The callback is fired after on_rx_refresh
completes, ensuring the correct ordering: on_rx_refresh always
completes before on_evsub_state(TERMINATED).

This is a centralized fix in evsub.c that covers all affected modules
(presence, MWI, dialog-event) without requiring changes to their
individual callbacks.

Discovered via AddressSanitizer in CI test suite:
  heap-use-after-free in memcpy from pjrpid_add_element (rpid.c:128)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
---
 pjsip/src/pjsip-simple/evsub.c | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/pjsip/src/pjsip-simple/evsub.c b/pjsip/src/pjsip-simple/evsub.c
index 9ed5793613..d7718af9f2 100644
--- a/pjsip/src/pjsip-simple/evsub.c
+++ b/pjsip/src/pjsip-simple/evsub.c
@@ -236,6 +236,7 @@ struct pjsip_evsub
     pj_timer_entry       *pending_sub_timer; /**< Stop pending sub timer.   */
     pjsip_tx_data        *pending_notify;/**< Pending NOTIFY to be sent.    */
     pj_bool_t             calling_on_rx_refresh;/**< Inside on_rx_refresh()?*/
+    pj_bool_t             deferred_state_notify;/**< Deferred TERMINATED notify */
     pj_grp_lock_t        *grp_lock;     /* Session group lock       */
 
     void                 *mod_data[PJSIP_MAX_MODULE];   /**< Module data.   */
@@ -626,8 +627,15 @@ static void set_state( pjsip_evsub *sub, pjsip_evsub_state state,
         event = &dummy_event;
     }
 
-    if (sub->user.on_evsub_state && sub->call_cb)
-        (*sub->user.on_evsub_state)(sub, event);
+    if (sub->user.on_evsub_state && sub->call_cb) {
+        if (state == PJSIP_EVSUB_STATE_TERMINATED &&
+            sub->calling_on_rx_refresh)
+        {
+            sub->deferred_state_notify = PJ_TRUE;
+        } else {
+            (*sub->user.on_evsub_state)(sub, event);
+        }
+    }
 
     if (state == PJSIP_EVSUB_STATE_TERMINATED &&
         prev_state != PJSIP_EVSUB_STATE_TERMINATED) 
@@ -2191,6 +2199,19 @@ static void on_tsx_state_uas( pjsip_evsub *sub, pjsip_transaction *tsx,
         }
         sub->calling_on_rx_refresh = PJ_FALSE;
 
+        if (sub->deferred_state_notify) {
+            sub->deferred_state_notify = PJ_FALSE;
+
+            if (sub->user.on_evsub_state && sub->call_cb)
+                (*sub->user.on_evsub_state)(sub, event);
+
+            if (sub->state == PJSIP_EVSUB_STATE_TERMINATED &&
+                sub->pending_tsx == 0)
+            {
+                evsub_destroy(sub);
+            }
+        }
+
         /* Application MUST specify final response! */
         PJ_ASSERT_ON_FAIL(st_code >= 200, {st_code=200; });
 
