xref: /aosp_15_r20/external/libcap/goapps/web/web.go (revision 2810ac1b38eead2603277920c78344c84ddf3aff)
1// Program web provides an example of a webserver using capabilities to
2// bind to a privileged port, and then drop all capabilities before
3// handling the first web request.
4//
5// This program can be compiled CGO_ENABLED=0 with the go1.16+
6// toolchain.
7//
8// Go versions prior to 1.16 use some cgo support provided by the
9// "kernel.org/pub/linux/libs/security/libcap/psx" package.
10//
11// To set this up, compile and empower this binary as follows (the
12// README contains a pointer to a full writeup for building this
13// package - go versions prior to 1.15 need some environment variable
14// workarounds):
15//
16//   go mod init web
17//   go mod tidy
18//   go build web.go
19//   sudo setcap cap_setpcap,cap_net_bind_service=p web
20//   ./web --port=80
21//
22// Make requests using wget and observe the log of web:
23//
24//   wget -o/dev/null -O/dev/stdout localhost:80
25package main
26
27import (
28	"flag"
29	"fmt"
30	"log"
31	"net"
32	"net/http"
33	"runtime"
34	"syscall"
35
36	"kernel.org/pub/linux/libs/security/libcap/cap"
37)
38
39var (
40	port     = flag.Int("port", 0, "port to listen on")
41	skipPriv = flag.Bool("skip", false, "skip raising the effective capability - will fail for low ports")
42)
43
44// ensureNotEUID aborts the program if it is running setuid something,
45// or being invoked by root.  That is, the preparer isn't setting up
46// the program correctly.
47func ensureNotEUID() {
48	euid := syscall.Geteuid()
49	uid := syscall.Getuid()
50	egid := syscall.Getegid()
51	gid := syscall.Getgid()
52	if uid != euid || gid != egid {
53		log.Fatalf("go runtime is setuid uids:(%d vs %d), gids(%d vs %d)", uid, euid, gid, egid)
54	}
55	if uid == 0 {
56		log.Fatalf("go runtime is running as root - cheating")
57	}
58}
59
60// listen creates a listener by raising effective privilege only to
61// bind to address and then lowering that effective privilege.
62func listen(network, address string) (net.Listener, error) {
63	if *skipPriv {
64		return net.Listen(network, address)
65	}
66
67	orig := cap.GetProc()
68	defer orig.SetProc() // restore original caps on exit.
69
70	c, err := orig.Dup()
71	if err != nil {
72		return nil, fmt.Errorf("failed to dup caps: %v", err)
73	}
74
75	if on, _ := c.GetFlag(cap.Permitted, cap.NET_BIND_SERVICE); !on {
76		return nil, fmt.Errorf("insufficient privilege to bind to low ports - want %q, have %q", cap.NET_BIND_SERVICE, c)
77	}
78
79	if err := c.SetFlag(cap.Effective, true, cap.NET_BIND_SERVICE); err != nil {
80		return nil, fmt.Errorf("unable to set capability: %v", err)
81	}
82
83	if err := c.SetProc(); err != nil {
84		return nil, fmt.Errorf("unable to raise capabilities %q: %v", c, err)
85	}
86	return net.Listen(network, address)
87}
88
89// Handler is used to abstract the ServeHTTP function.
90type Handler struct{}
91
92// ServeHTTP says hello from a single Go hardware thread and reveals
93// its capabilities.
94func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
95	runtime.LockOSThread()
96	// Get some numbers consistent to the current execution, so
97	// the returned web page demonstrates that the code execution
98	// is bouncing around on different kernel thread ids.
99	p := syscall.Getpid()
100	t := syscall.Gettid()
101	c := cap.GetProc()
102	runtime.UnlockOSThread()
103
104	log.Printf("Saying hello from proc: %d->%d, caps=%q", p, t, c)
105	fmt.Fprintf(w, "Hello from proc: %d->%d, caps=%q\n", p, t, c)
106}
107
108func main() {
109	flag.Parse()
110
111	if *port == 0 {
112		log.Fatal("please supply --port value")
113	}
114
115	ensureNotEUID()
116
117	ls, err := listen("tcp", fmt.Sprintf(":%d", *port))
118	if err != nil {
119		log.Fatalf("aborting: %v", err)
120	}
121	defer ls.Close()
122
123	if !*skipPriv {
124		if err := cap.ModeNoPriv.Set(); err != nil {
125			log.Fatalf("unable to drop all privilege: %v", err)
126		}
127	}
128
129	if err := http.Serve(ls, &Handler{}); err != nil {
130		log.Fatalf("server failed: %v", err)
131	}
132}
133