package credhelper 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 }