From 08349f246bb2a519181bdc7d560024f19dea4bb7 Mon Sep 17 00:00:00 2001 From: makigo Date: Sat, 16 May 2026 18:55:45 +0900 Subject: [PATCH] RC SM: fix heap corruption on UE teardown after rrcRelease event When xApp subscribes to RC SM Style 1 (Message Copy), the gNB's signal_rrc_msg() path on rrcRelease handed an async indication to the E2 agent thread, then the calling code proceeded to UE-context teardown while the encoder was still walking the indication structure. Three issues observed in production: 1. malloc_or_fail() leaves union/pointer fields in rc_ind_data and its sub-structures uninitialized; the encoder dereferences whatever garbage was in the unused union member and corrupts the heap. Fix: switch malloc_or_fail(sizeof(...)) to calloc_or_fail(1, sizeof(...)) in fill_rrc_msg_copy, fill_ue_id, and the rc_ind_data_t allocations. 2. The async indication path runs concurrently with UE-context removal. Add rc_teardown_lock/rc_teardown_unlock helpers (public wrappers around rc_mutex) so the RRC layer can serialize teardown against any in-flight signal_rrc_msg() critical section. 3. Wrap rrc_gNB_remove_ue_context() in rrc_gNB.c with rc_teardown_lock/unlock under E2_AGENT. With these three fixes plus the deployment-side MALLOC_CHECK_=3 backstop, a 20-minute challenging-schedule run completes with 22 RRC_RELEASE events processed cleanly and zero crashes (vs. SIGSEGV within 10-30 minutes previously). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c | 19 ++++++++++++------- .../RAN_FUNCTION/O-RAN/ran_func_rc_extern.h | 9 +++++++++ openair2/RRC/NR/rrc_gNB.c | 6 ++++++ 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c index 8ab3e431af..fc94626450 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc.c @@ -592,7 +592,7 @@ static rc_ind_data_t* fill_ue_rrc_state_change(const gNB_RRC_UE_t *rrc_ue_contex // Generate Indication Header rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR; - rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t)); + rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t)); *rc_ind->hdr.frmt_1.ev_trigger_id = cond_id; // Generate Indication Message @@ -629,11 +629,11 @@ static void send_aper_ric_ind(const uint32_t ric_req_id, rc_ind_data_t* rc_ind_d static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t cond_id) { - rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t)); + rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t)); // Generate Indication Header rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR; - rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t)); + rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t)); *rc_ind->hdr.frmt_1.ev_trigger_id = cond_id; // Generate Indication Message @@ -646,7 +646,7 @@ static rc_ind_data_t* fill_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint1 // Fill the RAN Parameter details for UE ID rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_UE_ID; rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE; - rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t)); + rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t)); rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE; const ngran_node_t node_type = get_e2_node_type(); @@ -734,11 +734,11 @@ void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_s static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_t cond_id) { - rc_ind_data_t* rc_ind = malloc_or_fail(sizeof(rc_ind_data_t)); + rc_ind_data_t* rc_ind = calloc_or_fail(1, sizeof(rc_ind_data_t)); // Generate Indication Header rc_ind->hdr.format = FORMAT_1_E2SM_RC_IND_HDR; - rc_ind->hdr.frmt_1.ev_trigger_id = malloc_or_fail(sizeof(uint32_t)); + rc_ind->hdr.frmt_1.ev_trigger_id = calloc_or_fail(1, sizeof(uint32_t)); *rc_ind->hdr.frmt_1.ev_trigger_id = cond_id; // Generate Indication Message @@ -751,7 +751,7 @@ static rc_ind_data_t* fill_rrc_msg_copy(const byte_array_t rrc_ba, const uint16_ rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_id = E2SM_RC_RS1_RRC_MESSAGE; rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.type = ELEMENT_KEY_FLAG_FALSE_RAN_PARAMETER_VAL_TYPE; - rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = malloc_or_fail(sizeof(ran_parameter_value_t)); + rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false = calloc_or_fail(1, sizeof(ran_parameter_value_t)); rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->type = OCTET_STRING_RAN_PARAMETER_VALUE; rc_ind->msg.frmt_1.seq_ran_param[0].ran_param_val.flag_false->octet_str_ran = copy_byte_array(rrc_ba); @@ -776,6 +776,11 @@ static void check_rrc_msg_copy(const nr_rrc_class_e nr_channel, const uint32_t r } } +/* Public lock/unlock used by RRC to serialize UE-context teardown against + * in-flight RC indications fired by signal_rrc_msg(). See header comment. */ +void rc_teardown_lock(void) { pthread_mutex_lock(&rc_mutex); } +void rc_teardown_unlock(void) { pthread_mutex_unlock(&rc_mutex); } + void signal_rrc_msg(const nr_rrc_class_e nr_channel, const uint32_t rrc_msg_id, const byte_array_t rrc_ba) { pthread_mutex_lock(&rc_mutex); diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h index 7d7df662c5..db575bdf33 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h @@ -16,4 +16,13 @@ void signal_ue_id(const gNB_RRC_UE_t *rrc_ue_context, const uint16_t class, cons void signal_rrc_state_changed_to(const gNB_RRC_UE_t *rrc_ue_context, const rrc_state_e2sm_rc_e rrc_state); +/* Serialize UE-context teardown against in-flight RC indications. + * Without this, signal_rrc_msg(rrcRelease) can hand a malloc'd indication + * to the async E2 agent thread; if the gNB then frees the UE state while + * the agent is still encoding, glibc detects heap corruption ("double free + * or corruption (!prev)") and the process aborts. + * The teardown caller must wrap rrc_gNB_remove_ue_context() in these. */ +void rc_teardown_lock(void); +void rc_teardown_unlock(void); + #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 4c47e6a23b..8d1bbf2b8a 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2734,7 +2734,13 @@ void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p) nr_pdcp_remove_UE(UE->rrc_ue_id); LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti); rrc_delete_ue_data(UE); +#ifdef E2_AGENT + rc_teardown_lock(); /* block in-flight RC indications during teardown */ +#endif rrc_gNB_remove_ue_context(rrc, ue_context_p); +#ifdef E2_AGENT + rc_teardown_unlock(); +#endif } static void rrc_CU_process_ue_context_release_complete(MessageDef *msg_p)