Medium severity6.5NVD Advisory· Published Mar 27, 2026· Updated Apr 20, 2026
CVE-2026-33903
CVE-2026-33903
Description
Ella Core is a 5G core designed for private networks. Versions prior to 1.7.0 panic when processing a specially crafted NGAP LocationReport message. An attacker able to send crafted NGAP messages to Ella Core can crash the process, causing service disruption for all connected subscribers. Version 1.7.0 adds guards in NGAP Location Report handler.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/ellanetworks/coreGo | < 1.7.0 | 1.7.0 |
Affected products
1Patches
1ec77a2ad4508fix: panic on missing IEs in NGAP Location Report (#1140)
6 files changed · +239 −0
internal/amf/ngap/handle_location_report.go+10 −0 modified@@ -90,6 +90,11 @@ func HandleLocationReport(ctx context.Context, amf *amfContext.AMF, ran *amfCont break } + if locationReportingRequestType.AreaOfInterestList == nil { + logger.WithTrace(ctx, ranUe.Log).Warn("AreaOfInterestList is nil, skipping area matching") + break + } + for _, uEPresenceInAreaOfInterestItem := range uEPresenceInAreaOfInterestList.List { uEPresence := uEPresenceInAreaOfInterestItem.UEPresence.Value referenceID := uEPresenceInAreaOfInterestItem.LocationReportingReferenceID.Value @@ -109,6 +114,11 @@ func HandleLocationReport(ctx context.Context, amf *amfContext.AMF, ran *amfCont logger.WithTrace(ctx, ranUe.Log).Info("sent location reporting control ngap message") case ngapType.EventTypePresentStopUePresenceInAreaOfInterest: + if locationReportingRequestType.LocationReportingReferenceIDToBeCancelled == nil { + logger.WithTrace(ctx, ranUe.Log).Warn("LocationReportingReferenceIDToBeCancelled is nil, skipping") + break + } + logger.WithTrace(ctx, ranUe.Log).Debug("To stop reporting UE presence in the area of interest", zap.Int64("ReferenceID", locationReportingRequestType.LocationReportingReferenceIDToBeCancelled.Value)) case ngapType.EventTypePresentCancelLocationReportingForTheUe:
internal/amf/ngap/handle_location_report_test.go+117 −0 modified@@ -98,3 +98,120 @@ func TestHandleLocationReport_UePresenceInAreaOfInterest_NilList(t *testing.T) { ngap.HandleLocationReport(context.Background(), amf, ran, msg) }) } + +// TestHandleLocationReport_StopUePresence_NilReferenceIDToBeCancelled verifies +// that a LocationReport with EventType=StopUePresenceInAreaOfInterest but +// without LocationReportingReferenceIDToBeCancelled does NOT panic. +// Reproduces GHSA-f2f3-9cx3-wcmf Bug 1. +func TestHandleLocationReport_StopUePresence_NilReferenceIDToBeCancelled(t *testing.T) { + ran := newTestRadio() + amf := newTestAMF() + ranUe := &amfContext.RanUe{ + RanUeNgapID: 1, + AmfUeNgapID: 1, + Radio: ran, + Log: logger.AmfLog, + } + ran.RanUEs[1] = ranUe + + msg := &ngapType.LocationReport{} + + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.LocationReportIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDRANUENGAPID}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentReject}, + Value: ngapType.LocationReportIEsValue{ + Present: ngapType.LocationReportIEsPresentRANUENGAPID, + RANUENGAPID: &ngapType.RANUENGAPID{Value: 1}, + }, + }) + + // EventType = StopUePresenceInAreaOfInterest, but + // LocationReportingReferenceIDToBeCancelled is nil (optional, omitted). + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.LocationReportIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDLocationReportingRequestType}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.LocationReportIEsValue{ + Present: ngapType.LocationReportIEsPresentLocationReportingRequestType, + LocationReportingRequestType: &ngapType.LocationReportingRequestType{ + EventType: ngapType.EventType{ + Value: ngapType.EventTypePresentStopUePresenceInAreaOfInterest, + }, + ReportArea: ngapType.ReportArea{ + Value: ngapType.ReportAreaPresentCell, + }, + // LocationReportingReferenceIDToBeCancelled deliberately nil + }, + }, + }) + + assertNoPanic(t, "HandleLocationReport(StopUePresence with nil ReferenceIDToBeCancelled)", func() { + ngap.HandleLocationReport(context.Background(), amf, ran, msg) + }) +} + +// TestHandleLocationReport_UePresence_NilAreaOfInterestList verifies that +// a LocationReport with EventType=UePresenceInAreaOfInterest and a non-nil +// UEPresenceInAreaOfInterestList but nil AreaOfInterestList does NOT panic. +// Reproduces GHSA-f2f3-9cx3-wcmf Bug 2. +func TestHandleLocationReport_UePresence_NilAreaOfInterestList(t *testing.T) { + ran := newTestRadio() + amf := newTestAMF() + ranUe := &amfContext.RanUe{ + RanUeNgapID: 1, + AmfUeNgapID: 1, + Radio: ran, + Log: logger.AmfLog, + } + ran.RanUEs[1] = ranUe + + msg := &ngapType.LocationReport{} + + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.LocationReportIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDRANUENGAPID}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentReject}, + Value: ngapType.LocationReportIEsValue{ + Present: ngapType.LocationReportIEsPresentRANUENGAPID, + RANUENGAPID: &ngapType.RANUENGAPID{Value: 1}, + }, + }) + + // EventType = UePresenceInAreaOfInterest with AreaOfInterestList nil (omitted). + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.LocationReportIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDLocationReportingRequestType}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.LocationReportIEsValue{ + Present: ngapType.LocationReportIEsPresentLocationReportingRequestType, + LocationReportingRequestType: &ngapType.LocationReportingRequestType{ + EventType: ngapType.EventType{ + Value: ngapType.EventTypePresentUePresenceInAreaOfInterest, + }, + ReportArea: ngapType.ReportArea{ + Value: ngapType.ReportAreaPresentCell, + }, + // AreaOfInterestList deliberately nil + }, + }, + }) + + // Provide a non-nil UEPresenceInAreaOfInterestList so the outer loop + // executes and the inner loop hits the nil AreaOfInterestList. + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.LocationReportIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDUEPresenceInAreaOfInterestList}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.LocationReportIEsValue{ + Present: ngapType.LocationReportIEsPresentUEPresenceInAreaOfInterestList, + UEPresenceInAreaOfInterestList: &ngapType.UEPresenceInAreaOfInterestList{ + List: []ngapType.UEPresenceInAreaOfInterestItem{ + { + LocationReportingReferenceID: ngapType.LocationReportingReferenceID{Value: 1}, + UEPresence: ngapType.UEPresence{Value: ngapType.UEPresencePresentIn}, + }, + }, + }, + }, + }) + + assertNoPanic(t, "HandleLocationReport(UePresence with nil AreaOfInterestList)", func() { + ngap.HandleLocationReport(context.Background(), amf, ran, msg) + }) +}
internal/amf/ngap/handle_ng_reset.go+2 −0 modified@@ -96,6 +96,8 @@ func HandleNGReset(ctx context.Context, ran *amfContext.Radio, msg *ngapType.NGR if ueAssociatedLogicalNGConnectionItem.RANUENGAPID != nil { logger.WithTrace(ctx, ran.Log).Warn("RANUENGAPID is not empty", zap.Int64("RanUeNgapID", ueAssociatedLogicalNGConnectionItem.RANUENGAPID.Value)) } + + continue } err := ranUe.Remove()
internal/amf/ngap/handle_ng_reset_test.go+34 −0 modified@@ -244,3 +244,37 @@ func TestHandleNGReset_EmptyIEs(t *testing.T) { ngap.HandleNGReset(context.Background(), ran, msg) }) } + +// TestHandleNGReset_PartOfNGInterface_UnknownUE verifies that a PartOfNGInterface +// reset referencing a RANUENGAPID that does not match any UE context does NOT +// panic or remove the wrong UE. This exercises the missing-continue bug where +// ranUe is nil after the lookup but Remove() is called anyway. +func TestHandleNGReset_PartOfNGInterface_UnknownUE(t *testing.T) { + fakeNGAPSender := &FakeNGAPSender{} + + ran := &amfContext.Radio{ + Log: logger.AmfLog, + NGAPSender: fakeNGAPSender, + RanUEs: map[int64]*amfContext.RanUe{}, + SupportedTAIs: make([]amfContext.SupportedTAI, 0), + } + + // Build an NGReset referencing a RANUENGAPID that doesn't exist. + msg, err := buildNGReset(&NGResetOpts{ + ResetType: ResetTypePresentPartOfNGInterface, + PartOfNGInterface: []NGInterface{ + {RanUENgapID: 999, AmfUENgapID: 999}, + }, + }) + if err != nil { + t.Fatalf("failed to build NGReset: %v", err) + } + + assertNoPanic(t, "HandleNGReset(PartOfNGInterface with unknown UE)", func() { + ngap.HandleNGReset(context.Background(), ran, msg.InitiatingMessage.Value.NGReset) + }) + + if len(fakeNGAPSender.SentNGResetAcknowledges) != 1 { + t.Fatalf("expected 1 NGResetAcknowledge, got %d", len(fakeNGAPSender.SentNGResetAcknowledges)) + } +}
internal/amf/ngap/handle_ue_context_release_complete.go+1 −0 modified@@ -151,6 +151,7 @@ func HandleUEContextReleaseComplete(ctx context.Context, amf *amfContext.AMF, ra smContext, ok := amfUe.SmContextFindByPDUSessionID(pduSessionID) if !ok { logger.WithTrace(ctx, ranUe.Log).Error("SmContext not found", zap.Uint8("PduSessionID", pduSessionID)) + continue } err := pdusession.DeactivateSmContext(ctx, smContext.Ref)
internal/amf/ngap/handle_ue_context_release_complete_test.go+75 −0 modified@@ -6,7 +6,10 @@ import ( "context" "testing" + amfContext "github.com/ellanetworks/core/internal/amf/context" "github.com/ellanetworks/core/internal/amf/ngap" + "github.com/ellanetworks/core/internal/amf/sctp" + "github.com/ellanetworks/core/internal/logger" "github.com/free5gc/ngap/ngapType" ) @@ -19,3 +22,75 @@ func TestHandleUEContextReleaseComplete_EmptyIEs(t *testing.T) { ngap.HandleUEContextReleaseComplete(context.Background(), amf, ran, msg) }) } + +// TestHandleUEContextReleaseComplete_SmContextNotFound verifies that a +// UEContextReleaseComplete referencing a PDU session ID that has no SmContext +// does NOT panic. Reproduces a nil pointer dereference on smContext.Ref when +// SmContextFindByPDUSessionID returns (nil, false) and the handler is missing +// a continue statement. +func TestHandleUEContextReleaseComplete_SmContextNotFound(t *testing.T) { + ran := newTestRadio() + amf := newTestAMF() + + // Create a UE in Registered state with an empty SmContextList. + amfUe := amfContext.NewAmfUe() + amfUe.SetState(amfContext.Registered) + amfUe.Log = logger.AmfLog + + ranUe := &amfContext.RanUe{ + RanUeNgapID: 1, + AmfUeNgapID: 100, + Radio: ran, + Log: logger.AmfLog, + AmfUe: amfUe, + } + amfUe.RanUe = ranUe + ran.RanUEs[1] = ranUe + + // Register the radio with the AMF so FindRanUeByAmfUeNgapID can find it. + amf.Radios = map[*sctp.SCTPConn]*amfContext.Radio{nil: ran} + + msg := &ngapType.UEContextReleaseComplete{} + + // AMFUENGAPID — mandatory, must match the ranUe. + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.UEContextReleaseCompleteIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDAMFUENGAPID}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.UEContextReleaseCompleteIEsValue{ + Present: ngapType.UEContextReleaseCompleteIEsPresentAMFUENGAPID, + AMFUENGAPID: &ngapType.AMFUENGAPID{Value: 100}, + }, + }) + + // RANUENGAPID — mandatory. + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.UEContextReleaseCompleteIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDRANUENGAPID}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.UEContextReleaseCompleteIEsValue{ + Present: ngapType.UEContextReleaseCompleteIEsPresentRANUENGAPID, + RANUENGAPID: &ngapType.RANUENGAPID{Value: 1}, + }, + }) + + // PDUSessionResourceListCxtRelCpl with a session ID that does NOT exist + // in the UE's SmContextList. This triggers SmContextFindByPDUSessionID + // returning (nil, false). + msg.ProtocolIEs.List = append(msg.ProtocolIEs.List, ngapType.UEContextReleaseCompleteIEs{ + Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDPDUSessionResourceListCxtRelCpl}, + Criticality: ngapType.Criticality{Value: ngapType.CriticalityPresentIgnore}, + Value: ngapType.UEContextReleaseCompleteIEsValue{ + Present: ngapType.UEContextReleaseCompleteIEsPresentPDUSessionResourceListCxtRelCpl, + PDUSessionResourceListCxtRelCpl: &ngapType.PDUSessionResourceListCxtRelCpl{ + List: []ngapType.PDUSessionResourceItemCxtRelCpl{ + { + PDUSessionID: ngapType.PDUSessionID{Value: 5}, // no SmContext for session 5 + }, + }, + }, + }, + }) + + assertNoPanic(t, "HandleUEContextReleaseComplete(SmContext not found)", func() { + ngap.HandleUEContextReleaseComplete(context.Background(), amf, ran, msg) + }) +}
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/ellanetworks/core/commit/ec77a2ad4508f8488cb356fd45b2f1efd92587f8nvdPatchWEB
- github.com/advisories/GHSA-f2f3-9cx3-wcmfghsaADVISORY
- github.com/ellanetworks/core/security/advisories/GHSA-f2f3-9cx3-wcmfnvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2026-33903ghsaADVISORY
- github.com/ellanetworks/core/releases/tag/v1.7.0nvdRelease NotesWEB
News mentions
0No linked articles in our index yet.