blob: d184e974e03c97e95f10e3d6f407233e53c87c00 [file] [log] [blame]
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -07001// Progam web provides an example of a webserver using capabilities to
Andrew G. Morganb2b267e2019-11-30 18:33:42 -08002// bind to a privileged port, and then drop all capabilities before
3// handling the first web request.
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -07004//
Andrew G. Morganb2b267e2019-11-30 18:33:42 -08005// This program cannot work reliably as a pure Go application without
6// the equivalent of the Go runtime patch that adds a POSIX semantics
Andrew G. Morgan876ac712020-07-02 21:36:16 -07007// wrapper around the system calls that change per-thread security
8// state. A patch for the pure Go compiler/runtime to add this support
9// is available here [2019-12-14]:
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080010//
Andrew G. Morgan3165de92019-12-15 10:54:19 -080011// https://go-review.googlesource.com/c/go/+/210639/
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080012//
Andrew G. Morganb2b267e2019-11-30 18:33:42 -080013// Until that patch, or something like it, is absorbed into the Go
14// runtime the only way to get capabilities to work reliably on the Go
Andrew G. Morganb0d13e82020-07-03 10:47:42 -070015// runtime is to use something like libpsx via CGo to do capability
Andrew G. Morgan1a61e6f2019-12-07 17:23:41 -080016// setting syscalls in C with POSIX semantics. As of this build of the
Andrew G. Morganb0d13e82020-07-03 10:47:42 -070017// Go "kernel.org/pub/linux/libs/security/libcap/cap" package,
18// courtesy of the "kernel.org/pub/linux/libs/security/libcap/psx"
19// package, this is how things work.
Andrew G. Morganb2b267e2019-11-30 18:33:42 -080020//
Andrew G. Morgan876ac712020-07-02 21:36:16 -070021// To set this up, compile and empower this binary as follows (read
22// over the detail in the psx package description if this doesn't
23// 'just' work):
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080024//
25// go build web.go
Andrew G. Morgan3165de92019-12-15 10:54:19 -080026// sudo setcap cap_setpcap,cap_net_bind_service=p web
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080027// ./web --port=80
28//
Andrew G. Morganbcba9b32019-11-16 15:45:07 -080029// Make requests using wget and observe the log of web:
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080030//
31// wget -o/dev/null -O/dev/stdout localhost:80
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070032package main
33
34import (
35 "flag"
36 "fmt"
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070037 "log"
38 "net"
39 "net/http"
40 "runtime"
41 "syscall"
Andrew G. Morgan876ac712020-07-02 21:36:16 -070042
Andrew G. Morganb0d13e82020-07-03 10:47:42 -070043 "kernel.org/pub/linux/libs/security/libcap/cap"
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070044)
45
46var (
47 port = flag.Int("port", 0, "port to listen on")
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070048 skipPriv = flag.Bool("skip", false, "skip raising the effective capability - will fail for low ports")
49)
50
51// ensureNotEUID aborts the program if it is running setuid something,
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080052// or being invoked by root. That is, the preparer isn't setting up
53// the program correctly.
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070054func ensureNotEUID() {
55 euid := syscall.Geteuid()
56 uid := syscall.Getuid()
57 egid := syscall.Getegid()
58 gid := syscall.Getgid()
59 if uid != euid || gid != egid {
Andrew G. Morgan3165de92019-12-15 10:54:19 -080060 log.Fatalf("go runtime is setuid uids:(%d vs %d), gids(%d vs %d)", uid, euid, gid, egid)
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070061 }
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080062 if uid == 0 {
63 log.Fatalf("go runtime is running as root - cheating")
64 }
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070065}
66
67// listen creates a listener by raising effective privilege only to
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080068// bind to address and then lowering that effective privilege.
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070069func listen(network, address string) (net.Listener, error) {
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080070 if *skipPriv {
71 return net.Listen(network, address)
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070072 }
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080073
74 orig := cap.GetProc()
75 defer orig.SetProc() // restore original caps on exit.
76
77 c, err := orig.Dup()
78 if err != nil {
79 return nil, fmt.Errorf("failed to dup caps: %v", err)
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070080 }
81
82 if on, _ := c.GetFlag(cap.Permitted, cap.NET_BIND_SERVICE); !on {
83 return nil, fmt.Errorf("insufficient privilege to bind to low ports - want %q, have %q", cap.NET_BIND_SERVICE, c)
84 }
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080085
86 if err := c.SetFlag(cap.Effective, true, cap.NET_BIND_SERVICE); err != nil {
87 return nil, fmt.Errorf("unable to set capability: %v", err)
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070088 }
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080089
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070090 if err := c.SetProc(); err != nil {
91 return nil, fmt.Errorf("unable to raise capabilities %q: %v", c, err)
92 }
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -070093 return net.Listen(network, address)
94}
95
96// Handler is used to abstract the ServeHTTP function.
97type Handler struct{}
98
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -080099// ServeHTTP says hello from a single Go hardware thread and reveals
100// its capabilities.
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700101func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800102 runtime.LockOSThread()
103 // Get some numbers consistent to the current execution, so
104 // the returned web page demonstrates that the code execution
105 // is bouncing around on different kernel thread ids.
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700106 p := syscall.Getpid()
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800107 t := syscall.Gettid()
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700108 c := cap.GetProc()
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800109 runtime.UnlockOSThread()
110
111 log.Printf("Saying hello from proc: %d->%d, caps=%q", p, t, c)
112 fmt.Fprintf(w, "Hello from proc: %d->%d, caps=%q\n", p, t, c)
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700113}
114
115func main() {
116 flag.Parse()
117
118 if *port == 0 {
119 log.Fatal("please supply --port value")
120 }
121
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800122 ensureNotEUID()
123
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700124 ls, err := listen("tcp", fmt.Sprintf(":%d", *port))
125 if err != nil {
126 log.Fatalf("aborting: %v", err)
127 }
128 defer ls.Close()
129
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800130 if !*skipPriv {
Andrew G. Morgan3165de92019-12-15 10:54:19 -0800131 if err := cap.ModeNoPriv.Set(); err != nil {
Andrew G. Morgan6120f3c2020-02-23 17:15:05 -0800132 log.Fatalf("unable to drop all privilege: %v", err)
Andrew G. Morgan2d2b7022019-11-16 09:14:22 -0800133 }
134 }
135
Andrew G. Morgan1e4c28c2019-05-27 07:48:54 -0700136 if err := http.Serve(ls, &Handler{}); err != nil {
137 log.Fatalf("server failed: %v", err)
138 }
139}