From 21ada5e1a7f9a30b3e0464ee7f3ea88250d87a8c Mon Sep 17 00:00:00 2001 From: Sukchan Lee Date: Sun, 8 Feb 2026 11:50:35 +0900 Subject: [PATCH] MME: Defer UE context removal on implicit detach without S1 context Problem When the implicit detach timer expires, the MME may initiate local UE context removal if no S1 context exists. In the previous implementation, mme_ue_remove() could be triggered directly from mme_send_delete_session_or_detach() in this path. This leads to a structural issue: - The UE context may be freed while the EMM FSM is still processing the implicit detach timer event. - Subsequent FSM operations (state transition, ENTRY/EXIT signals) may access the freed mme_ue. - This results in assertion failures or crashes such as: emm_state_registered: Assertion `mme_ue' failed Analysis Implicit detach handling executes within the EMM FSM context. Immediate UE context removal from this path violates the FSM lifecycle assumption that the context remains valid until the event handling and state transition complete. This creates a use-after-free risk and can also cause double-free depending on concurrent removal paths. Solution Introduce deferred UE context removal via FSM: 1. Add a new flag: mme_ue->ue_context_will_remove 2. Modify mme_send_delete_session_or_detach(): - If no S1 context exists, do not remove immediately. - Set ue_context_will_remove = true instead. 3. In implicit detach timer handling: - Check the flag and select the next state accordingly. 4. Introduce a new FSM state: emm_state_ue_context_will_remove - UE context removal is performed safely on ENTRY_SIG. This ensures: - UE context is not freed inside the original EMM handler. - FSM lifecycle is preserved. - Removal happens after state transition. Impact - Prevents crashes caused by use-after-free during implicit detach. - Avoids double-free scenarios. - Aligns UE context lifecycle with FSM design. This change only affects implicit detach paths where S1 context does not exist and does not alter normal detach procedures. Fixes: #4298 --- src/mme/emm-sm.c | 63 ++++++++++++++++++++++++++++++++++++++++++- src/mme/mme-context.h | 13 +++++++++ src/mme/mme-path.c | 16 +++++++++-- src/mme/mme-sm.h | 1 + 4 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/mme/emm-sm.c b/src/mme/emm-sm.c index c20c50a9d..dd49b1e51 100644 --- a/src/mme/emm-sm.c +++ b/src/mme/emm-sm.c @@ -269,6 +269,14 @@ void emm_state_registered(ogs_fsm_t *s, mme_event_t *e) case MME_TIMER_IMPLICIT_DETACH: ogs_info("[%s] Implicit Detach timer expired, detaching UE", mme_ue->imsi_bcd); + + /* + * Reset the deferral flag for this implicit detach handling. + * mme_send_delete_session_or_detach() may set this flag if the UE + * must be removed locally (e.g., no S1 context exists). + */ + mme_ue->ue_context_will_remove = false; + CLEAR_MME_UE_TIMER(mme_ue->t_implicit_detach); /* TS 24.301 5.3.5 * If the implicit detach timer expires before the UE contacts @@ -282,7 +290,18 @@ void emm_state_registered(ogs_fsm_t *s, mme_event_t *e) enb_ue_find_by_id(mme_ue->enb_ue_id), mme_ue); } - OGS_FSM_TRAN(s, &emm_state_de_registered); + /* + * Do not remove the UE context directly in this handler. + * + * If mme_send_delete_session_or_detach() decided that local removal + * is required, transition to a dedicated state that will remove the + * UE context on entry. Otherwise follow the normal de-registered + * transition for implicit detach. + */ + if (mme_ue->ue_context_will_remove == true) + OGS_FSM_TRAN(s, &emm_state_ue_context_will_remove); + else + OGS_FSM_TRAN(s, &emm_state_de_registered); break; default: @@ -1828,6 +1847,48 @@ void emm_state_initial_context_setup(ogs_fsm_t *s, mme_event_t *e) } } +/* + * EMM state: UE context will remove. + * + * This state exists to perform UE context removal at a safe point, + * after the triggering EMM event has completed its core handling + * and a state transition has been decided. + * + * It is primarily used by implicit detach paths where the UE may be + * removed locally (e.g., no S1 context) and we must avoid freeing + * mme_ue inside the original EMM timer handler. + */ +void emm_state_ue_context_will_remove(ogs_fsm_t *s, mme_event_t *e) +{ + mme_ue_t *mme_ue = NULL; + + ogs_assert(s); + ogs_assert(e); + + mme_sm_debug(e); + + mme_ue = mme_ue_find_by_id(e->mme_ue_id); + ogs_assert(mme_ue); + + switch (e->id) { + case OGS_FSM_ENTRY_SIG: + /* + * Remove UE context on state entry. + * + * MME_UE_REMOVE_WITH_PAGING_FAIL() handles corner cases where + * paging procedures may still be in progress. + */ + MME_UE_REMOVE_WITH_PAGING_FAIL(mme_ue); + break; + + case OGS_FSM_EXIT_SIG: + break; + + default: + ogs_error("Unknown event[%s]", mme_event_get_name(e)); + } +} + void emm_state_exception(ogs_fsm_t *s, mme_event_t *e) { int r, rv, xact_count; diff --git a/src/mme/mme-context.h b/src/mme/mme-context.h index 8ba2bccb3..2a3ddf661 100644 --- a/src/mme/mme-context.h +++ b/src/mme/mme-context.h @@ -546,6 +546,19 @@ struct mme_ue_s { /* flag: 1 = allow restoration of context, 0 = disallow */ bool can_restore_context; + /* + * ue_context_will_remove: + * + * Set when the UE context must be removed locally after the current + * EMM event completes, but we must not free the UE context inside + * the ongoing procedure. + * + * This flag is used to defer UE context removal to a dedicated EMM + * state (emm_state_ue_context_will_remove) to avoid use-after-free + * and double-free scenarios in implicit detach paths. + */ + bool ue_context_will_remove; + /* Memento of context fields */ mme_ue_memento_t memento; diff --git a/src/mme/mme-path.c b/src/mme/mme-path.c index 8370b0aec..9f002ccd3 100644 --- a/src/mme/mme-path.c +++ b/src/mme/mme-path.c @@ -85,8 +85,20 @@ void mme_send_delete_session_or_detach(enb_ue_t *enb_ue, mme_ue_t *mme_ue) S1AP_Cause_PR_nas, S1AP_CauseNas_normal_release, S1AP_UE_CTX_REL_UE_CONTEXT_REMOVE, 0)); } else { - ogs_warn("[%s] MME-UE Context Removed", mme_ue->imsi_bcd); - MME_UE_REMOVE_WITH_PAGING_FAIL(mme_ue); + /* + * No S1 context exists (eNB UE context already gone). + * + * Historically, this path removed the UE context immediately. + * That can free mme_ue while EMM FSM is still handling the timer + * event, which may lead to invalid FSM transitions or assertions. + * + * Defer UE removal to EMM FSM by setting ue_context_will_remove. + * The caller will transition to emm_state_ue_context_will_remove, + * and the removal will be performed on state entry. + */ + ogs_warn("[%s] No S1 Context - defer UE removal to FSM", + mme_ue->imsi_bcd); + mme_ue->ue_context_will_remove = true; } } break; diff --git a/src/mme/mme-sm.h b/src/mme/mme-sm.h index 0b817ca7b..a26ce3850 100644 --- a/src/mme/mme-sm.h +++ b/src/mme/mme-sm.h @@ -43,6 +43,7 @@ void emm_state_authentication(ogs_fsm_t *s, mme_event_t *e); void emm_state_security_mode(ogs_fsm_t *s, mme_event_t *e); void emm_state_initial_context_setup(ogs_fsm_t *s, mme_event_t *e); void emm_state_registered(ogs_fsm_t *s, mme_event_t *e); +void emm_state_ue_context_will_remove(ogs_fsm_t *s, mme_event_t *e); void emm_state_exception(ogs_fsm_t *s, mme_event_t *e); void esm_state_initial(ogs_fsm_t *s, mme_event_t *e);