update mounts

This commit is contained in:
criyle 2020-02-19 01:13:25 -05:00
parent a4c78dcf5d
commit a24933ac82
8 changed files with 100 additions and 175 deletions

View File

@ -218,6 +218,8 @@ func start() (*types.Result, error) {
FileSize: outputLimit << 20,
Stack: stackLimit << 20,
}
debug("rlimit: ", rlims)
debug("defaultMount: ", mount.DefaultMounts)
actionDefault := seccomp.ActionKill
if showDetails {
@ -274,7 +276,7 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("cannot make temp root for new namespace")
}
defer os.RemoveAll(root)
mounts, err := mount.NewBuilder().WithMounts(unshare.DefaultMounts).WithBind(root, "w", true).Build(true)
mounts, err := mount.NewBuilder().WithMounts(mount.DefaultMounts).WithBind(root, "w", true).Build(true)
if err != nil {
return nil, fmt.Errorf("cannot make rootfs mounts")
}
@ -374,6 +376,7 @@ func debug(v ...interface{}) {
}
}
// Status defines uoj/run_program constants
type Status int
// UOJ run_program constants

View File

@ -1,55 +0,0 @@
package daemon
import (
"github.com/criyle/go-sandbox/pkg/mount"
"golang.org/x/sys/unix"
)
const (
roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
mFlag = unix.MS_NOSUID | unix.MS_NOATIME | unix.MS_NODEV
)
// default parameters. I was tend to reuse the configs but it is hard since there are some
// cross device symbolic
var (
DefaultPath = "PATH=/usr/local/bin:/usr/bin:/bin"
// rootfs created by bind mounting
DefaultMounts = []mount.Mount{
{
Source: "/usr",
Target: "usr",
Flags: roBind,
},
{
Source: "/lib",
Target: "lib",
Flags: roBind,
},
{
Source: "/lib64",
Target: "lib64",
Flags: roBind,
},
{
Source: "/bin",
Target: "bin",
Flags: roBind,
},
// work dir at /w
{
Source: "tmpfs",
Target: "w",
FsType: "tmpfs",
Flags: mFlag,
},
// tmpfs at /tmp
{
Source: "tmpfs",
Target: "tmp",
FsType: "tmpfs",
Flags: mFlag,
},
}
)

View File

