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:
2026-05-16 18:55:45 +09:00
co-authored by Claude Opus 4.7
parent bfb177c38e
commit 08349f246b
3 changed files with 27 additions and 7 deletions
+12 -7
View File
@@ -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
+6
View File
@@ -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)