1
+// Copyright 2015 Matthew Holt and The Caddy Authors
2
+//
3
+// Licensed under the Apache License, Version 2.0 (the "License");
4
+// you may not use this file except in compliance with the License.
5
+// You may obtain a copy of the License at
6
+//
7
+// http://www.apache.org/licenses/LICENSE-2.0
8
+//
9
+// Unless required by applicable law or agreed to in writing, software
10
+// distributed under the License is distributed on an "AS IS" BASIS,
11
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+// See the License for the specific language governing permissions and
13
+// limitations under the License.
14
+
15
+//go:build linux
16
+
17
+package internal
18
+
19
+import (
20
+ "fmt"
21
+ "net"
22
+ "runtime"
23
+ "strconv"
24
+ "strings"
25
+
26
+ "golang.org/x/sys/unix"
27
+)
28
+
29
+// WithBindCapability calls listen, which should bind to address on
30
+// network, raising CAP_NET_BIND_SERVICE for the duration of the call
31
+// if address is a privileged TCP or UDP port and the capability is in
32
+// the permitted set but not the effective set.
33
+//
34
+// This allows the caddy binary to be granted the capability with
35
+// `setcap cap_net_bind_service=+p` instead of `=+ep`. With the effective
36
+// bit set, the kernel refuses to execute the binary at all when the
37
+// capability can't be granted (for example, in containers run with
38
+// `--cap-drop ALL`, or systemd units with an empty CapabilityBoundingSet),
39
+// even if Caddy never binds a privileged port. See "Safety checking for
40
+// capability-dumb binaries" in capabilities(7).
41
+func WithBindCapability(network, address string, listen func() (any, error)) (any, error) {
42
+ if !isPrivilegedPort(network, address) {
43
+ return listen()
44
+ }
45
+ if raise, err := shouldRaiseBindCapability(); err != nil || !raise {
46
+ return listen()
47
+ }
48
+
49
+ // capabilities are per-thread, so raise it on a locked OS thread
50
+ // and bind from there; the goroutine exits without unlocking, so
51
+ // the runtime terminates the thread instead of reusing it with
52
+ // the raised capability (or wedges it, if it is the main thread,
53
+ // which is why we also lower it again after binding)
54
+ type result struct {
55
+ ln any
56
+ err error
57
+ }
58
+ ch := make(chan result, 1)
59
+ go func() {
60
+ runtime.LockOSThread()
61
+ if err := setBindCapability(true); err != nil {
62
+ ch <- result{err: fmt.Errorf("raising CAP_NET_BIND_SERVICE: %v", err)}
63
+ return
64
+ }
65
+ ln, err := listen()
66
+ _ = setBindCapability(false)
67
+ ch <- result{ln, err}
68
+ }()
69
+ r := <-ch
70
+ return r.ln, r.err
71
+}
72
+
73
+// isPrivilegedPort reports whether address is a TCP or UDP
74
+// address with a port below 1024 (other than 0).
75
+func isPrivilegedPort(network, address string) bool {
76
+ if !strings.HasPrefix(network, "tcp") && !strings.HasPrefix(network, "udp") {
77
+ return false
78
+ }
79
+ _, portStr, err := net.SplitHostPort(address)
80
+ if err != nil {
81
+ return false
82
+ }
83
+ port, err := strconv.ParseUint(portStr, 10, 16)
84
+ return err == nil && port > 0 && port < 1024
85
+}
86
+
87
+const bindCapabilityMask = 1 << unix.CAP_NET_BIND_SERVICE
88
+
89
+// shouldRaiseBindCapability reports whether CAP_NET_BIND_SERVICE is
90
+// permitted but not effective for the calling thread.
91
+func shouldRaiseBindCapability() (bool, error) {
92
+ _, data, err := getCapabilities()
93
+ if err != nil {
94
+ return false, err
95
+ }
96
+ return data[0].Permitted&bindCapabilityMask != 0 &&
97
+ data[0].Effective&bindCapabilityMask == 0, nil
98
+}
99
+
100
+// setBindCapability adds or removes CAP_NET_BIND_SERVICE in the
101
+// effective set of the calling thread. To add it, it must already
102
+// be permitted.
103
+func setBindCapability(effective bool) error {
104
+ hdr, data, err := getCapabilities()
105
+ if err != nil {
106
+ return err
107
+ }
108
+ if effective {
109
+ data[0].Effective |= bindCapabilityMask
110
+ } else {
111
+ data[0].Effective &^= bindCapabilityMask
112
+ }
113
+ return unix.Capset(&hdr, &data[0])
114
+}
115
+
116
+func getCapabilities() (unix.CapUserHeader, [2]unix.CapUserData, error) {
117
+ hdr := unix.CapUserHeader{Version: unix.LINUX_CAPABILITY_VERSION_3}
118
+ var data [2]unix.CapUserData
119
+ err := unix.Capget(&hdr, &data[0])
120
+ return hdr, data, err
121
+}