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, FileSize: outputLimit << 20,
Stack: stackLimit << 20, Stack: stackLimit << 20,
} }
debug("rlimit: ", rlims)
debug("defaultMount: ", mount.DefaultMounts)
actionDefault := seccomp.ActionKill actionDefault := seccomp.ActionKill
if showDetails { if showDetails {
@ -274,7 +276,7 @@ func start() (*types.Result, error) {
return nil, fmt.Errorf("cannot make temp root for new namespace") return nil, fmt.Errorf("cannot make temp root for new namespace")
} }
defer os.RemoveAll(root) 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 { if err != nil {
return nil, fmt.Errorf("cannot make rootfs mounts") 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 type Status int
// UOJ run_program constants // 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" "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 // Builder builds instance of container masters
type Builder struct { type Builder struct {
// Root is container root mount path, empty uses current work path // Root is container root mount path, empty uses current work path
@ -56,7 +59,11 @@ func (b *Builder) Build() (*Master, error) {
// container mount points // container mount points
mounts := b.Mounts mounts := b.Mounts
if len(mounts) == 0 { 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) 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 { func (m Mount) String() string {
switch { switch {
case m.Flags|syscall.MS_BIND == syscall.MS_BIND: case m.Flags&syscall.MS_BIND == syscall.MS_BIND:
flag := "rw" flag := "rw"
if m.Flags|syscall.MS_RDONLY == syscall.MS_RDONLY { if m.Flags&syscall.MS_RDONLY == syscall.MS_RDONLY {
flag = "ro" flag = "ro"
} }
return fmt.Sprintf("bind[%s:%s:%s]", m.Source, m.Target, flag) return fmt.Sprintf("bind[%s:%s:%s]", m.Source, m.Target, flag)

View File

@ -2,6 +2,7 @@ package ptracer
import ( import (
"context" "context"
"fmt"
"runtime" "runtime"
"time" "time"
@ -14,31 +15,19 @@ import (
// Trace starts new goroutine and trace runner with ptrace // Trace starts new goroutine and trace runner with ptrace
func (t *Tracer) Trace(c context.Context) <-chan types.Result { func (t *Tracer) Trace(c context.Context) <-chan types.Result {
result := make(chan types.Result, 1) result := make(chan types.Result, 1)
start := make(chan struct{})
finish := make(chan struct{})
// run
go func() { go func() {
defer close(finish) result <- t.TraceRun(c)
ret, err := t.TraceRun(c.Done(), start)
ret.Error = err.Error()
result <- ret
}() }()
select {
case <-start:
case <-finish:
}
return result return result
} }
// TraceRun start and traces all child process by runner in the calling goroutine // 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 // 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 ( var (
status = types.StatusNormal
wstatus unix.WaitStatus // wait4 wait status wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage 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 traced = make(map[int]bool) // store all process that have set ptrace options
execved = false // store whether the runner process have successfully execvd execved = false // store whether the runner process have successfully execvd
pid int // store pid of wait4 result 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 { if err != nil {
t.Handler.Debug("start tracee failed: ", err) t.Handler.Debug("start tracee failed: ", err)
result.Status = types.StatusRunnerError result.Status = types.StatusRunnerError
return result, err result.Error = err.Error()
return
} }
close(start) cc, cancel := context.WithCancel(c)
finish := make(chan struct{}) defer cancel()
defer close(finish)
// handle cancelation // handle cancelation
go func() { go func() {
select { <-cc.Done()
case <-done: killAll(pgid)
tle = true
killAll(pgid)
case <-finish:
}
}() }()
// handler potential panic and tle // handler potential panic and tle
// also ensure processes was well terminated // also ensure processes was well terminated
defer func() { defer func() {
if tle { if err := recover(); err != nil {
err = types.StatusTimeLimitExceeded t.Handler.Debug("panic: ", err)
} result.Status = types.StatusRunnerError
if err2 := recover(); err2 != nil { result.Error = fmt.Sprintf("%v", err)
t.Handler.Debug(err2)
err = types.StatusRunnerError
} }
// kill all tracee upon return // kill all tracee upon return
killAll(pgid) killAll(pgid)
@ -90,7 +73,7 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
result.RunningTime = time.Since(fTime) result.RunningTime = time.Since(fTime)
}() }()
// trace unixs // ptrace pool loop
for { for {
if execved { if execved {
// Wait for all child in the process group // 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 { if err != nil {
t.Handler.Debug("wait4 failed: ", err) t.Handler.Debug("wait4 failed: ", err)
return result, types.StatusRunnerError result.Status = types.StatusRunnerError
result.Error = err.Error()
return
} }
t.Handler.Debug("------ ", pid, " ------") t.Handler.Debug("------ ", pid, " ------")
status := types.StatusNormal
if pid == pgid { if pid == pgid {
// update resource usage and check against limits // update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns 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, Memory: userMem,
} }
if status != types.StatusNormal { 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 pid == pgid {
if execved { if execved {
result.ExitStatus = wstatus.ExitStatus() 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 result.Status = types.StatusRunnerError
return result, types.StatusRunnerError result.Error = "child process exit before execve"
return
} }
case wstatus.Signaled(): case wstatus.Signaled():
@ -159,20 +149,21 @@ func (t *Tracer) TraceRun(done <-chan struct{}, start chan<- struct{}) (result t
} }
result.Status = status result.Status = status
result.ExitStatus = int(sig) result.ExitStatus = int(sig)
return result, status return
} }
unix.PtraceCont(pid, int(sig)) unix.PtraceCont(pid, int(sig))
case wstatus.Stopped(): case wstatus.Stopped():
// Set option if the process is newly forked // Set option if the process is newly forked
if !traced[pid] { if !traced[pid] {
t.Handler.Debug("set ptrace option") t.Handler.Debug("set ptrace option for", pid)
traced[pid] = true traced[pid] = true
// Ptrace set option valid if the tracee is stopped // Ptrace set option valid if the tracee is stopped
err = setPtraceOption(pid) err = setPtraceOption(pid)
if err != nil { if err != nil {
result.Status = types.StatusRunnerError 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) err := t.handleTrap(pid)
if err != nil { if err != nil {
result.Status = types.StatusDisallowedSyscall result.Status = types.StatusDisallowedSyscall
return result, err result.Error = err.Error()
return
} }
} else { } else {
t.Handler.Debug("ptrace seccomp before execve (should be the execve syscall)") 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 { if status != types.StatusNormal {
result.Status = status result.Status = status
return result, status return
} }
// Likely encountered SIGSEGV (segment violation) // Likely encountered SIGSEGV (segment violation)
// Or compiler child exited // 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) result := make(chan types.Result, 1)
start := make(chan struct{})
finish := make(chan struct{})
// run
go func() { go func() {
defer close(finish) result <- r.trace(c, ch)
ret, err2 := r.Trace(c.Done(), start, ch)
ret.Error = err2.Error()
result <- ret
}() }()
select {
case <-start:
case <-finish:
}
return result return result
} }
// Trace tracks child processes // Trace tracks child processes
func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{}, func (r *Runner) trace(c context.Context, runner *forkexec.Runner) (result types.Result) {
runner *forkexec.Runner) (result types.Result, err error) {
var ( var (
wstatus unix.WaitStatus // wait4 wait status wstatus unix.WaitStatus // wait4 wait status
rusage unix.Rusage // wait4 rusage rusage unix.Rusage // wait4 rusage
tle = false
status = types.StatusNormal status = types.StatusNormal
sTime = time.Now() // start time sTime = time.Now() // start time
fTime time.Time // finish time for setup 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) r.println("Starts: ", pgid, err)
if err != nil { if err != nil {
result.Status = types.StatusRunnerError result.Status = types.StatusRunnerError
return result, err result.Error = err.Error()
return
} }
close(start) cc, cancel := context.WithCancel(c)
finish := make(chan struct{}) defer cancel()
defer close(finish)
// handle cancel // handle cancel
go func() { go func() {
select { <-cc.Done()
case <-done: killAll(pgid)
tle = true
killAll(pgid)
case <-finish:
}
}() }()
// kill all tracee upon return
defer func() { defer func() {
if tle {
err = types.StatusTimeLimitExceeded
}
// kill all tracee upon return
killAll(pgid) killAll(pgid)
collectZombie(pgid) collectZombie(pgid)
result.SetUpTime = fTime.Sub(sTime) result.SetUpTime = fTime.Sub(sTime)
@ -104,17 +84,18 @@ func (r *Runner) Trace(done <-chan struct{}, start chan<- struct{},
}() }()
fTime = time.Now() fTime = time.Now()
loop:
for { for {
_, err := unix.Wait4(pgid, &wstatus, 0, &rusage) _, err := unix.Wait4(pgid, &wstatus, 0, &rusage)
r.println("wait4: ", wstatus) r.println("wait4: ", wstatus)
if err != nil { if err != nil {
return result, types.StatusRunnerError result.Status = types.StatusRunnerError
result.Error = err.Error()
return
} }
// update resource usage and check against limits // update resource usage and check against limits
userTime := time.Duration(rusage.Utime.Nano()) // ns 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 // check tle / mle
if userTime > r.Limit.TimeLimit { if userTime > r.Limit.TimeLimit {
@ -129,13 +110,15 @@ loop:
Memory: userMem, Memory: userMem,
} }
if status != types.StatusNormal { if status != types.StatusNormal {
return result, status return
} }
switch { switch {
case wstatus.Exited(): case wstatus.Exited():
result.Status = types.StatusNormal
result.ExitStatus = wstatus.ExitStatus() result.ExitStatus = wstatus.ExitStatus()
return result, nil return
case wstatus.Signaled(): case wstatus.Signaled():
sig := wstatus.Signal() sig := wstatus.Signal()
switch sig { switch sig {
@ -150,10 +133,9 @@ loop:
} }
result.Status = status result.Status = status
result.ExitStatus = int(sig) result.ExitStatus = int(sig)
break loop return
} }
} }
return result, status
} }
// kill all tracee according to pids // kill all tracee according to pids
@ -164,7 +146,6 @@ func killAll(pgid int) {
// collect died child processes // collect died child processes
func collectZombie(pgid int) { func collectZombie(pgid int) {
var wstatus unix.WaitStatus var wstatus unix.WaitStatus
// collect zombies
for { for {
if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOHANG, nil); err != nil { if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOHANG, nil); err != nil {
break break