feature: restrict symbols which can exit the interpreter process

* feature: restrict symbols which can exit the interpreter process

Some symbols such as os.Exit or log.Fatal, which make the current process
to exit, are now restricted. They are replaced by a version which panics
instead of exiting, as panics are recovered by Eval.

The restricted os.FindProcess version is identical to the original
except it errors when trying to return the self process, in order to
forbid killing or signaling the interpreter process from script.

The os/exec symbols are available only through unrestricted package.

The original symbols are stored in an unrestricted package, which
requires an explicit Use, as for unsafe and syscall packages.

The Use() interpreter method has been slightly modified to allow inplace
updating of package symbols, allowing to replace some symbols but not
the entire imported package.

A command line option -unrestricted has been added to yaegi CLI to use
the unrestricted symbols.

Fixes #486.

* fix: lint
This commit is contained in:
Marc Vertes
2020-07-08 22:35:04 +02:00
committed by GitHub
parent bc2b224bae
commit b3766509cc
18 changed files with 220 additions and 68 deletions

18
_test/restricted0.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"log"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
log.Fatal("log.Fatal does not exit")
println("not printed")
}
// Output:
// recover: log.Fatal does not exit

18
_test/restricted1.go Normal file
View File

@@ -0,0 +1,18 @@
package main
import (
"fmt"
"os"
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r)
}()
os.Exit(1)
println("not printed")
}
// Output:
// recover: os.Exit(1)

14
_test/restricted2.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import (
"fmt"
"os"
)
func main() {
p, err := os.FindProcess(os.Getpid())
fmt.Println(p, err)
}
// Output:
// <nil> restricted

23
_test/restricted3.go Normal file
View File

@@ -0,0 +1,23 @@
package main
import (
"bytes"
"fmt"
"log"
)
var (
buf bytes.Buffer
logger = log.New(&buf, "logger: ", log.Lshortfile)
)
func main() {
defer func() {
r := recover()
fmt.Println("recover:", r, buf.String())
}()
logger.Fatal("test log")
}
// Output:
// recover: test log logger: restricted.go:39: test log

View File

