VYPR
Medium severity4.3NVD Advisory· Published May 18, 2026· Updated May 18, 2026

CVE-2026-8782

CVE-2026-8782

Description

A weakness has been identified in omec-project amf up to 2.1.3-dev. This affects an unknown function of the file ngap/handler.go of the component NGAP Message Handler. This manipulation causes null pointer dereference. Remote exploitation of the attack is possible. The exploit has been made available to the public and could be used for attacks. Upgrading to version 2.2.0 mitigates this issue. It is recommended to upgrade the affected component. The same pull request fixes multiple security issues.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A null pointer dereference in omec-project AMF's NGAP handler (<= 2.1.3-dev) allows remote attackers to crash the 5G core control plane.

Vulnerability

A null pointer dereference vulnerability exists in the NGAP message handler of the omec-project Access and Mobility Management Function (AMF) up to version 2.1.3-dev. The flaw is located in the ngap/handler.go file, specifically within the HandleLocationReportingFailureIndication function, which processes a malformed LocationReportFailureIndication NGAP message [2]. The affected component is part of the 5G core network control plane [1]. The vulnerability is triggered when the AMF receives a specially crafted NGAP message, causing a nil pointer dereference and a panic [2]. Versions prior to the fix (v2.2.0) are affected [3][4].

Exploitation

An attacker with network access to the AMF's N2 interface can send a malformed LocationReportFailureIndication NGAP message to trigger the vulnerability [2]. The exploit does not require authentication or special privileges; remote exploitation is possible [2]. By sending the crafted NGAP message (e.g., hex sequence 00 11 40 19 80 00 03 FF FF 00 06 80 F6 9C 0B 6B 63 00 54 00 02 00 00 00 0F 40 02 00 00), the attacker causes the AMF to crash with a segmentation violation, as demonstrated in publicly available reproduction steps [2].

Impact

Successful exploitation results in a denial-of-service (DoS) condition of the AMF, causing it to crash and become unavailable. Since the AMF is a critical control plane function in the 5G core network handling registration, mobility management, and NAS signaling, its crash can disrupt network services for all connected UEs and gNodeBs [1]. The attack achieves a denial-of-service impact on system availability; confidentiality and integrity are not directly compromised [2].

Mitigation

The vulnerability is fixed in AMF version 2.2.0, released on April 23, 2026 [4]. Upgrading to v2.2.0 or later mitigates the issue [4]. The fix was introduced in pull request #666, which adds nil guards for remaining NGAP handlers [3][4]. The official Docker image omecproject/5gc-amf:v2.2.0 is available, and the latest release rel-2.2.1 also contains the fix [2][4]. No workarounds are documented; upgrading is the recommended action [2].

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2
  • Free5gc/Amfreferences2 versions
    (expand)+ 1 more
    • (no CPE)
    • (no CPE)range: <=2.1.3-dev

Patches

1
34bc6724acc9

