carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package console
4
5import (
6 "fmt"
7 "os"
8 "strconv"
9 "strings"
10 "sync"
11 "syscall"
12)
13
14var hostReportProcReadFile = os.ReadFile
15var hostReportProcessIdentity = sync.OnceValue(readHostReportProcessIdentity)
16
17func hostReportProcessStart(pid int) (string, error) {
18 raw, err := hostReportProcReadFile(fmt.Sprintf("/proc/%d/stat", pid))
19 if err != nil {
20 return "", err
21 }
22 end := strings.LastIndexByte(string(raw), ')')
23 if end < 0 {
24 return "", os.ErrInvalid
25 }
26 fields := strings.Fields(string(raw[end+1:]))
27 if len(fields) < 20 {
28 return "", os.ErrInvalid
29 }
30 start, err := strconv.ParseUint(fields[19], 10, 64)
31 if err != nil || start == 0 {
32 return "", os.ErrInvalid
33 }
34 return fields[19], nil
35}
36
37var hostReportPIDNamespace = func() (string, error) {
38 info, err := os.Stat("/proc/self/ns/pid")
39 if err != nil {
40 return "", err
41 }
42 st, ok := info.Sys().(*syscall.Stat_t)
43 if !ok || st.Ino == 0 {
44 return "", os.ErrInvalid
45 }
46 return strconv.FormatUint(st.Ino, 10), nil
47}
48
49func readHostReportProcessIdentity() string {
50 namespace, namespaceErr := hostReportPIDNamespace()
51 start, err := hostReportProcessStart(os.Getpid())
52 if err != nil || namespaceErr != nil {
53 return strconv.Itoa(os.Getpid())
54 }
55 return fmt.Sprintf("%s:%d:%s", namespace, os.Getpid(), start)
56}
57
58func hostReportProcessID(boot string) string {
59 return boot + ":" + hostReportProcessIdentity()
60}
61
62// Linux reserves PID_NS_INIT_INO (formerly PROC_PID_INIT_INO) for its initial
63// PID namespace. A container's own procfs also makes /proc/1 match self, so
64// that match alone cannot prove main's legacy boot:pid is local.
65// See include/uapi/linux/nsfs.h in the kernel source.
66const hostReportInitialPIDNamespace = "4026531836" // 0xEFFFFFFC
67
68var hostReportRootPIDNamespace = func() (string, error) {
69 info, err := os.Stat("/proc/1/ns/pid")
70 if err != nil {
71 return "", err
72 }
73 st, ok := info.Sys().(*syscall.Stat_t)
74 if !ok || st.Ino == 0 {
75 return "", os.ErrInvalid
76 }
77 return strconv.FormatUint(st.Ino, 10), nil
78}
79
80func hostReportProcessGone(previous, next string) bool {
81 old, current := strings.Split(previous, ":"), strings.Split(next, ":")
82 switch {
83 case len(old) == 4 && len(current) == 4 && old[0] == current[0] && old[1] == current[1]:
84 namespace, err := hostReportPIDNamespace()
85 if err != nil || namespace != old[1] || !hostReportBootMatches(old[0]) {
86 return false
87 }
88 if start, err := strconv.ParseUint(old[3], 10, 64); err != nil || start == 0 {
89 return false
90 }
91 return hostReportPIDAbsent(old[2])
92 case len(old) == 2 && len(current) >= 2 && old[0] == current[0]:
93 // Main's boot:pid. It carries no namespace, but main only ran in the
94 // host namespace, so accept it only from a process in that namespace
95 // on the same boot, and only with the same absence proof.
96 namespace, err := hostReportPIDNamespace()
97 if err != nil {
98 return false
99 }
100 root, err := hostReportRootPIDNamespace()
101 if err != nil || root != hostReportInitialPIDNamespace || root != namespace || !hostReportBootMatches(old[0]) {
102 return false
103 }
104 return hostReportPIDAbsent(old[1])
105 }
106 return false
107}
108
109func hostReportBootMatches(boot string) bool {
110 raw, err := hostReportProcReadFile("/proc/sys/kernel/random/boot_id")
111 return err == nil && strings.TrimSpace(string(raw)) == boot
112}
113
114func hostReportPIDAbsent(text string) bool {
115 pid, err := strconv.Atoi(text)
116 if err != nil || pid <= 0 {
117 return false
118 }
119 _, err = hostReportProcessStart(pid)
120 if !os.IsNotExist(err) {
121 return false
122 }
123 // A filtered proc mount may hide a live PID. Signal 0 sends no signal;
124 // only ESRCH in the matching PID namespace proves that PID is absent.
125 return syscall.Kill(pid, 0) == syscall.ESRCH
126}
127