Skip to content

Commit 9ace7ec

Browse files
Michael du BreuilMichael du Breuil
authored andcommitted
Sandbox: Add a linux landlock implementation
1 parent 009c40f commit 9ace7ec

File tree

8 files changed

+116
-36
lines changed

8 files changed

+116
-36
lines changed

cmd/gonic/gonic.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ import (
5252
)
5353

5454
func main() {
55-
sandbox.Init()
55+
sandbox := sandbox.Init()
5656
confListenAddr := flag.String("listen-addr", "0.0.0.0:4747", "listen address (optional)")
5757

5858
confTLSCert := flag.String("tls-cert", "", "path to TLS certificate (optional)")
@@ -102,7 +102,7 @@ func main() {
102102
flagconf.ParseEnv()
103103

104104
if *confConfigPath != "" {
105-
sandbox.ReadOnlyPath(*confConfigPath)
105+
sandbox.ReadOnlyFile(*confConfigPath)
106106
flagconf.ParseConfig(*confConfigPath)
107107
}
108108

@@ -121,21 +121,21 @@ func main() {
121121

122122
var err error
123123
for i, confMusicPath := range confMusicPaths {
124-
sandbox.ReadOnlyPath(confMusicPath.path)
124+
sandbox.ReadOnlyDir(confMusicPath.path)
125125
if confMusicPaths[i].path, err = validatePath(confMusicPath.path); err != nil {
126126
log.Fatalf("checking music dir %q: %v", confMusicPath.path, err)
127127
}
128128
}
129129

130-
sandbox.ReadWriteCreatePath(*confPodcastPath)
130+
sandbox.ReadWriteCreateDir(*confPodcastPath)
131131
if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil {
132132
log.Fatalf("checking podcast directory: %v", err)
133133
}
134-
sandbox.ReadWriteCreatePath(*confCachePath)
134+
sandbox.ReadWriteCreateDir(*confCachePath)
135135
if *confCachePath, err = validatePath(*confCachePath); err != nil {
136136
log.Fatalf("checking cache directory: %v", err)
137137
}
138-
sandbox.ReadWriteCreatePath(*confPlaylistsPath)
138+
sandbox.ReadWriteCreateDir(*confPlaylistsPath)
139139
if *confPlaylistsPath, err = validatePath(*confPlaylistsPath); err != nil {
140140
log.Fatalf("checking playlist directory: %v", err)
141141
}
@@ -149,7 +149,7 @@ func main() {
149149
log.Fatalf("couldn't create covers cache path: %v\n", err)
150150
}
151151

152-
dbc, err := db.New(*confDBPath, db.DefaultOptions())
152+
dbc, err := db.New(*confDBPath, sandbox, db.DefaultOptions())
153153
if err != nil {
154154
log.Fatalf("error opening database: %v\n", err)
155155
}
@@ -161,14 +161,15 @@ func main() {
161161
OriginalMusicPath: confMusicPaths[0].path,
162162
PlaylistsPath: *confPlaylistsPath,
163163
PodcastsPath: *confPodcastPath,
164+
Sandbox: sandbox,
164165
})
165166
if err != nil {
166167
log.Panicf("error migrating database: %v\n", err)
167168
}
168169

169170
if *confTLSCert != "" && *confTLSKey != "" {
170-
sandbox.ReadOnlyPath(*confTLSCert)
171-
sandbox.ReadOnlyPath(*confTLSKey)
171+
sandbox.ReadOnlyFile(*confTLSCert)
172+
sandbox.ReadOnlyFile(*confTLSKey)
172173
}
173174

174175
sandbox.AllPathsAdded()

db/db.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ type DB struct {
4444
*gorm.DB
4545
}
4646

47-
func New(path string, options url.Values) (*DB, error) {
47+
func New(path string, sandbox sandbox.Sandbox, options url.Values) (*DB, error) {
4848
// https://github.com/mattn/go-sqlite3#connection-string
4949
url := url.URL{
5050
Scheme: "file",
5151
Opaque: path,
5252
}
53-
sandbox.ReadWriteCreatePath(path)
54-
sandbox.ReadWriteCreatePath(path + "-wal")
55-
sandbox.ReadWriteCreatePath(path + "-shm")
56-
sandbox.ReadWriteCreatePath(path + "-journal")
53+
sandbox.ReadWriteCreateFile(path)
54+
sandbox.ReadWriteCreateFile(path + "-wal")
55+
sandbox.ReadWriteCreateFile(path + "-shm")
56+
sandbox.ReadWriteCreateFile(path + "-journal")
5757
url.RawQuery = options.Encode()
5858
db, err := gorm.Open("sqlite3", url.String())
5959
if err != nil {
@@ -65,7 +65,7 @@ func New(path string, options url.Values) (*DB, error) {
6565
}
6666

6767
func NewMock() (*DB, error) {
68-
return New(":memory:", mockOptions())
68+
return New(":memory:", sandbox.Init(), mockOptions())
6969
}
7070

7171
func (db *DB) InsertBulkLeftMany(table string, head []string, left int, col []int) error {
@@ -628,7 +628,7 @@ func join[T fmt.Stringer](in []T, sep string) string {
628628
}
629629

630630
func Dump(ctx context.Context, db *gorm.DB, to string) error {
631-
dest, err := New(to, url.Values{})
631+
dest, err := New(to, sandbox.Init(), url.Values{})
632632
if err != nil {
633633
return fmt.Errorf("create dest db: %w", err)
634634
}

db/migrations.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ type MigrationContext struct {
2525
OriginalMusicPath string
2626
PlaylistsPath string
2727
PodcastsPath string
28+
Sandbox sandbox.Sandbox
2829
}
2930

3031
func (db *DB) Migrate(ctx MigrationContext) error {
@@ -742,7 +743,7 @@ func backupDBPre016(tx *gorm.DB, ctx MigrationContext) error {
742743
return nil
743744
}
744745
backupPath := fmt.Sprintf("%s.%d.bak", ctx.DBPath, time.Now().Unix())
745-
sandbox.ReadWriteCreatePath(backupPath)
746+
ctx.Sandbox.ReadWriteCreateFile(backupPath)
746747
return Dump(context.Background(), tx, backupPath)
747748
}
748749

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ require (
4848
github.com/jinzhu/now v1.1.2 // indirect
4949
github.com/josharian/intern v1.0.0 // indirect
5050
github.com/json-iterator/go v1.1.12 // indirect
51+
github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3 // indirect
5152
github.com/lib/pq v1.3.0 // indirect
5253
github.com/mailru/easyjson v0.7.7 // indirect
5354
github.com/mattn/go-runewidth v0.0.16 // indirect
@@ -62,9 +63,10 @@ require (
6263
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
6364
golang.org/x/crypto v0.27.0 // indirect
6465
golang.org/x/image v0.20.0 // indirect
65-
golang.org/x/sys v0.25.0 // indirect
66+
golang.org/x/sys v0.26.0 // indirect
6667
golang.org/x/text v0.18.0 // indirect
6768
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
6869
gopkg.in/yaml.v2 v2.4.0 // indirect
6970
gopkg.in/yaml.v3 v3.0.1 // indirect
71+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect
7072
)

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
8585
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
8686
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
8787
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
88+
github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3 h1:zcMi8R8vP0WrrXlFMNUBpDy/ydo3sTnCcUPowq1XmSc=
89+
github.com/landlock-lsm/go-landlock v0.0.0-20250303204525-1544bccde3a3/go.mod h1:RSub3ourNF8Hf+swvw49Catm3s7HVf4hzdFxDUnEzdA=
8890
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
8991
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
9092
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
@@ -184,6 +186,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
184186
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
185187
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
186188
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
189+
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
190+
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
187191
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
188192
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
189193
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@@ -216,3 +220,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
216220
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
217221
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g=
218222
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056/go.mod h1:OxvTsCwKosqQ1q7B+8FwXqg4rKZ/UG9dUW+g/VL2xH4=
223+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI=
224+
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=

sandbox/none.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
1-
//go:build !openbsd
2-
// +build !openbsd
1+
//go:build !(openbsd || linux)
32

43
package sandbox
54

6-
func Init() {
5+
type Sandbox struct{}
6+
7+
func Init() Sandbox {
8+
return Sandbox{}
9+
}
10+
11+
func (box *Sandbox) ReadOnlyDir(path string) {
712
}
813

9-
func ReadOnlyPath(path string) {
14+
func (box *Sandbox) ReadOnlyFile(path string) {
1015
}
1116

12-
func ReadWritePath(path string) {
17+
func (box *Sandbox) ReadWriteCreateDir(path string) {
1318
}
1419

15-
func ReadWriteCreatePath(path string) {
20+
func (box *Sandbox) ReadWriteCreateFile(path string) {
1621
}
1722

18-
func AllPathsAdded() {
23+
func (box *Sandbox) AllPathsAdded() {
1924
}

sandbox/sandbox_linux.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package sandbox
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/landlock-lsm/go-landlock/landlock"
8+
)
9+
10+
type Sandbox struct {
11+
paths []landlock.Rule
12+
}
13+
14+
func Init() Sandbox {
15+
return Sandbox{make([]landlock.Rule, 0)}
16+
}
17+
18+
func (box *Sandbox) ExecPath(path string) {
19+
// landlock does not currently provide anything for us here
20+
}
21+
22+
func (box *Sandbox) ReadOnlyDir(path string) {
23+
box.paths = append(box.paths, landlock.RODirs(path))
24+
}
25+
26+
func (box *Sandbox) ReadOnlyFile(path string) {
27+
box.paths = append(box.paths, landlock.ROFiles(path))
28+
}
29+
30+
func (box *Sandbox) ReadWriteCreateDir(path string) {
31+
box.paths = append(box.paths, landlock.RWDirs(path))
32+
}
33+
34+
func (box *Sandbox) ReadWriteCreateFile(path string) {
35+
// landlock requires the file to already exist
36+
// so create it if it wasn't already present
37+
_, err := os.Stat(path)
38+
switch {
39+
case os.IsNotExist(err):
40+
file, err := os.Create(path)
41+
if err != nil {
42+
log.Fatal("Could not create file for landlock:", err)
43+
} else {
44+
file.Close()
45+
}
46+
}
47+
box.paths = append(box.paths, landlock.RWFiles(path))
48+
}
49+
50+
func (box *Sandbox) AllPathsAdded() {
51+
if err := landlock.V5.BestEffort().RestrictPaths(box.paths...); err != nil {
52+
log.Fatal("Could not enable landlock:", err)
53+
}
54+
// FIXME: clear paths
55+
}

sandbox/sandbox_openbsd.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import (
77
"golang.org/x/sys/unix"
88
)
99

10-
func Init() {
10+
type Sandbox struct{}
11+
12+
func Init() Sandbox {
13+
box := Sandbox{}
1114
if err := unix.PledgePromises("stdio rpath cpath wpath flock inet unveil dns proc exec fattr"); err != nil {
1215
log.Fatalf("failed to pledge: %v", err)
1316
}
@@ -17,10 +20,10 @@ func Init() {
1720
mpvPath, mpvErr := exec.LookPath("mpv")
1821
if ffmpegErr == nil || mpvErr == nil {
1922
if ffmpegErr == nil {
20-
ExecPath(ffmpegPath)
23+
box.ExecPath(ffmpegPath)
2124
}
2225
if mpvErr == nil {
23-
ExecPath(mpvPath)
26+
box.ExecPath(mpvPath)
2427
}
2528
} else {
2629
// we can restrict our permissions
@@ -29,34 +32,41 @@ func Init() {
2932
}
3033
}
3134
// needed to enable certificate validation
32-
ReadOnlyPath("/etc/ssl/cert.pem")
35+
box.ReadOnlyFile("/etc/ssl/cert.pem")
36+
return box
3337
}
3438

35-
func ExecPath(path string) {
39+
func (box *Sandbox) ExecPath(path string) {
3640
if err := unix.Unveil(path, "rx"); err != nil {
3741
log.Fatalf("failed to unveil exec for %s: %v", path, err)
3842
}
3943
}
4044

41-
func ReadOnlyPath(path string) {
45+
func (box *Sandbox) ReadOnlyDir(path string) {
46+
if err := unix.Unveil(path, "r"); err != nil {
47+
log.Fatalf("failed to unveil read for %s: %v", path, err)
48+
}
49+
}
50+
51+
func (box *Sandbox) ReadOnlyFile(path string) {
4252
if err := unix.Unveil(path, "r"); err != nil {
4353
log.Fatalf("failed to unveil read for %s: %v", path, err)
4454
}
4555
}
4656

47-
func ReadWritePath(path string) {
48-
if err := unix.Unveil(path, "rw"); err != nil {
49-
log.Fatalf("failed to unveil read/write for %s: %v", path, err)
57+
func (box *Sandbox) ReadWriteCreateDir(path string) {
58+
if err := unix.Unveil(path, "rwc"); err != nil {
59+
log.Fatalf("failed to unveil read/write/create for %s: %v", path, err)
5060
}
5161
}
5262

53-
func ReadWriteCreatePath(path string) {
63+
func (box *Sandbox) ReadWriteCreateFile(path string) {
5464
if err := unix.Unveil(path, "rwc"); err != nil {
5565
log.Fatalf("failed to unveil read/write/create for %s: %v", path, err)
5666
}
5767
}
5868

59-
func AllPathsAdded() {
69+
func (box *Sandbox) AllPathsAdded() {
6070
if err := unix.UnveilBlock(); err != nil {
6171
log.Fatalf("failed to finalize unveil: %v", err)
6272
}

0 commit comments

Comments
 (0)