From 5354ad8a26f694c55b27b867ffd054004db720f0 Mon Sep 17 00:00:00 2001 From: Bartosz Podrygajlo Date: Thu, 10 Apr 2025 21:12:44 +0200 Subject: [PATCH 01/15] Add support for TYPE_IPV4ADDR in yaml config module --- common/config/yaml/config_yaml.cpp | 15 ++++++++++ common/config/yaml/tests/CMakeLists.txt | 1 + common/config/yaml/tests/test_ipv4.yaml | 4 +++ common/config/yaml/tests/test_yaml_config.cpp | 30 +++++++++++++++++++ 4 files changed, 50 insertions(+) create mode 100644 common/config/yaml/tests/test_ipv4.yaml diff --git a/common/config/yaml/config_yaml.cpp b/common/config/yaml/config_yaml.cpp index 108dee1ac2..138163ce0e 100644 --- a/common/config/yaml/config_yaml.cpp +++ b/common/config/yaml/config_yaml.cpp @@ -25,6 +25,8 @@ extern "C" { #include "common/config/config_userapi.h" void *config_allocate_new(configmodule_interface_t *cfg, int sz, bool autoFree); void config_check_valptr(configmodule_interface_t *cfg, paramdef_t *cfgoptions, int elt_sz, int nb_elt); +#include "../config_userapi.h" +#include "../config_common.h" } #include #include @@ -98,6 +100,9 @@ void SetDefault(configmodule_interface_t *cfg, paramdef_t *param) param->uptr = (uint32_t *)param->defintarrayval; } break; + case TYPE_IPV4ADDR: + config_setdefault_ipv4addr(cfg, param, param->defstrval); + break; default: AssertFatal(false, "Unhandled type %d", param->type); } @@ -186,6 +191,16 @@ void SetNonDefault(configmodule_interface_t *cfg, const YAML::Node &node, paramd } break; } + case TYPE_IPV4ADDR: { + std::string ipv4addr = node[optname].as(); + char* ipv4addr_ptr = strdup(ipv4addr.c_str()); + int rst = config_assign_ipv4addr(cfg, param, ipv4addr_ptr); + free(ipv4addr_ptr); + if (rst < 0) { + fprintf(stderr,"[LIBCONFIG] %s not valid for %s \n", ipv4addr.c_str(), optname.c_str()); + } + break; + } default: AssertFatal(false, "Unhandled type %d", param->type); } diff --git a/common/config/yaml/tests/CMakeLists.txt b/common/config/yaml/tests/CMakeLists.txt index 6290f2fc15..d33fa57658 100644 --- a/common/config/yaml/tests/CMakeLists.txt +++ b/common/config/yaml/tests/CMakeLists.txt @@ -10,3 +10,4 @@ configure_file(test_string.yaml test_string.yaml COPYONLY) configure_file(test_list_of_mappings.yml test_list_of_mappings.yml COPYONLY) configure_file(test_int_array.yaml test_int_array.yaml COPYONLY) configure_file(test_read_mapping_as_list.yaml test_read_mapping_as_list.yaml COPYONLY) +configure_file(test_ipv4.yaml test_ipv4.yaml COPYONLY) diff --git a/common/config/yaml/tests/test_ipv4.yaml b/common/config/yaml/tests/test_ipv4.yaml new file mode 100644 index 0000000000..c7d1b4866f --- /dev/null +++ b/common/config/yaml/tests/test_ipv4.yaml @@ -0,0 +1,4 @@ +test: + ipv4_1: 0.0.0.0 + ipv4_2: 192.168.0.1 + #ipv4_3: check default diff --git a/common/config/yaml/tests/test_yaml_config.cpp b/common/config/yaml/tests/test_yaml_config.cpp index deaa52cd1a..384a1f406c 100644 --- a/common/config/yaml/tests/test_yaml_config.cpp +++ b/common/config/yaml/tests/test_yaml_config.cpp @@ -391,6 +391,36 @@ TEST(yaml_config, test_read_mapping_as_list) { end_configmodule(cfg); } +TEST(yaml_config, test_read_ipv4) { + configmodule_interface_t *cfg = static_cast(calloc(1, sizeof(*cfg))); + cfg->cfgP[0] = strdup("test_ipv4.yaml"); + EXPECT_EQ(config_yaml_init(cfg), 0); + + paramdef_t p = {}; + p.type = TYPE_IPV4ADDR; + strncpy(p.optname, "ipv4_1", sizeof(p.optname) - 1); + uint32_t addr = 0; + p.uptr = &addr; + p.defstrval = nullptr; + char prefix[] = "test"; + config_yaml_get(cfg, &p, 1, prefix); + printf("%x\n", addr); + + strncpy(p.optname, "ipv4_2", sizeof(p.optname) - 1); + config_yaml_get(cfg, &p, 1, prefix); + printf("%x\n", addr); + + p.defstrval = strdup("10.0.0.1"); + strncpy(p.optname, "ipv4_3", sizeof(p.optname) - 1); + config_yaml_get(cfg, &p, 1, prefix); + printf("%x\n", addr); + + config_yaml_end(cfg); + free(cfg->cfgP[0]); + end_configmodule(cfg); + free(p.defstrval); +} + int main(int argc, char** argv) { logInit(); From 729b0eb34814596e2d3bce5c28e0717cdafaa681 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 28 Apr 2025 10:14:38 +0200 Subject: [PATCH 02/15] Print errno in nr_sdap_rx_entity --- openair2/SDAP/nr_sdap/nr_sdap_entity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/SDAP/nr_sdap/nr_sdap_entity.c b/openair2/SDAP/nr_sdap/nr_sdap_entity.c index d93a508c33..7f2f3dd07c 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap_entity.c +++ b/openair2/SDAP/nr_sdap/nr_sdap_entity.c @@ -329,7 +329,7 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity, LOG_D(SDAP, "RX Entity offset : %d\n", offset); if (len != size-offset) - LOG_E(SDAP, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__); + LOG_E(SDAP, "write failed to fd %d! errno = %s\n", entity->pdusession_sock, strerror(errno)); } } From ce1766301711f0eb0369a8d3168605ed17a46163 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 28 Apr 2025 15:38:06 +0200 Subject: [PATCH 03/15] Release all user plane resources when going to IDLE Release all SDAP entities for a UE in nr_rrc_going_to_IDLE(). With this change the SDAP entities of the UE are cleaned up when going into RRC_IDLE, including tun sockets, threads etc. A new SDAP entity is created when re-attaching to the network. --- openair2/RRC/NR_UE/rrc_UE.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index b36c2d676d..4abc712507 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2562,6 +2562,9 @@ void nr_rrc_going_to_IDLE(NR_UE_RRC_INST_t *rrc, nr_pdcp_release_drb(rrc->ue_id, i); } } + // stop TUN threads and clean up SDAP entities + nr_sdap_delete_ue_entities(rrc->ue_id); + for (int i = 1; i < NR_NUM_SRB; i++) { if (rrc->Srb[i] != RB_NOT_PRESENT) { rrc->Srb[i] = RB_NOT_PRESENT; From ad2a8a89916fbdee695faadf6f2b2a5f6e340394 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 25 Apr 2025 11:45:25 +0200 Subject: [PATCH 04/15] Add 5 tests to force RRC_IDLE in a row in 5g_rfsimulator_24prb pipeline --- .../xml_files/container_5g_rfsim_24prb.xml | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/ci-scripts/xml_files/container_5g_rfsim_24prb.xml b/ci-scripts/xml_files/container_5g_rfsim_24prb.xml index f4ee341c94..5a59880b2f 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_24prb.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_24prb.xml @@ -34,9 +34,24 @@ 020002 030001 030002 + 040002 + 000004 + 020001 + 040002 + 000004 + 020001 + 040002 + 000004 + 020001 + 040002 + 000004 + 020001 + 040002 + 000004 + 020001 040001 000004 - 020002 + 020001 100001 222222 @@ -82,7 +97,7 @@ IdleSleep Sleep - 5 + 1 @@ -92,8 +107,8 @@ cacofonix rfsim5g_ext_dn cacofonix - -c 20 -i0.2 - 5 + -c 20 -i0.5 + 80 @@ -139,13 +154,19 @@ yes + + Custom_Command + Force RRC_IDLE (UE ID 0) + cacofonix + echo ciUE force_RRC_IDLE | nc 192.168.71.150 8091 + yes + + Undeploy_Object true Undeploy all OAI 5G stack ci-scripts/yaml_files/5g_rfsimulator_24prb - 1,0,0,0 - 1,0,0,0 0 0 From 844986ff92143db10e7fa6fc7eeac752b4c2efb2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 29 Apr 2025 15:32:53 +0200 Subject: [PATCH 05/15] SDAP: close TUN socket to unblock thread and clean interface shutdown * Close entity->pdusession_sock manually before pthread_join() to unblock sdap_tun_read_thread() * Update tun_destroy() to open a fresh socket for interface management, independent from the data socket * Fix check on success of change_interface_state() in tun_destroy(): is a boolean * Handle ENODEV error when bringing interface down: treat as success if the interface is already gone. * tun_destroy() is now handling INTERFACE_DOWN, so `INTERFACE_DOWN` in `tun_config()` is no longer needed --- common/utils/tun_if.c | 27 +++++++++++++++++++-------- common/utils/tun_if.h | 3 +-- openair2/SDAP/nr_sdap/nr_sdap.c | 6 +++++- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/common/utils/tun_if.c b/common/utils/tun_if.c index 87bba5a513..4bb1f7ec58 100644 --- a/common/utils/tun_if.c +++ b/common/utils/tun_if.c @@ -168,10 +168,14 @@ static bool change_interface_state(int sock_fd, const char *ifn, if_action_t if_ struct ifreq ifr = {0}; strncpy(ifr.ifr_name, ifn, sizeof(ifr.ifr_name)); + /* get flags of this interface: see netdevice(7) */ bool success = ioctl(sock_fd, SIOCGIFFLAGS, (caddr_t)&ifr) == 0; - if (!success) - goto fail_interface_state; + if (!success && if_action == INTERFACE_DOWN && errno == ENODEV) { + LOG_W(OIP, "trying to remove non-existant device %s\n", ifn); + return true; + } + LOG_D(OIP, "Got flags for %s: 0x%x (action: %s)\n", ifn, ifr.ifr_flags, action); if (if_action == INTERFACE_UP) { ifr.ifr_flags |= IFF_UP | IFF_NOARP | IFF_POINTOPOINT; @@ -183,6 +187,8 @@ static bool change_interface_state(int sock_fd, const char *ifn, if_action_t if_ success = ioctl(sock_fd, SIOCSIFFLAGS, (caddr_t)&ifr) == 0; if (!success) goto fail_interface_state; + LOG_D(OIP, "Interface %s successfully set %s\n", ifn, action); + return true; fail_interface_state: @@ -201,10 +207,6 @@ bool tun_config(const char* ifname, const char *ipv4, const char *ipv6) return false; } - if (!change_interface_state(sock_fd, ifname, INTERFACE_DOWN)) { - close(sock_fd); - return false; - } bool success = true; if (ipv4 != NULL) success = setInterfaceParameter(sock_fd, ifname, AF_INET, ipv4, SIOCSIFADDR); @@ -272,8 +274,17 @@ int tun_generate_ue_ifname(char *ifname, int instance_id, int pdu_session_id) return snprintf(ifname, IFNAMSIZ, "%s%d%s", "oaitun_ue", instance_id + 1, pdu_session_id == -1 ? "" : pdu_session_string); } -void tun_destroy(const char *dev, int fd) { - if (change_interface_state(fd, dev, INTERFACE_DOWN) == 0) { +void tun_destroy(const char *dev) +{ + // Use a new socket for ioctl operations + int fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + LOG_E(UTIL, "Failed to create socket for interface teardown: %d, %s\n", errno, strerror(errno)); + return; + } + + bool success = change_interface_state(fd, dev, INTERFACE_DOWN); + if (success) { LOG_I(UTIL, "Interface %s is now down.\n", dev); } else { LOG_E(UTIL, "Could not bring interface %s down.\n", dev); diff --git a/common/utils/tun_if.h b/common/utils/tun_if.h index e73b4be94e..8e1463624f 100644 --- a/common/utils/tun_if.h +++ b/common/utils/tun_if.h @@ -84,8 +84,7 @@ int tun_alloc(const char *dev); /*! * \brief This function destroys the TUN interface * \param[in] dev name of the interface - * \param[in] fd file descriptor of the allocated interface */ -void tun_destroy(const char *dev, int fd); +void tun_destroy(const char *dev); #endif /*TUN_IF_H_*/ diff --git a/openair2/SDAP/nr_sdap/nr_sdap.c b/openair2/SDAP/nr_sdap/nr_sdap.c index 44b8fa2cb0..946cc22ced 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap.c +++ b/openair2/SDAP/nr_sdap/nr_sdap.c @@ -194,6 +194,10 @@ void remove_ip_if(nr_sdap_entity_t *entity) DevAssert(entity != NULL); // Stop the read thread entity->stop_thread = true; + + // Close the socket: read() will get EBADF and exit + close(entity->pdusession_sock); + int ret = pthread_join(entity->pdusession_thread, NULL); AssertFatal(ret == 0, "pthread_join() failed, errno: %d, %s\n", errno, strerror(errno)); // Bring down the IP interface @@ -205,5 +209,5 @@ void remove_ip_if(nr_sdap_entity_t *entity) } else { tun_generate_ue_ifname(ifname, entity->ue_id, entity->pdusession_id != default_pdu ? entity->pdusession_id : -1); } - tun_destroy(ifname, entity->pdusession_sock); + tun_destroy(ifname); } From bcab56afb48bae044c0a675538ce26fd7d275c98 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 29 Apr 2025 15:36:54 +0200 Subject: [PATCH 06/15] sdap_tun_read_thread(): handle and log different errno --- openair2/SDAP/nr_sdap/nr_sdap.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openair2/SDAP/nr_sdap/nr_sdap.c b/openair2/SDAP/nr_sdap/nr_sdap.c index 946cc22ced..f6602c7597 100644 --- a/openair2/SDAP/nr_sdap/nr_sdap.c +++ b/openair2/SDAP/nr_sdap/nr_sdap.c @@ -121,8 +121,21 @@ static void *sdap_tun_read_thread(void *arg) while (!entity->stop_thread) { len = read(entity->pdusession_sock, &rx_buf, NL_MAX_PAYLOAD); if (len == -1) { - LOG_E(PDCP, "could not read(): errno %d %s\n", errno, strerror(errno)); - return NULL; + if (errno == EINTR) + continue; // interrupted system call + + if (errno == EBADF || errno == EINVAL) { + LOG_I(SDAP, "Socket closed, exiting TUN read thread for UE %ld, PDU session %d\n", entity->ue_id, entity->pdusession_id); + break; + } + + LOG_E(PDCP, "read() failed: errno %d (%s)\n", errno, strerror(errno)); + break; + } + + if (len == 0) { + LOG_W(SDAP, "TUN socket returned EOF - exiting thread\n"); + break; } LOG_D(SDAP, "read data of size %d\n", len); From f7a04de762d751e5968b69622707f11a54a0479b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 29 Apr 2025 15:50:32 +0200 Subject: [PATCH 07/15] Add ciUE telnet command to trigger UE initiated deregistration --- common/utils/telnetsrv/telnetsrv_ciUE.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/utils/telnetsrv/telnetsrv_ciUE.c b/common/utils/telnetsrv/telnetsrv_ciUE.c index 721cb0e5cd..90e044ed86 100644 --- a/common/utils/telnetsrv/telnetsrv_ciUE.c +++ b/common/utils/telnetsrv/telnetsrv_ciUE.c @@ -102,12 +102,21 @@ int force_crnti_ra(char *buf, int debug, telnet_printfunc_t prnt) return 0; } +static int force_deregistration(char *buf, int debug, telnet_printfunc_t prnt) +{ + MessageDef *msg = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_DEREGISTRATION_REQ); + NAS_DEREGISTRATION_REQ(msg).cause = AS_DETACH; + itti_send_msg_to_task(TASK_NAS_NRUE, 0, msg); + return 0; +} + /* Telnet shell command definitions */ static telnetshell_cmddef_t cicmds[] = { {"sync_state", "[UE_ID(int,opt)]", get_sync_state}, {"force_rlf", "", force_rlf}, {"force_RRC_IDLE", "", force_RRC_IDLE}, {"force_crnti_ra", "", force_crnti_ra}, + {"deregistration", "", force_deregistration}, {"", "", NULL}, }; From 4129619714371aecaf60723eaa243600dae5bad8 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 29 Apr 2025 15:57:16 +0200 Subject: [PATCH 08/15] Bugfix in nas_security_rx_process: process first the security type and then the header length This commit restores the behaviour before the changes introduced in 611c0554. The NAS Deregistration Accept message is unprotected, therefore the function will return NAS_SECURITY_UNPROTECTED that would then be considered as NAS_SECURITY_INTEGRITY_PASSED Unprotected message have a plain header of 3 octets, however security protected messages have a 7 octets header. --- openair3/NAS/NR_UE/nr_nas_msg.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index b1378c3360..37ed60edaf 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -133,11 +133,15 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b { if (nas->security_container == NULL) return NAS_SECURITY_NO_SECURITY_CONTEXT; - /* header is 7 bytes, require at least one byte of payload */ - if (pdu_length < 8) - return NAS_SECURITY_BAD_INPUT; - switch (pdu_buffer[1]) { + if (pdu_length < sizeof(fgmm_msg_header_t)) { + LOG_E(NAS, "Invalid buffer length = %d: must hold at least a plain 5GMM header\n", pdu_length); + return NAS_SECURITY_BAD_INPUT; + } + int security_type = pdu_buffer[1]; + LOG_D(NAS, "Security type is: %s\n", print_info(security_type, security_header_type_s, sizeofArray(security_header_type_s))); + + switch (security_type) { case PLAIN_5GS_MSG: return NAS_SECURITY_UNPROTECTED; break; @@ -154,6 +158,12 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b break; } + /* header is 7 bytes, require at least one byte of payload */ + if (pdu_length < 8) { + LOG_E(NAS, "Invalid buffer length = %d\n", pdu_length); + return NAS_SECURITY_BAD_INPUT; + } + /* synchronize NAS SQN, based on 24.501 4.4.3.1 */ // Sequence number int nas_sqn = pdu_buffer[6]; From 737b2f44c42518f8c94470952e8a4ddda888d911 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 2 May 2025 18:30:42 +0200 Subject: [PATCH 09/15] Print NAS_DOWNLINK_DATA_IND message type content The information about the payload carried by NAS DOWNLINK is helpful in the logging and also for debugging purposes, therefore it was added in this commit by enabling the already existing relevant function. --- openair3/NAS/NR_UE/nr_nas_msg.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 37ed60edaf..a40edad79d 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -1754,14 +1754,7 @@ void *nas_nrue(void *args_p) } break; case NAS_DOWNLINK_DATA_IND: { - LOG_I(NAS, - "[UE %ld] Received %s: length %u , buffer %p\n", - nas->UE_id, - ITTI_MSG_NAME(msg_p), - NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length, - NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data); as_nas_info_t initialNasMsg = {0}; - uint8_t *pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data; int pdu_length = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length; int msg_type = get_msg_type(pdu_buffer, pdu_length); @@ -1779,6 +1772,13 @@ void *nas_nrue(void *args_p) break; } + LOG_I(NAS, + "[UE %ld] Received %s type %s with length %u\n", + nas->UE_id, + ITTI_MSG_NAME(msg_p), + print_info(msg_type, message_text_info, sizeofArray(message_text_info)), + NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length); + switch (msg_type) { case FGS_IDENTITY_REQUEST: generateIdentityResponse(&initialNasMsg, *(pdu_buffer + 3), nas->uicc); From 330b8dc8fa0ffdd89bfc316674406a8c3532e572 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Sun, 4 May 2025 22:46:16 +0200 Subject: [PATCH 10/15] Add library for NAS Identity Request * Add Identity Request decoding function * Define type for Identity Type NAS IE * Target include directory in NAS IEs CMakeLists.txt (for NAS utils) --- .../NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt | 2 + .../NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h | 23 ++++++++---- .../NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt | 1 + .../5GS/5GMM/MSG/fgmm_identity_request.c | 37 +++++++++++++++++++ .../5GS/5GMM/MSG/fgmm_identity_request.h | 36 ++++++++++++++++++ 5 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c create mode 100644 openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt index 40bfcbabcd..a4bb516b89 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/CMakeLists.txt @@ -11,3 +11,5 @@ target_link_libraries(fgs_5gmm_ies_lib PUBLIC nas_common_ies_lib PRIVATE log_headers ) + +target_include_directories(fgs_5gmm_ies_lib PRIVATE ../../) diff --git a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h index b7e66019cc..4d6d4d7eed 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h +++ b/openair3/NAS/NR_UE/5GS/5GMM/IES/FGSMobileIdentity.h @@ -30,8 +30,8 @@ #include #include #include - #include "OctetString.h" +#include "fgs_nas_utils.h" #ifndef FGS_MOBILE_IDENTITY_H_ #define FGS_MOBILE_IDENTITY_H_ @@ -129,15 +129,22 @@ typedef struct { uint8_t macaddr; } Macaddr5GSMobileIdentity_t; +#define FOREACH_IDENTITY_TYPE(TYPE_DEF) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_NOIDENTITY, 0) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_SUCI, 1) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_5G_GUTI, 2) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_IMEI, 3) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_5GS_TMSI, 4) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_IMEISV, 5) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_MAC_ADDR, 6) \ + TYPE_DEF(FGS_MOBILE_IDENTITY_EUI64, 7) + +typedef enum { FOREACH_IDENTITY_TYPE(TO_ENUM) } fgs_identity_type_t; + +static const text_info_t fgs_identity_type_text[] = {FOREACH_IDENTITY_TYPE(TO_TEXT)}; + // clang-format off typedef union FGSMobileIdentity_tag { -#define FGS_MOBILE_IDENTITY_NOIDENTITY 0b000 -#define FGS_MOBILE_IDENTITY_SUCI 0b001 -#define FGS_MOBILE_IDENTITY_5G_GUTI 0b010 -#define FGS_MOBILE_IDENTITY_IMEI 0b011 -#define FGS_MOBILE_IDENTITY_5GS_TMSI 0b100 -#define FGS_MOBILE_IDENTITY_IMEISV 0b101 -#define FGS_MOBILE_IDENTITY_MAC_ADDR 0b110 NoIdentity5GSMobileIdentity_t noidentity; Suci5GSMobileIdentity_t suci; Guti5GSMobileIdentity_t guti; diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt index 3b51ec748c..bf47e478b9 100644 --- a/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(fgs_5gmm_lib OBJECT fgmm_service_reject.c RegistrationRequest.c RegistrationAccept.c + fgmm_identity_request.c FGSIdentityResponse.c FGSAuthenticationResponse.c FGSNASSecurityModeComplete.c diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c new file mode 100644 index 0000000000..b41035f6ea --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.c @@ -0,0 +1,37 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "fgmm_identity_request.h" +#include +#include "FGSMobileIdentity.h" + +int decode_fgs_identity_request(fgs_identity_request_msg_t *msg, const byte_array_t *buffer) +{ + if (buffer->len < 1) { + PRINT_NAS_ERROR("Failed to decode NAS Identity Request: invalid buffer length %ld\n", buffer->len); + return -1; + } + + // Bits 3-1 (least significant 3 bits) + msg->fgsmobileidentity = buffer->buf[0] & 0x07; + + return 1; +} diff --git a/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h new file mode 100644 index 0000000000..104f133ed1 --- /dev/null +++ b/openair3/NAS/NR_UE/5GS/5GMM/MSG/fgmm_identity_request.h @@ -0,0 +1,36 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef FGS_IDENTITY_REQUEST_H +#define FGS_IDENTITY_REQUEST_H + +#include +#include +#include "common/utils/ds/byte_array.h" +#include "FGSMobileIdentity.h" + +typedef struct { + fgs_identity_type_t fgsmobileidentity; +} fgs_identity_request_msg_t; + +int decode_fgs_identity_request(fgs_identity_request_msg_t *msg, const byte_array_t *buffer); + +#endif /* FGS_IDENTITY_REQUEST_H */ From 7f96a04c1b3c48a3f5e9373eab2bf669cd4e5174 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Sun, 4 May 2025 22:52:16 +0200 Subject: [PATCH 11/15] Add handler for NAS Identity Request * trigger Identity Response callback from the handler * process decoded message According to 4.4.4.2 3GPP TS 24.501, Identity Request with Identity Type SUCI is not expected to be integrity protected. --- openair3/NAS/NR_UE/nr_nas_msg.c | 44 ++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index a40edad79d..b539dc98ae 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -50,6 +50,7 @@ #include "SORTransparentContainer.h" #include "FGSIdentityResponse.h" #include "fgmm_authentication_request.h" +#include "fgmm_identity_request.h" #include "T.h" #include "aka_functions.h" #include "assertions.h" @@ -802,7 +803,7 @@ void generateServiceRequest(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas) } } -void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype, uicc_t *uicc) +static void generateIdentityResponse(as_nas_info_t *initialNasMsg, const uint8_t identitytype, uicc_t *uicc) { int size = sizeof(fgmm_msg_header_t); fgmm_nas_message_plain_t plain = {0}; @@ -823,6 +824,39 @@ void generateIdentityResponse(as_nas_info_t *initialNasMsg, uint8_t identitytype initialNasMsg->length = mm_msg_encode(&plain, initialNasMsg->nas_data, size); } +static void handle_identity_request(as_nas_info_t *initialNasMsg, nr_ue_nas_t *nas, const byte_array_t buffer) +{ + fgmm_msg_header_t mm_header = {0}; + fgs_identity_request_msg_t msg = {0}; + + // Decode plain NAS header + int decoded = decode_5gmm_msg_header(&mm_header, buffer.buf, buffer.len); + if (decoded < 0) { + LOG_E(NAS, "Failed to decode NAS Identity Request header\n"); + return; + } + + byte_array_t payload = {.buf = &buffer.buf[decoded], .len = buffer.len - decoded}; + + // Decode identity request payload + if (decode_fgs_identity_request(&msg, &payload) < 0) { + LOG_E(NAS, "Failed to decode Identity Request message\n"); + return; + } + + LOG_I(NAS, + "Received IDENTITY REQUEST for identity type: %s\n", + print_info(msg.fgsmobileidentity, fgs_identity_type_text, sizeofArray(fgs_identity_type_text))); + + if (mm_header.message_type == NAS_SECURITY_UNPROTECTED && msg.fgsmobileidentity != FGS_MOBILE_IDENTITY_SUCI) { + // see 3GPP TS 24.501 4.4.4.2 + LOG_E(NAS, "Only SUCI mobile identity is expected in a security-unprotected request\n"); + return; + } + + generateIdentityResponse(initialNasMsg, msg.fgsmobileidentity, nas->uicc); +} + static void generateAuthenticationResp(nr_ue_nas_t *nas, as_nas_info_t *initialNasMsg, uint8_t *buf) { derive_ue_keys(buf, nas); @@ -1757,6 +1791,7 @@ void *nas_nrue(void *args_p) as_nas_info_t initialNasMsg = {0}; uint8_t *pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data; int pdu_length = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length; + byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; int msg_type = get_msg_type(pdu_buffer, pdu_length); security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length); @@ -1781,13 +1816,11 @@ void *nas_nrue(void *args_p) switch (msg_type) { case FGS_IDENTITY_REQUEST: - generateIdentityResponse(&initialNasMsg, *(pdu_buffer + 3), nas->uicc); + handle_identity_request(&initialNasMsg, nas, buffer); break; - case FGS_AUTHENTICATION_REQUEST: { - byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; + case FGS_AUTHENTICATION_REQUEST: handle_fgmm_authentication_request(nas, &initialNasMsg, &buffer); break; - } case FGS_SECURITY_MODE_COMMAND: handle_security_mode_command(nas, &initialNasMsg, pdu_buffer, pdu_length); break; @@ -1813,7 +1846,6 @@ void *nas_nrue(void *args_p) exit(1); break; case FGS_SERVICE_ACCEPT: { - byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; handle_service_accept(nas, &buffer); break; } From e651a29a2acd4508db3d5b97f5450e364b088a28 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 15 May 2025 12:05:33 +0200 Subject: [PATCH 12/15] Use byte_array_t in nas_security_rx_process --- openair3/NAS/NR_UE/nr_nas_msg.c | 49 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index b539dc98ae..58cb008af3 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -70,6 +70,7 @@ #include "fgs_nas_utils.h" #include "fgmm_service_accept.h" #include "fgmm_service_reject.h" +#include "ds/byte_array.h" #define MAX_NAS_UE 4 @@ -130,16 +131,16 @@ static const char *print_info(uint8_t id, const text_info_t *array, uint8_t arra return "N/A"; } -static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_buffer, int pdu_length) +static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t buffer) { if (nas->security_container == NULL) return NAS_SECURITY_NO_SECURITY_CONTEXT; - if (pdu_length < sizeof(fgmm_msg_header_t)) { - LOG_E(NAS, "Invalid buffer length = %d: must hold at least a plain 5GMM header\n", pdu_length); + if (buffer.len < sizeof(fgmm_msg_header_t)) { + LOG_E(NAS, "Invalid buffer length = %ld: must hold at least a plain 5GMM header\n", buffer.len); return NAS_SECURITY_BAD_INPUT; } - int security_type = pdu_buffer[1]; + int security_type = buffer.buf[1]; LOG_D(NAS, "Security type is: %s\n", print_info(security_type, security_header_type_s, sizeofArray(security_header_type_s))); switch (security_type) { @@ -150,9 +151,9 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX: case INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX: /* only accept "integrity protected and ciphered" messages */ - if (pdu_buffer[6] == 0) - LOG_E(NAS, "Received nas_count_dl = %d\n", pdu_buffer[6]); - LOG_E(NAS, "todo: unhandled security type %s (pdu_buffer[1] = %d)\n", security_header_type_s[pdu_buffer[1]].text, pdu_buffer[1]); + if (buffer.buf[6] == 0) + LOG_E(NAS, "Received nas_count_dl = %d\n", buffer.buf[6]); + LOG_E(NAS, "todo: unhandled security type %s (buffer.buf[1] = %d)\n", security_header_type_s[buffer.buf[1]].text, buffer.buf[1]); return NAS_SECURITY_BAD_INPUT; break; default: @@ -160,14 +161,14 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b } /* header is 7 bytes, require at least one byte of payload */ - if (pdu_length < 8) { - LOG_E(NAS, "Invalid buffer length = %d\n", pdu_length); + if (buffer.len < 8) { + LOG_E(NAS, "Invalid buffer length = %ld\n", buffer.len); return NAS_SECURITY_BAD_INPUT; } /* synchronize NAS SQN, based on 24.501 4.4.3.1 */ // Sequence number - int nas_sqn = pdu_buffer[6]; + int nas_sqn = buffer.buf[6]; int target_sqn = nas->security.nas_count_dl & 0xff; if (nas_sqn != target_sqn) { if (nas_sqn < target_sqn) @@ -189,28 +190,28 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, uint8_t *pdu_b stream_cipher.count = nas->security.nas_count_dl; stream_cipher.bearer = 1; /* todo: don't hardcode */ stream_cipher.direction = 1; - stream_cipher.message = pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1; + stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH - 1; /* length in bits */ - stream_cipher.blength = (pdu_length - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1) << 3; + stream_cipher.blength = (buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH + 1) << 3; stream_compute_integrity(nas->security_container->integrity_algorithm, &stream_cipher, computed_mac); - uint8_t *received_mac = pdu_buffer + 2; + uint8_t *received_mac = buffer.buf + 2; if (memcmp(received_mac, computed_mac, NAS_INTEGRITY_SIZE) != 0) return NAS_SECURITY_INTEGRITY_FAILED; /* decipher */ - uint8_t payload_len = pdu_length - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; + uint8_t payload_len = buffer.len - SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; uint8_t buf[payload_len]; stream_cipher.context = nas->security_container->ciphering_context; stream_cipher.count = nas->security.nas_count_dl; stream_cipher.bearer = 1; /* todo: don't hardcode */ stream_cipher.direction = 1; - stream_cipher.message = pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; + stream_cipher.message = buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH; /* length in bits */ stream_cipher.blength = (payload_len) << 3; stream_compute_encrypt(nas->security_container->ciphering_algorithm, &stream_cipher, buf); - memcpy(pdu_buffer + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH, buf, payload_len); + memcpy(buffer.buf + SECURITY_PROTECTED_5GS_NAS_MESSAGE_HEADER_LENGTH, buf, payload_len); nas->security.nas_count_dl++; @@ -1720,25 +1721,23 @@ void *nas_nrue(void *args_p) NAS_CONN_ESTABLI_CNF(msg_p).errCode, NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); - uint8_t *pdu_buffer = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data; - int pdu_length = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length; - - security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length); + byte_array_t ba = {.buf = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data, .len = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length}; + security_state_t security_state = nas_security_rx_process(nas, ba); if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) { LOG_E(NAS, "NAS integrity failed, discard incoming message: security state is %s\n", security_state_info[security_state].text); break; } - int msg_type = get_msg_type(pdu_buffer, pdu_length); + int msg_type = get_msg_type(ba.buf, ba.len); if (msg_type == FGS_REGISTRATION_ACCEPT) { - handle_registration_accept(nas, pdu_buffer, pdu_length); + handle_registration_accept(nas, ba.buf, ba.len); } else if (msg_type == FGS_PDU_SESSION_ESTABLISHMENT_ACC) { - handle_pdu_session_accept(pdu_buffer, pdu_length, nas->UE_id); + handle_pdu_session_accept(ba.buf, ba.len, nas->UE_id); } // Free NAS buffer memory after use (coming from RRC) - free(pdu_buffer); + free_byte_array(ba); break; } @@ -1794,7 +1793,7 @@ void *nas_nrue(void *args_p) byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; int msg_type = get_msg_type(pdu_buffer, pdu_length); - security_state_t security_state = nas_security_rx_process(nas, pdu_buffer, pdu_length); + security_state_t security_state = nas_security_rx_process(nas, buffer); /* special cases accepted without protection */ if (security_state == NAS_SECURITY_UNPROTECTED) { /* for the moment, only FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING is accepted */ From f672d9088ce23dffbd8881b8a5855429022ce3cf Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Sun, 4 May 2025 23:05:54 +0200 Subject: [PATCH 13/15] Simplify handling of security check of received NAS messages NAS 3GPP TS 24.501 clause 4.4.4.2 specify which NAS messages are an exception to the integrity protection. All the other scenarios are expected to be integrity protected. The nas_security_rx_process function is returning: 1) NAS_SECURITY_NO_SECURITY_CONTEXT if no security context is established 2) NAS_SECURITY_UNPROTECTED for the messages listed in 4.4.4.2 3) NAS_SECURITY_INTEGRITY_PASSED if the integrity check is successful These 3 results can be all considered successful. The other not. Also, add a function to check whether a message is allowed to be integrity unprotected, as per clause 4.2.2.2. --- openair3/NAS/NR_UE/nr_nas_msg.c | 57 ++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 58cb008af3..3ea9bea9e7 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -78,10 +78,10 @@ static nr_ue_nas_t nr_ue_nas[MAX_NAS_UE] = {0}; #define FOREACH_STATE(TYPE_DEF) \ TYPE_DEF(NAS_SECURITY_NO_SECURITY_CONTEXT, 0) \ - TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 1) \ - TYPE_DEF(NAS_SECURITY_UNPROTECTED, 2) \ - TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 3) \ - TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 4) \ + TYPE_DEF(NAS_SECURITY_UNPROTECTED, 1) \ + TYPE_DEF(NAS_SECURITY_INTEGRITY_PASSED, 2) \ + TYPE_DEF(NAS_SECURITY_NEW_SECURITY_CONTEXT, 3) \ + TYPE_DEF(NAS_SECURITY_INTEGRITY_FAILED, 4) \ TYPE_DEF(NAS_SECURITY_BAD_INPUT, 5) const char *nr_release_cause_desc[] = {"RRC_CONNECTION_FAILURE", "RRC_RESUME_FAILURE", "OTHER"}; @@ -131,7 +131,27 @@ static const char *print_info(uint8_t id, const text_info_t *array, uint8_t arra return "N/A"; } -static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t buffer) +/** @brief Check whether a message belongs to the list of messages that + * are allowed to be integrity unprotected (4.4.4.2 3GPP TS 24.501) */ +static bool unprotected_allowed(byte_array_t buffer, fgs_nas_msg_t msg_type) +{ + switch (msg_type) { + case FGS_IDENTITY_REQUEST: // check on SUCI done in the handler + case FGS_AUTHENTICATION_REQUEST: + case FGS_AUTHENTICATION_RESULT: + case FGS_AUTHENTICATION_REJECT: + case FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING: // for non switch off: deregistration type IE set to NORMAL_DEREGISTRATION + return true; + case FGS_REGISTRATION_REJECT: + case FGS_SERVICE_REJECT: + // unprotected if the 5GMM cause is not #76 + return buffer.buf[4] != Not_authorized_for_this_CAG_or_authorized_for_CAG_cells_only; + default: + return false; + } +} + +static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t buffer, fgs_nas_msg_t msg_type) { if (nas->security_container == NULL) return NAS_SECURITY_NO_SECURITY_CONTEXT; @@ -145,7 +165,7 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t b switch (security_type) { case PLAIN_5GS_MSG: - return NAS_SECURITY_UNPROTECTED; + return unprotected_allowed(buffer, msg_type) ? NAS_SECURITY_UNPROTECTED : NAS_SECURITY_BAD_INPUT; break; case INTEGRITY_PROTECTED: case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX: @@ -1272,7 +1292,7 @@ static void generateDeregistrationRequest(nr_ue_nas_t *nas, as_nas_info_t *initi // Plain 5GMM fgs_deregistration_request_ue_originating_msg *mm_msg = &sp_msg.plain.mm_msg.fgs_deregistration_request_ue_originating; - mm_msg->deregistrationtype.switchoff = NORMAL_DEREGISTRATION; + mm_msg->deregistrationtype.switchoff = NORMAL_DEREGISTRATION; // note: in case this is changed to SWITCH_OFF, handle in unprotected_allowed mm_msg->deregistrationtype.reregistration_required = REREGISTRATION_NOT_REQUIRED; mm_msg->deregistrationtype.access_type = TGPP_ACCESS; mm_msg->naskeysetidentifier.naskeysetidentifier = 1; @@ -1426,7 +1446,7 @@ static void generatePduSessionEstablishRequest(nr_ue_nas_t *nas, as_nas_info_t * } } -static uint8_t get_msg_type(uint8_t *pdu_buffer, uint32_t length) +static fgs_nas_msg_t get_msg_type(uint8_t *pdu_buffer, uint32_t length) { if (pdu_buffer == NULL) goto error; @@ -1722,14 +1742,13 @@ void *nas_nrue(void *args_p) NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length); byte_array_t ba = {.buf = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.nas_data, .len = NAS_CONN_ESTABLI_CNF(msg_p).nasMsg.length}; - security_state_t security_state = nas_security_rx_process(nas, ba); - if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) { + fgs_nas_msg_t msg_type = get_msg_type(ba.buf, ba.len); + security_state_t security_state = nas_security_rx_process(nas, ba, msg_type); + if (security_state > NAS_SECURITY_INTEGRITY_PASSED) { LOG_E(NAS, "NAS integrity failed, discard incoming message: security state is %s\n", security_state_info[security_state].text); break; } - int msg_type = get_msg_type(ba.buf, ba.len); - if (msg_type == FGS_REGISTRATION_ACCEPT) { handle_registration_accept(nas, ba.buf, ba.len); } else if (msg_type == FGS_PDU_SESSION_ESTABLISHMENT_ACC) { @@ -1791,17 +1810,9 @@ void *nas_nrue(void *args_p) uint8_t *pdu_buffer = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.nas_data; int pdu_length = NAS_DOWNLINK_DATA_IND(msg_p).nasMsg.length; byte_array_t buffer = {.buf = pdu_buffer, .len = pdu_length}; - int msg_type = get_msg_type(pdu_buffer, pdu_length); - - security_state_t security_state = nas_security_rx_process(nas, buffer); - /* special cases accepted without protection */ - if (security_state == NAS_SECURITY_UNPROTECTED) { - /* for the moment, only FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING is accepted */ - if (msg_type == FGS_DEREGISTRATION_ACCEPT_UE_ORIGINATING) - security_state = NAS_SECURITY_INTEGRITY_PASSED; - } - - if (security_state != NAS_SECURITY_INTEGRITY_PASSED && security_state != NAS_SECURITY_NO_SECURITY_CONTEXT) { + fgs_nas_msg_t msg_type = get_msg_type(pdu_buffer, pdu_length); + security_state_t security_state = nas_security_rx_process(nas, buffer, msg_type); + if (security_state > NAS_SECURITY_INTEGRITY_PASSED) { LOG_E(NAS, "NAS integrity failed, discard incoming message\n"); break; } From d405cbb77f180bba40195376f9c55fe4d71b4ef5 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Sun, 4 May 2025 23:27:35 +0200 Subject: [PATCH 14/15] Handle security header type "integrity protected with new 5G NAS security context" The Security Mode Command is the only NAS message sending a security header type "integrity protected with new 5G NAS security context". Such header type is indicating that there is no valid security context and it is indicating the current one configured by the AMF. When receiving a Security Mode Command the "AMF shall reset the downlink NAS COUNT" and use it to integrity protect the initial Security Mode Command. "The AMF shall send the SECURITY MODE COMMAND message unciphered, but shall integrity protect [...] The AMF shall set the security header type of the message to "integrity protected with new 5G NAS security context". See 5.4.2.2 3GPP TS 24.501. This commit is ensuring the NAS DL count is reset when the relevant security header is received. Also, the security container is deleted since no more valid and NAS_SECURITY_NO_SECURITY_CONTEXT is returned. The setup of the new security context is done by "handle_security_mode_command", --- openair3/NAS/NR_UE/nr_nas_msg.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 3ea9bea9e7..e5fea020c7 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -167,8 +167,13 @@ static security_state_t nas_security_rx_process(nr_ue_nas_t *nas, byte_array_t b case PLAIN_5GS_MSG: return unprotected_allowed(buffer, msg_type) ? NAS_SECURITY_UNPROTECTED : NAS_SECURITY_BAD_INPUT; break; - case INTEGRITY_PROTECTED: case INTEGRITY_PROTECTED_WITH_NEW_SECU_CTX: + stream_security_container_delete(nas->security_container); + nas->security_container = NULL; + nas->security.nas_count_dl = 0; + return NAS_SECURITY_NO_SECURITY_CONTEXT; + break; + case INTEGRITY_PROTECTED: case INTEGRITY_PROTECTED_AND_CIPHERED_WITH_NEW_SECU_CTX: /* only accept "integrity protected and ciphered" messages */ if (buffer.buf[6] == 0) From 16740e5eb076d94fe66366ca2ca1f1e81361fa1a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 5 May 2025 09:43:22 +0200 Subject: [PATCH 15/15] Use UL NAS Count to derive KgNB (6.8 of 3GPP TS 33.501) The UE shall use the currently stored UL NAS count to derive the KgNB. Hardcoding the UL count to 0 is wrong, if there is a reset to be done it should be explicitly done elsewhere. Closes #832 --- openair3/NAS/NR_UE/nr_nas_msg.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index e5fea020c7..d152a5d4a3 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -510,7 +510,7 @@ static void derive_ue_keys(uint8_t *buf, nr_ue_nas_t *nas) derive_kausf(ck, ik, sqn, kausf, nas->uicc); derive_kseaf(kausf, kseaf, nas->uicc); derive_kamf(kseaf, kamf, 0x0000, nas->uicc); - derive_kgnb(kamf, 0, kgnb); + derive_kgnb(kamf, nas->security.nas_count_ul, kgnb); printf("kausf:"); for (int i = 0; i < 32; i++) {