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) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user