[MME] Fixes crash in building s1ap message

Because a race condition can occur between S6A Diameter and S1AP message,
the following error handling code has been added.

1. InitialUEMessage + Attach Request + PDN Connectivity request
2. Authentication-Information-Request/Authentication-Information-Answer
3. Authentication Request/Response
4. Security-mode command/complete
5. Update-Location-Request/Update-Location-Answer
6. Detach request/accept

In the ULR/ULA process in step 6, the PDN Connectivity request is
pushed to the queue as an ESM_MESSAGE because the NAS-Type is still
an Attach Request.

See the code below in 'mme-s6a-handler.c' for where the queue is pushed.

  if (mme_ue->nas_eps.type == MME_EPS_TYPE_ATTACH_REQUEST) {
      rv = nas_eps_send_emm_to_esm(mme_ue,
              &mme_ue->pdn_connectivity_request);
      if (rv != OGS_OK) {
          ogs_error("nas_eps_send_emm_to_esm() failed");
          return OGS_NAS_EMM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED;
      }
  } else if (mme_ue->nas_eps.type == MME_EPS_TYPE_TAU_REQUEST) {
      r = nas_eps_send_tau_accept(mme_ue,
              S1AP_ProcedureCode_id_InitialContextSetup);
      ogs_expect(r == OGS_OK);
      ogs_assert(r != OGS_ERROR);
  } else {
      ogs_error("Invalid Type[%d]", mme_ue->nas_eps.type);
      return OGS_NAS_EMM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED;
  }

If you perform step 7 Detach request/accept here,
the NAS-Type becomes Detach Request and the EMM state changes
to emm_state_de_registered().

Since the PDN, which is an ESM message that was previously queued,
should not be processed in de_registered, the message is ignored
through error handling below.

Otherwise, MME will crash because there is no active bearer
in the initial_context_setup_request build process.

See the code below in 's1ap-build.c' for where the crash occurs.
  ogs_list_for_each(&mme_ue->sess_list, sess) {
      ogs_list_for_each(&sess->bearer_list, bearer) {
          ...
          if (mme_ue->nas_eps.type == MME_EPS_TYPE_ATTACH_REQUEST) {
          } else if (OGS_FSM_CHECK(&bearer->sm, esm_state_inactive)) {
              ogs_warn("No active EPS bearer [%d]", bearer->ebi);
              ogs_warn("    IMSI[%s] NAS-EPS Type[%d] "
                      "ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
                      mme_ue->imsi_bcd, mme_ue->nas_eps.type,
                      enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
              continue;
          }
          ...
      }
  }
This commit is contained in:
Sukchan Lee 2024-02-02 21:15:52 +09:00
parent 93110d011e
commit 3f0979dab2
9 changed files with 139 additions and 7 deletions

View File

@ -74,7 +74,11 @@ static void epoll_init(ogs_pollset_t *pollset)
ogs_assert(context->map_hash);
context->epfd = epoll_create(pollset->capacity);
ogs_assert(context->epfd >= 0);
if (context->epfd < 0) {
ogs_log_message(OGS_LOG_FATAL, ogs_errno, "epoll_create() failed");
ogs_assert_if_reached();
return;
}
ogs_notify_init(pollset);
}

View File

@ -3764,6 +3764,7 @@ int mme_ue_set_imsi(mme_ue_t *mme_ue, char *imsi_bcd)
ogs_assert(mme_ue->sgw_ue);
mme_ue->sgw_ue->sgw_s11_teid = old_mme_ue->sgw_ue->sgw_s11_teid;
MME_UE_CHECK(OGS_LOG_WARN, old_mme_ue);
mme_ue_remove(old_mme_ue);
}
}

View File

