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);