mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
feat: Add support for Landlock based Sandbox for Linux (#238)
* feat: Initial implementation of landlock based sandbox driver * fix: Handle seccom probe failure * fix: Remove unnecessary seccomp probe * fix: Use file based policy load * fix: Keep bpf filter in memory * fix: Use TSYNC for seccom filter * fix: Use TSYNC for seccom filter * fix: Update landlock translator * fix: Landlock sandbox implementation * fix: Landlock + seccomp based sandboxing on Linux * fix: Misc fixes * fix: Cleanup sandbox files * fix: Handle mandatory deny API change post merge * fix: Landlock write access translation * chore: Fix linter issues * ci: Use /tmp for npm cache for landlock
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
//go:build linux
|
||||
|
||||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/safedep/dry/log"
|
||||
"github.com/safedep/pmg/sandbox/util"
|
||||
)
|
||||
|
||||
// expandGlobstarPattern expands patterns containing ** (recursive glob),
|
||||
// which filepath.Glob does not support. Splits the pattern at ** into a base
|
||||
// path and a suffix, walks the base with a depth limit, and collects entries
|
||||
// whose path ends with the suffix.
|
||||
//
|
||||
// If the base path does not yet exist, returns []string{basePath} so callers
|
||||
// can still grant coverage to the parent directory (matters for fresh
|
||||
// node_modules / pnpm caches that haven't been created yet).
|
||||
func expandGlobstarPattern(pattern string, maxDepth, maxPaths int) ([]string, error) {
|
||||
parts := strings.Split(pattern, "**")
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("only one ** globstar supported per pattern")
|
||||
}
|
||||
|
||||
basePath := strings.TrimSuffix(parts[0], "/")
|
||||
suffix := strings.TrimPrefix(parts[1], "/")
|
||||
|
||||
if basePath == "" {
|
||||
log.Debugf("Skipping globstar pattern '%s' with empty base path (would walk from root)", pattern)
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
expandedBase, err := util.ExpandVariables(basePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to expand base path: %w", err)
|
||||
}
|
||||
basePath = expandedBase
|
||||
|
||||
if _, err := os.Stat(basePath); os.IsNotExist(err) {
|
||||
return []string{basePath}, nil
|
||||
}
|
||||
|
||||
matches := []string{}
|
||||
if err := walkGlobWithDepthLimit(basePath, suffix, maxDepth, maxPaths, &matches); err != nil {
|
||||
return nil, fmt.Errorf("failed to walk directory tree: %w", err)
|
||||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
// walkGlobWithDepthLimit walks a directory tree from root, appending paths
|
||||
// whose suffix matches `suffix`. Stops at maxDepth levels (when > 0) and
|
||||
// after collecting maxPaths entries.
|
||||
func walkGlobWithDepthLimit(root, suffix string, maxDepth, maxPaths int, matches *[]string) error {
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
relPath, err := filepath.Rel(root, path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
depth := 0
|
||||
if relPath != "." {
|
||||
depth = len(strings.Split(relPath, string(filepath.Separator)))
|
||||
}
|
||||
if maxDepth > 0 && depth > maxDepth {
|
||||
if info.IsDir() {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if suffix == "" || strings.HasSuffix(path, suffix) {
|
||||
*matches = append(*matches, path)
|
||||
if len(*matches) >= maxPaths {
|
||||
return filepath.SkipAll
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// extractGlobParentDir extracts the parent directory of a glob pattern. Used
|
||||
// for coarse-grained fallback when expansion yields too many paths or the
|
||||
// target tree is unsuitable for fine-grained rules.
|
||||
//
|
||||
// Examples:
|
||||
// - ${CWD}/node_modules/** → ${CWD}/node_modules
|
||||
// - ${HOME}/.cache/pnpm/** → ${HOME}/.cache/pnpm
|
||||
// - /tmp/*.txt → /tmp
|
||||
// - /usr/lib/**/*.so → /usr/lib
|
||||
func extractGlobParentDir(pattern string) string {
|
||||
pattern = strings.TrimSuffix(pattern, "/**")
|
||||
pattern = strings.TrimSuffix(pattern, "/*")
|
||||
|
||||
idx := strings.IndexAny(pattern, "*?[")
|
||||
if idx >= 0 {
|
||||
pattern = pattern[:idx]
|
||||
pattern = filepath.Dir(pattern)
|
||||
}
|
||||
pattern = strings.TrimSuffix(pattern, string(filepath.Separator))
|
||||
|
||||
if pattern == "" || pattern == string(filepath.Separator) {
|
||||
return "."
|
||||
}
|
||||
return pattern
|
||||
}
|
||||
Reference in New Issue
Block a user