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 133 134 135 136 137 138 139
|
// Progam 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 cannot work reliably as a pure Go application without
// the equivalent of the Go runtime patch that adds a POSIX semantics
// wrapper around the system calls that change per-thread security
// state. A patch for the pure Go compiler/runtime to add this support
// is available here [2019-12-14]:
//
// https://go-review.googlesource.com/c/go/+/210639/
//
// Until that patch, or something like it, is absorbed into the Go
// runtime the only way to get capabilities to work reliably on the Go
// runtime is to use something like libpsx via CGo to do capability
// setting syscalls in C with POSIX semantics. As of this build of the
// Go "kernel.org/pub/linux/libs/security/libcap/cap" package,
// courtesy of the "kernel.org/pub/linux/libs/security/libcap/psx"
// package, this is how things work.
//
// To set this up, compile and empower this binary as follows (read
// over the detail in the psx package description if this doesn't
// 'just' work):
//
// 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)
}
}
|