Skip to content

Commit a679716

Browse files
authored
Limit wireproxy's permissions with landlock (#108)
* Limit wireproxy's permissions with landlock * Show better debug message * Fix crash when info is null * Fix crash when landlock ABI is outdated * remove /dev/std{in,out,err} from landlock restriction
1 parent eccf83a commit a679716

File tree

3 files changed

+108
-20
lines changed

3 files changed

+108
-20
lines changed

cmd/wireproxy/main.go

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,14 @@ package main
33
import (
44
"context"
55
"fmt"
6+
"github.com/landlock-lsm/go-landlock/landlock"
67
"log"
8+
"net"
79
"net/http"
810
"os"
911
"os/exec"
1012
"os/signal"
13+
"strconv"
1114
"syscall"
1215

1316
"github.com/akamensky/argparse"
@@ -21,22 +24,22 @@ const daemonProcess = "daemon-process"
2124

2225
var version = "1.0.8-dev"
2326

24-
// attempts to pledge and panic if it fails
25-
// this does nothing on non-OpenBSD systems
26-
func pledgeOrPanic(promises string) {
27-
err := protect.Pledge(promises)
27+
func panicIfError(err error) {
2828
if err != nil {
2929
log.Fatal(err)
3030
}
3131
}
3232

33+
// attempts to pledge and panic if it fails
34+
// this does nothing on non-OpenBSD systems
35+
func pledgeOrPanic(promises string) {
36+
panicIfError(protect.Pledge(promises))
37+
}
38+
3339
// attempts to unveil and panic if it fails
3440
// this does nothing on non-OpenBSD systems
3541
func unveilOrPanic(path string, flags string) {
36-
err := protect.Unveil(path, flags)
37-
if err != nil {
38-
log.Fatal(err)
39-
}
42+
panicIfError(protect.Unveil(path, flags))
4043
}
4144

4245
// get the executable path via syscalls or infer it from argv
@@ -48,6 +51,91 @@ func executablePath() string {
4851
return programPath
4952
}
5053

54+
func lock(stage string) {
55+
switch stage {
56+
case "boot":
57+
exePath := executablePath()
58+
// OpenBSD
59+
unveilOrPanic("/", "r")
60+
unveilOrPanic(exePath, "x")
61+
// only allow standard stdio operation, file reading, networking, and exec
62+
// also remove unveil permission to lock unveil
63+
pledgeOrPanic("stdio rpath inet dns proc exec")
64+
// Linux
65+
panicIfError(landlock.V1.BestEffort().RestrictPaths(
66+
landlock.RODirs("/"),
67+
))
68+
case "boot-daemon":
69+
case "read-config":
70+
// OpenBSD
71+
pledgeOrPanic("stdio rpath inet dns")
72+
case "ready":
73+
// no file access is allowed from now on, only networking
74+
// OpenBSD
75+
pledgeOrPanic("stdio inet dns")
76+
// Linux
77+
net.DefaultResolver.PreferGo = true // needed to lock down dependencies
78+
panicIfError(landlock.V1.BestEffort().RestrictPaths(
79+
landlock.ROFiles("/etc/resolv.conf"),
80+
landlock.ROFiles("/dev/fd"),
81+
landlock.ROFiles("/dev/zero"),
82+
landlock.ROFiles("/dev/urandom"),
83+
landlock.ROFiles("/etc/localtime"),
84+
landlock.ROFiles("/proc/self/stat"),
85+
landlock.ROFiles("/proc/self/status"),
86+
landlock.ROFiles("/usr/share/locale"),
87+
landlock.ROFiles("/proc/self/cmdline"),
88+
landlock.ROFiles("/usr/share/zoneinfo"),
89+
landlock.ROFiles("/proc/sys/kernel/version"),
90+
landlock.ROFiles("/proc/sys/kernel/ngroups_max"),
91+
landlock.ROFiles("/proc/sys/kernel/cap_last_cap"),
92+
landlock.ROFiles("/proc/sys/vm/overcommit_memory"),
93+
landlock.RWFiles("/dev/log"),
94+
landlock.RWFiles("/dev/null"),
95+
landlock.RWFiles("/dev/full"),
96+
landlock.RWFiles("/proc/self/fd"),
97+
))
98+
default:
99+
panic("invalid stage")
100+
}
101+
}
102+
103+
func extractPort(addr string) uint16 {
104+
_, portStr, err := net.SplitHostPort(addr)
105+
if err != nil {
106+
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
107+
}
108+
109+
port, err := strconv.Atoi(portStr)
110+
if err != nil {
111+
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
112+
}
113+
114+
return uint16(port)
115+
}
116+
117+
func lockNetwork(sections []wireproxy.RoutineSpawner, infoAddr *string) {
118+
var rules []landlock.Rule
119+
if infoAddr != nil && *infoAddr != "" {
120+
rules = append(rules, landlock.BindTCP(extractPort(*infoAddr)))
121+
}
122+
123+
for _, section := range sections {
124+
switch section := section.(type) {
125+
case *wireproxy.TCPServerTunnelConfig:
126+
rules = append(rules, landlock.ConnectTCP(extractPort(section.Target)))
127+
case *wireproxy.HTTPConfig:
128+
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
129+
case *wireproxy.TCPClientTunnelConfig:
130+
rules = append(rules, landlock.ConnectTCP(uint16(section.BindAddress.Port)))
131+
case *wireproxy.Socks5Config:
132+
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
133+
}
134+
}
135+
136+
panicIfError(landlock.V4.BestEffort().RestrictNet(rules...))
137+
}
138+
51139
func main() {
52140
s := make(chan os.Signal, 1)
53141
signal.Notify(s, syscall.SIGINT, syscall.SIGQUIT)
@@ -59,18 +147,12 @@ func main() {
59147
}()
60148

61149
exePath := executablePath()
62-
unveilOrPanic("/", "r")
63-
unveilOrPanic(exePath, "x")
64-
65-
// only allow standard stdio operation, file reading, networking, and exec
66-
// also remove unveil permission to lock unveil
67-
pledgeOrPanic("stdio rpath inet dns proc exec")
150+
lock("boot")
68151

69152
isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess
70153
args := os.Args
71154
if isDaemonProcess {
72-
// remove proc and exec if they are not needed
73-
pledgeOrPanic("stdio rpath inet dns")
155+
lock("boot-daemon")
74156
args = []string{args[0]}
75157
args = append(args, os.Args[2:]...)
76158
}
@@ -100,8 +182,7 @@ func main() {
100182
}
101183

102184
if !*daemon {
103-
// remove proc and exec if they are not needed
104-
pledgeOrPanic("stdio rpath inet dns")
185+
lock("read-config")
105186
}
106187

107188
conf, err := wireproxy.ParseConfig(*config)
@@ -114,6 +195,8 @@ func main() {
114195
return
115196
}
116197

198+
lockNetwork(conf.Routines, info)
199+
117200
if isDaemonProcess {
118201
os.Stdout, _ = os.Open(os.DevNull)
119202
os.Stderr, _ = os.Open(os.DevNull)
@@ -139,8 +222,7 @@ func main() {
139222
logLevel = device.LogLevelSilent
140223
}
141224

142-
// no file access is allowed from now on, only networking
143-
pledgeOrPanic("stdio inet dns")
225+
lock("ready")
144226

145227
tun, err := wireproxy.StartWireguard(conf.Device, logLevel)
146228
if err != nil {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ require (
1515

1616
require (
1717
github.com/google/btree v1.1.2 // indirect
18+
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a // indirect
1819
github.com/sourcegraph/conc v0.3.0 // indirect
1920
golang.org/x/crypto v0.19.0 // indirect
2021
golang.org/x/net v0.21.0 // indirect
2122
golang.org/x/sys v0.17.0 // indirect
2223
golang.org/x/time v0.5.0 // indirect
2324
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
2425
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
26+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
2527
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
88
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
99
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
1010
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
11+
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a h1:dz+a1MiMQksVhejeZwqJuzPawYQBwug74J8PPtkLl9U=
12+
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a/go.mod h1:1NY/VPO8xm3hXw3f+M65z+PJDLUaZA5cu7OfanxoUzY=
1113
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1214
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1315
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
@@ -33,5 +35,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3335
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
3436
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
3537
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
38+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
39+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
3640
suah.dev/protect v1.2.3 h1:aHeoNwZ9YPp64hrYaN0g0djNE1eRujgH63CrfRrUKdc=
3741
suah.dev/protect v1.2.3/go.mod h1:n1R3XIbsnryKX7C1PO88i5Wgo0v8OTXm9K9FIKt4rfs=

0 commit comments

Comments
 (0)