mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
decoupling rununshared and tracer
This commit is contained in:
parent
3ed7b771c8
commit
b8c180569f
12
README.md
12
README.md
@ -22,6 +22,7 @@ New Features:
|
|||||||
2. More architectures (arm32, arm64, x86)
|
2. More architectures (arm32, arm64, x86)
|
||||||
3. Allow multiple traced programs in different threads
|
3. Allow multiple traced programs in different threads
|
||||||
4. Allow pipes as input / output files
|
4. Allow pipes as input / output files
|
||||||
|
5. Use Linux Namespace to isolate file access (elimilate ptrace)
|
||||||
|
|
||||||
Default file access action:
|
Default file access action:
|
||||||
|
|
||||||
@ -34,13 +35,15 @@ Default file access action:
|
|||||||
Packages:
|
Packages:
|
||||||
|
|
||||||
- seccomp: provides utility function that wrappers libseccomp
|
- seccomp: provides utility function that wrappers libseccomp
|
||||||
- mount: provides utility function that wrappers mount syscall
|
|
||||||
- rlimit: provides utility function that defines rlimit syscall
|
|
||||||
- forkexec: fork-exec provides mount, unshare, ptrace, seccomp, capset before exec
|
- forkexec: fork-exec provides mount, unshare, ptrace, seccomp, capset before exec
|
||||||
- tracer: ptrace tracer and provides syscall trap filter context
|
- tracer: ptrace tracer and provides syscall trap filter context
|
||||||
- runprogram: wrapper to call forkexec and trecer
|
- runprogram: wrapper to call forkexec and trecer
|
||||||
- rununshared: wrapper to call forkexec and unshared namespaces
|
- rununshared: wrapper to call forkexec and unshared namespaces
|
||||||
- runconfig: defines arch & language specified trace condition
|
- runconfig: defines arch & language specified trace condition for seccomp and ptrace
|
||||||
|
- types: general runtime specs
|
||||||
|
- mount: provides utility function that wrappers mount syscall
|
||||||
|
- rlimit: provides utility function that defines rlimit syscall
|
||||||
|
- specs: provides general res / result data structures
|
||||||
|
|
||||||
Executable:
|
Executable:
|
||||||
|
|
||||||
@ -52,5 +55,4 @@ Configuations:
|
|||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
1. Use Linux Namespace to isolate file access (elimilate ptrace)
|
1. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4 rusage)
|
||||||
2. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4 rusage)
|
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/rlimit"
|
|
||||||
"github.com/criyle/go-judger/runconfig"
|
"github.com/criyle/go-judger/runconfig"
|
||||||
"github.com/criyle/go-judger/runprogram"
|
"github.com/criyle/go-judger/runprogram"
|
||||||
"github.com/criyle/go-judger/rununshared"
|
"github.com/criyle/go-judger/rununshared"
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/types/rlimit"
|
||||||
|
"github.com/criyle/go-judger/types/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -19,7 +19,7 @@ const (
|
|||||||
|
|
||||||
// Runner can be ptraced runner or namespaced runner
|
// Runner can be ptraced runner or namespaced runner
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
Start() (tracer.TraceResult, error)
|
Start() (specs.TraceResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
@ -122,7 +122,7 @@ func main() {
|
|||||||
WorkDir: "/w",
|
WorkDir: "/w",
|
||||||
Files: fds,
|
Files: fds,
|
||||||
RLimits: rlims,
|
RLimits: rlims,
|
||||||
ResLimits: tracer.ResLimit{
|
ResLimits: specs.ResLimit{
|
||||||
TimeLimit: timeLimit * 1e3,
|
TimeLimit: timeLimit * 1e3,
|
||||||
RealTimeLimit: realTimeLimit * 1e3,
|
RealTimeLimit: realTimeLimit * 1e3,
|
||||||
MemoryLimit: memoryLimit << 10,
|
MemoryLimit: memoryLimit << 10,
|
||||||
@ -143,7 +143,7 @@ func main() {
|
|||||||
Env: []string{pathEnv},
|
Env: []string{pathEnv},
|
||||||
WorkDir: workPath,
|
WorkDir: workPath,
|
||||||
RLimits: rlims,
|
RLimits: rlims,
|
||||||
TraceLimit: runprogram.TraceLimit{
|
TraceLimit: specs.ResLimit{
|
||||||
TimeLimit: timeLimit * 1e3,
|
TimeLimit: timeLimit * 1e3,
|
||||||
RealTimeLimit: realTimeLimit * 1e3,
|
RealTimeLimit: realTimeLimit * 1e3,
|
||||||
MemoryLimit: memoryLimit << 10,
|
MemoryLimit: memoryLimit << 10,
|
||||||
@ -173,17 +173,16 @@ func main() {
|
|||||||
|
|
||||||
// Run tracer
|
// Run tracer
|
||||||
rt, err := runner.Start()
|
rt, err := runner.Start()
|
||||||
println("used process_vm_readv: ", tracer.UseVMReadv)
|
|
||||||
println("results:", rt, err)
|
println("results:", rt, err)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c, ok := err.(tracer.TraceCode)
|
c, ok := err.(specs.TraceCode)
|
||||||
if !ok {
|
if !ok {
|
||||||
c = tracer.TraceCodeFatal
|
c = specs.TraceCodeFatal
|
||||||
}
|
}
|
||||||
// Handle fatal error from trace
|
// Handle fatal error from trace
|
||||||
fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitCode)
|
fmt.Fprintf(f, "%d %d %d %d\n", int(c), rt.UserTime, rt.UserMem, rt.ExitCode)
|
||||||
if c == tracer.TraceCodeFatal {
|
if c == specs.TraceCodeFatal {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -77,6 +77,7 @@ func (r *Runner) Start() (int, error) {
|
|||||||
if err1 != 0 || pid != 0 {
|
if err1 != 0 || pid != 0 {
|
||||||
// restore all signals
|
// restore all signals
|
||||||
afterFork()
|
afterFork()
|
||||||
|
syscall.ForkLock.Unlock()
|
||||||
|
|
||||||
// synchronize with child for uid / gid map
|
// synchronize with child for uid / gid map
|
||||||
if unshareUser {
|
if unshareUser {
|
||||||
@ -89,7 +90,6 @@ func (r *Runner) Start() (int, error) {
|
|||||||
unix.Close(p[1])
|
unix.Close(p[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
syscall.ForkLock.Unlock()
|
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
return int(pid), syscall.Errno(err1)
|
return int(pid), syscall.Errno(err1)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package forkexec
|
|||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/mount"
|
"github.com/criyle/go-judger/types/mount"
|
||||||
)
|
)
|
||||||
|
|
||||||
// prepareExec prepares execve parameters
|
// prepareExec prepares execve parameters
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
package forkexec
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
|
|
||||||
// RLimit is the resource limits defined by Linux setrlimit
|
|
||||||
type RLimit struct {
|
|
||||||
// Res is the resource type (e.g. syscall.RLIMIT_CPU)
|
|
||||||
Res int
|
|
||||||
// Rlim is the limit applied to that resource
|
|
||||||
Rlim syscall.Rlimit
|
|
||||||
}
|
|
||||||
@ -5,7 +5,8 @@ package forkexec
|
|||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/mount"
|
"github.com/criyle/go-judger/types/mount"
|
||||||
|
"github.com/criyle/go-judger/types/rlimit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Runner is the RunProgramConfig including the exec path, argv
|
// Runner is the RunProgramConfig including the exec path, argv
|
||||||
@ -16,10 +17,10 @@ type Runner struct {
|
|||||||
Args []string
|
Args []string
|
||||||
Env []string
|
Env []string
|
||||||
|
|
||||||
// Resource limit set by set rlimit
|
// POSIX Resource limit set by set rlimit
|
||||||
RLimits []RLimit
|
RLimits []rlimit.RLimit
|
||||||
|
|
||||||
// file disriptors for new process, from 0 to len - 1
|
// file disriptors map for new process, from 0 to len - 1
|
||||||
Files []uintptr
|
Files []uintptr
|
||||||
|
|
||||||
// work path set by chdir(dir) (current working directory for child)
|
// work path set by chdir(dir) (current working directory for child)
|
||||||
|
|||||||
@ -26,7 +26,7 @@ func writeIDMaps(pid int) error {
|
|||||||
|
|
||||||
// writeFile writes file
|
// writeFile writes file
|
||||||
func writeFile(path string, content []byte) error {
|
func writeFile(path string, content []byte) error {
|
||||||
fd, err := unix.Open(path, unix.O_RDWR, 0)
|
fd, err := unix.Open(path, unix.O_RDWR|unix.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import (
|
|||||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/tracer"
|
||||||
|
"github.com/criyle/go-judger/types/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tracerHandler struct {
|
type tracerHandler struct {
|
||||||
@ -121,7 +122,7 @@ func (h *tracerHandler) GetSyscallName(ctx *tracer.Context) (string, error) {
|
|||||||
|
|
||||||
func (h *tracerHandler) HandlerDisallow(name string) error {
|
func (h *tracerHandler) HandlerDisallow(name string) error {
|
||||||
if !h.Unsafe {
|
if !h.Unsafe {
|
||||||
return tracer.TraceCodeBan
|
return specs.TraceCodeBan
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ package runprogram
|
|||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/rlimit"
|
"github.com/criyle/go-judger/types/rlimit"
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/types/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunProgram defines the spec to run a program safely
|
// RunProgram defines the spec to run a program safely
|
||||||
@ -22,7 +22,7 @@ type RunProgram struct {
|
|||||||
RLimits rlimit.RLimits
|
RLimits rlimit.RLimits
|
||||||
|
|
||||||
// Res limit enforced by tracer
|
// Res limit enforced by tracer
|
||||||
TraceLimit TraceLimit
|
TraceLimit specs.ResLimit
|
||||||
|
|
||||||
// Allowed / Traced syscall names
|
// Allowed / Traced syscall names
|
||||||
// Notice: file access syscalls should be traced
|
// Notice: file access syscalls should be traced
|
||||||
@ -38,14 +38,11 @@ type RunProgram struct {
|
|||||||
ShowDetails, Unsafe bool
|
ShowDetails, Unsafe bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceLimit defines the limits enforced by tracer
|
|
||||||
type TraceLimit tracer.ResLimit
|
|
||||||
|
|
||||||
// TraceAction defines action against a syscall check
|
// TraceAction defines action against a syscall check
|
||||||
type TraceAction int
|
type TraceAction int
|
||||||
|
|
||||||
// BanRet defines the return value for a syscall ban acction
|
// BanRet defines the return value for a syscall ban acction
|
||||||
var BanRet syscall.Errno = syscall.EACCES
|
var BanRet = syscall.EACCES
|
||||||
|
|
||||||
// TraceAllow allow the access, trace ban ignores the syscall and set the
|
// TraceAllow allow the access, trace ban ignores the syscall and set the
|
||||||
// return value to BanRet, TraceKill stops the trace action
|
// return value to BanRet, TraceKill stops the trace action
|
||||||
|
|||||||
@ -6,10 +6,11 @@ import (
|
|||||||
"github.com/criyle/go-judger/forkexec"
|
"github.com/criyle/go-judger/forkexec"
|
||||||
"github.com/criyle/go-judger/seccomp"
|
"github.com/criyle/go-judger/seccomp"
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/tracer"
|
||||||
|
"github.com/criyle/go-judger/types/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start starts the tracing process
|
// Start starts the tracing process
|
||||||
func (r *RunProgram) Start() (rt tracer.TraceResult, err error) {
|
func (r *RunProgram) Start() (rt specs.TraceResult, err error) {
|
||||||
// build seccomp filter
|
// build seccomp filter
|
||||||
filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced)
|
filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -39,7 +40,7 @@ func (r *RunProgram) Start() (rt tracer.TraceResult, err error) {
|
|||||||
Unsafe: r.Unsafe,
|
Unsafe: r.Unsafe,
|
||||||
Handler: r.Handler,
|
Handler: r.Handler,
|
||||||
}
|
}
|
||||||
return tracer.Trace(th, ch, tracer.ResLimit(r.TraceLimit))
|
return tracer.Trace(th, ch, specs.ResLimit(r.TraceLimit))
|
||||||
}
|
}
|
||||||
|
|
||||||
// build filter builds the libseccomp filter according to the allow, trace and show details
|
// build filter builds the libseccomp filter according to the allow, trace and show details
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package rununshared
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/mount"
|
"github.com/criyle/go-judger/types/mount"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/criyle/go-judger/forkexec"
|
"github.com/criyle/go-judger/forkexec"
|
||||||
"github.com/criyle/go-judger/seccomp"
|
"github.com/criyle/go-judger/seccomp"
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/types/specs"
|
||||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@ -20,7 +20,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Start starts the unshared process
|
// Start starts the unshared process
|
||||||
func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
|
func (r *RunUnshared) Start() (rt specs.TraceResult, err error) {
|
||||||
filter, err := seccomp.BuildFilter(libseccomp.ActKill, libseccomp.ActTrap, r.SyscallAllowed, []string{})
|
filter, err := seccomp.BuildFilter(libseccomp.ActKill, libseccomp.ActTrap, r.SyscallAllowed, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println(err)
|
println(err)
|
||||||
@ -52,12 +52,12 @@ func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Trace tracks child processes
|
// Trace tracks child processes
|
||||||
func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult, err error) {
|
func (r *RunUnshared) Trace(runner *forkexec.Runner) (result specs.TraceResult, 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
|
tle = false
|
||||||
status = tracer.TraceCodeNormal
|
status = specs.TraceCodeNormal
|
||||||
sTime = time.Now().UnixNano() // start time
|
sTime = time.Now().UnixNano() // start time
|
||||||
fTime int64 // finish time for setup
|
fTime int64 // finish time for setup
|
||||||
)
|
)
|
||||||
@ -66,7 +66,7 @@ func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult,
|
|||||||
pgid, err := runner.Start()
|
pgid, err := runner.Start()
|
||||||
r.println("Starts: ", pgid, err)
|
r.println("Starts: ", pgid, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.TraceStatus = tracer.TraceCodeRE
|
result.TraceStatus = specs.TraceCodeRE
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
// Set real time limit, kill process after it
|
// Set real time limit, kill process after it
|
||||||
@ -78,7 +78,7 @@ func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult,
|
|||||||
defer func() {
|
defer func() {
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
if tle {
|
if tle {
|
||||||
err = tracer.TraceCodeTLE
|
err = specs.TraceCodeTLE
|
||||||
}
|
}
|
||||||
// kill all tracee upon return
|
// kill all tracee upon return
|
||||||
killAll(pgid)
|
killAll(pgid)
|
||||||
@ -94,7 +94,7 @@ func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult,
|
|||||||
pid, err := unix.Wait4(pgid, &wstatus, unix.WALL, &rusage)
|
pid, err := unix.Wait4(pgid, &wstatus, unix.WALL, &rusage)
|
||||||
r.println("wait4: ", wstatus)
|
r.println("wait4: ", wstatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, tracer.TraceCodeFatal
|
return result, specs.TraceCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
// update resource usage and check against limits
|
// update resource usage and check against limits
|
||||||
@ -103,17 +103,17 @@ func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult,
|
|||||||
|
|
||||||
// check tle / mle
|
// check tle / mle
|
||||||
if userTime > r.ResLimits.TimeLimit {
|
if userTime > r.ResLimits.TimeLimit {
|
||||||
status = tracer.TraceCodeTLE
|
status = specs.TraceCodeTLE
|
||||||
}
|
}
|
||||||
if userMem > r.ResLimits.MemoryLimit {
|
if userMem > r.ResLimits.MemoryLimit {
|
||||||
status = tracer.TraceCodeMLE
|
status = specs.TraceCodeMLE
|
||||||
}
|
}
|
||||||
result = tracer.TraceResult{
|
result = specs.TraceResult{
|
||||||
UserTime: userTime,
|
UserTime: userTime,
|
||||||
UserMem: userMem,
|
UserMem: userMem,
|
||||||
TraceStatus: status,
|
TraceStatus: status,
|
||||||
}
|
}
|
||||||
if status != tracer.TraceCodeNormal {
|
if status != specs.TraceCodeNormal {
|
||||||
return result, status
|
return result, status
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,13 +125,13 @@ func (r *RunUnshared) Trace(runner *forkexec.Runner) (result tracer.TraceResult,
|
|||||||
sig := wstatus.Signal()
|
sig := wstatus.Signal()
|
||||||
switch sig {
|
switch sig {
|
||||||
case unix.SIGXCPU:
|
case unix.SIGXCPU:
|
||||||
status = tracer.TraceCodeTLE
|
status = specs.TraceCodeTLE
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = tracer.TraceCodeOLE
|
status = specs.TraceCodeOLE
|
||||||
case unix.SIGSYS:
|
case unix.SIGSYS:
|
||||||
status = tracer.TraceCodeBan
|
status = specs.TraceCodeBan
|
||||||
default:
|
default:
|
||||||
status = tracer.TraceCodeRE
|
status = specs.TraceCodeRE
|
||||||
}
|
}
|
||||||
result.TraceStatus = status
|
result.TraceStatus = status
|
||||||
return result, status
|
return result, status
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
package rununshared
|
package rununshared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/criyle/go-judger/mount"
|
"github.com/criyle/go-judger/types/mount"
|
||||||
"github.com/criyle/go-judger/rlimit"
|
"github.com/criyle/go-judger/types/rlimit"
|
||||||
"github.com/criyle/go-judger/tracer"
|
"github.com/criyle/go-judger/types/specs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunUnshared runs program in unshared namespaces
|
// RunUnshared runs program in unshared namespaces
|
||||||
@ -22,7 +22,7 @@ type RunUnshared struct {
|
|||||||
RLimits rlimit.RLimits
|
RLimits rlimit.RLimits
|
||||||
|
|
||||||
// Resource limit enforced by tracer
|
// Resource limit enforced by tracer
|
||||||
ResLimits tracer.ResLimit
|
ResLimits specs.ResLimit
|
||||||
|
|
||||||
// Allowed syscall names
|
// Allowed syscall names
|
||||||
SyscallAllowed []string
|
SyscallAllowed []string
|
||||||
|
|||||||
@ -12,70 +12,12 @@ const (
|
|||||||
TraceKill
|
TraceKill
|
||||||
)
|
)
|
||||||
|
|
||||||
// TraceCode is the error type
|
|
||||||
type TraceCode int
|
|
||||||
|
|
||||||
// Different end condtion
|
|
||||||
const (
|
|
||||||
TraceCodeNormal TraceCode = iota // 0
|
|
||||||
TraceCodeInvalid // 1
|
|
||||||
TraceCodeRE // 2
|
|
||||||
TraceCodeMLE // 3
|
|
||||||
TraceCodeTLE // 4
|
|
||||||
TraceCodeOLE // 5
|
|
||||||
TraceCodeBan // 6
|
|
||||||
TraceCodeFatal // 7
|
|
||||||
)
|
|
||||||
|
|
||||||
func (t TraceCode) Error() string {
|
|
||||||
switch t {
|
|
||||||
case TraceCodeNormal:
|
|
||||||
return ""
|
|
||||||
case TraceCodeRE:
|
|
||||||
return "runtime error"
|
|
||||||
case TraceCodeTLE:
|
|
||||||
return "time limit exceeded"
|
|
||||||
case TraceCodeMLE:
|
|
||||||
return "memory limit exceeded"
|
|
||||||
case TraceCodeOLE:
|
|
||||||
return "output limit exceeded"
|
|
||||||
case TraceCodeBan:
|
|
||||||
return "syscall banned"
|
|
||||||
case TraceCodeFatal:
|
|
||||||
return "handle failed"
|
|
||||||
default:
|
|
||||||
return "invalid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceStat is the time usages in ns
|
|
||||||
type TraceStat struct {
|
|
||||||
SetUpTime int64
|
|
||||||
RunningTime int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceResult is the result returned by strat trace
|
|
||||||
type TraceResult struct {
|
|
||||||
UserTime uint64 // used user CPU time (in ms)
|
|
||||||
UserMem uint64 // used user memory (in kb)
|
|
||||||
ExitCode int // exit code
|
|
||||||
TraceStatus TraceCode // the final status for the process
|
|
||||||
TraceStat // collects time for the process
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runner represents the process runner
|
// Runner represents the process runner
|
||||||
type Runner interface {
|
type Runner interface {
|
||||||
// Starts starts the child process and return pid and error if failed
|
// Starts starts the child process and return pid and error if failed
|
||||||
Start() (int, error)
|
Start() (int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResLimit represents the resource limit for traced process
|
|
||||||
type ResLimit struct {
|
|
||||||
TimeLimit uint64 // user CPU time limit (in ms)
|
|
||||||
RealTimeLimit uint64 // sig_kill will force the process to exit after this limit (in ms)
|
|
||||||
MemoryLimit uint64 // user memory limit (in kB)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler defines customized handler for traced syscall
|
// Handler defines customized handler for traced syscall
|
||||||
type Handler interface {
|
type Handler interface {
|
||||||
Handle(*Context) TraceAction
|
Handle(*Context) TraceAction
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/criyle/go-judger/types/specs"
|
||||||
unix "golang.org/x/sys/unix"
|
unix "golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,7 +18,7 @@ const (
|
|||||||
// Trace traces all child process that created by runner
|
// Trace traces all child process that created by runner
|
||||||
// this function should called only once and in the same thread that
|
// this function should called only once and in the same thread that
|
||||||
// exec tracee
|
// exec tracee
|
||||||
func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult, err error) {
|
func Trace(handler Handler, runner Runner, limits specs.ResLimit) (result specs.TraceResult, 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
|
||||||
@ -39,7 +40,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
handler.Debug("tracer started: ", pgid, err)
|
handler.Debug("tracer started: ", pgid, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.Debug("start tracee failed: ", err)
|
handler.Debug("start tracee failed: ", err)
|
||||||
result.TraceStatus = TraceCodeRE
|
result.TraceStatus = specs.TraceCodeRE
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,11 +55,11 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
defer func() {
|
defer func() {
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
if tle {
|
if tle {
|
||||||
err = TraceCodeTLE
|
err = specs.TraceCodeTLE
|
||||||
}
|
}
|
||||||
if err2 := recover(); err2 != nil {
|
if err2 := recover(); err2 != nil {
|
||||||
handler.Debug(err2)
|
handler.Debug(err2)
|
||||||
err = TraceCodeFatal
|
err = specs.TraceCodeFatal
|
||||||
}
|
}
|
||||||
// kill all tracee upon return
|
// kill all tracee upon return
|
||||||
killAll(pgid)
|
killAll(pgid)
|
||||||
@ -78,11 +79,11 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.Debug("wait4 failed: ", err)
|
handler.Debug("wait4 failed: ", err)
|
||||||
return result, TraceCodeFatal
|
return result, specs.TraceCodeFatal
|
||||||
}
|
}
|
||||||
handler.Debug("------ ", pid, " ------")
|
handler.Debug("------ ", pid, " ------")
|
||||||
|
|
||||||
status := TraceCodeNormal
|
status := specs.TraceCodeNormal
|
||||||
if pid == pgid {
|
if pid == pgid {
|
||||||
if initMem == 0 {
|
if initMem == 0 {
|
||||||
initMem = uint64(rusage.Maxrss)
|
initMem = uint64(rusage.Maxrss)
|
||||||
@ -94,17 +95,17 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
|
|
||||||
// check tle / mle
|
// check tle / mle
|
||||||
if userTime > limits.TimeLimit {
|
if userTime > limits.TimeLimit {
|
||||||
status = TraceCodeTLE
|
status = specs.TraceCodeTLE
|
||||||
}
|
}
|
||||||
if userMem > limits.MemoryLimit {
|
if userMem > limits.MemoryLimit {
|
||||||
status = TraceCodeMLE
|
status = specs.TraceCodeMLE
|
||||||
}
|
}
|
||||||
result = TraceResult{
|
result = specs.TraceResult{
|
||||||
UserTime: userTime,
|
UserTime: userTime,
|
||||||
UserMem: userMem,
|
UserMem: userMem,
|
||||||
TraceStatus: status,
|
TraceStatus: status,
|
||||||
}
|
}
|
||||||
if status != TraceCodeNormal {
|
if status != specs.TraceCodeNormal {
|
||||||
return result, status
|
return result, status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,8 +120,8 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
result.ExitCode = wstatus.ExitStatus()
|
result.ExitCode = wstatus.ExitStatus()
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
result.TraceStatus = TraceCodeFatal
|
result.TraceStatus = specs.TraceCodeFatal
|
||||||
return result, TraceCodeFatal
|
return result, specs.TraceCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
case wstatus.Signaled():
|
case wstatus.Signaled():
|
||||||
@ -130,13 +131,13 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
delete(traced, pid)
|
delete(traced, pid)
|
||||||
switch sig {
|
switch sig {
|
||||||
case unix.SIGXCPU:
|
case unix.SIGXCPU:
|
||||||
status = TraceCodeTLE
|
status = specs.TraceCodeTLE
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = TraceCodeOLE
|
status = specs.TraceCodeOLE
|
||||||
case unix.SIGSYS:
|
case unix.SIGSYS:
|
||||||
status = TraceCodeBan
|
status = specs.TraceCodeBan
|
||||||
default:
|
default:
|
||||||
status = TraceCodeRE
|
status = specs.TraceCodeRE
|
||||||
}
|
}
|
||||||
result.TraceStatus = status
|
result.TraceStatus = status
|
||||||
return result, status
|
return result, status
|
||||||
@ -151,7 +152,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
// 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.TraceStatus = TraceCodeFatal
|
result.TraceStatus = specs.TraceCodeFatal
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,7 +165,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
// give the customized handle for syscall
|
// give the customized handle for syscall
|
||||||
err := handleTrap(handler, pid)
|
err := handleTrap(handler, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.TraceStatus = TraceCodeBan
|
result.TraceStatus = specs.TraceCodeBan
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -193,11 +194,11 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
|||||||
// check if cpu rlimit hit
|
// check if cpu rlimit hit
|
||||||
switch stopSig {
|
switch stopSig {
|
||||||
case unix.SIGXCPU:
|
case unix.SIGXCPU:
|
||||||
status = TraceCodeTLE
|
status = specs.TraceCodeTLE
|
||||||
case unix.SIGXFSZ:
|
case unix.SIGXFSZ:
|
||||||
status = TraceCodeOLE
|
status = specs.TraceCodeOLE
|
||||||
}
|
}
|
||||||
if status != TraceCodeNormal {
|
if status != specs.TraceCodeNormal {
|
||||||
result.TraceStatus = status
|
result.TraceStatus = status
|
||||||
return result, status
|
return result, status
|
||||||
}
|
}
|
||||||
@ -246,7 +247,7 @@ func handleTrap(handler Handler, pid int) error {
|
|||||||
return ctx.skipSyscall()
|
return ctx.skipSyscall()
|
||||||
|
|
||||||
case TraceKill:
|
case TraceKill:
|
||||||
return TraceCodeBan
|
return specs.TraceCodeBan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,6 @@ package rlimit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/criyle/go-judger/forkexec"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RLimits defines the rlimit applied by setrlimit syscall to traced process
|
// RLimits defines the rlimit applied by setrlimit syscall to traced process
|
||||||
@ -16,45 +14,53 @@ type RLimits struct {
|
|||||||
AddressSpace uint64 // in kb
|
AddressSpace uint64 // in kb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RLimit is the resource limits defined by Linux setrlimit
|
||||||
|
type RLimit struct {
|
||||||
|
// Res is the resource type (e.g. syscall.RLIMIT_CPU)
|
||||||
|
Res int
|
||||||
|
// Rlim is the limit applied to that resource
|
||||||
|
Rlim syscall.Rlimit
|
||||||
|
}
|
||||||
|
|
||||||
func getRlimit(cur, max uint64) syscall.Rlimit {
|
func getRlimit(cur, max uint64) syscall.Rlimit {
|
||||||
return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)}
|
return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrepareRLimit creates rlimit structures for tracee
|
// PrepareRLimit creates rlimit structures for tracee
|
||||||
// TimeLimit in s, SizeLimit in byte
|
// TimeLimit in s, SizeLimit in byte
|
||||||
func (r *RLimits) PrepareRLimit() []forkexec.RLimit {
|
func (r *RLimits) PrepareRLimit() []RLimit {
|
||||||
var ret []forkexec.RLimit
|
var ret []RLimit
|
||||||
if r.CPU > 0 {
|
if r.CPU > 0 {
|
||||||
cpuHard := r.CPUHard
|
cpuHard := r.CPUHard
|
||||||
if cpuHard < r.CPU {
|
if cpuHard < r.CPU {
|
||||||
cpuHard = r.CPU
|
cpuHard = r.CPU
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = append(ret, forkexec.RLimit{
|
ret = append(ret, RLimit{
|
||||||
Res: syscall.RLIMIT_CPU,
|
Res: syscall.RLIMIT_CPU,
|
||||||
Rlim: getRlimit(r.CPU, cpuHard),
|
Rlim: getRlimit(r.CPU, cpuHard),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.Data > 0 {
|
if r.Data > 0 {
|
||||||
ret = append(ret, forkexec.RLimit{
|
ret = append(ret, RLimit{
|
||||||
Res: syscall.RLIMIT_DATA,
|
Res: syscall.RLIMIT_DATA,
|
||||||
Rlim: getRlimit(r.Data<<10, r.Data<<10),
|
Rlim: getRlimit(r.Data<<10, r.Data<<10),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.FileSize > 0 {
|
if r.FileSize > 0 {
|
||||||
ret = append(ret, forkexec.RLimit{
|
ret = append(ret, RLimit{
|
||||||
Res: syscall.RLIMIT_FSIZE,
|
Res: syscall.RLIMIT_FSIZE,
|
||||||
Rlim: getRlimit(r.FileSize<<10, r.FileSize<<10),
|
Rlim: getRlimit(r.FileSize<<10, r.FileSize<<10),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.Stack > 0 {
|
if r.Stack > 0 {
|
||||||
ret = append(ret, forkexec.RLimit{
|
ret = append(ret, RLimit{
|
||||||
Res: syscall.RLIMIT_STACK,
|
Res: syscall.RLIMIT_STACK,
|
||||||
Rlim: getRlimit(r.Stack<<10, r.Stack<<10),
|
Rlim: getRlimit(r.Stack<<10, r.Stack<<10),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if r.AddressSpace > 0 {
|
if r.AddressSpace > 0 {
|
||||||
ret = append(ret, forkexec.RLimit{
|
ret = append(ret, RLimit{
|
||||||
Res: syscall.RLIMIT_AS,
|
Res: syscall.RLIMIT_AS,
|
||||||
Rlim: getRlimit(r.AddressSpace<<10, r.AddressSpace<<10),
|
Rlim: getRlimit(r.AddressSpace<<10, r.AddressSpace<<10),
|
||||||
})
|
})
|
||||||
59
types/specs/specs.go
Normal file
59
types/specs/specs.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package specs
|
||||||
|
|
||||||
|
// TraceCode is the error type
|
||||||
|
type TraceCode int
|
||||||
|
|
||||||
|
// Different end condtion
|
||||||
|
const (
|
||||||
|
TraceCodeNormal TraceCode = iota // 0
|
||||||
|
TraceCodeInvalid // 1
|
||||||
|
TraceCodeRE // 2
|
||||||
|
TraceCodeMLE // 3
|
||||||
|
TraceCodeTLE // 4
|
||||||
|
TraceCodeOLE // 5
|
||||||
|
TraceCodeBan // 6
|
||||||
|
TraceCodeFatal // 7
|
||||||
|
)
|
||||||
|
|
||||||
|
func (t TraceCode) Error() string {
|
||||||
|
switch t {
|
||||||
|
case TraceCodeNormal:
|
||||||
|
return ""
|
||||||
|
case TraceCodeRE:
|
||||||
|
return "runtime error"
|
||||||
|
case TraceCodeTLE:
|
||||||
|
return "time limit exceeded"
|
||||||
|
case TraceCodeMLE:
|
||||||
|
return "memory limit exceeded"
|
||||||
|
case TraceCodeOLE:
|
||||||
|
return "output limit exceeded"
|
||||||
|
case TraceCodeBan:
|
||||||
|
return "syscall banned"
|
||||||
|
case TraceCodeFatal:
|
||||||
|
return "handle failed"
|
||||||
|
default:
|
||||||
|
return "invalid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceStat is the time usages in ns
|
||||||
|
type TraceStat struct {
|
||||||
|
SetUpTime int64
|
||||||
|
RunningTime int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceResult is the result returned by strat trace
|
||||||
|
type TraceResult struct {
|
||||||
|
UserTime uint64 // used user CPU time (in ms)
|
||||||
|
UserMem uint64 // used user memory (in kb)
|
||||||
|
ExitCode int // exit code
|
||||||
|
TraceStatus TraceCode // the final status for the process
|
||||||
|
TraceStat // collects time for the process
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResLimit represents the resource limit for traced process
|
||||||
|
type ResLimit struct {
|
||||||
|
TimeLimit uint64 // user CPU time limit (in ms)
|
||||||
|
RealTimeLimit uint64 // sig_kill will force the process to exit after this limit (in ms)
|
||||||
|
MemoryLimit uint64 // user memory limit (in kB)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user