Memory safety: data race leading to nil pointer dereference (use-after-init) in Go

HIGH
victoriametrics/victoriametrics
Commit: f95b483a13af
Affected: Versions prior to 1.139.0 (pre-fix), e.g. 1.138.x and older
2026-04-10 07:10 UTC

Description

The commit fixes a data race and potential nil pointer dereference in the storage startup sequence. Previously, FreeDiskSpaceWatcher could start before the Storage.table was fully initialized (opened), leading to the watcher potentially reading an uninitialized or partially initialized table and dereferencing a nil or invalid pointer. The fix initializes the isReadOnly state earlier and starts FreeDiskSpaceWatcher after openTable, ensuring the shared storage state is fully initialized before the watcher accesses it. This mitigates a memory safety issue (use-after-init / nil pointer dereference) that could cause crashes or undefined behavior in concurrent operation.

Proof of Concept

package main import ( "fmt" "time" ) type Table struct{ val int } func (t *Table) SomeMethod() int { // dereference to simulate potential nil dereference if t is nil return t.val + 1 } type Storage struct { table *Table } func (s *Storage) startFreeDiskSpaceWatcher() { go func() { for { // This line will panic if s.table is nil _ = s.table.SomeMethod() time.Sleep(1 * time.Millisecond) } }() } func main() { s := &Storage{} s.startFreeDiskSpaceWatcher() // Delay to create a race window before the table is allocated time.Sleep(2 * time.Millisecond) s.table = &Table{val: 0} time.Sleep(100 * time.Millisecond) fmt.Println("Done") }

Commit Details

Author: Noureldin

Date: 2026-04-10 06:33 UTC

Message:

lib/storage: fixes data race at startFreeDiskSpaceWatcher Previously, Storage.table was initialized after startFreeDiskSpaceWatcher was called. This created a potential data race condition: if openTable took a long time to complete and freed disk space during that window, the free disk space watcher could read an uninitialized (or partially initialized) Storage.table, leading to an invalid memory address or nil pointer dereference panic. This commit properly initializes s.isReadOnly state during storage start and starts FreeDiskSpaceWatcher after openTable. Bug was introduced in github.com/VictoriaMetrics/VictoriaMetrics/commit/27b958ba8bc66578206ddac26ccf47b2cc3e8101 Fixes https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10747

Triage Assessment

Vulnerability Type: Memory safety (data race / nil pointer dereference)

Confidence: HIGH

Reasoning:

The commit fixes a data race and potential nil pointer dereference by ensuring proper initialization order of storage state and starting the FreeDiskSpaceWatcher after the table is opened. This mitigates a memory safety issue (use-after-free-/nil dereference) that could lead to crashes or undefined behavior, which in practice is a security-relevant vulnerability in concurrent code.

Verification Assessment

Vulnerability Type: Memory safety: data race leading to nil pointer dereference (use-after-init) in Go

Confidence: HIGH

Affected Versions: Versions prior to 1.139.0 (pre-fix), e.g. 1.138.x and older

Code Diff

diff --git a/lib/storage/storage.go b/lib/storage/storage.go index d74d6a6e77729..3b9ecda7ae398 100644 --- a/lib/storage/storage.go +++ b/lib/storage/storage.go @@ -268,7 +268,8 @@ func MustOpenStorage(path string, opts OpenOptions) *Storage { // check for free disk space before opening the table // to prevent unexpected part merges. See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/4023 - s.startFreeDiskSpaceWatcher() + freeSpaceBytes := fs.MustGetFreeSpace(s.path) + s.isReadOnly.Store(freeSpaceBytes < freeDiskSpaceLimitBytes) // Load data tablePath := filepath.Join(path, dataDirname) @@ -314,6 +315,7 @@ func MustOpenStorage(path string, opts OpenOptions) *Storage { s.startCurrHourMetricIDsUpdater() s.startNextDayMetricIDsUpdater() s.startLegacyRetentionWatcher() + s.startFreeDiskSpaceWatcher() return s }
← Back to Alerts View on GitHub →