@ -146,6 +146,15 @@ typedef struct mme_context_s {
/* Generator for unique identification */
uint32_t mme_ue_s1ap_id; /* mme_ue_s1ap_id generator */
#define MME_UE_CHECK(level, __mME) \
do { \
ogs_log_message(level, 0, "IMSI [%s] NAS-EPS Type[%d]", \
(__mME) ? (__mME)->imsi_bcd : "No MME_UE", \
(__mME) ? (__mME)->nas_eps.type : 0); \
ogs_log_message(level, 0, "mme_ue[%p:%p]", \
(__mME), mme_ue_cycle((__mME))); \
} while(0)
#define MME_UE_LIST_CHECK \
if (ogs_log_get_domain_level(OGS_LOG_DOMAIN) >= OGS_LOG_TRACE) { \
mme_ue_t *mme_ue = NULL; \
@ -154,8 +163,9 @@ typedef struct mme_context_s {
mme_sess_t *sess = NULL; \
mme_bearer_t *bearer = NULL; \
ogs_list_for_each(&mme_self()->mme_ue_list, mme_ue) { \
ogs_trace("MME_UE(%p) [%s] MME_S11_TEID[%d]", \
mme_ue, mme_ue->imsi_bcd, mme_ue->mme_s11_teid); \
ogs_trace("MME_UE(%p:%p) [%s] MME_S11_TEID[%d]", \
mme_ue, mme_ue_cycle(mme_ue), \
mme_ue->imsi_bcd, mme_ue->mme_s11_teid); \
if (mme_ue->sgw_ue) { \
sgw_ue = mme_ue->sgw_ue; \
ogs_trace("SGW_UE(%p) MME_UE(%p) SGW_S11_TEID[%d]", \

View File

@ -86,6 +86,7 @@ void mme_send_delete_session_or_detach(mme_ue_t *mme_ue)
if (mme_ue->location_updated_but_not_canceled_yet == true) {
mme_s6a_send_pur(mme_ue);
} else {
MME_UE_CHECK(OGS_LOG_WARN, mme_ue);
mme_ue_remove(mme_ue);
}
}

View File

@ -707,6 +707,7 @@ void mme_s11_handle_delete_session_response(
if (mme_ue->location_updated_but_not_canceled_yet == true) {
mme_s6a_send_pur(mme_ue);
} else {
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
mme_ue_remove(mme_ue);
}
} else {

View File

@ -143,6 +143,7 @@ uint8_t mme_s6a_handle_pua(
if (s6a_message->result_code != ER_DIAMETER_SUCCESS) {
ogs_error("Purge UE failed for IMSI[%s] [%d]", mme_ue->imsi_bcd,
s6a_message->result_code);
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
mme_ue_remove(mme_ue);
return OGS_ERROR;
}
@ -150,6 +151,7 @@ uint8_t mme_s6a_handle_pua(
if (pua_message->pua_flags & OGS_DIAM_S6A_PUA_FLAGS_FREEZE_MTMSI)
ogs_debug("Freeze M-TMSI requested but not implemented.");
MME_UE_CHECK(OGS_LOG_DEBUG, mme_ue);
mme_ue_remove(mme_ue);
return OGS_OK;
@ -224,6 +226,7 @@ void mme_s6a_handle_clr(mme_ue_t *mme_ue, ogs_diam_s6a_message_t *s6a_message)
*/
if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_de_registered)) {
ogs_warn("UE has already been de-registered");
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
mme_ue_remove(mme_ue);
return;
}

View File

@ -255,6 +255,7 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
mme_ue = mme_ue_find_by_message(&nas_message);
if (!mme_ue) {
mme_ue = mme_ue_add(enb_ue);
MME_UE_CHECK(OGS_LOG_DEBUG, mme_ue);
if (mme_ue == NULL) {
r = s1ap_send_ue_context_release_command(enb_ue,
S1AP_Cause_PR_misc,
@ -319,6 +320,17 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
}
ogs_assert(mme_ue);
if (!OGS_FSM_STATE(&mme_ue->sm)) {
ogs_fatal("MESSAGE[%d]", nas_message.emm.h.message_type);
ogs_fatal("ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
enb_ue ? enb_ue->enb_ue_s1ap_id : 0,
enb_ue ? enb_ue->mme_ue_s1ap_id : 0);
ogs_fatal("context [%p:%p]", enb_ue, mme_ue);
ogs_fatal("cycle [%p:%p]",
enb_ue_cycle(enb_ue), mme_ue_cycle(mme_ue));
ogs_fatal("IMSI [%s]", mme_ue ? mme_ue->imsi_bcd : "No MME_UE");
ogs_assert_if_reached();
}
ogs_assert(OGS_FSM_STATE(&mme_ue->sm));
e->mme_ue = mme_ue;
@ -351,6 +363,103 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
break;
}
#define ESM_MESSAGE_CHECK \
do { \
ogs_error("emm_state_exception"); \
ogs_error("nas_type:%d, create_action:%d", \
e->nas_type, e->create_action); \
ogs_error("esm.message[EBI:%d,PTI:%d,TYPE:%d]", \
nas_message.esm.h.eps_bearer_identity, \
nas_message.esm.h.procedure_transaction_identity, \
nas_message.esm.h.message_type); \
} while(0)
/*
* Because a race condition can occur between S6A Diameter and S1AP message,
* the following error handling code has been added.
*
* 1. InitialUEMessage + Attach Request + PDN Connectivity request
* 2. Authentication-Information-Request/Authentication-Information-Answer
* 3. Authentication Request/Response
* 4. Security-mode command/complete
* 5. Update-Location-Request/Update-Location-Answer
* 6. Detach request/accept
*
* In the ULR/ULA process in step 6, the PDN Connectivity request is
* pushed to the queue as an ESM_MESSAGE because the NAS-Type is still
* an Attach Request.
*
* See the code below in 'mme-s6a-handler.c' for where the queue is pushed.
*
* if (mme_ue->nas_eps.type == MME_EPS_TYPE_ATTACH_REQUEST) {
* rv = nas_eps_send_emm_to_esm(mme_ue,
* &mme_ue->pdn_connectivity_request);
* if (rv != OGS_OK) {
* ogs_error("nas_eps_send_emm_to_esm() failed");
* return OGS_NAS_EMM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED;
* }
* } else if (mme_ue->nas_eps.type == MME_EPS_TYPE_TAU_REQUEST) {
* r = nas_eps_send_tau_accept(mme_ue,
* S1AP_ProcedureCode_id_InitialContextSetup);
* ogs_expect(r == OGS_OK);
* ogs_assert(r != OGS_ERROR);
* } else {
* ogs_error("Invalid Type[%d]", mme_ue->nas_eps.type);
* return OGS_NAS_EMM_CAUSE_PROTOCOL_ERROR_UNSPECIFIED;
* }
*
* If you perform step 7 Detach request/accept here,
* the NAS-Type becomes Detach Request and the EMM state changes
* to emm_state_de_registered().
*
* Since the PDN, which is an ESM message that was previously queued,
* should not be processed in de_registered, the message is ignored
* through error handling below.
*
* Otherwise, MME will crash because there is no active bearer
* in the initial_context_setup_request build process.
*
* See the code below in 's1ap-build.c' for where the crash occurs.
* ogs_list_for_each(&mme_ue->sess_list, sess) {
* ogs_list_for_each(&sess->bearer_list, bearer) {
* ...
* if (mme_ue->nas_eps.type == MME_EPS_TYPE_ATTACH_REQUEST) {
* } else if (OGS_FSM_CHECK(&bearer->sm, esm_state_inactive)) {
* ogs_warn("No active EPS bearer [%d]", bearer->ebi);
* ogs_warn(" IMSI[%s] NAS-EPS Type[%d] "
* "ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
* mme_ue->imsi_bcd, mme_ue->nas_eps.type,
* enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
* continue;
* }
* ...
* }
* }
*/
if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_de_registered)) {
ESM_MESSAGE_CHECK;
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
ogs_pkbuf_free(pkbuf);
break;
} else if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_authentication)) {
ESM_MESSAGE_CHECK;
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
ogs_pkbuf_free(pkbuf);
break;
} else if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_security_mode)) {
ESM_MESSAGE_CHECK;
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
ogs_pkbuf_free(pkbuf);
break;
} else if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_initial_context_setup)) {
} else if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_registered)) {
} else if (OGS_FSM_CHECK(&mme_ue->sm, emm_state_exception)) {
ESM_MESSAGE_CHECK;
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
ogs_pkbuf_free(pkbuf);
break;
}
bearer = mme_bearer_find_or_add_by_message(
mme_ue, &nas_message, e->create_action);
if (!bearer) {
@ -459,6 +568,7 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
switch (s6a_message->cmd_code) {
case OGS_DIAM_S6A_CMD_CODE_AUTHENTICATION_INFORMATION:
ogs_debug("OGS_DIAM_S6A_CMD_CODE_AUTHENTICATION_INFORMATION");
emm_cause = mme_s6a_handle_aia(mme_ue, s6a_message);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_info("[%s] Attach reject [OGS_NAS_EMM_CAUSE:%d]",
@ -481,6 +591,7 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
}
break;
case OGS_DIAM_S6A_CMD_CODE_UPDATE_LOCATION:
ogs_debug("OGS_DIAM_S6A_CMD_CODE_UPDATE_LOCATION");
emm_cause = mme_s6a_handle_ula(mme_ue, s6a_message);
if (emm_cause != OGS_NAS_EMM_CAUSE_REQUEST_ACCEPTED) {
ogs_info("[%s] Attach reject [OGS_NAS_EMM_CAUSE:%d]",
@ -505,13 +616,16 @@ void mme_state_operational(ogs_fsm_t *s, mme_event_t *e)
mme_ue->location_updated_but_not_canceled_yet = true;
break;
case OGS_DIAM_S6A_CMD_CODE_PURGE_UE:
ogs_debug("OGS_DIAM_S6A_CMD_CODE_PURGE_UE");
mme_s6a_handle_pua(mme_ue, s6a_message);
break;
case OGS_DIAM_S6A_CMD_CODE_CANCEL_LOCATION:
ogs_debug("OGS_DIAM_S6A_CMD_CODE_CANCEL_LOCATION");
mme_ue->location_updated_but_not_canceled_yet = false;
mme_s6a_handle_clr(mme_ue, s6a_message);
break;
case OGS_DIAM_S6A_CMD_CODE_INSERT_SUBSCRIBER_DATA:
ogs_debug("OGS_DIAM_S6A_CMD_CODE_INSERT_SUBSCRIBER_DATA");
mme_s6a_handle_idr(mme_ue, s6a_message);
break;
default:

View File

@ -575,10 +575,7 @@ ogs_pkbuf_t *s1ap_build_initial_context_setup_request(
}
if (!E_RABToBeSetupListCtxtSUReq->list.count) {
ogs_error(" IMSI[%s] NAS-EPS Type[%d] "
"ENB_UE_S1AP_ID[%d] MME_UE_S1AP_ID[%d]",
mme_ue->imsi_bcd, mme_ue->nas_eps.type,
enb_ue->enb_ue_s1ap_id, enb_ue->mme_ue_s1ap_id);
MME_UE_CHECK(OGS_LOG_ERROR, mme_ue);
ogs_list_for_each(&mme_ue->sess_list, sess) {
ogs_error(" APN[%s]",
sess->session ? sess->session->name : "Unknown");

View File

@ -1949,6 +1949,7 @@ void s1ap_handle_ue_context_release_action(enb_ue_t *enb_ue)
if (mme_ue->location_updated_but_not_canceled_yet == true) {
mme_s6a_send_pur(mme_ue);
} else {
MME_UE_CHECK(OGS_LOG_DEBUG, mme_ue);
mme_ue_remove(mme_ue);
}
break;