mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
fix race problem of setpgid and refactor some package
This commit is contained in:
parent
7cfcc5c391
commit
a122ceed5a
61
cmd/run_program/handle.go
Normal file
61
cmd/run_program/handle.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/criyle/go-judger/runprogram"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
fs *fileSets
|
||||
sc syscallCounter
|
||||
showDetails bool
|
||||
}
|
||||
|
||||
func (h *handler) print(v ...interface{}) {
|
||||
if h.showDetails {
|
||||
fmt.Fprintln(os.Stderr, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) onDgsFileDetect(name string) runprogram.TraceAction {
|
||||
if h.fs.isSoftBanFile(name) {
|
||||
return runprogram.TraceBan
|
||||
}
|
||||
h.print("Dangerous fileopen: ", name)
|
||||
return runprogram.TraceKill
|
||||
}
|
||||
|
||||
func (h *handler) CheckRead(fn string) runprogram.TraceAction {
|
||||
if !h.fs.isReadableFile(fn) {
|
||||
return h.onDgsFileDetect(fn)
|
||||
}
|
||||
return runprogram.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) CheckWrite(fn string) runprogram.TraceAction {
|
||||
if !h.fs.isWritableFile(fn) {
|
||||
return h.onDgsFileDetect(fn)
|
||||
}
|
||||
return runprogram.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) CheckStat(fn string) runprogram.TraceAction {
|
||||
if !h.fs.isStatableFile(fn) {
|
||||
return h.onDgsFileDetect(fn)
|
||||
}
|
||||
return runprogram.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) CheckSyscall(syscallName string) runprogram.TraceAction {
|
||||
// if it is traced, then try to count syscall
|
||||
if inside, allow := h.sc.check(syscallName); inside {
|
||||
if allow {
|
||||
return runprogram.TraceAllow
|
||||
}
|
||||
return runprogram.TraceKill
|
||||
}
|
||||
// if it is traced but not counted, it should be soft banned
|
||||
return runprogram.TraceBan
|
||||
}
|
||||
@ -4,11 +4,9 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
secutil "github.com/criyle/go-judger/secutil"
|
||||
tracee "github.com/criyle/go-judger/tracee"
|
||||
tracer "github.com/criyle/go-judger/tracer"
|
||||
"github.com/criyle/go-judger/runprogram"
|
||||
"github.com/criyle/go-judger/tracer"
|
||||
)
|
||||
|
||||
// TODO: syscall handle, file access checker
|
||||
@ -64,24 +62,6 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Ptrace require running at the same OS thread
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
// build seccomp filter
|
||||
filter, err := buildFilter(showDetails, allow, trace)
|
||||
if err != nil {
|
||||
println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer filter.Release()
|
||||
|
||||
bpf, err := secutil.FilterToBPF(filter)
|
||||
if err != nil {
|
||||
println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// open input / output / err files
|
||||
files, err := prepareFiles(inputFileName, outputFileName, errorFileName)
|
||||
if err != nil {
|
||||
@ -100,20 +80,28 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Rlimit
|
||||
rlimit := prepareRLimit(timeLimit, realTimeLimit, outputLimit<<20, stackLimit<<20)
|
||||
|
||||
// get tracee
|
||||
ch := &tracee.Runner{
|
||||
runner := &runprogram.RunProgram{
|
||||
Args: args,
|
||||
Env: []string{"PATH=/"},
|
||||
RLimits: rlimit,
|
||||
Files: fds,
|
||||
WorkDir: workPath,
|
||||
BPF: bpf,
|
||||
RLimits: runprogram.RLimits{
|
||||
CPU: timeLimit,
|
||||
CPUHard: realTimeLimit,
|
||||
FileSize: outputLimit,
|
||||
Stack: stackLimit,
|
||||
},
|
||||
TraceLimit: runprogram.TraceLimit{
|
||||
TimeLimit: timeLimit * 1e3,
|
||||
RealTimeLimit: realTimeLimit * 1e3,
|
||||
MemoryLimit: memoryLimit << 10,
|
||||
},
|
||||
Files: fds,
|
||||
SyscallAllowed: allow,
|
||||
SyscallTraced: trace,
|
||||
ShowDetails: showDetails,
|
||||
Unsafe: unsafe,
|
||||
Handler: &handler{fs, sc, showDetails},
|
||||
}
|
||||
// get syscall handler
|
||||
h := &handler{fs, sc, showDetails}
|
||||
|
||||
var f *os.File
|
||||
if result == "stdout" {
|
||||
@ -129,17 +117,8 @@ func main() {
|
||||
defer f.Close()
|
||||
}
|
||||
|
||||
tracer.ShowDetails = showDetails
|
||||
tracer.Unsafe = unsafe
|
||||
limits := tracer.ResLimit{
|
||||
TimeLimit: timeLimit * 1e3,
|
||||
RealTimeLimit: realTimeLimit * 1e3,
|
||||
MemoryLimit: memoryLimit << 10,
|
||||
}
|
||||
|
||||
// Run tracer
|
||||
rt, err := tracer.Trace(h, ch, limits)
|
||||
|
||||
rt, err := runner.Start()
|
||||
println("used process_vm_readv: ", tracer.UseVMReadv)
|
||||
|
||||
if err != nil {
|
||||
40
cmd/run_program/run_util.go
Normal file
40
cmd/run_program/run_util.go
Normal file
@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
// prepareFile opens file for new process
|
||||
func prepareFiles(inputFile, outputFile, errorFile string) ([]*os.File, error) {
|
||||
var err error
|
||||
files := make([]*os.File, 3)
|
||||
if inputFile != "" {
|
||||
files[0], err = os.OpenFile(inputFile, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if outputFile != "" {
|
||||
files[1], err = os.OpenFile(outputFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if errorFile != "" {
|
||||
files[2], err = os.OpenFile(errorFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
openerror:
|
||||
closeFiles(files)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// closeFiles close all file in the list
|
||||
func closeFiles(files []*os.File) {
|
||||
for _, f := range files {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,148 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
tracer "github.com/criyle/go-judger/tracer"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
fs *fileSets
|
||||
sc syscallCounter
|
||||
showDetails bool
|
||||
}
|
||||
|
||||
func softBanSyscall(ctx *tracer.Context) tracer.TraceAction {
|
||||
ctx.SetReturnValue(-int(syscall.EACCES))
|
||||
return tracer.TraceBan
|
||||
}
|
||||
|
||||
func getFileMode(flags uint) string {
|
||||
switch flags & syscall.O_ACCMODE {
|
||||
case syscall.O_RDONLY:
|
||||
return "r "
|
||||
case syscall.O_WRONLY:
|
||||
return "w "
|
||||
case syscall.O_RDWR:
|
||||
return "wr"
|
||||
default:
|
||||
return "??"
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) print(v ...interface{}) {
|
||||
if h.showDetails {
|
||||
fmt.Fprintln(os.Stderr, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) onDgsFileDetect(ctx *tracer.Context, name string) tracer.TraceAction {
|
||||
if h.fs.isSoftBanFile(name) {
|
||||
return softBanSyscall(ctx)
|
||||
}
|
||||
h.print("Dangerous fileopen: (killed)", name)
|
||||
return tracer.TraceKill
|
||||
}
|
||||
|
||||
func (h *handler) checkOpen(ctx *tracer.Context, addr uint, flags uint) tracer.TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) &&
|
||||
(flags&syscall.O_CREAT == 0) &&
|
||||
(flags&syscall.O_EXCL == 0) &&
|
||||
(flags&syscall.O_TRUNC == 0)
|
||||
|
||||
h.print("open: ", fn, getFileMode(flags))
|
||||
if isReadOnly {
|
||||
if realPath(fn) != "" && !h.fs.isReadableFile(fn) {
|
||||
return h.onDgsFileDetect(ctx, fn)
|
||||
}
|
||||
} else {
|
||||
if realPath(fn) != "" && !h.fs.isWritableFile(fn) {
|
||||
return h.onDgsFileDetect(ctx, fn)
|
||||
}
|
||||
}
|
||||
return tracer.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) checkRead(ctx *tracer.Context, addr uint) tracer.TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.print("check read: ", fn)
|
||||
if !h.fs.isReadableFile(fn) {
|
||||
return h.onDgsFileDetect(ctx, fn)
|
||||
}
|
||||
return tracer.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) checkWrite(ctx *tracer.Context, addr uint) tracer.TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.print("check write: ", fn)
|
||||
if !h.fs.isWritableFile(fn) {
|
||||
return h.onDgsFileDetect(ctx, fn)
|
||||
}
|
||||
return tracer.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) checkStat(ctx *tracer.Context, addr uint) tracer.TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.print("check stat: ", fn)
|
||||
if !h.fs.isStatableFile(fn) {
|
||||
return h.onDgsFileDetect(ctx, fn)
|
||||
}
|
||||
return tracer.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) Handle(ctx *tracer.Context) tracer.TraceAction {
|
||||
syscallNo := ctx.SyscallNo()
|
||||
syscallName, err := libseccomp.ScmpSyscall(syscallNo).GetName()
|
||||
h.print("syscall: ", syscallNo, syscallName, err)
|
||||
|
||||
switch syscallName {
|
||||
case "open":
|
||||
return h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1())
|
||||
case "openat":
|
||||
return h.checkOpen(ctx, ctx.Arg1(), ctx.Arg2())
|
||||
|
||||
case "readlink":
|
||||
return h.checkRead(ctx, ctx.Arg0())
|
||||
case "readlinkat":
|
||||
return h.checkRead(ctx, ctx.Arg1())
|
||||
|
||||
case "unlink":
|
||||
return h.checkWrite(ctx, ctx.Arg0())
|
||||
case "unlinkat":
|
||||
return h.checkWrite(ctx, ctx.Arg1())
|
||||
|
||||
case "access":
|
||||
return h.checkStat(ctx, ctx.Arg0())
|
||||
|
||||
case "stat", "stat64":
|
||||
return h.checkStat(ctx, ctx.Arg0())
|
||||
case "lstat", "lstat64":
|
||||
return h.checkStat(ctx, ctx.Arg0())
|
||||
|
||||
case "execve":
|
||||
return h.checkRead(ctx, ctx.Arg0())
|
||||
|
||||
case "chmod":
|
||||
return h.checkWrite(ctx, ctx.Arg0())
|
||||
case "rename":
|
||||
return h.checkWrite(ctx, ctx.Arg0())
|
||||
default:
|
||||
// if it is traced, then try to count syscall
|
||||
if inside, allow := h.sc.check(syscallName); !allow {
|
||||
return tracer.TraceKill
|
||||
} else if !inside {
|
||||
// if it is traced but not counted, it should be soft banned
|
||||
return softBanSyscall(ctx)
|
||||
}
|
||||
}
|
||||
return tracer.TraceAllow
|
||||
}
|
||||
|
||||
func (h *handler) GetSyscallName(ctx *tracer.Context) (string, error) {
|
||||
syscallNo := ctx.SyscallNo()
|
||||
return libseccomp.ScmpSyscall(syscallNo).GetName()
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
secutil "github.com/criyle/go-judger/secutil"
|
||||
tracee "github.com/criyle/go-judger/tracee"
|
||||
tracer "github.com/criyle/go-judger/tracer"
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
// build filter builds the libseccomp filter according to the allow, trace and show details
|
||||
func buildFilter(showDetails bool, allow, trace []string) (*libseccomp.ScmpFilter, error) {
|
||||
// make filter
|
||||
var defaultAction libseccomp.ScmpAction
|
||||
// if debug, allow all syscalls and output what was blocked
|
||||
if showDetails {
|
||||
defaultAction = libseccomp.ActTrace.SetReturnCode(tracer.MsgDisallow)
|
||||
} else {
|
||||
defaultAction = libseccomp.ActKill
|
||||
}
|
||||
return secutil.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(tracer.MsgHandle), allow, trace)
|
||||
}
|
||||
|
||||
// prepareFile opens file for new process
|
||||
func prepareFiles(inputFile, outputFile, errorFile string) ([]*os.File, error) {
|
||||
var err error
|
||||
files := make([]*os.File, 3)
|
||||
if inputFile != "" {
|
||||
files[0], err = os.OpenFile(inputFile, os.O_RDONLY, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if outputFile != "" {
|
||||
files[1], err = os.OpenFile(outputFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
if errorFile != "" {
|
||||
files[2], err = os.OpenFile(errorFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0755)
|
||||
if err != nil {
|
||||
goto openerror
|
||||
}
|
||||
}
|
||||
return files, nil
|
||||
openerror:
|
||||
closeFiles(files)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// closeFiles close all file in the list
|
||||
func closeFiles(files []*os.File) {
|
||||
for _, f := range files {
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getRlimit(cur, max uint) syscall.Rlimit {
|
||||
return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)}
|
||||
}
|
||||
|
||||
// prepareRLimit creates rlimit structures for tracee
|
||||
// TimeLimit in s, SizeLimit in byte
|
||||
func prepareRLimit(TimeLimit, RealTimeLimit, OutputLimit, StackLimit uint) []tracee.RLimit {
|
||||
return []tracee.RLimit{
|
||||
// CPU limit
|
||||
{
|
||||
Res: syscall.RLIMIT_CPU,
|
||||
Rlim: getRlimit(TimeLimit, RealTimeLimit),
|
||||
},
|
||||
// File limit
|
||||
{
|
||||
Res: syscall.RLIMIT_FSIZE,
|
||||
Rlim: getRlimit(OutputLimit, OutputLimit),
|
||||
},
|
||||
// Stack limit
|
||||
{
|
||||
Res: syscall.RLIMIT_STACK,
|
||||
Rlim: getRlimit(StackLimit, StackLimit),
|
||||
},
|
||||
}
|
||||
}
|
||||
137
runprogram/handle.go
Normal file
137
runprogram/handle.go
Normal file
@ -0,0 +1,137 @@
|
||||
package runprogram
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
|
||||
"github.com/criyle/go-judger/tracer"
|
||||
)
|
||||
|
||||
type tracerHandler struct {
|
||||
ShowDetails, Unsafe bool
|
||||
Handler Handler
|
||||
}
|
||||
|
||||
func (h *tracerHandler) Debug(v ...interface{}) {
|
||||
if h.ShowDetails {
|
||||
fmt.Fprintln(os.Stderr, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *tracerHandler) checkOpen(ctx *tracer.Context, addr uint, flags uint) TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
isReadOnly := (flags&syscall.O_ACCMODE == syscall.O_RDONLY) &&
|
||||
(flags&syscall.O_CREAT == 0) &&
|
||||
(flags&syscall.O_EXCL == 0) &&
|
||||
(flags&syscall.O_TRUNC == 0)
|
||||
|
||||
h.Debug("open: ", fn, getFileMode(flags))
|
||||
if isReadOnly {
|
||||
return h.Handler.CheckRead(fn)
|
||||
}
|
||||
return h.Handler.CheckWrite(fn)
|
||||
}
|
||||
|
||||
func (h *tracerHandler) checkRead(ctx *tracer.Context, addr uint) TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.Debug("check read: ", fn)
|
||||
return h.Handler.CheckRead(fn)
|
||||
}
|
||||
|
||||
func (h *tracerHandler) checkWrite(ctx *tracer.Context, addr uint) TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.Debug("check write: ", fn)
|
||||
return h.Handler.CheckWrite(fn)
|
||||
}
|
||||
|
||||
func (h *tracerHandler) checkStat(ctx *tracer.Context, addr uint) TraceAction {
|
||||
fn := ctx.GetString(uintptr(addr))
|
||||
h.Debug("check stat: ", fn)
|
||||
return h.Handler.CheckStat(fn)
|
||||
}
|
||||
|
||||
func (h *tracerHandler) Handle(ctx *tracer.Context) tracer.TraceAction {
|
||||
var (
|
||||
action TraceAction
|
||||
syscallNo = ctx.SyscallNo()
|
||||
syscallName, err = libseccomp.ScmpSyscall(syscallNo).GetName()
|
||||
)
|
||||
h.Debug("syscall: ", syscallNo, syscallName, err)
|
||||
|
||||
switch syscallName {
|
||||
case "open":
|
||||
action = h.checkOpen(ctx, ctx.Arg0(), ctx.Arg1())
|
||||
case "openat":
|
||||
action = h.checkOpen(ctx, ctx.Arg1(), ctx.Arg2())
|
||||
|
||||
case "readlink":
|
||||
action = h.checkRead(ctx, ctx.Arg0())
|
||||
case "readlinkat":
|
||||
action = h.checkRead(ctx, ctx.Arg1())
|
||||
|
||||
case "unlink":
|
||||
action = h.checkWrite(ctx, ctx.Arg0())
|
||||
case "unlinkat":
|
||||
action = h.checkWrite(ctx, ctx.Arg1())
|
||||
|
||||
case "access":
|
||||
action = h.checkStat(ctx, ctx.Arg0())
|
||||
|
||||
case "stat", "stat64":
|
||||
action = h.checkStat(ctx, ctx.Arg0())
|
||||
case "lstat", "lstat64":
|
||||
action = h.checkStat(ctx, ctx.Arg0())
|
||||
|
||||
case "execve":
|
||||
action = h.checkRead(ctx, ctx.Arg0())
|
||||
|
||||
case "chmod":
|
||||
action = h.checkWrite(ctx, ctx.Arg0())
|
||||
case "rename":
|
||||
action = h.checkWrite(ctx, ctx.Arg0())
|
||||
default:
|
||||
action = h.Handler.CheckSyscall(syscallName)
|
||||
}
|
||||
|
||||
switch action {
|
||||
case TraceAllow:
|
||||
return tracer.TraceAllow
|
||||
case TraceBan:
|
||||
return softBanSyscall(ctx)
|
||||
default:
|
||||
return tracer.TraceKill
|
||||
}
|
||||
}
|
||||
|
||||
func (h *tracerHandler) GetSyscallName(ctx *tracer.Context) (string, error) {
|
||||
syscallNo := ctx.SyscallNo()
|
||||
return libseccomp.ScmpSyscall(syscallNo).GetName()
|
||||
}
|
||||
|
||||
func (h *tracerHandler) HandlerDisallow(name string) error {
|
||||
if !h.Unsafe {
|
||||
return tracer.TraceCodeBan
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func softBanSyscall(ctx *tracer.Context) tracer.TraceAction {
|
||||
ctx.SetReturnValue(-int(BanRet))
|
||||
return tracer.TraceBan
|
||||
}
|
||||
|
||||
func getFileMode(flags uint) string {
|
||||
switch flags & syscall.O_ACCMODE {
|
||||
case syscall.O_RDONLY:
|
||||
return "r "
|
||||
case syscall.O_WRONLY:
|
||||
return "w "
|
||||
case syscall.O_RDWR:
|
||||
return "wr"
|
||||
default:
|
||||
return "??"
|
||||
}
|
||||
}
|
||||
63
runprogram/rlimit.go
Normal file
63
runprogram/rlimit.go
Normal file
@ -0,0 +1,63 @@
|
||||
package runprogram
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/criyle/go-judger/tracee"
|
||||
)
|
||||
|
||||
// RLimits defines the rlimit applied by setrlimit syscall to traced process
|
||||
type RLimits struct {
|
||||
CPU uint // in s
|
||||
CPUHard uint // in s
|
||||
Data uint // in kb
|
||||
FileSize uint // in kb
|
||||
Stack uint // in kb
|
||||
AddressSpace uint // in kb
|
||||
}
|
||||
|
||||
func getRlimit(cur, max uint64) syscall.Rlimit {
|
||||
return syscall.Rlimit{Cur: uint64(cur), Max: uint64(max)}
|
||||
}
|
||||
|
||||
// prepareRLimit creates rlimit structures for tracee
|
||||
// TimeLimit in s, SizeLimit in byte
|
||||
func (r *RLimits) prepareRLimit() []tracee.RLimit {
|
||||
var ret []tracee.RLimit
|
||||
if r.CPU > 0 {
|
||||
cpuHard := r.CPUHard
|
||||
if cpuHard < r.CPU {
|
||||
cpuHard = r.CPU
|
||||
}
|
||||
|
||||
ret = append(ret, tracee.RLimit{
|
||||
Res: syscall.RLIMIT_CPU,
|
||||
Rlim: getRlimit(uint64(r.CPU), uint64(cpuHard)),
|
||||
})
|
||||
}
|
||||
if r.Data > 0 {
|
||||
ret = append(ret, tracee.RLimit{
|
||||
Res: syscall.RLIMIT_DATA,
|
||||
Rlim: getRlimit(uint64(r.Data)<<10, uint64(r.Data)<<10),
|
||||
})
|
||||
}
|
||||
if r.FileSize > 0 {
|
||||
ret = append(ret, tracee.RLimit{
|
||||
Res: syscall.RLIMIT_FSIZE,
|
||||
Rlim: getRlimit(uint64(r.FileSize)<<10, uint64(r.FileSize)<<10),
|
||||
})
|
||||
}
|
||||
if r.Stack > 0 {
|
||||
ret = append(ret, tracee.RLimit{
|
||||
Res: syscall.RLIMIT_STACK,
|
||||
Rlim: getRlimit(uint64(r.Stack)<<10, uint64(r.Stack)<<10),
|
||||
})
|
||||
}
|
||||
if r.AddressSpace > 0 {
|
||||
ret = append(ret, tracee.RLimit{
|
||||
Res: syscall.RLIMIT_AS,
|
||||
Rlim: getRlimit(uint64(r.AddressSpace)<<10, uint64(r.AddressSpace)<<10),
|
||||
})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
63
runprogram/runprogram.go
Normal file
63
runprogram/runprogram.go
Normal file
@ -0,0 +1,63 @@
|
||||
package runprogram
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/criyle/go-judger/tracer"
|
||||
)
|
||||
|
||||
// RunProgram defines the spec to run a program safely
|
||||
type RunProgram struct {
|
||||
// argv and env for the child process
|
||||
// work path set by setcwd (current working directory for child)
|
||||
Args []string
|
||||
Env []string
|
||||
WorkDir string
|
||||
|
||||
// file disriptors for new process, from 0 to len - 1
|
||||
Files []uintptr
|
||||
|
||||
// Resource limit set by set rlimit
|
||||
RLimits RLimits
|
||||
|
||||
// Res limit enforced by tracer
|
||||
TraceLimit TraceLimit
|
||||
|
||||
// Allowed / Traced syscall names
|
||||
// Notice: file access syscalls should be traced
|
||||
// If traced syscall is file access, it will checked by file access handler
|
||||
// otherwise it will checked by syscall access handler
|
||||
SyscallAllowed []string
|
||||
SyscallTraced []string
|
||||
|
||||
// Traced syscall handler
|
||||
Handler Handler
|
||||
|
||||
// ShowDetails / Unsafe debug flag
|
||||
ShowDetails, Unsafe bool
|
||||
}
|
||||
|
||||
// TraceLimit defines the limits enforced by tracer
|
||||
type TraceLimit tracer.ResLimit
|
||||
|
||||
// TraceAction defines action against a syscall check
|
||||
type TraceAction int
|
||||
|
||||
// BanRet defines the return value for a syscall ban acction
|
||||
var BanRet syscall.Errno = syscall.EACCES
|
||||
|
||||
// TraceAllow allow the access, trace ban ignores the syscall and set the
|
||||
// return value to BanRet, TraceKill stops the trace action
|
||||
const (
|
||||
TraceAllow = iota + 1
|
||||
TraceBan
|
||||
TraceKill
|
||||
)
|
||||
|
||||
// Handler defines the action when a file access encountered
|
||||
type Handler interface {
|
||||
CheckRead(string) TraceAction
|
||||
CheckWrite(string) TraceAction
|
||||
CheckStat(string) TraceAction
|
||||
CheckSyscall(string) TraceAction
|
||||
}
|
||||
55
runprogram/runprogram_run.go
Normal file
55
runprogram/runprogram_run.go
Normal file
@ -0,0 +1,55 @@
|
||||
package runprogram
|
||||
|
||||
import (
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
|
||||
"github.com/criyle/go-judger/secutil"
|
||||
"github.com/criyle/go-judger/tracee"
|
||||
"github.com/criyle/go-judger/tracer"
|
||||
)
|
||||
|
||||
// Start starts the tracing process
|
||||
func (r *RunProgram) Start() (rt tracer.TraceResult, err error) {
|
||||
// build seccomp filter
|
||||
filter, err := buildFilter(r.ShowDetails, r.SyscallAllowed, r.SyscallTraced)
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
defer filter.Release()
|
||||
|
||||
bpf, err := secutil.FilterToBPF(filter)
|
||||
if err != nil {
|
||||
println(err)
|
||||
return
|
||||
}
|
||||
|
||||
ch := &tracee.Runner{
|
||||
Args: r.Args,
|
||||
Env: r.Env,
|
||||
RLimits: r.RLimits.prepareRLimit(),
|
||||
Files: r.Files,
|
||||
WorkDir: r.WorkDir,
|
||||
BPF: bpf,
|
||||
}
|
||||
|
||||
th := &tracerHandler{
|
||||
ShowDetails: r.ShowDetails,
|
||||
Unsafe: r.Unsafe,
|
||||
Handler: r.Handler,
|
||||
}
|
||||
return tracer.Trace(th, ch, tracer.ResLimit(r.TraceLimit))
|
||||
}
|
||||
|
||||
// build filter builds the libseccomp filter according to the allow, trace and show details
|
||||
func buildFilter(showDetails bool, allow, trace []string) (*libseccomp.ScmpFilter, error) {
|
||||
// make filter
|
||||
var defaultAction libseccomp.ScmpAction
|
||||
// if debug, allow all syscalls and output what was blocked
|
||||
if showDetails {
|
||||
defaultAction = libseccomp.ActTrace.SetReturnCode(tracer.MsgDisallow)
|
||||
} else {
|
||||
defaultAction = libseccomp.ActKill
|
||||
}
|
||||
return secutil.BuildFilter(defaultAction, libseccomp.ActTrace.SetReturnCode(tracer.MsgHandle), allow, trace)
|
||||
}
|
||||
@ -25,7 +25,6 @@ const (
|
||||
TraceCodeOLE // 5
|
||||
TraceCodeBan // 6
|
||||
TraceCodeFatal // 7
|
||||
|
||||
)
|
||||
|
||||
func (t TraceCode) Error() string {
|
||||
@ -74,4 +73,7 @@ type ResLimit struct {
|
||||
type Handler interface {
|
||||
Handle(*Context) TraceAction
|
||||
GetSyscallName(*Context) (string, error)
|
||||
|
||||
Debug(v ...interface{})
|
||||
HandlerDisallow(string) error
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package tracer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
@ -16,13 +14,6 @@ const (
|
||||
MsgHandle
|
||||
)
|
||||
|
||||
var (
|
||||
// ShowDetails is switch to trun on / off whether to show log message
|
||||
ShowDetails bool
|
||||
// Unsafe determines whether to terminate tracing when bad syscall caught
|
||||
Unsafe bool
|
||||
)
|
||||
|
||||
// Trace traces all child process that created by runner
|
||||
// this function should called only once and in the same thread that
|
||||
// exec tracee
|
||||
@ -41,7 +32,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
|
||||
// Start the runner
|
||||
pgid, err := runner.Start()
|
||||
println("tracer started: ", pgid, err)
|
||||
handler.Debug("tracer started: ", pgid, err)
|
||||
if err != nil {
|
||||
result.TraceStatus = TraceCodeRE
|
||||
return result, err
|
||||
@ -61,7 +52,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
err = TraceCodeTLE
|
||||
}
|
||||
if err2 := recover(); err2 != nil {
|
||||
println(err2)
|
||||
handler.Debug(err2)
|
||||
err = TraceCodeFatal
|
||||
}
|
||||
// kill all tracee upon return
|
||||
@ -71,13 +62,19 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
|
||||
// trace unixs
|
||||
for {
|
||||
// Wait for all child
|
||||
pid, err := unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage)
|
||||
var pid int
|
||||
if execved {
|
||||
// Wait for all child in the process group
|
||||
pid, err = unix.Wait4(-pgid, &wstatus, unix.WALL, &rusage)
|
||||
} else {
|
||||
// Ensure the process have called setpgid
|
||||
pid, err = unix.Wait4(pgid, &wstatus, unix.WALL, &rusage)
|
||||
}
|
||||
if err != nil {
|
||||
println("wait4 failed: ", err)
|
||||
handler.Debug("wait4 failed: ", err)
|
||||
return result, TraceCodeFatal
|
||||
}
|
||||
println("------ ", pid, " ------")
|
||||
handler.Debug("------ ", pid, " ------")
|
||||
|
||||
// update resource usage and check against limits
|
||||
userTime := uint(rusage.Utime.Sec*1e3 + rusage.Utime.Usec/1e3) // ms
|
||||
@ -104,7 +101,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
switch {
|
||||
case wstatus.Exited():
|
||||
delete(traced, pid)
|
||||
println("process exited: ", pid, wstatus.ExitStatus())
|
||||
handler.Debug("process exited: ", pid, wstatus.ExitStatus())
|
||||
if execved {
|
||||
result.ExitCode = wstatus.ExitStatus()
|
||||
return result, nil
|
||||
@ -114,7 +111,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
|
||||
case wstatus.Signaled():
|
||||
sig := wstatus.Signal()
|
||||
println("ptrace signaled: ", sig)
|
||||
handler.Debug("ptrace signaled: ", sig)
|
||||
if pid == pgid {
|
||||
switch sig {
|
||||
case unix.SIGXCPU:
|
||||
@ -134,7 +131,7 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
case wstatus.Stopped():
|
||||
// Set option if the process is newly forked
|
||||
if !traced[pid] {
|
||||
println("set ptrace option")
|
||||
handler.Debug("set ptrace option")
|
||||
traced[pid] = true
|
||||
// Ptrace set option valid if the tracee is stopped
|
||||
err = setPtraceOption(pid)
|
||||
@ -156,31 +153,31 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
return result, err
|
||||
}
|
||||
} else {
|
||||
println("ptrace seccomp before execve (should be the execve syscall)")
|
||||
handler.Debug("ptrace seccomp before execve (should be the execve syscall)")
|
||||
}
|
||||
|
||||
case unix.PTRACE_EVENT_CLONE:
|
||||
println("ptrace stop clone")
|
||||
handler.Debug("ptrace stop clone")
|
||||
case unix.PTRACE_EVENT_VFORK:
|
||||
println("ptrace stop vfork")
|
||||
handler.Debug("ptrace stop vfork")
|
||||
case unix.PTRACE_EVENT_FORK:
|
||||
println("ptrace stop fork")
|
||||
handler.Debug("ptrace stop fork")
|
||||
case unix.PTRACE_EVENT_EXEC:
|
||||
// forked tracee have successfully called execve
|
||||
execved = true
|
||||
println("ptrace stop exec")
|
||||
handler.Debug("ptrace stop exec")
|
||||
|
||||
default:
|
||||
println("ptrace unexpected trap cause: ", trapCause)
|
||||
handler.Debug("ptrace unexpected trap cause: ", trapCause)
|
||||
}
|
||||
} else {
|
||||
// Likely encountered SIGSEGV (segment violation)
|
||||
if stopSig != unix.SIGSTOP {
|
||||
println("ptrace unexpected stop signal: ", stopSig)
|
||||
handler.Debug("ptrace unexpected stop signal: ", stopSig)
|
||||
result.TraceStatus = TraceCodeRE
|
||||
return result, TraceCodeRE
|
||||
}
|
||||
println("ptrace stopped")
|
||||
handler.Debug("ptrace stopped")
|
||||
}
|
||||
unix.PtraceCont(pid, 0)
|
||||
}
|
||||
@ -189,26 +186,22 @@ func Trace(handler Handler, runner Runner, limits ResLimit) (result TraceResult,
|
||||
|
||||
// handleTrap handles the seccomp trap including the custom handle
|
||||
func handleTrap(handler Handler, pid int) error {
|
||||
println("seccomp traced")
|
||||
handler.Debug("seccomp traced")
|
||||
msg, err := unix.PtraceGetEventMsg(pid)
|
||||
if err != nil {
|
||||
println(err)
|
||||
handler.Debug(err)
|
||||
return err
|
||||
}
|
||||
switch int16(msg) {
|
||||
case MsgDisallow:
|
||||
ctx, err := getTrapContext(pid)
|
||||
if err != nil {
|
||||
println(err)
|
||||
handler.Debug(err)
|
||||
return err
|
||||
}
|
||||
if ShowDetails {
|
||||
syscallName, err := handler.GetSyscallName(ctx)
|
||||
println("disallowed syscall: ", ctx.SyscallNo(), syscallName, err)
|
||||
}
|
||||
if !Unsafe {
|
||||
return TraceCodeBan
|
||||
}
|
||||
syscallName, err := handler.GetSyscallName(ctx)
|
||||
handler.Debug("disallowed syscall: ", ctx.SyscallNo(), syscallName, err)
|
||||
return handler.HandlerDisallow(syscallName)
|
||||
|
||||
case MsgHandle:
|
||||
if handler != nil {
|
||||
@ -230,7 +223,7 @@ func handleTrap(handler Handler, pid int) error {
|
||||
|
||||
default:
|
||||
// undefined seccomp message, possible set up filter wrong
|
||||
println("unknown seccomp trap message: ", msg)
|
||||
handler.Debug("unknown seccomp trap message: ", msg)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -242,13 +235,6 @@ func setPtraceOption(pid int) error {
|
||||
unix.PTRACE_O_TRACEFORK|unix.PTRACE_O_TRACECLONE|unix.PTRACE_O_TRACEEXEC|unix.PTRACE_O_TRACEVFORK)
|
||||
}
|
||||
|
||||
// println only print when debug flag is on
|
||||
func println(v ...interface{}) {
|
||||
if ShowDetails {
|
||||
fmt.Fprintln(os.Stderr, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// kill all tracee according to pids
|
||||
func killAll(pgid int) {
|
||||
unix.Kill(-pgid, unix.SIGKILL)
|
||||
@ -259,10 +245,8 @@ func collectZombie(pgid int) {
|
||||
// collect zombies
|
||||
for {
|
||||
var wstatus unix.WaitStatus
|
||||
if p, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil {
|
||||
if _, err := unix.Wait4(-pgid, &wstatus, unix.WALL|unix.WNOWAIT, nil); err != nil {
|
||||
break
|
||||
} else {
|
||||
println("collect: ", p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user