aboutsummaryrefslogtreecommitdiff
path: root/goapps/web/web.go
blob: 5f9c5cbc87999404b17e914079b57a7ac85f7ba9 (plain)
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)
	}
}