-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PoC for a shell interpreter sandbox.
This uses mvdan/sh/interp to provide a builtin POSIX shell interpreter whose file operations are (mostly) restricted to a sandbox root. I say mostly because the builtin shell globbing (eg. `echo /etc/*`) still allows listing directories outside the sandbox, though accessing them is prevented. The basics seem to work though there would be quite a bit more work fleshing out the supported utilities. Security wise I'm not sure this gives us much, as one of the goals of this is to allow executables _within_ the sandbox to be executed (eg. Java's keytool). If this is allowed then the package can basically execute arbitrary code without restriction, so I don't think there are many/any security benefits whatsoever. However there are other benefits: consistent shell support across any OS, including Windows. There is no need to rely on particular versions of bash being present. Some safety guarantees around accidentally violating the sandbox - eg. a script that accidentally rm's some files. Are these benefits large enough to warrant fleshing this out? I am not certain.
- Loading branch information
1 parent
32f65ca
commit 8db710d
Showing
13 changed files
with
507 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
.henv | ||
.hermit | ||
testdata/env | ||
build | ||
pkgs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,7 @@ linters: | |
- paralleltest | ||
- ifshort # so annoying | ||
- prealloc | ||
- nolintlint | ||
|
||
linters-settings: | ||
govet: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package sandbox | ||
|
||
import ( | ||
"fmt" | ||
"path/filepath" | ||
"strings" | ||
|
||
"github.com/alecthomas/kong" | ||
"github.com/pkg/errors" | ||
"mvdan.cc/sh/v3/interp" | ||
) | ||
|
||
var builtins = map[string]func() builtinCmd{ | ||
"ls": func() builtinCmd { return &lsCmd{} }, | ||
"mkdir": func() builtinCmd { return &mkdirCmd{} }, | ||
"rm": func() builtinCmd { return &rmCmd{} }, | ||
"cat": func() builtinCmd { return &catCmd{} }, | ||
"grep": func() builtinCmd { return &grepCmd{} }, | ||
} | ||
|
||
type cmdCtx struct { | ||
*Sandbox | ||
interp.HandlerContext | ||
runner *interp.Runner | ||
} | ||
|
||
// Sanitise a path within the sandbox. | ||
func (c *cmdCtx) Sanitise(path string) (string, error) { | ||
if !filepath.IsAbs(path) { | ||
path = filepath.Join(c.Dir, path) | ||
} | ||
if !strings.HasPrefix(path, c.root) { | ||
return "", errors.Wrap(ErrSandboxViolation, path) | ||
} | ||
return path, nil | ||
} | ||
|
||
type builtinCmd interface { | ||
Run(bctx cmdCtx) error | ||
} | ||
|
||
func runBuiltinCmd(bctx cmdCtx, args []string) (present bool, err error) { | ||
defer func() { | ||
if status, ok := recover().(int); ok { | ||
present = true | ||
if status != 0 { | ||
err = interp.NewExitStatus(uint8(status)) | ||
} | ||
} | ||
}() | ||
// fmt.Fprintf(bctx.Stderr, "+ %s\n", shellquote.Join(args...)) | ||
factory, ok := builtins[args[0]] | ||
if !ok { | ||
return false, nil | ||
} | ||
cmd := factory() | ||
exitStatus := 0 | ||
_, err = kong.Must(cmd, | ||
kong.Exit(func(i int) { panic(i) }), | ||
kong.ShortUsageOnError(), | ||
kong.Name(args[0]), | ||
).Parse(args[1:]) | ||
if err != nil { | ||
fmt.Fprintf(bctx.Stderr, "%s: %s\n", args[0], err) | ||
return true, interp.NewExitStatus(1) | ||
} | ||
if exitStatus != 0 { | ||
return true, nil | ||
} | ||
err = cmd.Run(bctx) | ||
if err != nil { | ||
fmt.Fprintf(bctx.Stderr, "%s: %s\n", args[0], err) | ||
return true, interp.NewExitStatus(1) | ||
} | ||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package sandbox | ||
|
||
import ( | ||
"io" | ||
"os" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type catCmd struct { | ||
Paths []string `arg:"" optional:"" help:"Files to cat, if any."` | ||
} | ||
|
||
func (c *catCmd) Run(ctx cmdCtx) error { | ||
if len(c.Paths) == 0 { | ||
_, err := io.Copy(ctx.Stdout, ctx.Stdin) | ||
return errors.WithStack(err) | ||
} | ||
for _, path := range c.Paths { | ||
var err error | ||
path, err = ctx.Sanitise(path) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
r, err := os.Open(path) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
_, err = io.Copy(ctx.Stdout, r) | ||
_ = r.Close() | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package sandbox | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"io" | ||
"os" | ||
"regexp" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
type grepCmd struct { | ||
Invert bool `short:"v" help:"Invert match."` | ||
List bool `short:"l" help:"List matching filenames."` | ||
Pattern string `arg:"" help:"Pattern to match."` | ||
Files []string `arg:"" optional:"" help:"Files to search."` | ||
} | ||
|
||
func (g *grepCmd) Run(ctx cmdCtx) error { | ||
re, err := regexp.CompilePOSIX(g.Pattern) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
if len(g.Files) == 0 { | ||
return errors.WithStack(g.grep(ctx, re, "-", ctx.Stdin)) | ||
} | ||
for _, file := range g.Files { | ||
file, err = ctx.Sanitise(file) | ||
if err != nil { | ||
return errors.Wrap(err, "grep") | ||
} | ||
r, err := os.Open(file) | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
err = g.grep(ctx, re, file, r) | ||
_ = r.Close() | ||
if err != nil { | ||
return errors.WithStack(err) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (g *grepCmd) grep(ctx cmdCtx, re *regexp.Regexp, filename string, r io.Reader) error { | ||
s := bufio.NewScanner(r) | ||
for s.Scan() { | ||
line := s.Bytes() | ||
if re.Find(line) == nil { | ||
continue | ||
} | ||
if g.List { | ||
fmt.Fprintln(ctx.Stdout, filename) | ||
return nil | ||
} | ||
fmt.Fprintln(ctx.Stdout, string(line)) | ||
} | ||
fmt.Fprint(ctx.Stdout) | ||
return errors.WithStack(s.Err()) | ||
} |
Oops, something went wrong.