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