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