@ -13,6 +13,9 @@ import (
"golang.org/x/sys/unix"
)
// DefaultPath defines path evironment variable for container init process
const DefaultPath = "PATH=/usr/local/bin:/usr/bin:/bin"
// Builder builds instance of container masters
type Builder struct {
// Root is container root mount path, empty uses current work path
@ -56,7 +59,11 @@ func (b *Builder) Build() (*Master, error) {
// container mount points
mounts := b.Mounts
if len(mounts) == 0 {
if mounts, err = mount.NewBuilder().WithMounts(DefaultMounts).Build(true); err != nil {
if mounts, err = mount.NewBuilder().
WithMounts(mount.DefaultMounts).
WithTmpfs("w", ""). // work dir
WithTmpfs("tmp", ""). // tmp
Build(true); err != nil {
return nil, fmt.Errorf("daemon: failed to build rootfs mount %v", err)
}
}

34
pkg/mount/default.go Normal file
View File

@ -0,0 +1,34 @@
package mount
import "golang.org/x/sys/unix"
// RoBind is read-only bind mount flags
const (
RoBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
)
var (
// DefaultMounts is a example of minimal rootfs
DefaultMounts = []Mount{
{
Source: "/usr",
Target: "usr",
Flags: RoBind,
},
{
Source: "/lib",
Target: "lib",
Flags: RoBind,
},
{
Source: "/lib64",
Target: "lib64",
Flags: RoBind,
},
{
Source: "/bin",
Target: "bin",
Flags: RoBind,
},
}
)

View File

@ -26,9 +26,9 @@ func (m *Mount) Mount() error {
func (m Mount) String() string {
switch {
case m.Flags|syscall.MS_BIND == syscall.MS_BIND:
case m.Flags&syscall.MS_BIND == syscall.MS_BIND:
flag := "rw"
if m.Flags|syscall.MS_RDONLY == syscall.MS_RDONLY {
if m.Flags&syscall.MS_RDONLY == syscall.MS_RDONLY {
flag = "ro"
}
return fmt.Sprintf("bind[%s:%s:%s]", m.Source, m.Target, flag)

View File

@ -2,6 +2,7 @@ package ptracer
import (
"context"
"fmt"
"runtime"
"time"
@ -14,31 +15,19 @@ import (
// Trace starts new goroutine and trace runner with ptrace
func (t *Tracer) Trace(c context.Context) <-chan types.Result {
result := make(chan types.Result, 1)
start := make(chan struct{})
finish := make(chan struct{})
// run
go func() {
defer close(finish)
ret, err := t.TraceRun(c.Done(), start)
ret.Error = err.Error()
result <- ret
result <- t.TraceRun(c)
}()
select {
case <-start:
case <-finish:
}
return result
}
// TraceRun start and traces all child process by runner in the calling goroutine
// parameter done used to cancel work, start is used notify child starts
func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result types.Result, err error) {
func (t *Tracer) TraceRun(c context.Context) (result types.Result) {
var (
status = types.StatusNormal
wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage
tle bool // whether the timmer triggered due to timeout
traced = make(map[int]bool) // store all process that have set ptrace options
execved = false // store whether the runner process have successfully execvd
pid int // store pid of wait4 result
@ -56,32 +45,26 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
if err != nil {
t.Handler.Debug("start tracee failed: ", err)
result.Status = types.StatusRunnerError
return result, err
result.Error = err.Error()
return
}
close(start)
finish := make(chan struct{})
defer close(finish)
cc, cancel := context.WithCancel(c)
defer cancel()
// handle cancelation
go func() {
select {
case <-done:
tle = true
killAll(pgid)
case <-finish:
}
<-cc.Done()
killAll(pgid)
}()
// handler potential panic and tle
// also ensure processes was well terminated
defer func() {
if tle {
err = types.StatusTimeLimitExceeded
}
if err2 := recover(); err2 != nil {
t.Handler.Debug(err2)
err = types.StatusRunnerError
if err := recover(); err != nil {
t.Handler.Debug("panic: ", err)
result.Status = types.StatusRunnerError
result.Error = fmt.Sprintf("%v", err)
}
// kill all tracee upon return
killAll(pgid)
@ -90,7 +73,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
result.RunningTime = time.Since(fTime)
}()
// trace unixs
// ptrace pool loop
for {
if execved {
// Wait for all child in the process group
@ -101,11 +84,12 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
}
if err != nil {
t.Handler.Debug("wait4 failed: ", err)
return result, types.StatusRunnerError
result.Status = types.StatusRunnerError
result.Error = err.Error()
return
}
t.Handler.Debug("------ ", pid, " ------")
status := types.StatusNormal
if pid == pgid {
// update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns
@ -124,7 +108,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
Memory: userMem,
}
if status != types.StatusNormal {
return result, status
return
}
}
@ -136,10 +120,16 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
if pid == pgid {
if execved {
result.ExitStatus = wstatus.ExitStatus()
return result, nil
if result.ExitStatus == 0 {
result.Status = types.StatusNormal
} else {
result.Status = types.StatusNonzeroExitStatus
}
return
}
result.Status = types.StatusRunnerError
return result, types.StatusRunnerError
result.Error = "child process exit before execve"
return
}
case wstatus.Signaled():
@ -159,20 +149,21 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
}
result.Status = status
result.ExitStatus = int(sig)
return result, status
return
}
unix.PtraceCont(pid, int(sig))
case wstatus.Stopped():
// Set option if the process is newly forked
if !traced[pid] {
t.Handler.Debug("set ptrace option")
t.Handler.Debug("set ptrace option for", pid)
traced[pid] = true
// Ptrace set option valid if the tracee is stopped
err = setPtraceOption(pid)
if err != nil {
result.Status = types.StatusRunnerError
return result, err
result.Error = err.Error()
return
}
}
@ -185,7 +176,8 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
err := t.handleTrap(pid)
if err != nil {
result.Status = types.StatusDisallowedSyscall
return result, err
result.Error = err.Error()
return
}
} else {
t.Handler.Debug("ptrace seccomp before execve (should be the execve syscall)")
@ -219,7 +211,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
}
if status != types.StatusNormal {
result.Status = status
return result, status
return
}
// Likely encountered SIGSEGV (segment violation)
// Or compiler child exited

View File

@ -1,37 +0,0 @@
// +build linux
package unshare
import (
"golang.org/x/sys/unix"
"github.com/criyle/go-sandbox/pkg/mount"
)
const roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
var (
// DefaultMounts is a example of minimal rootfs
DefaultMounts = []mount.Mount{
{
Source: "/usr",
Target: "usr",
Flags: roBind,
},
{
Source: "/lib",
Target: "lib",
Flags: roBind,
},
{
Source: "/lib64",
Target: "lib64",
Flags: roBind,
},
{
Source: "/bin",
Target: "bin",
Flags: roBind,
},
}
)

View File

@ -40,31 +40,18 @@ func (r *Runner) Run(c context.Context) <-chan types.Result {
}
result := make(chan types.Result, 1)
start := make(chan struct{})
finish := make(chan struct{})
// run
go func() {
defer close(finish)
ret, err2 := r.Trace(c.Done(), start, ch)
ret.Error = err2.Error()
result <- ret
result <- r.trace(c, ch)
}()
select {
case <-start:
case <-finish:
}
return result
}
// Trace tracks child processes
func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
runner *forkexec.Runner) (result types.Result, err error) {
func (r *Runner) trace(c context.Context, runner *forkexec.Runner) (result types.Result) {
var (
wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage
tle = false
status = types.StatusNormal
sTime = time.Now() // start time
fTime time.Time // finish time for setup
@ -75,28 +62,21 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
r.println("Starts: ", pgid, err)
if err != nil {
result.Status = types.StatusRunnerError
return result, err
result.Error = err.Error()
return
}
close(start)
finish := make(chan struct{})
defer close(finish)
cc, cancel := context.WithCancel(c)
defer cancel()
// handle cancel
go func() {
select {
case <-done:
tle = true
killAll(pgid)
case <-finish:
}
<-cc.Done()
killAll(pgid)
}()
// kill all tracee upon return
defer func() {
if tle {
err = types.StatusTimeLimitExceeded
}
// kill all tracee upon return
killAll(pgid)
collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime)
@ -104,17 +84,18 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
}()
fTime = time.Now()
loop:
for {
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
r.println("wait4: ", wstatus)
if err != nil {
return result, types.StatusRunnerError
result.Status = types.StatusRunnerError
result.Error = err.Error()
return
}
// update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns
userMem := types.Size(rusage.Maxrss << 10) // bytes // kb
userMem := types.Size(rusage.Maxrss << 10) // bytes
// check tle / mle
if userTime > r.Limit.TimeLimit {
@ -129,13 +110,15 @@ loop:
Memory: userMem,
}
if status != types.StatusNormal {
return result, status
return
}
switch {
case wstatus.Exited():
result.Status = types.StatusNormal
result.ExitStatus = wstatus.ExitStatus()
return result, nil
return
case wstatus.Signaled():
sig := wstatus.Signal()
switch sig {
@ -150,10 +133,9 @@ loop:
}
result.Status = status
result.ExitStatus = int(sig)
break loop
return
}
}
return result, status
}
// kill all tracee according to pids
@ -164,7 +146,6 @@ func killAll(pgid int) {
// collect died child processes
func collectZombie(pgid int) {
var wstatus unix.WaitStatus
// collect zombies
for {
if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOHANG, nil); err != nil {
break