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:#fff

Objective

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

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:#fff

Objective

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

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:#fff

Objective

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

Pass Criteria

URLLC average latency measurably lower than eMBB under load. PCF 5QI=82 rule confirmed in logs.