Bug: odd/even indication bit of the IMEI is left uninitialized in nas_5g::send_identity_response(), causing intermittent NAS Identity Response rejection by strict 5GC implementations
Summary
When srsUE answers a 5GS Identity Request for IMEI with an Identity Response, the odd/even indication bit of the 5GS mobile identity IE is never explicitly set for the IMEI case. Since imei_s::odd_even_indicator has no default member initializer, the transmitted bit ends up holding whatever value was already in that memory location — non-deterministically 0 ("even") or 1 ("odd").
Since a real IMEI always has 15 digits (an odd count), the only spec-compliant value is 1 ("odd"). When the bit happens to come out as 0, the resulting IE is inconsistent with 3GPP TS 24.501 §9.11.3.4 (an "even" indication implies the last semi-octet must be filler 1111, but here it holds a real digit). Some 5GC implementations (e.g. Open5GS) don't cross-validate this and accept the message anyway; other, stricter NAS decoders reject it outright, and the registration fails with no indication at the NGAP layer that anything is wrong (NGAP just carries the NAS-PDU as an opaque blob, so both the accepted and rejected captures look identical at that layer).
The exact same uninitialized-read pattern exists for imeisv_s::odd_even_indicator in the IMEISV case, though there it "accidentally" tends to work more often because the correct value for IMEISV (16 digits, even) happens to coincide with a zero-initialized bool.
How this was found
We run a lab 5G SA testbed (OCUDU gNB + srsUE, ZMQ loopback RF) registering against a proprietary, strictly-conformant 5GC. Registration was failing intermittently: the core kept resending the NAS Identity Request (PEI/IMEI) roughly every 5s (timer T3570), gave up after 5 attempts, and released the UE context — even though authentication and security setup had already completed successfully.
To isolate the problem, we captured the N2 (NGAP/SCTP) interface on the AMF side and compared two traces against the same core: our emulated UE (srsUE via OCUDU) and, as a reference, a real commercial handset registering through a separate 5G femtocell (its own independent CU/DU stack, unrelated to OCUDU/srsRAN). Both traces proceed identically through Authentication Request/Response and Security Mode Command/Complete — confirming USIM credentials, PLMN and security algorithms are all correct. The divergence appears immediately after: the real handset's Identity Response is accepted and the registration reaches Registration Accept + PDU session establishment, while srsUE's Identity Response is rejected every time, triggering the repeated Identity Request/T3570/UEContextReleaseCommand sequence described above.
| Step |
srsUE (OCUDU) |
Real handset (femtocell) |
| RRC Connected |
✔ |
✔ |
| Authentication Request/Response |
✔ |
✔ |
| Security Mode Command/Complete |
✔ |
✔ |
| Identity Request (IMEI) |
5 attempts, never accepted |
not requested by the core in this run |
| Registration Accept |
✘ |
✔ |
| Outcome |
UEContextReleaseCommand |
Registration complete |
Diffing the Identity Response NAS-PDU byte-for-byte between a rejected srsUE attempt and an accepted real-handset attempt showed every byte identical except one nibble — which is what led us to the odd/even indication bit and from there to the missing initialization in send_identity_response().
Why this isn't an OCUDU (gNB) issue: the gNB never touches NAS-PDU content — it's integrity-protected/ciphered end-to-end between UE and AMF and is forwarded by the RAN as an opaque blob over NGAP. The buggy code lives entirely in nas_5g.cc, which is compiled into the srsue binary, not into the gNB. The real-handset reference trace above also went through a completely different RAN (a commercial femtocell with its own CU/DU, no OCUDU/srsRAN involved at all) and had no issue — confirming the divergence tracks the UE's NAS stack, not the radio/gNB side.
Affected code
srsue/src/stack/upper/nas_5g.cc, function nas_5g::send_identity_response():
case (identity_type_5gs_t::identity_types_::imei): {
srsran::nas_5g::mobile_identity_5gs_t::imei_s& imei = identity_response.mobile_identity.set_imei();
usim->get_imei_vec(imei.imei.data(), 15);
// <-- imei.odd_even_indicator is never set here
} break;
case (identity_type_5gs_t::identity_types_::imeisv): {
srsran::nas_5g::mobile_identity_5gs_t::imeisv_s& imeisv = identity_response.mobile_identity.set_imeisv();
usim->get_imei_vec(imeisv.imeisv.data(), 15);
imeisv.imeisv[14] = ue_svn_oct1;
imeisv.imeisv[15] = ue_svn_oct2;
// <-- imeisv.odd_even_indicator is never set here either
} break;
lib/include/srsran/asn1/nas_5g_ies.h:
class imei_s
{
public:
bool odd_even_indicator; // no default initializer
std::array<uint8_t, 15> imei;
...
};
Expected behavior
The Identity Response for IMEI should always encode odd/even indication = 1 (odd), deterministically, on every attempt (a real IMEI always has 15 digits).
Actual behavior
The bit is effectively random across runs, because it's read from uninitialized memory. Roughly 1 in N attempts encodes it as 0 (even), which strict 5GC NAS decoders reject.
Evidence
Two UplinkNASTransport / Identity Response NGAP dissections, same lab, same IMEI family, decoded with a null-ciphering NAS security context (EEA0) so the embedded plain NAS message is directly visible:
Rejected attempt (srsUE/OCUDU emulated UE):
NAS-PDU: 7e0281ac0f88017e005c00083305034574570043
Security header type: Integrity protected and ciphered (2)
Message type: Identity response (0x5c)
5GS mobile identity
Length: 8
Odd/even indication: Even number of identity digits <-- bit = 0
Type of identity: IMEI (3)
IMEI: 350305447750034
Accepted attempt (real commercial handset via femtocell):
NAS-PDU: 7e024aef41b7017e005c00083b05034574570003
Security header type: Integrity protected and ciphered (2)
Message type: Identity response (0x5c)
5GS mobile identity
Length: 8
Odd/even indication: Odd number of identity digits <-- bit = 1
Type of identity: IMEI (3)
IMEI: 350305447750030
Only bit 4 of the type-of-identity octet differs (0x33 vs 0x3b); everything else in the IE, including all 15 IMEI digit nibbles, follows the same encoding.
Suggested fix
case (identity_type_5gs_t::identity_types_::imei): {
srsran::nas_5g::mobile_identity_5gs_t::imei_s& imei = identity_response.mobile_identity.set_imei();
usim->get_imei_vec(imei.imei.data(), 15);
imei.odd_even_indicator = true; // IMEI is always 15 digits -> odd
} break;
case (identity_type_5gs_t::identity_types_::imeisv): {
srsran::nas_5g::mobile_identity_5gs_t::imeisv_s& imeisv = identity_response.mobile_identity.set_imeisv();
usim->get_imei_vec(imeisv.imeisv.data(), 15);
imeisv.imeisv[14] = ue_svn_oct1;
imeisv.imeisv[15] = ue_svn_oct2;
imeisv.odd_even_indicator = false; // IMEISV is always 16 digits -> even
} break;
We have this fix applied and rebuilt locally, and confirmed it makes the registration succeed deterministically against the same strict 5GC. Happy to open a PR with it if useful — flagging as an issue first in case there's a preferred process or additional context we're missing.
Environment
- srsRAN_4G commit:
6bcbd9e5b (branch agpl_next, merge of next)
- UE: srsUE (ZMQ loopback RF) against an OCUDU gNB (ZMQ)
- 5GC: proprietary/commercial 5GC with strict NAS IE conformance checking (Open5GS does not reproduce this, as it doesn't cross-validate the odd/even bit against IE content)
- PLMN 001/01, identity type requested: IMEI
Bug:
odd/even indicationbit of the IMEI is left uninitialized innas_5g::send_identity_response(), causing intermittent NAS Identity Response rejection by strict 5GC implementationsSummary
When srsUE answers a 5GS
Identity Requestfor IMEI with anIdentity Response, theodd/even indicationbit of the5GS mobile identityIE is never explicitly set for the IMEI case. Sinceimei_s::odd_even_indicatorhas no default member initializer, the transmitted bit ends up holding whatever value was already in that memory location — non-deterministically 0 ("even") or 1 ("odd").Since a real IMEI always has 15 digits (an odd count), the only spec-compliant value is
1("odd"). When the bit happens to come out as0, the resulting IE is inconsistent with 3GPP TS 24.501 §9.11.3.4 (an "even" indication implies the last semi-octet must be filler1111, but here it holds a real digit). Some 5GC implementations (e.g. Open5GS) don't cross-validate this and accept the message anyway; other, stricter NAS decoders reject it outright, and the registration fails with no indication at the NGAP layer that anything is wrong (NGAP just carries the NAS-PDU as an opaque blob, so both the accepted and rejected captures look identical at that layer).The exact same uninitialized-read pattern exists for
imeisv_s::odd_even_indicatorin the IMEISV case, though there it "accidentally" tends to work more often because the correct value for IMEISV (16 digits, even) happens to coincide with a zero-initialized bool.How this was found
We run a lab 5G SA testbed (OCUDU gNB + srsUE, ZMQ loopback RF) registering against a proprietary, strictly-conformant 5GC. Registration was failing intermittently: the core kept resending the NAS
Identity Request(PEI/IMEI) roughly every 5s (timer T3570), gave up after 5 attempts, and released the UE context — even though authentication and security setup had already completed successfully.To isolate the problem, we captured the N2 (NGAP/SCTP) interface on the AMF side and compared two traces against the same core: our emulated UE (srsUE via OCUDU) and, as a reference, a real commercial handset registering through a separate 5G femtocell (its own independent CU/DU stack, unrelated to OCUDU/srsRAN). Both traces proceed identically through
Authentication Request/ResponseandSecurity Mode Command/Complete— confirming USIM credentials, PLMN and security algorithms are all correct. The divergence appears immediately after: the real handset'sIdentity Responseis accepted and the registration reachesRegistration Accept+ PDU session establishment, while srsUE'sIdentity Responseis rejected every time, triggering the repeatedIdentity Request/T3570/UEContextReleaseCommandsequence described above.UEContextReleaseCommandDiffing the
Identity ResponseNAS-PDU byte-for-byte between a rejected srsUE attempt and an accepted real-handset attempt showed every byte identical except one nibble — which is what led us to theodd/even indicationbit and from there to the missing initialization insend_identity_response().Why this isn't an OCUDU (gNB) issue: the gNB never touches NAS-PDU content — it's integrity-protected/ciphered end-to-end between UE and AMF and is forwarded by the RAN as an opaque blob over NGAP. The buggy code lives entirely in
nas_5g.cc, which is compiled into the srsue binary, not into the gNB. The real-handset reference trace above also went through a completely different RAN (a commercial femtocell with its own CU/DU, no OCUDU/srsRAN involved at all) and had no issue — confirming the divergence tracks the UE's NAS stack, not the radio/gNB side.Affected code
srsue/src/stack/upper/nas_5g.cc, functionnas_5g::send_identity_response():lib/include/srsran/asn1/nas_5g_ies.h:Expected behavior
The
Identity Responsefor IMEI should always encodeodd/even indication = 1 (odd), deterministically, on every attempt (a real IMEI always has 15 digits).Actual behavior
The bit is effectively random across runs, because it's read from uninitialized memory. Roughly 1 in N attempts encodes it as
0 (even), which strict 5GC NAS decoders reject.Evidence
Two
UplinkNASTransport/Identity ResponseNGAP dissections, same lab, same IMEI family, decoded with a null-ciphering NAS security context (EEA0) so the embedded plain NAS message is directly visible:Rejected attempt (srsUE/OCUDU emulated UE):
Accepted attempt (real commercial handset via femtocell):
Only bit 4 of the type-of-identity octet differs (
0x33vs0x3b); everything else in the IE, including all 15 IMEI digit nibbles, follows the same encoding.Suggested fix
We have this fix applied and rebuilt locally, and confirmed it makes the registration succeed deterministically against the same strict 5GC. Happy to open a PR with it if useful — flagging as an issue first in case there's a preferred process or additional context we're missing.
Environment
6bcbd9e5b(branchagpl_next, merge ofnext)