xref: /aosp_15_r20/external/libcap/goapps/captrace/captrace.go (revision 2810ac1b38eead2603277920c78344c84ddf3aff)
1// Program captrace traces processes and notices when they attempt
2// kernel actions that require Effective capabilities.
3//
4// The reference material for developing this tool was the the book
5// "Linux Observabililty with BPF" by David Calavera and Lorenzo
6// Fontana.
7package main
8
9import (
10	"bufio"
11	"flag"
12	"fmt"
13	"io"
14	"log"
15	"os"
16	"os/exec"
17	"strconv"
18	"strings"
19	"sync"
20	"syscall"
21	"time"
22
23	"kernel.org/pub/linux/libs/security/libcap/cap"
24)
25
26var (
27	bpftrace = flag.String("bpftrace", "bpftrace", "command to launch bpftrace")
28	debug    = flag.Bool("debug", false, "more output")
29	pid      = flag.Int("pid", -1, "PID of target process to trace (-1 = trace all)")
30)
31
32type thread struct {
33	PPID, Datum int
34	Value       cap.Value
35	Token       string
36}
37
38// mu protects these two maps.
39var mu sync.Mutex
40
41// tids tracks which PIDs we are following.
42var tids = make(map[int]int)
43
44// cache tracks in-flight cap_capable invocations.
45var cache = make(map[int]*thread)
46
47// event adds or resolves a capability event.
48func event(add bool, tid int, th *thread) {
49	mu.Lock()
50	defer mu.Unlock()
51
52	if len(tids) != 0 {
53		if _, ok := tids[th.PPID]; !ok {
54			if *debug {
55				log.Printf("dropped %d %d %v event", th.PPID, tid, *th)
56			}
57			return
58		}
59		tids[tid] = th.PPID
60		tids[th.PPID] = th.PPID
61	}
62
63	if add {
64		cache[tid] = th
65	} else {
66		if b, ok := cache[tid]; ok {
67			detail := ""
68			if th.Datum < 0 {
69				detail = fmt.Sprintf(" (%v)", syscall.Errno(-th.Datum))
70			}
71			task := ""
72			if th.PPID != tid {
73				task = fmt.Sprintf("+{%d}", tid)
74			}
75			log.Printf("%-16s %d%s opt=%d %q -> %d%s", b.Token, b.PPID, task, b.Datum, b.Value, th.Datum, detail)
76		}
77		delete(cache, tid)
78	}
79}
80
81// tailTrace tails the bpftrace command output recognizing lines of
82// interest.
83func tailTrace(cmd *exec.Cmd, out io.Reader) {
84	launched := false
85	sc := bufio.NewScanner(out)
86	for sc.Scan() {
87		fields := strings.Split(sc.Text(), " ")
88		if len(fields) < 4 {
89			continue // ignore
90		}
91		if !launched {
92			launched = true
93			mu.Unlock()
94		}
95		switch fields[0] {
96		case "CB":
97			if len(fields) < 6 {
98				continue
99			}
100			pid, err := strconv.Atoi(fields[1])
101			if err != nil {
102				continue
103			}
104			th := &thread{
105				PPID: pid,
106			}
107			tid, err := strconv.Atoi(fields[2])
108			if err != nil {
109				continue
110			}
111			c, err := strconv.Atoi(fields[3])
112			if err != nil {
113				continue
114			}
115			th.Value = cap.Value(c)
116			aud, err := strconv.Atoi(fields[4])
117			if err != nil {
118				continue
119			}
120			th.Datum = aud
121			th.Token = strings.Join(fields[5:], " ")
122			event(true, tid, th)
123		case "CE":
124			if len(fields) < 4 {
125				continue
126			}
127			pid, err := strconv.Atoi(fields[1])
128			if err != nil {
129				continue
130			}
131			th := &thread{
132				PPID: pid,
133			}
134			tid, err := strconv.Atoi(fields[2])
135			if err != nil {
136				continue
137			}
138			aud, err := strconv.Atoi(fields[3])
139			if err != nil {
140				continue
141			}
142			th.Datum = aud
143			event(false, tid, th)
144		default:
145			if *debug {
146				fmt.Println("unparsable:", fields)
147			}
148		}
149	}
150	if err := sc.Err(); err != nil {
151		log.Fatalf("scanning failed: %v", err)
152	}
153}
154
155// tracer invokes bpftool it returns an error if the invocation fails.
156func tracer() (*exec.Cmd, error) {
157	cmd := exec.Command(*bpftrace, "-e", `kprobe:cap_capable {
158    printf("CB %d %d %d %d %s\n", pid, tid, arg2, arg3, comm);
159}
160kretprobe:cap_capable {
161    printf("CE %d %d %d\n", pid, tid, retval);
162}`)
163	out, err := cmd.StdoutPipe()
164	cmd.Stderr = os.Stderr
165	if err != nil {
166		return nil, fmt.Errorf("unable to create stdout for %q: %v", *bpftrace, err)
167	}
168	mu.Lock() // Unlocked on first ouput from tracer.
169	if err := cmd.Start(); err != nil {
170		return nil, fmt.Errorf("failed to start %q: %v", *bpftrace, err)
171	}
172	go tailTrace(cmd, out)
173	return cmd, nil
174}
175
176func main() {
177	flag.Usage = func() {
178		fmt.Fprintf(flag.CommandLine.Output(), `Usage: %s [options] [command ...]
179
180This tool monitors cap_capable() kernel execution to summarize when
181Effective Flag capabilities are checked in a running process{thread}.
182The monitoring is performed indirectly using the bpftrace tool.
183
184Each line logged has a timestamp at which the tracing program is able to
185summarize the return value of the check. A return value of " -> 0" implies
186the check succeeded and confirms the process{thread} does have the
187specified Effective capability.
188
189The listed "opt=" value indicates some auditing context for why the
190kernel needed to check the capability was Effective.
191
192Options:
193`, os.Args[0])
194		flag.PrintDefaults()
195	}
196	flag.Parse()
197
198	tr, err := tracer()
199	if err != nil {
200		log.Fatalf("failed to start tracer: %v", err)
201	}
202
203	mu.Lock()
204
205	if *pid != -1 {
206		tids[*pid] = *pid
207	} else if len(flag.Args()) != 0 {
208		args := flag.Args()
209		cmd := exec.Command(args[0], args[1:]...)
210		cmd.Stdin = os.Stdin
211		cmd.Stdout = os.Stdout
212		cmd.Stderr = os.Stderr
213		if err := cmd.Start(); err != nil {
214			log.Fatalf("failed to start %v: %v", flag.Args(), err)
215		}
216		tids[cmd.Process.Pid] = cmd.Process.Pid
217
218		// waiting for the trace to complete is racy, so we sleep
219		// to obtain the last events then kill the tracer and wait
220		// for it to exit. Defers are in reverse order.
221		defer tr.Wait()
222		defer tr.Process.Kill()
223		defer time.Sleep(1 * time.Second)
224
225		tr = cmd
226	}
227
228	mu.Unlock()
229	tr.Wait()
230}
231