Skip to content

Commit

Permalink
Initial implementation of a sandbox for OpenBSD
Browse files Browse the repository at this point in the history
Leverages pledge and unveil, and leaves a public API for other
systems to follow. The API was designed to match the OpenBSD side
as that's the initial target, if a BPF/capsicum implementation is
brought forward it may be worth changing the API, and we should be
okay with that.
  • Loading branch information
Michael du Breuil authored and WickedShell committed Nov 19, 2024
1 parent 643ffff commit 548bef8
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 2 deletions.
19 changes: 18 additions & 1 deletion cmd/gonic/gonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"go.senan.xyz/gonic/listenbrainz"
"go.senan.xyz/gonic/playlist"
"go.senan.xyz/gonic/podcast"
"go.senan.xyz/gonic/sandbox"
"go.senan.xyz/gonic/scanner"
"go.senan.xyz/gonic/scrobble"
"go.senan.xyz/gonic/server/ctrladmin"
Expand All @@ -51,6 +52,7 @@ import (
)

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

confTLSCert := flag.String("tls-cert", "", "path to TLS certificate (optional)")
Expand Down Expand Up @@ -98,7 +100,11 @@ func main() {

flag.Parse()
flagconf.ParseEnv()
flagconf.ParseConfig(*confConfigPath)

if *confConfigPath != "" {
sandbox.ReadOnlyPath(*confConfigPath)
flagconf.ParseConfig(*confConfigPath)
}

if *confShowVersion {
fmt.Printf("v%s\n", gonic.Version)
Expand All @@ -115,17 +121,21 @@ func main() {

var err error
for i, confMusicPath := range confMusicPaths {
sandbox.ReadOnlyPath(confMusicPath.path)
if confMusicPaths[i].path, err = validatePath(confMusicPath.path); err != nil {
log.Fatalf("checking music dir %q: %v", confMusicPath.path, err)
}
}

sandbox.ReadWriteCreatePath(*confPodcastPath)
if *confPodcastPath, err = validatePath(*confPodcastPath); err != nil {
log.Fatalf("checking podcast directory: %v", err)
}
sandbox.ReadWriteCreatePath(*confCachePath)
if *confCachePath, err = validatePath(*confCachePath); err != nil {
log.Fatalf("checking cache directory: %v", err)
}
sandbox.ReadWriteCreatePath(*confPlaylistsPath)
if *confPlaylistsPath, err = validatePath(*confPlaylistsPath); err != nil {
log.Fatalf("checking playlist directory: %v", err)
}
Expand Down Expand Up @@ -156,6 +166,13 @@ func main() {
log.Panicf("error migrating database: %v\n", err)
}

if *confTLSCert != "" && *confTLSKey != "" {
sandbox.ReadOnlyPath(*confTLSCert)
sandbox.ReadOnlyPath(*confTLSKey)
}

sandbox.AllPathsAdded()

var musicPaths []ctrlsubsonic.MusicPath
for _, pa := range confMusicPaths {
musicPaths = append(musicPaths, ctrlsubsonic.MusicPath{Alias: pa.alias, Path: pa.path})
Expand Down
6 changes: 6 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"github.com/jinzhu/gorm"
"github.com/mattn/go-sqlite3"

"go.senan.xyz/gonic/sandbox"

// TODO: remove this dep
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
)
Expand Down Expand Up @@ -48,6 +50,10 @@ func New(path string, options url.Values) (*DB, error) {
Scheme: "file",
Opaque: path,
}
sandbox.ReadWriteCreatePath(path)
sandbox.ReadWriteCreatePath(path + "-wal")
sandbox.ReadWriteCreatePath(path + "-shm")
sandbox.ReadWriteCreatePath(path + "-journal")
url.RawQuery = options.Encode()
db, err := gorm.Open("sqlite3", url.String())
if err != nil {
Expand Down
5 changes: 4 additions & 1 deletion db/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/jinzhu/gorm"
"go.senan.xyz/gonic/fileutil"
"go.senan.xyz/gonic/playlist"
"go.senan.xyz/gonic/sandbox"
"go.senan.xyz/gonic/server/ctrlsubsonic/specid"
"gopkg.in/gormigrate.v1"
)
Expand Down Expand Up @@ -734,7 +735,9 @@ func backupDBPre016(tx *gorm.DB, ctx MigrationContext) error {
if !ctx.Production {
return nil
}
return Dump(context.Background(), tx, fmt.Sprintf("%s.%d.bak", ctx.DBPath, time.Now().Unix()))
backupPath := fmt.Sprintf("%s.%d.bak", ctx.DBPath, time.Now().Unix())
sandbox.ReadWriteCreatePath(backupPath)
return Dump(context.Background(), tx, backupPath)
}

func migrateAlbumTagArtistString(tx *gorm.DB, _ MigrationContext) error {
Expand Down
19 changes: 19 additions & 0 deletions sandbox/none.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//go:build !openbsd
// +build !openbsd

package sandbox

func Init() {
}

func ReadOnlyPath(path string) {
}

func ReadWritePath(path string) {
}

func ReadWriteCreatePath(path string) {
}

func AllPathsAdded() {
}
63 changes: 63 additions & 0 deletions sandbox/sandbox_openbsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sandbox

import (
"log"
"os/exec"

"golang.org/x/sys/unix"
)

func Init() {
if err := unix.PledgePromises("stdio rpath cpath wpath flock inet unveil dns proc exec fattr"); err != nil {
log.Fatalf("failed to pledge: %v", err)
}
// find the transcoding and jukebox paths before doing any other unveils
// otherwise looking for it will fail
ffmpegPath, ffmpegErr := exec.LookPath("ffmpeg")
mpvPath, mpvErr := exec.LookPath("mpv")
if ffmpegErr == nil || mpvErr == nil {
if ffmpegErr == nil {
ExecPath(ffmpegPath)
}
if mpvErr == nil {
ExecPath(mpvPath)
}
} else {
// we can restrict our permissions
if err := unix.PledgePromises("stdio rpath cpath wpath flock inet unveil dns"); err != nil {
log.Fatalf("failed to pledge: %v", err)
}
}
// needed to enable certificate validation
ReadOnlyPath("/etc/ssl/cert.pem")
}

func ExecPath(path string) {
if err := unix.Unveil(path, "rx"); err != nil {
log.Fatalf("failed to unveil exec for %s: %v", path, err)
}
}

func ReadOnlyPath(path string) {
if err := unix.Unveil(path, "r"); err != nil {
log.Fatalf("failed to unveil read for %s: %v", path, err)
}
}

func ReadWritePath(path string) {
if err := unix.Unveil(path, "rw"); err != nil {
log.Fatalf("failed to unveil read/write for %s: %v", path, err)
}
}

func ReadWriteCreatePath(path string) {
if err := unix.Unveil(path, "rwc"); err != nil {
log.Fatalf("failed to unveil read/write/create for %s: %v", path, err)
}
}

func AllPathsAdded() {
if err := unix.UnveilBlock(); err != nil {
log.Fatalf("failed to finalize unveil: %v", err)
}
}

0 comments on commit 548bef8

Please sign in to comment.