* plugin sandboxing * remove unused type * better symlink handling, better remounting, better test, whitespace fixes, and comment on the remounting * fix test compile error * big simplification for getting mount flags * mask statfs flags to the ones we're interested in
160 строки
4.1 KiB
Go
160 строки
4.1 KiB
Go
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
|
// See License.txt for license information.
|
|
|
|
package sandbox
|
|
|
|
import (
|
|
"context"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost-server/plugin/rpcplugin/rpcplugintest"
|
|
)
|
|
|
|
func TestNewProcess(t *testing.T) {
|
|
if err := CheckSupport(); err != nil {
|
|
t.Skip("sandboxing not supported:", err)
|
|
}
|
|
|
|
dir, err := ioutil.TempDir("", "")
|
|
require.NoError(t, err)
|
|
defer os.RemoveAll(dir)
|
|
|
|
ping := filepath.Join(dir, "ping.exe")
|
|
rpcplugintest.CompileGo(t, `
|
|
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"syscall"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/mattermost/mattermost-server/plugin/rpcplugin"
|
|
)
|
|
|
|
var failures int
|
|
|
|
type T struct {}
|
|
func (T) Errorf(format string, args ...interface{}) {
|
|
fmt.Printf(format, args...)
|
|
failures++
|
|
}
|
|
func (T) FailNow() {
|
|
os.Exit(1)
|
|
}
|
|
|
|
func init() {
|
|
if len(os.Args) > 0 && os.Args[0] == "exitImmediately" {
|
|
os.Exit(0)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
t := &T{}
|
|
|
|
pwd, err := os.Getwd()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "/dir", pwd)
|
|
|
|
assert.Equal(t, 0, os.Getgid(), "we should see ourselves as root")
|
|
assert.Equal(t, 0, os.Getuid(), "we should see ourselves as root")
|
|
|
|
f, err := ioutil.TempFile("", "")
|
|
require.NoError(t, err, "we should be able to create temporary files")
|
|
f.Close()
|
|
|
|
_, err = os.Stat("ping.exe")
|
|
assert.NoError(t, err, "we should be able to read files in the working directory")
|
|
|
|
buf := make([]byte, 20)
|
|
n, err := rand.Read(buf)
|
|
assert.Equal(t, 20, n)
|
|
assert.NoError(t, err, "we should be able to read from /dev/urandom")
|
|
|
|
f, err = os.Create("/dev/zero")
|
|
require.NoError(t, err, "we should be able to write to /dev/zero")
|
|
defer f.Close()
|
|
n, err = f.Write([]byte("foo"))
|
|
assert.Equal(t, 3, n)
|
|
require.NoError(t, err, "we should be able to write to /dev/zero")
|
|
|
|
f, err = os.Create("/dir/foo")
|
|
if f != nil {
|
|
defer f.Close()
|
|
}
|
|
assert.Error(t, err, "we shouldn't be able to write to this read-only mount point")
|
|
|
|
_, err = ioutil.ReadFile("/etc/resolv.conf")
|
|
require.NoError(t, err, "we should be able to read /etc/resolv.conf")
|
|
|
|
resp, err := http.Get("https://github.com")
|
|
require.NoError(t, err, "we should be able to use the network")
|
|
resp.Body.Close()
|
|
|
|
status, err := ioutil.ReadFile("/proc/self/status")
|
|
require.NoError(t, err, "we should be able to read from /proc")
|
|
assert.Regexp(t, status, "CapEff:\\s+0000000000000000", "we should have no effective capabilities")
|
|
|
|
require.NoError(t, os.MkdirAll("/tmp/dir2", 0755))
|
|
err = syscall.Mount("/dir", "/tmp/dir2", "", syscall.MS_BIND, "")
|
|
assert.Equal(t, syscall.EPERM, err, "we shouldn't be allowed to mount things")
|
|
|
|
cmd := exec.Command("/proc/self/exe")
|
|
cmd.Args = []string{"exitImmediately"}
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Pdeathsig: syscall.SIGTERM,
|
|
}
|
|
assert.NoError(t, cmd.Run(), "we should be able to re-exec ourself")
|
|
|
|
cmd = exec.Command("/proc/self/exe")
|
|
cmd.Args = []string{"exitImmediately"}
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Cloneflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWUSER,
|
|
Pdeathsig: syscall.SIGTERM,
|
|
}
|
|
assert.Error(t, cmd.Run(), "we shouldn't be able to create new namespaces anymore")
|
|
|
|
ipc, err := rpcplugin.InheritedProcessIPC()
|
|
require.NoError(t, err)
|
|
defer ipc.Close()
|
|
_, err = ipc.Write([]byte("ping"))
|
|
require.NoError(t, err)
|
|
|
|
if failures > 0 {
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
`, ping)
|
|
|
|
p, ipc, err := NewProcess(context.Background(), &Configuration{
|
|
MountPoints: []*MountPoint{
|
|
{
|
|
Source: dir,
|
|
Destination: "/dir",
|
|
ReadOnly: true,
|
|
},
|
|
},
|
|
WorkingDirectory: "/dir",
|
|
}, "/dir/ping.exe")
|
|
require.NoError(t, err)
|
|
defer ipc.Close()
|
|
b := make([]byte, 10)
|
|
n, err := ipc.Read(b)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 4, n)
|
|
assert.Equal(t, "ping", string(b[:4]))
|
|
require.NoError(t, p.Wait())
|
|
}
|