Address multiple cases of crashes due to malformed NGAP input (#666)

https://github.com/omec-project/amfGabriel ArroboApr 21, 2026via nvd-ref
12 files changed · +431 29
  • .github/workflows/main.yml+1 0 modified
    @@ -115,6 +115,7 @@ jobs:
           repository-projects: read
           security-events: write
           statuses: read
    +      vulnerability-alerts: read
         uses: omec-project/.github/.github/workflows/scorecard-analysis.yml@0bce626c84023cadf638ceff2af19c73a7201bf8 # v0.0.26
         with:
           branch_name: ${{ github.ref }}
    
  • gmm/handler.go+14 2 modified
    @@ -571,7 +571,13 @@ func HandleRegistrationRequest(ctx ctxt.Context, ue *context.AmfUe, anType model
     		if err != nil {
     			return fmt.Errorf("decode SUCI failed: %w", err)
     		}
    -		ue.PlmnId = util.PlmnIdStringToModels(plmnId)
    +		plmnID, err := util.PlmnIdStringToModels(plmnId)
    +		if err != nil {
    +			err = fmt.Errorf("invalid SUCI: %w", err)
    +			ue.GmmLog.Errorln(err)
    +			return err
    +		}
    +		ue.PlmnId = plmnID
     		ue.GmmLog.Debugf("SUCI: %s", ue.Suci)
     	case nasMessage.MobileIdentity5GSType5gGuti:
     		guamiFromUeGutiTmp, guti := nasConvert.GutiToString(mobileIdentity5GSContents)
    @@ -1521,7 +1527,13 @@ func HandleIdentityResponse(ue *context.AmfUe, identityResponse *nasMessage.Iden
     		if err != nil {
     			return fmt.Errorf("decode SUCI failed: %w", err)
     		}
    -		ue.PlmnId = util.PlmnIdStringToModels(plmnId)
    +		plmnID, err := util.PlmnIdStringToModels(plmnId)
    +		if err != nil {
    +			err = fmt.Errorf("invalid SUCI: %w", err)
    +			ue.GmmLog.Errorln(err)
    +			return err
    +		}
    +		ue.PlmnId = plmnID
     		ue.GmmLog.Debugf("get SUCI: %s", ue.Suci)
     	case nasMessage.MobileIdentity5GSType5gGuti:
     		if ue.MacFailed {
    
  • gmm/handler_test.go+15 0 modified
    @@ -24,6 +24,21 @@ import (
     	"go.uber.org/zap"
     )
     
    +func TestHandleIdentityResponseRejectsUndecodableSuci(t *testing.T) {
    +	ue := &context.AmfUe{GmmLog: zap.NewNop().Sugar()}
    +	identityResponse := nasMessage.NewIdentityResponse(0)
    +	identityResponse.SetLen(1)
    +	identityResponse.Buffer[0] = nasMessage.MobileIdentity5GSTypeSuci
    +
    +	err := HandleIdentityResponse(ue, identityResponse)
    +	if err == nil {
    +		t.Fatal("expected malformed SUCI identity response to fail")
    +	}
    +	if !strings.Contains(err.Error(), "decode SUCI failed") {
    +		t.Fatalf("expected SUCI decode failure, got %v", err)
    +	}
    +}
    +
     func newFuzzUE(fd *FuzzData) *context.AmfUe {
     	ue := &context.AmfUe{
     		GmmLog:       zap.NewNop().Sugar(),
    
  • metrics/telemetry.go+21 0 modified
    @@ -12,7 +12,9 @@
     package metrics
     
     import (
    +	"encoding/hex"
     	"net/http"
    +	"unicode/utf8"
     
     	"github.com/omec-project/amf/logger"
     	"github.com/prometheus/client_golang/prometheus"
    @@ -71,10 +73,29 @@ func InitMetrics() {
     
     // IncrementNgapMsgStats increments message level stats
     func IncrementNgapMsgStats(amfID, msgType, direction, result, reason string) {
    +	amfID = sanitizeLabelValue(amfID)
    +	msgType = sanitizeLabelValue(msgType)
    +	direction = sanitizeLabelValue(direction)
    +	result = sanitizeLabelValue(result)
    +	reason = sanitizeLabelValue(reason)
     	amfStats.ngapMsg.WithLabelValues(amfID, msgType, direction, result, reason).Inc()
     }
     
    +// sanitizeLabelValue ensures a string is valid UTF-8 for use as Prometheus label value
    +func sanitizeLabelValue(s string) string {
    +	if utf8.ValidString(s) {
    +		return s
    +	}
    +	// If not valid UTF-8, convert to hex representation with prefix
    +	// to make it clear this is sanitized data
    +	return "hex:" + hex.EncodeToString([]byte(s))
    +}
    +
     // SetGnbSessProfileStats maintains Session profile info
     func SetGnbSessProfileStats(id, ip, state, tac string, count uint64) {
    +	id = sanitizeLabelValue(id)
    +	ip = sanitizeLabelValue(ip)
    +	state = sanitizeLabelValue(state)
    +	tac = sanitizeLabelValue(tac)
     	amfStats.gnbSessionProfile.WithLabelValues(id, ip, state, tac).Set(float64(count))
     }
    
  • nas/nas_security/security.go+4 0 modified
    @@ -185,6 +185,10 @@ func FetchUeContextWithMobileIdentity(payload []byte) *context.AmfUe {
     		}
     	} else if msg.GmmHeader.GetMessageType() == nas.MsgTypeDeregistrationRequestUEOriginatingDeregistration {
     		mobileIdentity5GSContents := msg.DeregistrationRequestUEOriginatingDeregistration.GetMobileIdentity5GSContents()
    +		if len(mobileIdentity5GSContents) == 0 {
    +			logger.CommLog.Errorln("MobileIdentity5GSContents is empty")
    +			return nil
    +		}
     		if nasMessage.MobileIdentity5GSType5gGuti == nasConvert.GetTypeOfIdentity(mobileIdentity5GSContents[0]) {
     			_, guti = nasConvert.GutiToString(mobileIdentity5GSContents)
     			logger.CommLog.Debugf("Guti received in Deregistraion Request Message: %v", guti)
    
  • ngap/dispatcher.go+39 3 modified
    @@ -30,6 +30,11 @@ import (
     var tracer = otel.Tracer("amf/ngap")
     
     func DispatchLb(ctx ctxt.Context, sctplbMsg *sdcoreAmfServer.SctplbMessage, Amf2RanMsgChan chan *sdcoreAmfServer.AmfMessage) {
    +	if sctplbMsg == nil {
    +		logger.NgapLog.Errorln("dispatchLb received nil SCTP LB message")
    +		return
    +	}
    +
     	logger.NgapLog.Infof("dispatchLb GnbId:%v GnbIp: %v %T", sctplbMsg.GnbId, sctplbMsg.GnbIpAddr, Amf2RanMsgChan)
     	var ran *context.AmfRan
     	amfSelf := context.AMF_Self()
    @@ -53,6 +58,11 @@ func DispatchLb(ctx ctxt.Context, sctplbMsg *sdcoreAmfServer.SctplbMessage, Amf2
     		logger.NgapLog.Infoln("dispatchLb, Create new Amf RAN with GnbIpAddress", sctplbMsg.GnbIpAddr)
     	}
     
    +	if ran == nil {
    +		logger.NgapLog.Errorln("dispatchLb could not resolve or create RAN context")
    +		return
    +	}
    +
     	if len(sctplbMsg.Msg) == 0 {
     		logger.NgapLog.Infof("dispatchLb, Message of size 0 - ", sctplbMsg.GnbId)
     		ran.Log.Infoln("RAN close the connection")
    @@ -122,6 +132,11 @@ func DispatchLb(ctx ctxt.Context, sctplbMsg *sdcoreAmfServer.SctplbMessage, Amf2
     }
     
     func Dispatch(conn net.Conn, msg []byte) {
    +	if conn == nil {
    +		logger.NgapLog.Errorln("dispatch received nil connection")
    +		return
    +	}
    +
     	var ran *context.AmfRan
     	amfSelf := context.AMF_Self()
     
    @@ -175,6 +190,15 @@ func NgapMsgHandler(ue *context.AmfUe, msg context.NgapMsg) {
     }
     
     func DispatchNgapMsg(ctx ctxt.Context, ran *context.AmfRan, pdu *ngapType.NGAPPDU, sctplbMsg *sdcoreAmfServer.SctplbMessage) {
    +	if pdu == nil {
    +		if ran != nil && ran.Log != nil {
    +			ran.Log.Errorln("DispatchNgapMsg received nil NGAP PDU")
    +		} else {
    +			logger.NgapLog.Errorln("DispatchNgapMsg received nil NGAP PDU")
    +		}
    +		return
    +	}
    +
     	var code int64
     	switch pdu.Present {
     	case ngapType.NGAPPDUPresentInitiatingMessage:
    @@ -219,7 +243,11 @@ func DispatchNgapMsg(ctx ctxt.Context, ran *context.AmfRan, pdu *ngapType.NGAPPD
     	case ngapType.NGAPPDUPresentInitiatingMessage:
     		initiatingMessage := pdu.InitiatingMessage
     		if initiatingMessage == nil {
    -			ran.Log.Errorln("Initiating Message is nil")
    +			if ran != nil && ran.Log != nil {
    +				ran.Log.Errorln("Initiating Message is nil")
    +			} else {
    +				logger.NgapLog.Errorln("Initiating Message is nil")
    +			}
     			return
     		}
     
    @@ -281,7 +309,11 @@ func DispatchNgapMsg(ctx ctxt.Context, ran *context.AmfRan, pdu *ngapType.NGAPPD
     	case ngapType.NGAPPDUPresentSuccessfulOutcome:
     		successfulOutcome := pdu.SuccessfulOutcome
     		if successfulOutcome == nil {
    -			ran.Log.Errorln("successful Outcome is nil")
    +			if ran != nil && ran.Log != nil {
    +				ran.Log.Errorln("successful Outcome is nil")
    +			} else {
    +				logger.NgapLog.Errorln("successful Outcome is nil")
    +			}
     			return
     		}
     		metrics.IncrementNgapMsgStats(context.AMF_Self().NfId,
    @@ -316,7 +348,11 @@ func DispatchNgapMsg(ctx ctxt.Context, ran *context.AmfRan, pdu *ngapType.NGAPPD
     	case ngapType.NGAPPDUPresentUnsuccessfulOutcome:
     		unsuccessfulOutcome := pdu.UnsuccessfulOutcome
     		if unsuccessfulOutcome == nil {
    -			ran.Log.Errorln("unsuccessful Outcome is nil")
    +			if ran != nil && ran.Log != nil {
    +				ran.Log.Errorln("unsuccessful Outcome is nil")
    +			} else {
    +				logger.NgapLog.Errorln("unsuccessful Outcome is nil")
    +			}
     			return
     		}
     		metrics.IncrementNgapMsgStats(context.AMF_Self().NfId,
    
  • ngap/dispatcher_test.go+41 0 added
    @@ -0,0 +1,41 @@
    +// Copyright (C) 2026 Intel Corporation
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +//
    +
    +package ngap
    +
    +import (
    +	ctxt "context"
    +	"testing"
    +
    +	"github.com/omec-project/amf/context"
    +	"github.com/omec-project/amf/protos/sdcoreAmfServer"
    +	"go.uber.org/zap"
    +)
    +
    +func TestDispatchLbIgnoresMissingRanContext(t *testing.T) {
    +	msg := &sdcoreAmfServer.SctplbMessage{
    +		Msg: []byte{0x00},
    +	}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("DispatchLb panicked with missing RAN identity: %v", recovered)
    +		}
    +	}()
    +
    +	DispatchLb(ctxt.Background(), msg, make(chan *sdcoreAmfServer.AmfMessage, 1))
    +}
    +
    +func TestDispatchNgapMsgIgnoresNilPdu(t *testing.T) {
    +	ran := &context.AmfRan{Log: zap.NewNop().Sugar()}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("DispatchNgapMsg panicked with nil PDU: %v", recovered)
    +		}
    +	}()
    +
    +	DispatchNgapMsg(ctxt.Background(), ran, nil, nil)
    +}
    
  • ngap/handler.go+125 21 modified
    @@ -32,6 +32,22 @@ import (
     	mi "github.com/omec-project/util/metricinfo"
     )
     
    +func findRanUeByRanNgapID(ran *context.AmfRan, ranUENGAPID *ngapType.RANUENGAPID) *context.RanUe {
    +	if ranUENGAPID == nil {
    +		ran.Log.Errorln("RANUENGAPID is nil")
    +		return nil
    +	}
    +	return ran.RanUeFindByRanUeNgapID(ranUENGAPID.Value)
    +}
    +
    +func findRanUeByAmfNgapID(ran *context.AmfRan, aMFUENGAPID *ngapType.AMFUENGAPID) *context.RanUe {
    +	if aMFUENGAPID == nil {
    +		ran.Log.Errorln("AMFUENGAPID is nil")
    +		return nil
    +	}
    +	return context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +}
    +
     func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context.RanUe, *ngapType.AMFUENGAPID) {
     	amfSelf := context.AMF_Self()
     
    @@ -82,11 +98,11 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					ran.Log.Debugln("decode IE 5G-S-TMSI")
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     			if ranUe == nil {
     				var err error
     
    -				if fiveGSTMSI != nil {
    +				if fiveGSTMSI != nil && rANUENGAPID != nil {
     					servedGuami := amfSelf.ServedGuamiList[0]
     
     					// <5G-S-TMSI> := <AMF Set ID><AMF Pointer><5G-TMSI>
    @@ -134,7 +150,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     		case ngapType.ProcedureCodeHandoverCancel:
     			ngapMsg := initiatingMessage.Value.HandoverCancel
     			for i := 0; i < len(ngapMsg.ProtocolIEs.List); i++ {
    @@ -151,7 +167,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     		case ngapType.ProcedureCodeUEContextReleaseRequest:
     			ngapMsg := initiatingMessage.Value.UEContextReleaseRequest
     			for i := 0; i < len(ngapMsg.ProtocolIEs.List); i++ {
    @@ -173,9 +189,9 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     			if ranUe == nil {
    -				ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +				ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     			}
     		case ngapType.ProcedureCodeNASNonDeliveryIndication:
     			ngapMsg := initiatingMessage.Value.NASNonDeliveryIndication
    @@ -193,7 +209,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     		case ngapType.ProcedureCodeLocationReportingFailureIndication:
     		case ngapType.ProcedureCodeErrorIndication:
     		case ngapType.ProcedureCodeUERadioCapabilityInfoIndication:
    @@ -212,7 +228,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodeHandoverNotification:
     			ngapMsg := initiatingMessage.Value.HandoverNotify
    @@ -266,7 +282,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodePathSwitchRequest:
     			ngapMsg := initiatingMessage.Value.PathSwitchRequest
    @@ -282,7 +298,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     		case ngapType.ProcedureCodeLocationReport:
     		case ngapType.ProcedureCodeUplinkUEAssociatedNRPPaTransport:
     		case ngapType.ProcedureCodeUplinkRANConfigurationTransfer:
    @@ -300,7 +316,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     		case ngapType.ProcedureCodeCellTrafficTrace:
     		case ngapType.ProcedureCodeUplinkRANStatusTransfer:
     		case ngapType.ProcedureCodeUplinkNonUEAssociatedNRPPaTransport:
    @@ -329,7 +345,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     
     		case ngapType.ProcedureCodePDUSessionResourceRelease:
     			ngapMsg := successfulOutcome.Value.PDUSessionResourceReleaseResponse
    @@ -347,7 +363,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodeUERadioCapabilityCheck:
     		case ngapType.ProcedureCodeAMFConfigurationUpdate:
    @@ -367,7 +383,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodeUEContextModification:
     			ngapMsg := successfulOutcome.Value.UEContextModificationResponse
    @@ -385,7 +401,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodePDUSessionResourceSetup:
     			ngapMsg := successfulOutcome.Value.PDUSessionResourceSetupResponse
    @@ -403,7 +419,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodePDUSessionResourceModify:
     			ngapMsg := successfulOutcome.Value.PDUSessionResourceModifyResponse
    @@ -437,7 +453,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     		}
     	case ngapType.NGAPPDUPresentUnsuccessfulOutcome:
     		unsuccessfulOutcome := message.UnsuccessfulOutcome
    @@ -463,7 +479,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodeUEContextModification:
     			ngapMsg := unsuccessfulOutcome.Value.UEContextModificationFailure
    @@ -481,7 +497,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					aMFUENGAPID = ie.Value.AMFUENGAPID
     				}
     			}
    -			ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
    +			ranUe = findRanUeByRanNgapID(ran, rANUENGAPID)
     
     		case ngapType.ProcedureCodeHandoverResourceAllocation:
     			ngapMsg := unsuccessfulOutcome.Value.HandoverFailure
    @@ -497,7 +513,7 @@ func FetchRanUeContext(ran *context.AmfRan, message *ngapType.NGAPPDU) (*context
     					}
     				}
     			}
    -			ranUe = context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
    +			ranUe = findRanUeByAmfNgapID(ran, aMFUENGAPID)
     		}
     	}
     	return ranUe, aMFUENGAPID
    @@ -581,6 +597,13 @@ func HandleNGSetupRequest(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     	if pagingDRX != nil {
     		ran.Log.Debugf("PagingDRX[%d]", pagingDRX.Value)
     	}
    +	if supportedTAList == nil {
    +		ran.Log.Warnln("NG-Setup failure: SupportedTAList is missing")
    +		cause.Present = ngapType.CausePresentMisc
    +		cause.Misc = &ngapType.CauseMisc{Value: ngapType.CauseMiscPresentUnspecified}
    +		ngap_message.SendNGSetupFailure(ran, cause)
    +		return
    +	}
     
     	// Clearing any existing contents of ran.SupportedTAList
     	if len(ran.SupportedTAList) != 0 {
    @@ -809,6 +832,10 @@ func HandleNGReset(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     			}
     		}
     	}
    +	if resetType == nil {
    +		ran.Log.Errorln("ResetType is missing")
    +		return
    +	}
     
     	printAndGetCause(ran, cause)
     
    @@ -850,6 +877,7 @@ func HandleNGReset(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     				if ueAssociatedLogicalNGConnectionItem.RANUENGAPID != nil {
     					ran.Log.Warnf("RanUeNgapID[%d]", ueAssociatedLogicalNGConnectionItem.RANUENGAPID.Value)
     				}
    +				continue
     			}
     
     			err := ranUe.Remove()
    @@ -1358,6 +1386,10 @@ func HandleUERadioCapabilityCheckResponse(ran *context.AmfRan, message *ngapType
     			ran.Log.Debugln("decode IE CriticalityDiagnostics")
     		}
     	}
    +	if rANUENGAPID == nil {
    +		ran.Log.Errorln("RanUeNgapID is missing")
    +		return
    +	}
     
     	ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
     	if ranUe == nil {
    @@ -2245,6 +2277,14 @@ func HandlePDUSessionResourceModifyIndication(ctx ctxt.Context, ran *context.Amf
     		ngap_message.SendErrorIndication(ran, nil, nil, nil, &criticalityDiagnostics)
     		return
     	}
    +	if rANUENGAPID == nil {
    +		ran.Log.Errorln("RanUeNgapID is missing")
    +		return
    +	}
    +	if pduSessionResourceModifyIndicationList == nil {
    +		ran.Log.Errorln("PDUSessionResourceModifyListModInd is missing")
    +		return
    +	}
     
     	ranUe = ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
     	if ranUe == nil {
    @@ -2277,6 +2317,7 @@ func HandlePDUSessionResourceModifyIndication(ctx ctxt.Context, ran *context.Amf
     		smContext, ok := amfUe.SmContextFindByPDUSessionID(pduSessionID)
     		if !ok {
     			ranUe.Log.Errorf("SmContext[PDU Session ID:%d] not found", pduSessionID)
    +			continue
     		}
     		response, errResponse, _, err := consumer.SendUpdateSmContextN2Info(ctx, amfUe, smContext,
     			models.N2SmInfoType_PDU_RES_MOD_IND, transfer)
    @@ -3012,6 +3053,14 @@ func HandleHandoverNotify(ctx ctxt.Context, ran *context.AmfRan, message *ngapTy
     			}
     		}
     	}
    +	if aMFUENGAPID == nil {
    +		ran.Log.Errorln("AMFUENGAPID is missing")
    +		return
    +	}
    +	if rANUENGAPID == nil {
    +		ran.Log.Errorln("RANUENGAPID is missing")
    +		return
    +	}
     
     	targetUe := ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
     
    @@ -3324,12 +3373,17 @@ func HandleHandoverRequestAcknowledge(ctx ctxt.Context, ran *context.AmfRan, mes
     		criticalityDiagnostics := buildCriticalityDiagnostics(&procedureCode, &triggeringMessage,
     			&procedureCriticality, &iesCriticalityDiagnostics)
     		ngap_message.SendErrorIndication(ran, nil, nil, nil, &criticalityDiagnostics)
    +		return
     	}
     
     	if criticalityDiagnostics != nil {
     		printCriticalityDiagnostics(ran, criticalityDiagnostics)
     	}
     
    +	if aMFUENGAPID == nil {
    +		ran.Log.Errorln("AMFUENGAPID is missing")
    +		return
    +	}
     	targetUe := context.AMF_Self().RanUeFindByAmfUeNgapID(aMFUENGAPID.Value)
     	if targetUe == nil {
     		ran.Log.Errorf("No UE Context[AMFUENGAPID: %d]", aMFUENGAPID.Value)
    @@ -4046,6 +4100,13 @@ func HandleRanConfigurationUpdate(ran *context.AmfRan, message *ngapType.NGAPPDU
     			ran.Log.Debugf("decode IE PagingDRX = [%d]", pagingDRX.Value)
     		}
     	}
    +	if supportedTAList == nil {
    +		ran.Log.Warnln("RanConfigurationUpdate failure: Supported TA List is missing")
    +		cause.Present = ngapType.CausePresentMisc
    +		cause.Misc = &ngapType.CauseMisc{Value: ngapType.CauseMiscPresentUnspecified}
    +		ngap_message.SendRanConfigurationUpdateFailure(ran, cause, nil)
    +		return
    +	}
     
     	for i := 0; i < len(supportedTAList.List); i++ {
     		supportedTAItem := supportedTAList.List[i]
    @@ -4380,6 +4441,18 @@ func HandleLocationReport(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     			}
     		}
     	}
    +	if rANUENGAPID == nil {
    +		ran.Log.Errorln("RanUeNgapID is missing")
    +		return
    +	}
    +	if userLocationInformation == nil {
    +		ran.Log.Warnln("userLocationInformation is missing")
    +		return
    +	}
    +	if locationReportingRequestType == nil {
    +		ran.Log.Warnln("LocationReportingRequestType is missing")
    +		return
    +	}
     
     	ranUe := ran.RanUeFindByRanUeNgapID(rANUENGAPID.Value)
     	if ranUe == nil {
    @@ -4400,6 +4473,10 @@ func HandleLocationReport(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     
     	case ngapType.EventTypePresentUePresenceInAreaOfInterest:
     		ranUe.Log.Debugln("to report UE presence in the area of interest")
    +		if uEPresenceInAreaOfInterestList == nil {
    +			ran.Log.Warnln("UEPresenceInAreaOfInterestList is missing")
    +			return
    +		}
     		for _, uEPresenceInAreaOfInterestItem := range uEPresenceInAreaOfInterestList.List {
     			uEPresence := uEPresenceInAreaOfInterestItem.UEPresence.Value
     			referenceID := uEPresenceInAreaOfInterestItem.LocationReportingReferenceID.Value
    @@ -4818,27 +4895,54 @@ func HandleCellTrafficTrace(ran *context.AmfRan, message *ngapType.NGAPPDU) {
     }
     
     func printAndGetCause(ran *context.AmfRan, cause *ngapType.Cause) (present int, value aper.Enumerated) {
    +	if ran == nil {
    +		return 0, 0
    +	}
    +	if cause == nil {
    +		ran.Log.Errorln("Cause is nil")
    +		return 0, 0
    +	}
     	present = cause.Present
     	switch cause.Present {
     	case ngapType.CausePresentRadioNetwork:
    +		if cause.RadioNetwork == nil {
    +			ran.Log.Errorln("Cause RadioNetwork is nil")
    +			return present, 0
    +		}
     		ran.Log.Warnf("Cause RadioNetwork[%d]", cause.RadioNetwork.Value)
     		value = cause.RadioNetwork.Value
     	case ngapType.CausePresentTransport:
    +		if cause.Transport == nil {
    +			ran.Log.Errorln("Cause Transport is nil")
    +			return present, 0
    +		}
     		ran.Log.Warnf("Cause Transport[%d]", cause.Transport.Value)
     		value = cause.Transport.Value
     	case ngapType.CausePresentProtocol:
    +		if cause.Protocol == nil {
    +			ran.Log.Errorln("Cause Protocol is nil")
    +			return present, 0
    +		}
     		ran.Log.Warnf("Cause Protocol[%d]", cause.Protocol.Value)
     		value = cause.Protocol.Value
     	case ngapType.CausePresentNas:
    +		if cause.Nas == nil {
    +			ran.Log.Errorln("Cause Nas is nil")
    +			return present, 0
    +		}
     		ran.Log.Warnf("Cause Nas[%d]", cause.Nas.Value)
     		value = cause.Nas.Value
     	case ngapType.CausePresentMisc:
    +		if cause.Misc == nil {
    +			ran.Log.Errorln("Cause Misc is nil")
    +			return present, 0
    +		}
     		ran.Log.Warnf("Cause Misc[%d]", cause.Misc.Value)
     		value = cause.Misc.Value
     	default:
     		ran.Log.Errorf("Invalid Cause group[%d]", cause.Present)
     	}
    -	return
    +	return present, value
     }
     
     func printCriticalityDiagnostics(ran *context.AmfRan, criticalityDiagnostics *ngapType.CriticalityDiagnostics) {
    
  • ngap/handler_test.go+116 0 added
    @@ -0,0 +1,116 @@
    +// Copyright (C) 2026 Intel Corporation
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +//
    +
    +package ngap
    +
    +import (
    +	ctxt "context"
    +	"testing"
    +
    +	"github.com/omec-project/amf/context"
    +	"github.com/omec-project/ngap/ngapType"
    +	"go.uber.org/zap"
    +)
    +
    +func TestHandleHandoverNotifyIgnoresMissingIDs(t *testing.T) {
    +	ran := &context.AmfRan{Log: zap.NewNop().Sugar()}
    +	pdu := &ngapType.NGAPPDU{
    +		Present: ngapType.NGAPPDUPresentInitiatingMessage,
    +		InitiatingMessage: &ngapType.InitiatingMessage{
    +			ProcedureCode: ngapType.ProcedureCode{Value: ngapType.ProcedureCodeHandoverNotification},
    +			Value: ngapType.InitiatingMessageValue{
    +				Present:        ngapType.InitiatingMessagePresentHandoverNotify,
    +				HandoverNotify: &ngapType.HandoverNotify{},
    +			},
    +		},
    +	}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("HandleHandoverNotify panicked with missing IDs: %v", recovered)
    +		}
    +	}()
    +
    +	HandleHandoverNotify(ctxt.Background(), ran, pdu)
    +}
    +
    +func TestHandleHandoverRequestAcknowledgeIgnoresMissingAmfUeNgapID(t *testing.T) {
    +	ran := &context.AmfRan{Log: zap.NewNop().Sugar()}
    +	pdu := &ngapType.NGAPPDU{
    +		Present: ngapType.NGAPPDUPresentSuccessfulOutcome,
    +		SuccessfulOutcome: &ngapType.SuccessfulOutcome{
    +			ProcedureCode: ngapType.ProcedureCode{Value: ngapType.ProcedureCodeHandoverResourceAllocation},
    +			Value: ngapType.SuccessfulOutcomeValue{
    +				Present: ngapType.SuccessfulOutcomePresentHandoverRequestAcknowledge,
    +				HandoverRequestAcknowledge: &ngapType.HandoverRequestAcknowledge{
    +					ProtocolIEs: ngapType.ProtocolIEContainerHandoverRequestAcknowledgeIEs{
    +						List: []ngapType.HandoverRequestAcknowledgeIEs{
    +							{
    +								Id: ngapType.ProtocolIEID{Value: ngapType.ProtocolIEIDTargetToSourceTransparentContainer},
    +								Value: ngapType.HandoverRequestAcknowledgeIEsValue{
    +									Present:                            ngapType.HandoverRequestAcknowledgeIEsPresentTargetToSourceTransparentContainer,
    +									TargetToSourceTransparentContainer: &ngapType.TargetToSourceTransparentContainer{},
    +								},
    +							},
    +						},
    +					},
    +				},
    +			},
    +		},
    +	}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("HandleHandoverRequestAcknowledge panicked with missing AMFUENGAPID: %v", recovered)
    +		}
    +	}()
    +
    +	HandleHandoverRequestAcknowledge(ctxt.Background(), ran, pdu)
    +}
    +
    +func TestFetchRanUeContextReturnsNilForMissingIDs(t *testing.T) {
    +	self := context.AMF_Self()
    +	previousReady := self.Rcvd
    +	self.Rcvd = true
    +	defer func() {
    +		self.Rcvd = previousReady
    +	}()
    +
    +	ran := &context.AmfRan{Log: zap.NewNop().Sugar()}
    +	pdu := &ngapType.NGAPPDU{
    +		Present: ngapType.NGAPPDUPresentInitiatingMessage,
    +		InitiatingMessage: &ngapType.InitiatingMessage{
    +			ProcedureCode: ngapType.ProcedureCode{Value: ngapType.ProcedureCodeInitialUEMessage},
    +			Value: ngapType.InitiatingMessageValue{
    +				Present:          ngapType.InitiatingMessagePresentInitialUEMessage,
    +				InitialUEMessage: &ngapType.InitialUEMessage{},
    +			},
    +		},
    +	}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("FetchRanUeContext panicked on missing IDs: %v", recovered)
    +		}
    +	}()
    +
    +	ranUe, amfID := FetchRanUeContext(ran, pdu)
    +	if ranUe != nil || amfID != nil {
    +		t.Fatal("expected no UE context to be resolved from a malformed InitialUEMessage")
    +	}
    +}
    +
    +func TestPrintAndGetCauseIgnoresNilCauseMembers(t *testing.T) {
    +	ran := &context.AmfRan{Log: zap.NewNop().Sugar()}
    +
    +	defer func() {
    +		if recovered := recover(); recovered != nil {
    +			t.Fatalf("printAndGetCause panicked on malformed cause: %v", recovered)
    +		}
    +	}()
    +
    +	printAndGetCause(ran, nil)
    +	printAndGetCause(ran, &ngapType.Cause{Present: ngapType.CausePresentProtocol})
    +}
    
  • util/convert.go+11 2 modified
    @@ -40,10 +40,19 @@ func SeparateAmfId(amfid string) (regionId, setId, ptrId string, err error) {
     	return
     }
     
    -func PlmnIdStringToModels(plmnId string) (plmnID models.PlmnId) {
    +func PlmnIdStringToModels(plmnId string) (plmnID models.PlmnId, err error) {
    +	if len(plmnId) < 5 || len(plmnId) > 6 {
    +		return models.PlmnId{}, fmt.Errorf("invalid PLMN ID length %d", len(plmnId))
    +	}
    +	for _, digit := range plmnId {
    +		if digit < '0' || digit > '9' {
    +			return models.PlmnId{}, fmt.Errorf("invalid PLMN ID %q", plmnId)
    +		}
    +	}
    +
     	plmnID.Mcc = plmnId[:3]
     	plmnID.Mnc = plmnId[3:]
    -	return
    +	return plmnID, nil
     }
     
     func TACConfigToModels(intString string) (hexString string) {
    
  • util/convert_test.go+43 0 added
    @@ -0,0 +1,43 @@
    +// Copyright (C) 2026 Intel Corporation
    +//
    +// SPDX-License-Identifier: Apache-2.0
    +
    +package util
    +
    +import "testing"
    +
    +func TestPlmnIdStringToModels(t *testing.T) {
    +	tests := []struct {
    +		name      string
    +		input     string
    +		wantMcc   string
    +		wantMnc   string
    +		wantError bool
    +	}{
    +		{name: "valid two digit mnc", input: "12345", wantMcc: "123", wantMnc: "45"},
    +		{name: "valid three digit mnc", input: "123456", wantMcc: "123", wantMnc: "456"},
    +		{name: "empty", input: "", wantError: true},
    +		{name: "too short", input: "1234", wantError: true},
    +		{name: "too long", input: "1234567", wantError: true},
    +		{name: "non digits", input: "12a45", wantError: true},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			plmnID, err := PlmnIdStringToModels(tt.input)
    +			if tt.wantError {
    +				if err == nil {
    +					t.Fatal("expected error")
    +				}
    +				return
    +			}
    +
    +			if err != nil {
    +				t.Fatalf("unexpected error: %v", err)
    +			}
    +			if plmnID.Mcc != tt.wantMcc || plmnID.Mnc != tt.wantMnc {
    +				t.Fatalf("expected MCC/MNC %s/%s, got %s/%s", tt.wantMcc, tt.wantMnc, plmnID.Mcc, plmnID.Mnc)
    +			}
    +		})
    +	}
    +}
    
  • VERSION+1 1 modified
    @@ -1 +1 @@
    -2.1.3-dev
    +2.2.0
    

Vulnerability mechanics

Root cause

"Missing nil checks on NGAP protocol IE pointers (RANUENGAPID, AMFUENGAPID, SupportedTAList, ResetType, etc.) before dereferencing their `.Value` field causes a nil pointer dereference panic."

Attack vector

An attacker with network access to the AMF's NGAP endpoint can send a crafted NGAP message (e.g., InitialUEMessage, HandoverNotify, NGSetupRequest, NGReset) that omits mandatory or optional Information Elements. Because the handler code in `ngap/handler.go` and `ngap/dispatcher.go` directly accesses `.Value` on pointers like `rANUENGAPID`, `aMFUENGAPID`, `supportedTAList`, or `resetType` without checking for nil, the malformed input triggers a nil pointer dereference [CWE-476]. The attack is remote, requires low-privilege access (authenticated RAN), and results in a denial-of-service crash of the AMF process (availability impact).

Affected code

The primary vulnerable file is `ngap/handler.go`, specifically the `FetchRanUeContext` function and multiple handler functions (`HandleNGSetupRequest`, `HandleNGReset`, `HandleHandoverNotify`, `HandleHandoverRequestAcknowledge`, `HandleLocationReport`, `HandlePDUSessionResourceModifyIndication`, `HandleUERadioCapabilityCheckResponse`, `HandleRanConfigurationUpdate`, and `printAndGetCause`). Supporting fixes are in `ngap/dispatcher.go` (`DispatchLb`, `Dispatch`, `DispatchNgapMsg`).

What the fix does

The patch introduces wrapper functions `findRanUeByRanNgapID` and `findRanUeByAmfNgapID` that check whether the NGAP ID pointer is nil before accessing `.Value`, returning nil instead of panicking [patch_id=424441]. It also adds explicit nil guards for `supportedTAList` in `HandleNGSetupRequest` and `HandleRanConfigurationUpdate`, for `resetType` in `HandleNGReset`, for `rANUENGAPID`/`aMFUENGAPID` in several handlers, and for `pdu`/`conn`/`ran` in the dispatcher functions. Additionally, `printAndGetCause` now checks each cause sub-pointer (RadioNetwork, Transport, etc.) before dereferencing, and a missing `continue` was added after a nil `ranUe` check in `HandleNGReset` to prevent a subsequent nil dereference. These changes ensure that malformed NGAP input results in a logged error and graceful return rather than a panic.

Preconditions

  • networkAttacker must be able to send NGAP messages to the AMF (network access to the SCTP/NGAP endpoint)
  • authAttacker must have credentials or be able to establish a connection as a RAN node (low-privilege authenticated access)
  • inputThe crafted NGAP message must omit one or more mandatory or optional Information Elements (e.g., RANUENGAPID, AMFUENGAPID, SupportedTAList, ResetType)

Reproduction

The bundle includes a public exploit/PoC reference. A crafted NGAP message that omits the RANUENGAPID IE in an InitialUEMessage triggers a nil pointer dereference in `FetchRanUeContext`. The test `TestFetchRanUeContextReturnsNilForMissingIDs` in `ngap/handler_test.go` demonstrates this by sending an `InitialUEMessage` with no ProtocolIEs, which previously caused a panic and now returns nil safely. Similarly, `TestHandleHandoverNotifyIgnoresMissingIDs` sends a `HandoverNotify` with no IEs, and `TestHandleHandoverRequestAcknowledgeIgnoresMissingAmfUeNgapID` sends a `HandoverRequestAcknowledge` missing the AMFUENGAPID IE. Sending any of these malformed messages to a vulnerable AMF (before patch) causes a crash.

Generated on May 19, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.