From 678aa48a12b1a1fa11414c192c46eec782be3082 Mon Sep 17 00:00:00 2001 From: Jacquin Antoine Date: Sun, 19 Apr 2026 14:34:30 +0200 Subject: [PATCH] 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 --- .../ja4ebpf/internal/correlation/manager.go | 17 +++++++++++++++++ .../ja4ebpf/internal/correlation/session.go | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) 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.