fix(correlation): prevent race condition in session manager
Fixed potential use-after-free in Update() when gcRound deletes a session between GetOrCreate() and acquiring the session lock. Changes: - Add 'deleted' flag to SessionState - Mark sessions as deleted before removing from map in gcRound - Check deleted flag in Update and recreate session if needed This ensures updates to deleted sessions create a new session instead of modifying a freed/dangling reference. Race detector verified: go test ./internal/correlation/... -race Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -84,10 +84,21 @@ func (m *Manager) GetOrCreate(key SessionKey) *SessionState {
|
|||||||
|
|
||||||
// Update applique la fonction fn sur la session identifiée par key,
|
// Update applique la fonction fn sur la session identifiée par key,
|
||||||
// en créant la session si elle n'existe pas encore.
|
// en créant la session si elle n'existe pas encore.
|
||||||
|
// Thread-safe : vérifie le flag deleted pour éviter les use-after-free.
|
||||||
func (m *Manager) Update(key SessionKey, fn func(*SessionState)) {
|
func (m *Manager) Update(key SessionKey, fn func(*SessionState)) {
|
||||||
s := m.GetOrCreate(key)
|
s := m.GetOrCreate(key)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
// Vérifier si la session a été marquée comme supprimée par gcRound
|
||||||
|
if s.deleted {
|
||||||
|
// La session a été supprimée, recréer une nouvelle session
|
||||||
|
s = m.GetOrCreate(key)
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
fn(s)
|
fn(s)
|
||||||
s.LastActivity = time.Now()
|
s.LastActivity = time.Now()
|
||||||
}
|
}
|
||||||
@ -128,6 +139,12 @@ func (m *Manager) gcRound(slowlorisThreshold time.Duration) {
|
|||||||
|
|
||||||
if expired || slowloris {
|
if expired || slowloris {
|
||||||
toDelete = append(toDelete, key)
|
toDelete = append(toDelete, key)
|
||||||
|
// Marquer la session comme supprimée AVANT de l'envoyer à ReadyCh
|
||||||
|
// pour éviter les race conditions avec Update()
|
||||||
|
s.mu.Lock()
|
||||||
|
s.deleted = true
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
// Envoyer sans bloquer (drop si le canal est plein)
|
// Envoyer sans bloquer (drop si le canal est plein)
|
||||||
select {
|
select {
|
||||||
case m.ReadyCh <- s:
|
case m.ReadyCh <- s:
|
||||||
|
|||||||
@ -88,7 +88,8 @@ type SessionState struct {
|
|||||||
FirstSeen time.Time // horodatage de création de la session
|
FirstSeen time.Time // horodatage de création de la session
|
||||||
LastActivity time.Time // horodatage de la dernière activité
|
LastActivity time.Time // horodatage de la dernière activité
|
||||||
|
|
||||||
mu sync.Mutex // protection des modifications concurrentes
|
deleted bool // true si la session a été supprimée du map par gcRound
|
||||||
|
mu sync.Mutex // protection des modifications concurrentes
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExpired indique si la session n'a reçu aucune activité depuis timeout.
|
// IsExpired indique si la session n'a reçu aucune activité depuis timeout.
|
||||||
|
|||||||
Reference in New Issue
Block a user