🏡 index : quine-global/quintodrome.git

package plugins

import (
	"context"
	"io/fs"
	"path/filepath"

	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/tetratelabs/wazero"
	experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
	"github.com/tetratelabs/wazero/experimental/sysfs"
	"github.com/tetratelabs/wazero/sys"
)

// jailedFS confines a mount to its root: wazero resolves guest paths by bare
// concatenation. Named field, not embedding, so a new sys.FS method can't slip through.
type jailedFS struct {
	fs experimentalsys.FS
}

// escapes reports whether a guest path can resolve outside the mount root.
// WASI only validates "/"-separated paths, missing `..\`, `C:x` and device names.
func escapes(path string) bool {
	switch path {
	case "", ".", "/": // how wazero opens the mount root itself
		return false
	}
	return !filepath.IsLocal(path)
}

func (jailedFS) Symlink(string, string) experimentalsys.Errno {
	return experimentalsys.EPERM
}

func (j jailedFS) OpenFile(path string, flag experimentalsys.Oflag, perm fs.FileMode) (experimentalsys.File, experimentalsys.Errno) {
	if escapes(path) {
		return nil, experimentalsys.EPERM
	}
	return j.fs.OpenFile(path, flag, perm)
}

func (j jailedFS) Lstat(path string) (sys.Stat_t, experimentalsys.Errno) {
	if escapes(path) {
		return sys.Stat_t{}, experimentalsys.EPERM
	}
	return j.fs.Lstat(path)
}

func (j jailedFS) Stat(path string) (sys.Stat_t, experimentalsys.Errno) {
	if escapes(path) {
		return sys.Stat_t{}, experimentalsys.EPERM
	}
	return j.fs.Stat(path)
}

func (j jailedFS) Mkdir(path string, perm fs.FileMode) experimentalsys.Errno {
	if escapes(path) {
		return experimentalsys.EPERM
	}
	return j.fs.Mkdir(path, perm)
}

func (j jailedFS) Chmod(path string, perm fs.FileMode) experimentalsys.Errno {
	if escapes(path) {
		return experimentalsys.EPERM
	}
	return j.fs.Chmod(path, perm)
}

func (j jailedFS) Rename(from, to string) experimentalsys.Errno {
	if escapes(from) || escapes(to) {
		return experimentalsys.EPERM
	}
	return j.fs.Rename(from, to)
}

func (j jailedFS) Rmdir(path string) experimentalsys.Errno {
	if escapes(path) {
		return experimentalsys.EPERM
	}
	return j.fs.Rmdir(path)
}

func (j jailedFS) Unlink(path string) experimentalsys.Errno {
	if escapes(path) {
		return experimentalsys.EPERM
	}
	return j.fs.Unlink(path)
}

func (j jailedFS) Link(oldPath, newPath string) experimentalsys.Errno {
	if escapes(oldPath) || escapes(newPath) {
		return experimentalsys.EPERM
	}
	return j.fs.Link(oldPath, newPath)
}

func (j jailedFS) Readlink(path string) (string, experimentalsys.Errno) {
	if escapes(path) {
		return "", experimentalsys.EPERM
	}
	return j.fs.Readlink(path)
}

func (j jailedFS) Utimens(path string, atim, mtim int64) experimentalsys.Errno {
	if escapes(path) {
		return experimentalsys.EPERM
	}
	return j.fs.Utimens(path, atim, mtim)
}

// mount is a host directory exposed to a plugin at guestPath.
type mount struct {
	hostPath  string
	guestPath string
	readOnly  bool
}

// buildMounts lists the libraries the plugin may reach through the filesystem.
func buildMounts(ctx context.Context, libraries model.Libraries, allowedLibraryIDs []int, allLibraries, allowWriteAccess bool) []mount {
	allowedLibrarySet := make(map[int]struct{}, len(allowedLibraryIDs))
	for _, id := range allowedLibraryIDs {
		allowedLibrarySet[id] = struct{}{}
	}
	var mounts []mount
	for _, lib := range libraries {
		_, allowed := allowedLibrarySet[lib.ID]
		if allLibraries || allowed {
			m := mount{hostPath: lib.Path, guestPath: toPluginMountPoint(int32(lib.ID)), readOnly: !allowWriteAccess}
			mounts = append(mounts, m)
			log.Trace(ctx, "Added library to plugin mounts", "libraryID", lib.ID, "mountPoint", m.guestPath, "readOnly", m.readOnly, "hostPath", m.hostPath)
		}
	}
	if allowWriteAccess {
		log.Info(ctx, "Granting read-write filesystem access to libraries", "libraryCount", len(mounts), "allLibraries", allLibraries)
	} else {
		log.Debug(ctx, "Granting read-only filesystem access to libraries", "libraryCount", len(mounts), "allLibraries", allLibraries)
	}
	return mounts
}

// buildFSConfig mounts each host directory jailed to its root.
func buildFSConfig(mounts []mount) wazero.FSConfig {
	if len(mounts) == 0 {
		return nil
	}
	cfg := wazero.NewFSConfig()
	for _, m := range mounts {
		var mounted experimentalsys.FS = jailedFS{fs: sysfs.DirFS(m.hostPath)}
		if m.readOnly {
			mounted = &sysfs.ReadFS{FS: mounted}
		}
		cfg = cfg.(sysfs.FSConfig).WithSysFSMount(mounted, m.guestPath)
	}
	return cfg
}