GHSA-cpwg-x64r-rgwg
gorest InMemorySecret2FA race condition allows process crash via concurrent map access (CWE-362)
Packages
| Ecosystem | Package | Affected | Fixed in |
|---|---|---|---|
| Go | github.com/pilinux/gorest | < 1.12.2 | 1.12.2 |
Details
As written in the advisory (Markdown source, shown as plain text).
## Vulnerability: CWE-362 — Concurrent Map Access Race Condition in InMemorySecret2FA
**CWE:** CWE-362 (Concurrent Execution using Shared Resource with Improper Synchronization)
### Affected Component
- `github.com/pilinux/gorest` — Go REST API boilerplate
- InMemorySecret2FA — in-memory 2FA secret store
### Vulnerability Locations
| File | Line | Role |
|------|------|------|
| `database/model/twoFA.go` | 43 | Global `map[uint64]Secret2FA` — bare map, no sync.RWMutex |
| `handler/login.go` | 139 | Map write during user login |
| `handler/twoFA.go` | 205 | Map write during 2FA setup |
| `handler/twoFA.go` | 272 | Map write during 2FA activation |
| `handler/twoFA.go` | 575 | Map write during 2FA verification |
| `handler/twoFA.go` | 189 | Map read during 2FA operations |
| `handler/twoFA.go` | 245 | Map read during 2FA operations |
| `handler/twoFA.go` | 491 | Map read during 2FA operations |
| `service/common.go` | 79 | Map delete |
### Data Flow
```
Multiple HTTP goroutines (concurrent requests)
│
├── handler/login.go:139 ─► map write ──┐
├── handler/twoFA.go:205 ─► map write ──┼── InMemorySecret2FA (bare map)
├── handler/twoFA.go:189 ─► map read ───┤ ▲ NO sync.RWMutex
├── handler/twoFA.go:245 ─► map read ───┤ │
├── handler/twoFA.go:491 ─► map read ───┤ │
└── service/common.go:79 ─► map delete ─┘ │
│
Go runtime detects concurrent map │
read+write or write+write │
│ │
▼ │
fatal error: concurrent map read and map write │
fatal error: concurrent map writes │
│ │
▼ │
Process crash (DoS) ──────────────────────┘
```
### Description
The `InMemorySecret2FA` in `database/model/twoFA.go` was defined as a package-level `map[uint64]Secret2FA` — a bare Go map with no synchronization primitive. Multiple HTTP handlers in `handler/login.go` and `handler/twoFA.go` read from and wrote to this map concurrently. Go's runtime detects unsynchronized concurrent map access and throws an unrecoverable `fatal error`, which crashes the entire process.
This is a CWE-362 race condition: the shared resource (the map) is accessed concurrently without proper synchronization, and the failure mode is a hard process crash (denial of service).
### Trigger Conditions
1. Two users with 2FA enabled logging in simultaneously — concurrent map writes
2. One user logging in (map write) while another performs 2FA verification (map read)
3. Any concurrent combination of the 9 affected handler locations
### Proof of Concept
```bash
# Simulate two concurrent logins with 2FA enabled
for i in 1 2; do
curl -X POST http://target:8080/api/v1/login -H "Content-Type: application/json" -d "{"email":"user${i}@example.com","password":"testpass"}" &
done
wait
# Go runtime output:
# fatal error: concurrent map writes
# goroutine 34 [running]:
# runtime.throw({0x...})
# runtime/map.go:...
```
### Impact
- **Availability (High):** Hard process crash via Go runtime fatal error. No recovery possible — the process exits. An attacker can repeat the concurrent requests to crash the service on demand.
- **Confidentiality (None):** The crash itself does not leak data.
- **Integrity (None):** No data corruption (Go prevents it by crashing).
### Fix (PR #391)
Introduced `Secret2FAStore` struct with `sync.RWMutex` protection:
```go
// BEFORE: database/model/twoFA.go — bare map, no protection
var InMemorySecret2FA map[uint64]Secret2FA
// AFTER: Wrapped with sync.RWMutex
type Secret2FAStore struct {
mu sync.RWMutex
data map[uint64]Secret2FA
}
func (s *Secret2FAStore) Get(key uint64) (Secret2FA, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
v, ok := s.data[key]
return cloneSecret2FA(v), ok
}
func (s *Secret2FAStore) Set(key uint64, value Secret2FA) {
s.mu.Lock()
defer s.mu.Unlock()
s.data[key] = cloneSecret2FA(value)
}
func (s *Secret2FAStore) Delete(key uint64) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.data, key)
}
// cloneSecret2FA returns a deep copy of a Secret2FA.
// This prevents external code from mutating the store's data
// through shared slice backing arrays.
func cloneSecret2FA(v Secret2FA) Secret2FA {
out := Secret2FA{Image: v.Image}
if v.PassHash != nil {
out.PassHash = append([]byte(nil), v.PassHash...)
}
if v.KeySalt != nil {
out.KeySalt = append([]byte(nil), v.KeySalt...)
}
if v.Secret != nil {
out.Secret = append([]byte(nil), v.Secret...)
}
return out
}
```
All 9 handler call sites updated from direct map access to store method calls.
### Not Vulnerable (verified during audit)
- JWT: RSA keys from files, appleboy/gin-jwt middleware — correct
- Password hashing: Argon2 via pilinux/argon2 — correct
- SQL queries: GORM parameterized — correct
- CORS: validates wildcard+credentials combination at config load — correct
### Patched Versions
All versions after PR #391 merge.
### Resources
- Fix PR: https://github.com/pilinux/gorest/pull/391
### Credit
Reported by @saaa99999999 via manual security audit.
References
External links from the advisory. They lead to third-party sites.