Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.3-dev
2.2.0
Comment thread
gab-arrobo marked this conversation as resolved.
16 changes: 14 additions & 2 deletions gmm/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 15 additions & 0 deletions gmm/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
21 changes: 21 additions & 0 deletions metrics/telemetry.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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))
}
4 changes: 4 additions & 0 deletions nas/nas_security/security.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
42 changes: 39 additions & 3 deletions ngap/dispatcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
41 changes: 41 additions & 0 deletions ngap/dispatcher_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Loading