GitHub Security Advisory: GHSA-27ph-8q4f-h7m7
Package: github.com/free5gc/bsf (GO)
Severity: MODERATE (CVSS 6.5)
CWE: CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition'), CWE-820: Missing Synchronization
Vulnerable versions: < 1.0.2
Patched in: 1.0.2

Summary: free5GC's BSF concurrent PUT /nbsf-management/v1/subscriptions/{subId} crashes the BSF process via concurrent map read/write on Subscriptions

Details:
### Summary
free5GC'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.

### Details
Validated 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-22
- BSF endpoint: `http://10.100.200.11:8000`

Read side (locked):
```go
func (c *BSFContext) GetSubscription(subId string) (*BsfSubscription, bool) {
    c.mutex.RLock()
    defer c.mutex.RUnlock()

    sub, exists := c.Subscriptions[subId]
    return sub, exists
}
```

Unsafe write side in the create-if-absent branch of `ReplaceIndividualSubcription` (no `Lock()`):
```go
subscription.SubId = subId
bsfContext.BsfSelf.Subscriptions[subId] = subscription
```

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.

### PoC
Reproduced end-to-end against the running BSF at `http://10.100.200.11:8000`.

1. Obtain a valid `nbsf-management` token from NRF:
```
curl -sS -X POST 'http://10.100.200.3:8000/oauth2/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data 'grant_type=client_credentials&nfType=NEF&nfInstanceId=eb9990de-4cd3-41b0-b5d9-c2102b088c57&targetNfType=BSF&scope=nbsf-management'
```

2. Send concurrent PUT requests against fresh `subId`