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