GHSA-27ph-8q4f-h7m7 free5GC's BSF concurrent PUT /nbsf-management/v1/subscriptions/{subId} crashes the BSF process via concurrent map read/write on Subscriptions
Summaryfree5GC's BSF `PUT /nbsf-management/v1/subscriptions/{subId}` handler has an unsynchronized write on the global `Subscriptions` map. The handler first reads the map under `RLock()` via `BSFContext.GetSubscription(subId)`, but if the subscription does not exist, `ReplaceIndividualSubcription()` writes back to the same map directly without taking the mutex (`bsfContext.BsfSelf.Subscriptions[subId] = subscription`). Under concurrent authenticated PUT load, one goroutine can read while another writes the map, which causes the Go runtime to abort the process with `fatal error: concurrent map read and map write` (Go runtime panics that come from concurrent map access bypass `recover()` and terminate the process). The BSF container exits with code `2` -- the entire BSF SBI surface goes down until restart.This endpoint requires a valid `nbsf-management` OAuth2 access token (PR:L, NOT PR:N), so this is scored as an authenticated process-kill DoS.DetailsValidated against the BSF container in the official Docker compose lab.Source repo tag: `v4.2.1`Running Docker image: `free5gc/bsf:v4.2.1`Docker validation date: 2026-03-22BSF endpoint: `http://10.100.200.11:8000`Read side (locked):Unsafe write side in the create-if-absent branch of `ReplaceIndividualSubcription` (no `Lock()`):Under concurrent traffic, the Go runtime detects the unsynchronized read/write on `c.Subscriptions` and aborts the process. Go's `concurrent map read and map write` fatal is NOT a normal panic -- it is unrecoverable, Gin's recovery middleware does not catch it, and the BSF process terminates.Code evidence (paths in `free5gc/bsf`):Read side (locked):`NFs/bsf/internal/sbi/processor/subscriptions.go:81``NFs/bsf/internal/context/context.go:726``NFs/bsf/internal/context/context.go:730`Unsafe write side (the create-if-absent branch in PUT, no lock):`NFs/bsf/internal/sbi/processor/subscriptions.go:111``NFs/bsf/internal/sbi/processor/subscriptions.go:114`The normal locked helpers (`CreateSubscription()`, `GetSubscription()`, `UpdateSubscription()`, `DeleteSubscription()`) DO take the mutex correctly. The bug is specific to the inline write inside the PUT create-if-absent branch.PoCReproduced end-to-end against the running BSF at `http://10.100.200.11:8000`.Obtain a valid `nbsf-management` token from NRF:Send concurrent PUT requests against fresh `subId` values (the validated lab uses 64 worker threads x 50 fresh subIds = 3200 concurrent PUTs):BSF container logs (`docker logs bsf`) show the Go runtime fatal that terminated the process:Container state confirms exit code 2:ImpactUnsynchronized concurrent access (CWE-362) to a shared map (`BsfSelf.Subscriptions`), combined with missing synchronization on the create-if-absent branch (CWE-820). Go's runtime detects concurrent map read/write and terminates the process via a non-recoverable fatal error -- Gin's `recover()` middleware does NOT catch this class of fatal, unlike ordinary nil-deref panics. The whole BSF process exits, dropping BSF's `nbsf-management` SBI surface (PCF binding lookups for SMF, AF -> PCF binding discovery, etc.) until restart.Any party that holds (or can obtain) a valid `nbsf-management` token can:Drive the create-if-absent code path at high concurrency by PUTting a stream of fresh `subId` values, deterministically tripping the runtime fatal and killing the BSF process.Repeat the trigger after every restart to sustain the outage.No Confidentiality impact (the crash returns no attacker-readable data). No persistent Integrity impact (BSF subscription state is in-memory and is lost when the process dies). The whole impact concentrates in Availability: complete loss of BSF service via concurrent attacker traffic on a single endpoint.Affected: free5gc v4.2.1.Upstream issue: https://github.com/free5gc/free5gc/issues/926
Upstream fix: https://github.com/free5gc/bsf/pull/7
CVSS v3.1 score: 6.5