1// Progam 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 cannot work reliably as a pure Go application without 6// the equivalent of the Go runtime patch that adds a POSIX semantics 7// 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]: 10// 11// https://go-review.googlesource.com/c/go/+/210639/ 12// 13// 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 15// runtime is to use something like libpsx via CGo to do capability 16// setting syscalls in C with POSIX semantics. As of this build of the 17// 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. 20// 21// 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): 24// 25// go build web.go 26// sudo setcap cap_setpcap,cap_net_bind_service=p web 27// ./web --port=80 28// 29// Make requests using wget and observe the log of web: 30// 31// wget -o/dev/null -O/dev/stdout localhost:80 32package main 33 34import ( 35 "flag" 36 "fmt" 37 "log" 38 "net" 39 "net/http" 40 "runtime" 41 "syscall" 42 43 "kernel.org/pub/linux/libs/security/libcap/cap" 44) 45 46var ( 47 port = flag.Int("port", 0, "port to listen on") 48 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, 52// or being invoked by root. That is, the preparer isn't setting up 53// the program correctly. 54func ensureNotEUID() { 55 euid := syscall.Geteuid() 56 uid := syscall.Getuid() 57 egid := syscall.Getegid() 58 gid := syscall.Getgid() 59 if uid != euid || gid != egid { 60 log.Fatalf("go runtime is setuid uids:(%d vs %d), gids(%d vs %d)", uid, euid, gid, egid) 61 } 62 if uid == 0 { 63 log.Fatalf("go runtime is running as root - cheating") 64 } 65} 66 67// listen creates a listener by raising effective privilege only to 68// bind to address and then lowering that effective privilege. 69func listen(network, address string) (net.Listener, error) { 70 if *skipPriv { 71 return net.Listen(network, address) 72 } 73 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) 80 } 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 } 85 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) 88 } 89 90 if err := c.SetProc(); err != nil { 91 return nil, fmt.Errorf("unable to raise capabilities %q: %v", c, err) 92 } 93 return net.Listen(network, address) 94} 95 96// Handler is used to abstract the ServeHTTP function. 97type Handler struct{} 98 99// ServeHTTP says hello from a single Go hardware thread and reveals 100// its capabilities. 101func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 102 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. 106 p := syscall.Getpid() 107 t := syscall.Gettid() 108 c := cap.GetProc() 109 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) 113} 114 115func main() { 116 flag.Parse() 117 118 if *port == 0 { 119 log.Fatal("please supply --port value") 120 } 121 122 ensureNotEUID() 123 124 ls, err := listen("tcp", fmt.Sprintf(":%d", *port)) 125 if err != nil { 126 log.Fatalf("aborting: %v", err) 127 } 128 defer ls.Close() 129 130 if !*skipPriv { 131 if err := cap.ModeNoPriv.Set(); err != nil { 132 log.Fatalf("unable to drop all privilege: %v", err) 133 } 134 } 135 136 if err := http.Serve(ls, &Handler{}); err != nil { 137 log.Fatalf("server failed: %v", err) 138 } 139} 140