TP-06_slice_isolation
TP-06 — Network Slice Isolation Tests
Domain: Network Slicing Security and QoS
Standards: 3GPP TS 33.501 §5.15 · 3GPP TS 23.501 §5.15 · GSMA FS.40 v3.0 §5.5
Prerequisites: TP-00 complete; 5G SA lab with at least 2 slices configured (SST=1 eMBB + SST=2/3 URLLC/IoT)
TC-SLICE-01: Unauthorized Slice Access
Threat Model
flowchart TD
subgraph UE_REQ["UE Request"]
UE["UERANSIM UE\nSubscribed: SST=1 eMBB only\nRequested: SST=2 URLLC"]
end
subgraph ACCESS_CTRL["Access Control Chain"]
AMF["AMF\nReceives Registration Request\nwith Requested NSSAI:\nSST=2 SD=000002"]
NSSF["NSSF\nSlice Selection Function\nChecks subscriber entitlement"]
UDM["UDM\nSubscribed S-NSSAI list:\n[SST=1 SD=000001]"]
end
UE -->|Registration Request\nRequested NSSAI: SST=2| AMF
AMF -->|Nnssf_NSSelection_Get\n{Requested NSSAI: SST=2}| NSSF
NSSF -->|Check subscriber slice entitlement| UDM
UDM -->|Subscribed NSSAI: SST=1 ONLY| NSSF
NSSF -->|Response| RESULT{"SST=2 allowed?"}
RESULT -->|Yes — misconfigured| VULN["❌ CRITICAL:\nUE granted URLLC slice\nwithout entitlement\nBilling fraud, SLA violation"]
RESULT -->|No — correct| ALLOWED["✅ Allowed NSSAI:\nSST=1 only\nSST=2 rejected"]
ALLOWED -->|Registration Accept\n{Allowed NSSAI: SST=1}| UE
UE -->|PDU Session Est.\nfor SST=2| REJECT["AMF: PDU Session Reject\n5GMM cause #37\nSlice not subscribed"]
style VULN fill:#c0392b,color:#fff
style ALLOWED fill:#27ae60,color:#fff
style REJECT fill:#27ae60,color:#fffObjective
Verify AMF/NSSF enforces slice subscription; UE cannot access slices it is not provisioned for.
Steps
# 1. Configure subscriber with ONLY eMBB slice (SST=1)
docker exec open5gs-mongodb mongosh open5gs --eval '
db.subscribers.updateOne(
{imsi: "001010000000001"},
{$set: {
"slice": [{
"sst": 1,
"sd": "000001",
"default_indicator": true,
"session": [{
"name": "internet",
"type": 3,
"qos": {"index": 9, "arp": {"priority_level": 8,
"pre_emption_capability": 1, "pre_emption_vulnerability": 1}}
}]
}]
}}
)'
# 2. Configure UERANSIM UE to request URLLC slice (SST=2, not subscribed)
cp ~/open5gs-lab/5g/config/ue.yaml /tmp/ue-slice-test.yaml
# Modify to request SST=2
python3 -c "
import re
with open('/tmp/ue-slice-test.yaml') as f: content = f.read()
# Replace slice section to request SST=2
content = re.sub(r'sst: 1', 'sst: 2', content)
content = re.sub(r'sd: .+', 'sd: \"000002\"', content)
with open('/tmp/ue-slice-test.yaml', 'w') as f: f.write(content)
print('Modified UE config to request SST=2 (URLLC — not subscribed)')
"
# 3. Attempt registration requesting unauthorized slice
docker run --rm --cap-add=NET_ADMIN \
--network open5gs-lab_ran \
--name ueransim-slice-test \
-v /tmp/ue-slice-test.yaml:/etc/ueransim/ue.yaml \
louisroyer/ueransim-ue:latest &
sleep 15
# 4. Check what NSSAI was granted
docker logs open5gs-amf 2>&1 | grep -E "NSSAI|slice|SST" | tail -20
# 5. Verify UE received ONLY SST=1 in Allowed NSSAI
docker logs ueransim-slice-test 2>&1 | grep -E "Allowed NSSAI|slice|SST"
# Should show: Allowed NSSAI: SST=1 only, SST=2 rejected
# 6. Attempt PDU session on unauthorized slice
# (UERANSIM will try after registration if configured)
docker logs ueransim-slice-test 2>&1 | grep -E "PDU.*reject\|cause.*37\|slice"
# Cleanup
docker rm -f ueransim-slice-test
Expected Results
- Registration Accept: Allowed NSSAI contains only
SST=1 SD=000001 SST=2NOT in Allowed NSSAI- PDU Session Establishment for SST=2 rejected with 5GMM cause #37
Network slicing subscription check failed - AMF/NSSF logs: slice selection rejected for SST=2
Pass Criteria
UE cannot access any slice not in its subscribed S-NSSAI list. Rejection cause #37 confirmed in NAS.
TC-SLICE-02: Cross-Slice Traffic Leakage
Threat Model
graph TD
subgraph SLICE_A["Slice A — eMBB (SST=1)"]
UE_A["UE-A\nIP: 10.45.0.2\nDNN: internet"]
UPF_A["UPF-A\nN6: 10.45.0.0/24"]
end
subgraph SLICE_B["Slice B — IoT (SST=3)"]
UE_B["UE-B\nIP: 10.46.0.2\nDNN: iot"]
UPF_B["UPF-B\nN6: 10.46.0.0/24"]
end
subgraph ATTACK["Cross-Slice Attack Attempts"]
ATK_A["UE-A tries to reach UE-B IP\n10.46.0.2 from uesimtun0"]
ATK_B["UE-A GTP-U with IoT TEID\n(cross-inject)"]
end
UE_A -->|PDU in Slice A| UPF_A
UE_B -->|PDU in Slice B| UPF_B
ATK_A -->|Routed via uesimtun0| UPF_A
UPF_A -->|PDR check:\n10.46.0.0/24 not in Slice A FAR| DROP1["✅ DROPPED\nNo route to Slice B DN"]
ATK_B -->|N3 with Slice B TEID| UPF_B
UPF_B -->|Source IP check:\nnot UE-A gNB on Slice B| DROP2["✅ DROPPED\nTEID binding enforced"]
DROP1 -.->|If leak| LEAK["❌ CRITICAL:\nCross-slice traffic\nIsolation broken"]
DROP2 -.->|If leak| LEAK
style DROP1 fill:#27ae60,color:#fff
style DROP2 fill:#27ae60,color:#fff
style LEAK fill:#c0392b,color:#fffObjective
Verify user-plane traffic cannot cross between network slices through UPF routing or GTP-U injection.
Steps
# Prerequisites: Two slices configured in Open5GS
# Slice A: SST=1, DNN=internet, UPF pool 10.45.0.0/24
# Slice B: SST=3, DNN=iot, UPF pool 10.46.0.0/24
# 1. Register UE-A on eMBB slice
docker compose -f ~/open5gs-lab/5g/docker-compose.yml up -d ue
sleep 10
UE_A_IP=$(docker exec ueransim-ue ip addr show uesimtun0 | grep "inet " | awk '{print $2}' | cut -d/ -f1)
echo "UE-A IP: ${UE_A_IP}" # Should be 10.45.x.x
# 2. Register UE-B on IoT slice (separate config)
# Add second subscriber with SST=3
docker exec open5gs-mongodb mongosh open5gs --eval '
db.subscribers.insertOne({
imsi: "001010000000002",
security: {
k: "465B5CE8B199B49FAA5F0A2EE238A6BC",
opc: "E8ED289DEBA952E4283B54E88E6183CA",
amf: "8000", sqn: NumberLong(0)
},
slice: [{sst: 3, sd: "000003", default_indicator: true,
session: [{name: "iot", type: 3,
qos: {index: 9, arp: {priority_level: 8,
pre_emption_capability: 1, pre_emption_vulnerability: 1}}
}]
}]
})'
# Start UE-B
docker run -d --name ueransim-ue-b \
--cap-add=NET_ADMIN \
--network open5gs-lab_ran \
-v ~/open5gs-lab/5g/config/ue-iot.yaml:/etc/ueransim/ue.yaml \
louisroyer/ueransim-ue:latest
sleep 15
UE_B_IP=$(docker exec ueransim-ue-b ip addr show uesimtun0 2>/dev/null | grep "inet " | awk '{print $2}' | cut -d/ -f1)
echo "UE-B IP: ${UE_B_IP}" # Should be 10.46.x.x
# 3. Attempt cross-slice traffic: UE-A tries to reach UE-B
echo "=== Cross-slice ping test ==="
docker exec ueransim-ue ping -c 3 -W 2 -I uesimtun0 ${UE_B_IP} 2>&1
# Expected: 100% packet loss — different DN subnet, no routing between slices
# 4. Capture at UPF to confirm packets don't cross
sudo tshark -i br-$(docker network ls --filter name=ran -q) \
-Y "ip.src == ${UE_A_IP} && ip.dst == ${UE_B_IP}" \
-c 10 -w /tmp/cross-slice.pcap &
sleep 5
docker exec ueransim-ue ping -c 5 -I uesimtun0 ${UE_B_IP}
sleep 3
tshark -r /tmp/cross-slice.pcap 2>/dev/null | wc -l
echo "Packets captured crossing slices (must be 0):"
# Cleanup
docker rm -f ueransim-ue-b
Expected Results
pingfrom UE-A to UE-B IP: 100% packet loss- Zero packets captured at UPF crossing between slice subnets
- UPF routing table: Slice A DN
10.45.0.0/24and Slice B DN10.46.0.0/24are isolated
Pass Criteria
Zero cross-slice packets at both UE interfaces and UPF. Hard isolation verified.
TC-SLICE-03: URLLC Slice QoS Enforcement Under Load
Threat Model
graph TD
subgraph LOAD["High Load Scenario"]
UE_EMBB["UE-eMBB\nHeavy iperf3 UDP\n~500Mbps saturation"]
UE_URLLC["UE-URLLC\nLatency-sensitive\n5QI=82 PDB=1ms"]
end
subgraph UPF_QOS["UPF QoS Enforcement"]
QER_EMBB["QER: eMBB\nMBR-DL=100Mbps\n5QI=9 (non-GBR)"]
QER_URLLC["QER: URLLC\nGBR-DL=10Mbps\nPriority=1\n5QI=82"]
SCHEDULER["UPF Packet Scheduler\n(Priority-based)"]
end
UE_EMBB -->|Flood traffic| SCHEDULER
UE_URLLC -->|Low-latency traffic| SCHEDULER
SCHEDULER -->|Priority: URLLC first| QER_URLLC
SCHEDULER -->|Best-effort| QER_EMBB
QER_URLLC -->|Protected| RESULT_URLLC["✅ URLLC latency <5ms\nEven under eMBB load"]
QER_EMBB -->|Throttled| RESULT_EMBB["eMBB: degraded\nbut no starvation"]
subgraph THREAT["Threat: QoS Misconfiguration"]
MISS["If URLLC 5QI not configured\nor PCF policy not enforced"]
MISS --> IMPACT["❌ URLLC traffic treated as\nbest-effort\nLatency spikes → URLLC SLA violated"]
end
style RESULT_URLLC fill:#27ae60,color:#fff
style IMPACT fill:#c0392b,color:#fffObjective
Verify URLLC slice (5QI=82) maintains low latency even when eMBB slice is saturating the UPF.
Steps
# Prerequisites: URLLC slice configured in SMF/PCF with 5QI=82 GBR QoS flow
# Add URLLC subscriber
docker exec open5gs-mongodb mongosh open5gs --eval '
db.subscribers.insertOne({
imsi: "001010000000003",
security: {
k: "465B5CE8B199B49FAA5F0A2EE238A6BC",
opc: "E8ED289DEBA952E4283B54E88E6183CA",
amf: "8000", sqn: NumberLong(0)
},
slice: [{sst: 2, sd: "000002", default_indicator: true,
session: [{name: "urllc", type: 3,
ambr: {downlink: {value: 10, unit: 2}, uplink: {value: 5, unit: 2}},
qos: {index: 82, arp: {priority_level: 2,
pre_emption_capability: 2, pre_emption_vulnerability: 1}}
}]
}]
})'
# 1. Start URLLC UE
docker run -d --name ueransim-ue-urllc \
--cap-add=NET_ADMIN \
--network open5gs-lab_ran \
-v ~/open5gs-lab/5g/config/ue-urllc.yaml:/etc/ueransim/ue.yaml \
louisroyer/ueransim-ue:latest
sleep 15
# 2. Start eMBB UE (already running as default UE)
# Generate heavy eMBB load in background
docker exec ueransim-ue sh -c \
"iperf3 -c 8.8.8.8 -u -b 100M -t 60 -B \$(ip addr show uesimtun0 | grep 'inet ' | awk '{print \$2}' | cut -d/ -f1) &" 2>/dev/null || \
echo "iperf3 server not available — use ping flood as substitute"
# Substitute: ping flood from eMBB
docker exec ueransim-ue ping -f -I uesimtun0 8.8.8.8 &
FLOODPID=$!
# 3. Measure URLLC latency during eMBB load
echo "=== URLLC latency during eMBB load ==="
docker exec ueransim-ue-urllc \
ping -c 100 -i 0.01 -I uesimtun0 8.8.8.8 2>&1 | tail -5
# 4. Measure eMBB latency for comparison
echo "=== eMBB latency during self-load ==="
docker exec ueransim-ue \
ping -c 100 -i 0.01 -I uesimtun0 8.8.8.8 2>&1 | tail -5
# 5. Kill flood
kill $FLOODPID 2>/dev/null
# 6. Check PCF has applied 5QI=82 rule
docker logs open5gs-pcf 2>&1 | grep -E "5QI|QoS|urllc|82" | tail -10
# Cleanup
docker rm -f ueransim-ue-urllc
Expected Results
- URLLC UE average RTT: lower than eMBB UE during load
- PCF log confirms 5QI=82 rule applied to URLLC PDU session
- eMBB RTT increases under self-generated flood; URLLC RTT remains more stable
Pass Criteria
URLLC average latency measurably lower than eMBB under load. PCF 5QI=82 rule confirmed in logs.