mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
update mounts
This commit is contained in:
parent
a4c78dcf5d
commit
a24933ac82
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -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
34
pkg/mount/default.go
Normal 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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user