From 0265f31f8402de79faff14bf1bbbb0ab7fc3079c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Sun, 6 Sep 2026 15:51:35 +0800 Subject: [PATCH] Fix Linux process path lookup after privilege drop --- common/process/searcher_linux.go | 39 ++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/common/process/searcher_linux.go b/common/process/searcher_linux.go index 573899c2..570e23d3 100644 --- a/common/process/searcher_linux.go +++ b/common/process/searcher_linux.go @@ -22,7 +22,11 @@ import ( "github.com/sagernet/sing/contrab/maphash" ) -const pathProc = "/proc" +const ( + pathProc = "/proc" + + processPathsAllUsers = ^uint32(0) +) var _ Searcher = (*linuxSearcher)(nil) @@ -123,25 +127,30 @@ func (s *linuxSearcher) resolveSocketByNetlink(network string, source netip.Addr return dumpSocketDiag(family, protocol, source, destination) } +// The socket keeps the uid it was created with, while /proc reflects the +// current uid of the process, so a socket created before a privilege drop +// only appears under a scan of all users. func (s *linuxSearcher) findProcessPath(targetInode, uid uint32) (string, error) { - if cached, ok := s.processPathCache.Get(uid); ok { - if processPath, found := cached.entries[targetInode]; found { + for _, scanUID := range []uint32{uid, processPathsAllUsers} { + if cached, ok := s.processPathCache.Get(scanUID); ok { + if processPath, found := cached.entries[targetInode]; found { + return processPath, nil + } + } + processPaths, err := buildProcessPaths(scanUID) + if err != nil { + return "", err + } + s.processPathCache.Add(scanUID, &uidProcessPaths{entries: processPaths}) + processPath, found := processPaths[targetInode] + if found { return processPath, nil } } - processPaths, err := buildProcessPathsByUID(uid) - if err != nil { - return "", err - } - s.processPathCache.Add(uid, &uidProcessPaths{entries: processPaths}) - processPath, found := processPaths[targetInode] - if !found { - return "", E.New("process of uid(", uid, "), inode(", targetInode, ") not found") - } - return processPath, nil + return "", E.New("process of uid(", uid, "), inode(", targetInode, ") not found") } -func buildProcessPathsByUID(uid uint32) (map[uint32]string, error) { +func buildProcessPaths(uid uint32) (map[uint32]string, error) { files, err := os.ReadDir(pathProc) if err != nil { return nil, err @@ -159,7 +168,7 @@ func buildProcessPathsByUID(uid uint32) (map[uint32]string, error) { } return nil, err } - if info.Sys().(*syscall.Stat_t).Uid != uid { + if uid != processPathsAllUsers && info.Sys().(*syscall.Stat_t).Uid != uid { continue } processPath := filepath.Join(pathProc, file.Name())