Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Limit wireproxy's permissions with landlock #108

Merged
merged 5 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 102 additions & 20 deletions cmd/wireproxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package main
import (
"context"
"fmt"
"github.com/landlock-lsm/go-landlock/landlock"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"strconv"
"syscall"

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

var version = "1.0.8-dev"

// attempts to pledge and panic if it fails
// this does nothing on non-OpenBSD systems
func pledgeOrPanic(promises string) {
err := protect.Pledge(promises)
func panicIfError(err error) {
if err != nil {
log.Fatal(err)
}
}

// attempts to pledge and panic if it fails
// this does nothing on non-OpenBSD systems
func pledgeOrPanic(promises string) {
panicIfError(protect.Pledge(promises))
}

// attempts to unveil and panic if it fails
// this does nothing on non-OpenBSD systems
func unveilOrPanic(path string, flags string) {
err := protect.Unveil(path, flags)
if err != nil {
log.Fatal(err)
}
panicIfError(protect.Unveil(path, flags))
}

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

func lock(stage string) {
switch stage {
case "boot":
exePath := executablePath()
// OpenBSD
unveilOrPanic("/", "r")
unveilOrPanic(exePath, "x")
// only allow standard stdio operation, file reading, networking, and exec
// also remove unveil permission to lock unveil
pledgeOrPanic("stdio rpath inet dns proc exec")
// Linux
panicIfError(landlock.V1.BestEffort().RestrictPaths(
landlock.RODirs("/"),
))
case "boot-daemon":
case "read-config":
// OpenBSD
pledgeOrPanic("stdio rpath inet dns")
case "ready":
// no file access is allowed from now on, only networking
// OpenBSD
pledgeOrPanic("stdio inet dns")
// Linux
net.DefaultResolver.PreferGo = true // needed to lock down dependencies
panicIfError(landlock.V1.BestEffort().RestrictPaths(
landlock.ROFiles("/etc/resolv.conf"),
landlock.ROFiles("/dev/fd"),
landlock.ROFiles("/dev/zero"),
landlock.ROFiles("/dev/urandom"),
landlock.ROFiles("/etc/localtime"),
landlock.ROFiles("/proc/self/stat"),
landlock.ROFiles("/proc/self/status"),
landlock.ROFiles("/usr/share/locale"),
landlock.ROFiles("/proc/self/cmdline"),
landlock.ROFiles("/usr/share/zoneinfo"),
landlock.ROFiles("/proc/sys/kernel/version"),
landlock.ROFiles("/proc/sys/kernel/ngroups_max"),
landlock.ROFiles("/proc/sys/kernel/cap_last_cap"),
landlock.ROFiles("/proc/sys/vm/overcommit_memory"),
landlock.RWFiles("/dev/log"),
landlock.RWFiles("/dev/null"),
landlock.RWFiles("/dev/full"),
landlock.RWFiles("/proc/self/fd"),
))
default:
panic("invalid stage")
}
}

func extractPort(addr string) uint16 {
_, portStr, err := net.SplitHostPort(addr)
if err != nil {
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
}

port, err := strconv.Atoi(portStr)
if err != nil {
panic(fmt.Errorf("failed to extract port from %s: %w", addr, err))
}

return uint16(port)
}

func lockNetwork(sections []wireproxy.RoutineSpawner, infoAddr *string) {
var rules []landlock.Rule
if infoAddr != nil && *infoAddr != "" {
rules = append(rules, landlock.BindTCP(extractPort(*infoAddr)))
}

for _, section := range sections {
switch section := section.(type) {
case *wireproxy.TCPServerTunnelConfig:
rules = append(rules, landlock.ConnectTCP(extractPort(section.Target)))
case *wireproxy.HTTPConfig:
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
case *wireproxy.TCPClientTunnelConfig:
rules = append(rules, landlock.ConnectTCP(uint16(section.BindAddress.Port)))
case *wireproxy.Socks5Config:
rules = append(rules, landlock.BindTCP(extractPort(section.BindAddress)))
}
}

panicIfError(landlock.V4.BestEffort().RestrictNet(rules...))
}

func main() {
s := make(chan os.Signal, 1)
signal.Notify(s, syscall.SIGINT, syscall.SIGQUIT)
Expand All @@ -59,18 +147,12 @@ func main() {
}()

exePath := executablePath()
unveilOrPanic("/", "r")
unveilOrPanic(exePath, "x")

// only allow standard stdio operation, file reading, networking, and exec
// also remove unveil permission to lock unveil
pledgeOrPanic("stdio rpath inet dns proc exec")
lock("boot")

isDaemonProcess := len(os.Args) > 1 && os.Args[1] == daemonProcess
args := os.Args
if isDaemonProcess {
// remove proc and exec if they are not needed
pledgeOrPanic("stdio rpath inet dns")
lock("boot-daemon")
args = []string{args[0]}
args = append(args, os.Args[2:]...)
}
Expand Down Expand Up @@ -100,8 +182,7 @@ func main() {
}

if !*daemon {
// remove proc and exec if they are not needed
pledgeOrPanic("stdio rpath inet dns")
lock("read-config")
}

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

lockNetwork(conf.Routines, info)

if isDaemonProcess {
os.Stdout, _ = os.Open(os.DevNull)
os.Stderr, _ = os.Open(os.DevNull)
Expand All @@ -139,8 +222,7 @@ func main() {
logLevel = device.LogLevelSilent
}

// no file access is allowed from now on, only networking
pledgeOrPanic("stdio inet dns")
lock("ready")

tun, err := wireproxy.StartWireguard(conf.Device, logLevel)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ require (

require (
github.com/google/btree v1.1.2 // indirect
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a h1:dz+a1MiMQksVhejeZwqJuzPawYQBwug74J8PPtkLl9U=
github.com/landlock-lsm/go-landlock v0.0.0-20240216195629-efb66220540a/go.mod h1:1NY/VPO8xm3hXw3f+M65z+PJDLUaZA5cu7OfanxoUzY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
Expand All @@ -33,5 +35,7 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69 h1:IdrOs1ZgwGw5CI+BH6GgVVlOt+LAXoPyh7enr8lfaXs=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.69/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
suah.dev/protect v1.2.3 h1:aHeoNwZ9YPp64hrYaN0g0djNE1eRujgH63CrfRrUKdc=
suah.dev/protect v1.2.3/go.mod h1:n1R3XIbsnryKX7C1PO88i5Wgo0v8OTXm9K9FIKt4rfs=
Loading