@@ -115,6 +115,17 @@ type Wrap struct {
Method []Method
}
// restricted map defines symbols for which a special implementation is provided.
var restricted = map[string]bool{
"osExit": true,
"osFindProcess": true,
"logFatal": true,
"logFatalf": true,
"logFatalln": true,
"logLogger": true,
"logNew": true,
}
func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, error) {
p, err := importer.ForCompiler(token.NewFileSet(), "source", nil).Import(pkgName)
if err != nil {
@@ -150,6 +161,10 @@ func genContent(dest, pkgName, license string, skip map[string]bool) ([]byte, er
if skip[pname] {
continue
}
if rname := path.Base(pkgName) + name; restricted[rname] {
// Restricted symbol, locally provided by stdlib wrapper.
pname = rname
}
switch o := o.(type) {
case *types.Const:

View File

@@ -96,17 +96,20 @@ import (
"github.com/containous/yaegi/interp"
"github.com/containous/yaegi/stdlib"
"github.com/containous/yaegi/stdlib/syscall"
"github.com/containous/yaegi/stdlib/unrestricted"
"github.com/containous/yaegi/stdlib/unsafe"
)
func main() {
var interactive bool
var useUnsafe bool
var useSyscall bool
var useUnrestricted bool
var useUnsafe bool
var tags string
var cmd string
flag.BoolVar(&interactive, "i", false, "start an interactive REPL")
flag.BoolVar(&useSyscall, "syscall", false, "include syscall symbols")
flag.BoolVar(&useUnrestricted, "unrestricted", false, "include unrestricted symbols")
flag.StringVar(&tags, "tags", "", "set a list of build tags")
flag.BoolVar(&useUnsafe, "unsafe", false, "include usafe symbols")
flag.StringVar(&cmd, "e", "", "set the command to be executed (instead of script or/and shell)")
@@ -128,6 +131,10 @@ func main() {
if useUnsafe {
i.Use(unsafe.Symbols)
}
if useUnrestricted {
// Use of unrestricted symbols should always follow use of stdlib symbols, to update them.
i.Use(unrestricted.Symbols)
}
if cmd != `` {
i.REPL(strings.NewReader(cmd), os.Stderr)

View File

@@ -474,7 +474,14 @@ func (interp *Interpreter) Use(values Exports) {
continue
}
interp.binPkg[k] = v
if interp.binPkg[k] == nil {
interp.binPkg[k] = v
continue
}
for s, sym := range v {
interp.binPkg[k][s] = sym
}
}
}

View File

@@ -75,6 +75,10 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "redeclaration-global4.go" || // expect error
file.Name() == "redeclaration-global5.go" || // expect error
file.Name() == "redeclaration-global6.go" || // expect error
file.Name() == "restricted0.go" || // expect error
file.Name() == "restricted1.go" || // expect error
file.Name() == "restricted2.go" || // expect error
file.Name() == "restricted3.go" || // expect error
file.Name() == "server6.go" || // syntax parsing
file.Name() == "server5.go" || // syntax parsing
file.Name() == "server4.go" || // syntax parsing

View File

@@ -2763,7 +2763,7 @@ func convertLiteralValue(n *node, t reflect.Type) {
case n.typ.cat == nilT:
// Create a zero value of target type.
n.rval = reflect.New(t).Elem()
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface:
case !(n.kind == basicLit || n.rval.IsValid()) || t == nil || t.Kind() == reflect.Interface || t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Interface:
// Skip non-constant values, undefined target type or interface target type.
case n.rval.IsValid():
// Convert constant value to target type.

View File

@@ -14,9 +14,9 @@ import (
func init() {
Symbols["log"] = map[string]reflect.Value{
// function, constant and variable definitions
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"Fatal": reflect.ValueOf(logFatal),
"Fatalf": reflect.ValueOf(logFatalf),
"Fatalln": reflect.ValueOf(logFatalln),
"Flags": reflect.ValueOf(log.Flags),
"LUTC": reflect.ValueOf(constant.MakeFromLiteral("32", token.INT, 0)),
"Ldate": reflect.ValueOf(constant.MakeFromLiteral("1", token.INT, 0)),
@@ -25,7 +25,7 @@ func init() {
"Lshortfile": reflect.ValueOf(constant.MakeFromLiteral("16", token.INT, 0)),
"LstdFlags": reflect.ValueOf(constant.MakeFromLiteral("3", token.INT, 0)),
"Ltime": reflect.ValueOf(constant.MakeFromLiteral("2", token.INT, 0)),
"New": reflect.ValueOf(log.New),
"New": reflect.ValueOf(logNew),
"Output": reflect.ValueOf(log.Output),
"Panic": reflect.ValueOf(log.Panic),
"Panicf": reflect.ValueOf(log.Panicf),
@@ -40,6 +40,6 @@ func init() {
"Writer": reflect.ValueOf(log.Writer),
// type definitions
"Logger": reflect.ValueOf((*log.Logger)(nil)),
"Logger": reflect.ValueOf((*logLogger)(nil)),
}
}

View File

@@ -31,10 +31,10 @@ func init() {
"ErrNotExist": reflect.ValueOf(&os.ErrNotExist).Elem(),
"ErrPermission": reflect.ValueOf(&os.ErrPermission).Elem(),
"Executable": reflect.ValueOf(os.Executable),
"Exit": reflect.ValueOf(os.Exit),
"Exit": reflect.ValueOf(osExit),
"Expand": reflect.ValueOf(os.Expand),
"ExpandEnv": reflect.ValueOf(os.ExpandEnv),
"FindProcess": reflect.ValueOf(os.FindProcess),
"FindProcess": reflect.ValueOf(osFindProcess),
"Getegid": reflect.ValueOf(os.Getegid),
"Getenv": reflect.ValueOf(os.Getenv),
"Geteuid": reflect.ValueOf(os.Geteuid),

View File

@@ -1,25 +0,0 @@
// Code generated by 'goexports os/exec'. DO NOT EDIT.
// +build go1.13,!go1.14
package stdlib
import (
"os/exec"
"reflect"
)
func init() {
Symbols["os/exec"] = map[string]reflect.Value{
// function, constant and variable definitions
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
// type definitions
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
}

View File

@@ -14,9 +14,9 @@ import (
func init() {
Symbols["log"] = map[string]reflect.Value{
// function, constant and variable definitions
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"Fatal": reflect.ValueOf(logFatal),
"Fatalf": reflect.ValueOf(logFatalf),
"Fatalln": reflect.ValueOf(logFatalln),
"Flags": reflect.ValueOf(log.Flags),
"LUTC": reflect.ValueOf(constant.MakeFromLiteral("32", token.INT, 0)),
"Ldate": reflect.ValueOf(constant.MakeFromLiteral("1", token.INT, 0)),
@@ -26,7 +26,7 @@ func init() {
"Lshortfile": reflect.ValueOf(constant.MakeFromLiteral("16", token.INT, 0)),
"LstdFlags": reflect.ValueOf(constant.MakeFromLiteral("3", token.INT, 0)),
"Ltime": reflect.ValueOf(constant.MakeFromLiteral("2", token.INT, 0)),
"New": reflect.ValueOf(log.New),
"New": reflect.ValueOf(logNew),
"Output": reflect.ValueOf(log.Output),
"Panic": reflect.ValueOf(log.Panic),
"Panicf": reflect.ValueOf(log.Panicf),
@@ -41,6 +41,6 @@ func init() {
"Writer": reflect.ValueOf(log.Writer),
// type definitions
"Logger": reflect.ValueOf((*log.Logger)(nil)),
"Logger": reflect.ValueOf((*logLogger)(nil)),
}
}

View File

@@ -31,10 +31,10 @@ func init() {
"ErrNotExist": reflect.ValueOf(&os.ErrNotExist).Elem(),
"ErrPermission": reflect.ValueOf(&os.ErrPermission).Elem(),
"Executable": reflect.ValueOf(os.Executable),
"Exit": reflect.ValueOf(os.Exit),
"Exit": reflect.ValueOf(osExit),
"Expand": reflect.ValueOf(os.Expand),
"ExpandEnv": reflect.ValueOf(os.ExpandEnv),
"FindProcess": reflect.ValueOf(os.FindProcess),
"FindProcess": reflect.ValueOf(osFindProcess),
"Getegid": reflect.ValueOf(os.Getegid),
"Getenv": reflect.ValueOf(os.Getenv),
"Geteuid": reflect.ValueOf(os.Geteuid),

View File

@@ -1,25 +0,0 @@
// Code generated by 'goexports os/exec'. DO NOT EDIT.
// +build go1.14,!go1.15
package stdlib
import (
"os/exec"
"reflect"
)
func init() {
Symbols["os/exec"] = map[string]reflect.Value{
// function, constant and variable definitions
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
// type definitions
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
}

55
stdlib/restricted.go Normal file
View File

@@ -0,0 +1,55 @@
package stdlib
import (
"errors"
"io"
"log"
"os"
"strconv"
)
var errRestricted = errors.New("restricted")
// osExit invokes panic instead of exit.
func osExit(code int) { panic("os.Exit(" + strconv.Itoa(code) + ")") }
// osFindProcess returns os.FindProcess, except for self process.
func osFindProcess(pid int) (*os.Process, error) {
if pid == os.Getpid() {
return nil, errRestricted
}
return os.FindProcess(pid)
}
// The following functions call Panic instead of Fatal to avoid exit.
func logFatal(v ...interface{}) { log.Panic(v...) }
func logFatalf(f string, v ...interface{}) { log.Panicf(f, v...) }
func logFatalln(v ...interface{}) { log.Panicln(v...) }
type logLogger struct {
l *log.Logger
}
// logNew Returns a wrapped logger.
func logNew(out io.Writer, prefix string, flag int) *logLogger {
return &logLogger{log.New(out, prefix, flag)}
}
// The following methods call Panic instead of Fatal to avoid exit.
func (l *logLogger) Fatal(v ...interface{}) { l.l.Panic(v...) }
func (l *logLogger) Fatalf(f string, v ...interface{}) { l.l.Panicf(f, v...) }
func (l *logLogger) Fatalln(v ...interface{}) { l.l.Panicln(v...) }
// The following methods just forward to wrapped logger.
func (l *logLogger) Flags() int { return l.l.Flags() }
func (l *logLogger) Output(d int, s string) error { return l.l.Output(d, s) }
func (l *logLogger) Panic(v ...interface{}) { l.l.Panic(v...) }
func (l *logLogger) Panicf(f string, v ...interface{}) { l.l.Panicf(f, v...) }
func (l *logLogger) Panicln(v ...interface{}) { l.l.Panicln(v...) }
func (l *logLogger) Prefix() string { return l.l.Prefix() }
func (l *logLogger) Print(v ...interface{}) { l.l.Print(v...) }
func (l *logLogger) Printf(f string, v ...interface{}) { l.l.Printf(f, v...) }
func (l *logLogger) Println(v ...interface{}) { l.l.Println(v...) }
func (l *logLogger) SetFlags(flag int) { l.l.SetFlags(flag) }
func (l *logLogger) SetOutput(w io.Writer) { l.l.SetOutput(w) }
func (l *logLogger) Writer() io.Writer { return l.l.Writer() }

View File

@@ -43,7 +43,7 @@ func init() {
//go:generate ../cmd/goexports/goexports net net/http net/http/cgi net/http/cookiejar net/http/fcgi
//go:generate ../cmd/goexports/goexports net/http/httptest net/http/httptrace net/http/httputil net/http/pprof
//go:generate ../cmd/goexports/goexports net/mail net/rpc net/rpc/jsonrpc net/smtp net/textproto net/url
//go:generate ../cmd/goexports/goexports os os/exec os/signal os/user
//go:generate ../cmd/goexports/goexports os os/signal os/user
//go:generate ../cmd/goexports/goexports path path/filepath reflect regexp regexp/syntax
//go:generate ../cmd/goexports/goexports runtime runtime/debug runtime/pprof runtime/trace
//go:generate ../cmd/goexports/goexports sort strconv strings sync sync/atomic

View File

@@ -0,0 +1,41 @@
// Package unrestricted provides the original version of standard library symbols which may cause the interpreter process to exit.
package unrestricted
import (
"log"
"os"
"os/exec"
"reflect"
)
// Symbols stores the map of syscall package symbols.
var Symbols = map[string]map[string]reflect.Value{}
func init() {
Symbols["os"] = map[string]reflect.Value{
"Exit": reflect.ValueOf(os.Exit),
"FindProcess": reflect.ValueOf(os.FindProcess),
}
Symbols["os/exec"] = map[string]reflect.Value{
"Command": reflect.ValueOf(exec.Command),
"CommandContext": reflect.ValueOf(exec.CommandContext),
"ErrNotFound": reflect.ValueOf(&exec.ErrNotFound).Elem(),
"LookPath": reflect.ValueOf(exec.LookPath),
"Cmd": reflect.ValueOf((*exec.Cmd)(nil)),
"Error": reflect.ValueOf((*exec.Error)(nil)),
"ExitError": reflect.ValueOf((*exec.ExitError)(nil)),
}
Symbols["log"] = map[string]reflect.Value{
"Fatal": reflect.ValueOf(log.Fatal),
"Fatalf": reflect.ValueOf(log.Fatalf),
"Fatalln": reflect.ValueOf(log.Fatalln),
"New": reflect.ValueOf(log.New),
"Logger": reflect.ValueOf((*log.Logger)(nil)),
}
Symbols["github.com/containous/yaegi/stdlib/unrestricted"] = map[string]reflect.Value{
"Symbols": reflect.ValueOf(Symbols),
}
}