diff --git a/services/ja4ebpf/internal/correlation/manager.go b/services/ja4ebpf/internal/correlation/manager.go index 1fa195a..50187cb 100644 --- a/services/ja4ebpf/internal/correlation/manager.go +++ b/services/ja4ebpf/internal/correlation/manager.go @@ -84,10 +84,21 @@ func (m *Manager) GetOrCreate(key SessionKey) *SessionState { // Update applique la fonction fn sur la session identifiée par key, // 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)) { s := m.GetOrCreate(key) + s.mu.Lock() 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) s.LastActivity = time.Now() } @@ -128,6 +139,12 @@ func (m *Manager) gcRound(slowlorisThreshold time.Duration) { if expired || slowloris { 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) select { case m.ReadyCh <- s: diff --git a/services/ja4ebpf/internal/correlation/session.go b/services/ja4ebpf/internal/correlation/session.go index b23d429..fa21568 100644 --- a/services/ja4ebpf/internal/correlation/session.go +++ b/services/ja4ebpf/internal/correlation/session.go @@ -88,7 +88,8 @@ type SessionState struct { FirstSeen time.Time // horodatage de création de la session 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.