//go:build linux package instancectl import ( "fmt" "net" "os" "strconv" "strings" ) type AuthenticatedPeer struct { UID uint32 PID int ProcessStart, BootID, ControllerID string } func procStart(pid int) (string, error) { b, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return "", err } i := strings.LastIndexByte(string(b), ')') if i < 0 { return "", fmt.Errorf("malformed proc stat") } f := strings.Fields(string(b[i+1:])) if len(f) <= 19 { return "", fmt.Errorf("malformed proc stat") } if _, err := strconv.ParseUint(f[19], 10, 64); err != nil { return "", err } return f[19], nil } // AuthenticatePeerTuple binds the accepted Unix peer to kernel identity and // rereads starttime to reject PID reuse during collection. func AuthenticatePeerTuple(conn net.Conn, expectedUID uint32, expectedPID int, controllerID string) (AuthenticatedPeer, error) { if conn == nil || expectedPID <= 0 || controllerID == "" { return AuthenticatedPeer{}, fmt.Errorf("invalid peer expectation") } u, err := peerCred(conn) if err != nil { return AuthenticatedPeer{}, err } if u.Uid != expectedUID || int(u.Pid) != expectedPID { return AuthenticatedPeer{}, fmt.Errorf("peer identity mismatch") } start, err := procStart(expectedPID) if err != nil { return AuthenticatedPeer{}, err } boot, err := os.ReadFile("/proc/sys/kernel/random/boot_id") if err != nil { return AuthenticatedPeer{}, err } start2, err := procStart(expectedPID) if err != nil || start2 != start { return AuthenticatedPeer{}, fmt.Errorf("peer process changed during authentication") } return AuthenticatedPeer{UID: u.Uid, PID: int(u.Pid), ProcessStart: start, BootID: strings.TrimSpace(string(boot)), ControllerID: controllerID}, nil }