From 7a3664eac0ae774a2613ad7aa4609836619a2177 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Mon, 11 May 2026 20:20:08 +0200 Subject: [PATCH] fix(ps): Avoid return module temporary copy on stack under released lock Module is a copy of the slice element, re-used on each iteration. Returning &mod gives the caller a pointer to that temporary copy on the stack. Once the function returns, that memory is either stale or (if the compiler escapes it to the heap) points to a value that is disconnected from the original slice. Any mutation through the pointer won't affect ps.Modules, and the pointed-to value may be unexpected if the compiler reuses the variable. Secondary, returning a pointer under a released lock the caller receives a *Module but the RUnlock runs before the caller uses it. If another goroutine mutates or removes that module from the slice concurrently, the caller is reading (or writing through) the pointer without any lock held. --- pkg/ps/types/types_windows.go | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/pkg/ps/types/types_windows.go b/pkg/ps/types/types_windows.go index 26621249e..ee1359813 100644 --- a/pkg/ps/types/types_windows.go +++ b/pkg/ps/types/types_windows.go @@ -497,7 +497,9 @@ func (ps *PS) RemoveHandle(handle windows.Handle) { // AddModule adds a new module to this process state. func (ps *PS) AddModule(mod Module) { + ps.RLock() m := ps.FindModuleByAddr(mod.BaseAddress) + ps.RUnlock() if m != nil { return } @@ -522,9 +524,9 @@ func (ps *PS) RemoveModule(addr va.Address) { func (ps *PS) FindModule(path string) *Module { ps.RLock() defer ps.RUnlock() - for _, mod := range ps.Modules { - if filepath.Base(mod.Name) == filepath.Base(path) { - return &mod + for i := range ps.Modules { + if filepath.Base(ps.Modules[i].Name) == filepath.Base(path) { + return &ps.Modules[i] } } return nil @@ -532,11 +534,9 @@ func (ps *PS) FindModule(path string) *Module { // FindModuleByAddr finds the module by its base address. func (ps *PS) FindModuleByAddr(addr va.Address) *Module { - ps.RLock() - defer ps.RUnlock() - for _, mod := range ps.Modules { - if mod.BaseAddress == addr { - return &mod + for i := range ps.Modules { + if ps.Modules[i].BaseAddress == addr { + return &ps.Modules[i] } } return nil @@ -549,7 +549,9 @@ var queryLiveModules = func(pid uint32) []sys.ProcessModule { // FindModuleByVa finds the module name by // probing the range of the given virtual address. func (ps *PS) FindModuleByVa(addr va.Address) *Module { + ps.RLock() mod := ps.findModuleByVa(addr) + ps.RUnlock() if mod != nil { return mod } @@ -569,8 +571,7 @@ func (ps *PS) FindModuleByVa(addr va.Address) *Module { if addr < b || addr >= b.Inc(size) { continue } - - mod := Module{ + mod := &Module{ Name: m.Name, BaseAddress: b, Size: size, @@ -578,22 +579,21 @@ func (ps *PS) FindModuleByVa(addr va.Address) *Module { } ps.Lock() - ps.Modules = append(ps.Modules, mod) + ps.Modules = append(ps.Modules, *mod) ps.Unlock() - return &mod + return mod } return nil } func (ps *PS) findModuleByVa(addr va.Address) *Module { - ps.RLock() - defer ps.RUnlock() - for _, mod := range ps.Modules { - end := mod.BaseAddress.Inc(mod.Size) - if addr >= mod.BaseAddress && addr < end { - return &mod + for i := range ps.Modules { + end := ps.Modules[i].BaseAddress.Inc(ps.Modules[i].Size) + if addr >= ps.Modules[i].BaseAddress && addr < end { + mod := &ps.Modules[i] + return mod } } return nil