Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 1 | // Progam web provides an example of a webserver using capabilities to |
Andrew G. Morgan | b2b267e | 2019-11-30 18:33:42 -0800 | [diff] [blame] | 2 | // bind to a privileged port, and then drop all capabilities before |
| 3 | // handling the first web request. |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 4 | // |
Andrew G. Morgan | b2b267e | 2019-11-30 18:33:42 -0800 | [diff] [blame] | 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 |
Andrew G. Morgan | 876ac71 | 2020-07-02 21:36:16 -0700 | [diff] [blame] | 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]: |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 10 | // |
Andrew G. Morgan | 3165de9 | 2019-12-15 10:54:19 -0800 | [diff] [blame] | 11 | // https://go-review.googlesource.com/c/go/+/210639/ |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 12 | // |
Andrew G. Morgan | b2b267e | 2019-11-30 18:33:42 -0800 | [diff] [blame] | 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 |
Andrew G. Morgan | b0d13e8 | 2020-07-03 10:47:42 -0700 | [diff] [blame] | 15 | // runtime is to use something like libpsx via CGo to do capability |
Andrew G. Morgan | 1a61e6f | 2019-12-07 17:23:41 -0800 | [diff] [blame] | 16 | // setting syscalls in C with POSIX semantics. As of this build of the |
Andrew G. Morgan | b0d13e8 | 2020-07-03 10:47:42 -0700 | [diff] [blame] | 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. |
Andrew G. Morgan | b2b267e | 2019-11-30 18:33:42 -0800 | [diff] [blame] | 20 | // |
Andrew G. Morgan | 876ac71 | 2020-07-02 21:36:16 -0700 | [diff] [blame] | 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): |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 24 | // |
| 25 | // go build web.go |
Andrew G. Morgan | 3165de9 | 2019-12-15 10:54:19 -0800 | [diff] [blame] | 26 | // sudo setcap cap_setpcap,cap_net_bind_service=p web |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 27 | // ./web --port=80 |
| 28 | // |
Andrew G. Morgan | bcba9b3 | 2019-11-16 15:45:07 -0800 | [diff] [blame] | 29 | // Make requests using wget and observe the log of web: |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 30 | // |
| 31 | // wget -o/dev/null -O/dev/stdout localhost:80 |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 32 | package main |
| 33 | |
| 34 | import ( |
| 35 | "flag" |
| 36 | "fmt" |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 37 | "log" |
| 38 | "net" |
| 39 | "net/http" |
| 40 | "runtime" |
| 41 | "syscall" |
Andrew G. Morgan | 876ac71 | 2020-07-02 21:36:16 -0700 | [diff] [blame] | 42 | |
Andrew G. Morgan | b0d13e8 | 2020-07-03 10:47:42 -0700 | [diff] [blame] | 43 | "kernel.org/pub/linux/libs/security/libcap/cap" |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 44 | ) |
| 45 | |
| 46 | var ( |
| 47 | port = flag.Int("port", 0, "port to listen on") |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 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, |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 52 | // or being invoked by root. That is, the preparer isn't setting up |
| 53 | // the program correctly. |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 54 | func 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. Morgan | 3165de9 | 2019-12-15 10:54:19 -0800 | [diff] [blame] | 60 | log.Fatalf("go runtime is setuid uids:(%d vs %d), gids(%d vs %d)", uid, euid, gid, egid) |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 61 | } |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 62 | if uid == 0 { |
| 63 | log.Fatalf("go runtime is running as root - cheating") |
| 64 | } |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 65 | } |
| 66 | |
| 67 | // listen creates a listener by raising effective privilege only to |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 68 | // bind to address and then lowering that effective privilege. |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 69 | func listen(network, address string) (net.Listener, error) { |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 70 | if *skipPriv { |
| 71 | return net.Listen(network, address) |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 72 | } |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 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) |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 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 | } |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 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) |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 88 | } |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 89 | |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 90 | if err := c.SetProc(); err != nil { |
| 91 | return nil, fmt.Errorf("unable to raise capabilities %q: %v", c, err) |
| 92 | } |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 93 | return net.Listen(network, address) |
| 94 | } |
| 95 | |
| 96 | // Handler is used to abstract the ServeHTTP function. |
| 97 | type Handler struct{} |
| 98 | |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 99 | // ServeHTTP says hello from a single Go hardware thread and reveals |
| 100 | // its capabilities. |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 101 | func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 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. |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 106 | p := syscall.Getpid() |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 107 | t := syscall.Gettid() |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 108 | c := cap.GetProc() |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 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) |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 113 | } |
| 114 | |
| 115 | func main() { |
| 116 | flag.Parse() |
| 117 | |
| 118 | if *port == 0 { |
| 119 | log.Fatal("please supply --port value") |
| 120 | } |
| 121 | |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 122 | ensureNotEUID() |
| 123 | |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 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 | |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 130 | if !*skipPriv { |
Andrew G. Morgan | 3165de9 | 2019-12-15 10:54:19 -0800 | [diff] [blame] | 131 | if err := cap.ModeNoPriv.Set(); err != nil { |
Andrew G. Morgan | 6120f3c | 2020-02-23 17:15:05 -0800 | [diff] [blame] | 132 | log.Fatalf("unable to drop all privilege: %v", err) |
Andrew G. Morgan | 2d2b702 | 2019-11-16 09:14:22 -0800 | [diff] [blame] | 133 | } |
| 134 | } |
| 135 | |
Andrew G. Morgan | 1e4c28c | 2019-05-27 07:48:54 -0700 | [diff] [blame] | 136 | if err := http.Serve(ls, &Handler{}); err != nil { |
| 137 | log.Fatalf("server failed: %v", err) |
| 138 | } |
| 139 | } |