Files
at-container-registry/cmd/credential-helper/process_darwin.go

108 lines
2.6 KiB
Go

package main
import (
"bytes"
"encoding/binary"
"fmt"
"unsafe"
"golang.org/x/sys/unix"
)
// getProcessArgs uses kern.procargs2 sysctl to get process arguments.
// This is the same mechanism ps(1) uses on macOS — no exec.Command needed.
//
// The kern.procargs2 buffer layout:
//
// [4 bytes: argc as int32]
// [executable path\0]
// [padding \0 bytes]
// [argv[0]\0][argv[1]\0]...[argv[argc-1]\0]
// [env vars...]
func getProcessArgs(pid int) ([]string, error) {
// kern.procargs2 MIB: CTL_KERN=1, KERN_PROCARGS2=49
mib := []int32{1, 49, int32(pid)} //nolint:mnd
// First call to get buffer size
n := uintptr(0)
if err := sysctl(mib, nil, &n, nil, 0); err != nil {
return nil, fmt.Errorf("sysctl size query for pid %d: %w", pid, err)
}
buf := make([]byte, n)
if err := sysctl(mib, &buf[0], &n, nil, 0); err != nil {
return nil, fmt.Errorf("sysctl read for pid %d: %w", pid, err)
}
buf = buf[:n]
if len(buf) < 4 {
return nil, fmt.Errorf("procargs2 buffer too short for pid %d", pid)
}
// First 4 bytes: argc
argc := int(binary.LittleEndian.Uint32(buf[:4]))
pos := 4
// Skip executable path (null-terminated)
end := bytes.IndexByte(buf[pos:], 0)
if end == -1 {
return nil, fmt.Errorf("no null terminator in exec path for pid %d", pid)
}
pos += end + 1
// Skip padding null bytes
for pos < len(buf) && buf[pos] == 0 {
pos++
}
// Read argc arguments
args := make([]string, 0, argc)
for i := 0; i < argc && pos < len(buf); i++ {
end := bytes.IndexByte(buf[pos:], 0)
if end == -1 {
args = append(args, string(buf[pos:]))
break
}
args = append(args, string(buf[pos:pos+end]))
pos += end + 1
}
if len(args) == 0 {
return nil, fmt.Errorf("no args found for pid %d", pid)
}
return args, nil
}
// getParentPID uses kern.proc.pid sysctl to find the parent PID.
func getParentPID(pid int) (int, error) {
// kern.proc.pid MIB: CTL_KERN=1, KERN_PROC=14, KERN_PROC_PID=1
mib := []int32{1, 14, 1, int32(pid)} //nolint:mnd
var kinfo unix.KinfoProc
n := uintptr(unsafe.Sizeof(kinfo))
if err := sysctl(mib, (*byte)(unsafe.Pointer(&kinfo)), &n, nil, 0); err != nil {
return 0, fmt.Errorf("sysctl kern.proc.pid for pid %d: %w", pid, err)
}
return int(kinfo.Eproc.Ppid), nil
}
// sysctl is a thin wrapper around unix.Sysctl raw syscall.
func sysctl(mib []int32, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
_, _, errno := unix.Syscall6(
unix.SYS___SYSCTL,
uintptr(unsafe.Pointer(&mib[0])),
uintptr(len(mib)),
uintptr(unsafe.Pointer(old)),
uintptr(unsafe.Pointer(oldlen)),
uintptr(unsafe.Pointer(new)),
newlen,
)
if errno != 0 {
return errno
}
return nil
}