Merge pull request #3000 from chengzhichao-xydt/codex/pid-verify-process-identity
fix(pid): verify process identity in singleton PID check
This commit is contained in:
commit
976ecc68b7
3 changed files with 56 additions and 2 deletions
|
|
@ -64,7 +64,14 @@ func WritePidFile(homePath, host string, port int) (*PidFileData, error) {
|
||||||
// pass the isProcessRunning check, blocking new gateway starts.
|
// pass the isProcessRunning check, blocking new gateway starts.
|
||||||
// Treat recorded PID 1 as always stale.
|
// Treat recorded PID 1 as always stale.
|
||||||
if data.PID != 1 && isProcessRunning(data.PID) {
|
if data.PID != 1 && isProcessRunning(data.PID) {
|
||||||
return nil, fmt.Errorf("gateway is already running (PID: %d, version: %s)", data.PID, data.Version)
|
// Verify the process is actually a picoclaw instance.
|
||||||
|
// If the PID was reused by an unrelated process
|
||||||
|
// (e.g. systemd-resolved after a kill -9), treat
|
||||||
|
// the PID file as stale and proceed with startup.
|
||||||
|
if isPicoclawProcess(data.PID) {
|
||||||
|
return nil, fmt.Errorf("gateway is already running (PID: %d, version: %s)", data.PID, data.Version)
|
||||||
|
}
|
||||||
|
logger.Warnf("found pid file (PID: %d) but process is not picoclaw", data.PID)
|
||||||
}
|
}
|
||||||
logger.Warnf("not running (PID: %d) so will remove the pid file: %s", data.PID, pidPath)
|
logger.Warnf("not running (PID: %d) so will remove the pid file: %s", data.PID, pidPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ package pid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,7 +20,7 @@ func isProcessRunning(pid int) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// Signal(nil) does not kill the process but checks existence on Unix.
|
// Signal(0) does not kill the process but checks existence on Unix.
|
||||||
err = p.Signal(syscall.Signal(0))
|
err = p.Signal(syscall.Signal(0))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return true
|
return true
|
||||||
|
|
@ -27,3 +29,16 @@ func isProcessRunning(pid int) bool {
|
||||||
// EPERM means the process exists but we are not allowed to signal it.
|
// EPERM means the process exists but we are not allowed to signal it.
|
||||||
return errors.As(err, &errno) && errno == syscall.EPERM
|
return errors.As(err, &errno) && errno == syscall.EPERM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isPicoclawProcess reads /proc/<pid>/comm to confirm the process name
|
||||||
|
// contains "picoclaw". Returns false when the comm file can be read and
|
||||||
|
// the name does not match (e.g., PID was reused by an unrelated process).
|
||||||
|
// Returns true if /proc/<pid>/comm is unreadable so the call site falls
|
||||||
|
// back to trusting the liveness check alone.
|
||||||
|
func isPicoclawProcess(pid int) bool {
|
||||||
|
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid))
|
||||||
|
if err != nil {
|
||||||
|
return true // cannot verify — trust liveness check
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.TrimSpace(string(data)), "picoclaw")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
package pid
|
package pid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
@ -12,6 +13,7 @@ var (
|
||||||
procOpenProcess = kernel32.NewProc("OpenProcess")
|
procOpenProcess = kernel32.NewProc("OpenProcess")
|
||||||
procGetExitCodeProcess = kernel32.NewProc("GetExitCodeProcess")
|
procGetExitCodeProcess = kernel32.NewProc("GetExitCodeProcess")
|
||||||
procCloseHandle = kernel32.NewProc("CloseHandle")
|
procCloseHandle = kernel32.NewProc("CloseHandle")
|
||||||
|
procQueryFullProcessImageNameW = kernel32.NewProc("QueryFullProcessImageNameW")
|
||||||
processQueryLimitedInformation = uint32(0x1000)
|
processQueryLimitedInformation = uint32(0x1000)
|
||||||
stillActive = uint32(259)
|
stillActive = uint32(259)
|
||||||
)
|
)
|
||||||
|
|
@ -40,3 +42,33 @@ func isProcessRunning(pid int) bool {
|
||||||
}
|
}
|
||||||
return exitCode == stillActive
|
return exitCode == stillActive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isPicoclawProcess uses QueryFullProcessImageNameW to confirm the
|
||||||
|
// process image name contains "picoclaw". Returns false when the name
|
||||||
|
// clearly does not match. Returns true if the query fails, falling
|
||||||
|
// back to trusting the liveness check alone.
|
||||||
|
func isPicoclawProcess(pid int) bool {
|
||||||
|
handle, _, _ := procOpenProcess.Call(
|
||||||
|
uintptr(processQueryLimitedInformation),
|
||||||
|
0,
|
||||||
|
uintptr(pid),
|
||||||
|
)
|
||||||
|
if handle == 0 {
|
||||||
|
return true // cannot open — trust liveness check
|
||||||
|
}
|
||||||
|
defer procCloseHandle.Call(handle)
|
||||||
|
|
||||||
|
var buf [260]uint16
|
||||||
|
var size uint32 = 260
|
||||||
|
ret, _, _ := procQueryFullProcessImageNameW.Call(
|
||||||
|
uintptr(handle),
|
||||||
|
0, // WIN32_NAME_FORMAT
|
||||||
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
|
uintptr(unsafe.Pointer(&size)),
|
||||||
|
)
|
||||||
|
if ret == 0 {
|
||||||
|
return true // cannot verify — trust liveness check
|
||||||
|
}
|
||||||
|
name := strings.ToLower(syscall.UTF16ToString(buf[:size]))
|
||||||
|
return strings.Contains(name, "picoclaw")
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue