mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-09-26 23:19:11 +08:00
Compare commits
19 Commits
05ec977ca5
...
de10456275
Author | SHA1 | Date | |
---|---|---|---|
![]() |
de10456275 | ||
![]() |
2ba894ffd1 | ||
![]() |
10235abbf2 | ||
![]() |
396ac9723e | ||
![]() |
f9deb2dc1a | ||
![]() |
f0dd44f466 | ||
![]() |
659099a2c0 | ||
![]() |
f8361a08a0 | ||
![]() |
d32acd7591 | ||
![]() |
f15b953065 | ||
![]() |
3d8333e952 | ||
![]() |
f6f057edf9 | ||
![]() |
51b53cf60b | ||
![]() |
cf936ae31f | ||
![]() |
0b6b557947 | ||
![]() |
4154f44d83 | ||
![]() |
369229e0d5 | ||
![]() |
d58b2485a2 | ||
![]() |
6407c89d90 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,7 +2,6 @@
|
||||
.DS_Store
|
||||
|
||||
# Test Env
|
||||
test*/
|
||||
env*.sh
|
||||
|
||||
# Test Files
|
||||
@ -10,3 +9,5 @@ env*.sh
|
||||
/test
|
||||
|
||||
.vscode
|
||||
runprog
|
||||
*.test
|
||||
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM golang
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app/cmd/runprog
|
||||
RUN apt update && apt install -y gcc libstdc++6 libseccomp-dev
|
||||
|
||||
RUN go build
|
16
README.md
16
README.md
@ -13,8 +13,9 @@ Notice: Only works on Linux since ptrace, unshare, cgroup are available only on
|
||||
## Build & Install
|
||||
|
||||
- install latest go compiler from [golang/download](https://golang.org/dl/)
|
||||
- install libseccomp library: (for Ubuntu) `apt install libseccomp-dev`
|
||||
- build & install: `go install github.com/criyle/go-sandbox/...`
|
||||
- download repository: `git clone githuc.com/criyle/go-sandbox`
|
||||
- build: `go build ./cmd/runprog`
|
||||
- or install directly: `go install github.com/criyle/go-sandbox/cmd/runprog@latest`
|
||||
|
||||
## Technologies
|
||||
|
||||
@ -45,6 +46,12 @@ Default file access syscall check:
|
||||
2. Use Linux Control Groups to limit & acct CPU & memory (eliminated wait4.rusage)
|
||||
3. Container tech with execveat memfd, sethostname, setdomainname
|
||||
|
||||
### prefork containers
|
||||
|
||||
Utilize the linux namespace + cgroup but create container in advance to reduce the duplicated effort of creating mount points. See Pre-forked container protocol and environment for design details.
|
||||
|
||||
On kernel >= 5.7 with cgroup v2, the new `clone3(CLONE_INTO_CGROUP)` with `vfork` is available to reduce the resource consumption of create new address spaces as well.
|
||||
|
||||
## Design
|
||||
|
||||
### Result Status
|
||||
@ -183,8 +190,11 @@ type Environment interface {
|
||||
|
||||
## Kernel Versions
|
||||
|
||||
- 6.1: `pids.peak` in cgroup v2
|
||||
- 5.19: `memory.peak` in cgroup v2
|
||||
- 4.15: cgroup v2
|
||||
- 5.7: `clone3` with `CLONE_INTO_CGROUP`
|
||||
- 5.3: `clone3`
|
||||
- 4.15: cgroup v2 (also need support in the Linux distribution)
|
||||
- 4.14: SECCOMP_RET_KILL_PROCESS
|
||||
- 4.6: CLONE_NEWCGROUP
|
||||
- 3.19: execveat()
|
||||
|
@ -16,6 +16,10 @@ var (
|
||||
"/dev/urandom",
|
||||
"/proc/meminfo",
|
||||
"/etc/localtime",
|
||||
"/usr/lib/libstdc++.so.6",
|
||||
"/usr/lib/libc.so.6",
|
||||
"/usr/lib/libm.so.6",
|
||||
"/usr/lib/libgcc_s.so.1",
|
||||
}
|
||||
|
||||
// default write permission files
|
||||
@ -75,6 +79,17 @@ var (
|
||||
"clock_gettime",
|
||||
|
||||
"restart_syscall",
|
||||
|
||||
|
||||
"futex",
|
||||
"gettid",
|
||||
"getpid",
|
||||
"prlimit64",
|
||||
"getrandom",
|
||||
"set_tid_address",
|
||||
"set_robust_list",
|
||||
"rseq",
|
||||
"newfstatat",
|
||||
}
|
||||
|
||||
// default syscalls to trace
|
||||
@ -108,71 +123,11 @@ var (
|
||||
// config for different type of program
|
||||
// workpath and arg0 have additional read / stat permission
|
||||
runptraceConfig = map[string]ProgramConfig{
|
||||
"python2.7": {
|
||||
Syscall: SyscallConfig{
|
||||
ExtraAllow: []string{
|
||||
"futex", "getdents", "getdents64", "prlimit64", "getpid", "sysinfo",
|
||||
},
|
||||
ExtraCount: map[string]int{
|
||||
"set_tid_address": 1,
|
||||
"set_robust_list": 1,
|
||||
},
|
||||
},
|
||||
FileAccess: FileAccessConfig{
|
||||
ExtraRead: []string{
|
||||
"/usr/bin/python2.7",
|
||||
"/usr/lib/python2.7/",
|
||||
"/usr/bin/lib/python2.7/",
|
||||
"/usr/local/lib/python2.7/",
|
||||
"/usr/lib/pymodules/python2.7/",
|
||||
"/usr/bin/Modules/",
|
||||
"/usr/bin/pybuilddir.txt",
|
||||
"/usr/lib/locale/",
|
||||
"./answer.code",
|
||||
},
|
||||
ExtraStat: []string{
|
||||
"/usr", "/usr/bin",
|
||||
},
|
||||
},
|
||||
RunCommand: []string{"/usr/bin/python2.7", "-E", "-s", "-B"},
|
||||
},
|
||||
"python3": {
|
||||
Syscall: SyscallConfig{
|
||||
ExtraAllow: []string{
|
||||
"futex", "getdents", "getdents64", "prlimit64", "getpid", "sysinfo", "getrandom",
|
||||
},
|
||||
ExtraCount: map[string]int{
|
||||
"set_tid_address": 1,
|
||||
"set_robust_list": 1,
|
||||
},
|
||||
},
|
||||
FileAccess: FileAccessConfig{
|
||||
ExtraRead: []string{
|
||||
"/usr/bin/python3",
|
||||
"/usr/lib/python3/",
|
||||
"/usr/bin/python3.6",
|
||||
"/usr/lib/python3.6/",
|
||||
"/usr/bin/lib/python3.6/",
|
||||
"/usr/local/lib/python3.6/",
|
||||
"/usr/bin/pyvenv.cfg",
|
||||
"/usr/pyvenv.cfg",
|
||||
"/usr/bin/Modules",
|
||||
"/usr/bin/pybuilddir.txt",
|
||||
"/usr/lib/dist-python",
|
||||
"/usr/lib/locale/",
|
||||
"./answer.code",
|
||||
},
|
||||
ExtraStat: []string{
|
||||
"/usr", "/usr/bin", "/usr/lib", "/usr/lib/python36.zip",
|
||||
},
|
||||
},
|
||||
RunCommand: []string{"/usr/bin/python3", "-I", "-B"},
|
||||
},
|
||||
"compiler": {
|
||||
Syscall: SyscallConfig{
|
||||
ExtraAllow: []string{
|
||||
"gettid", "set_tid_address", "set_robust_list", "futex",
|
||||
"getpid", "vfork", "fork", "clone", "execve", "wait4",
|
||||
"set_tid_address", "set_robust_list", "futex",
|
||||
"vfork", "fork", "clone", "execve", "wait4",
|
||||
"clock_gettime", "clock_getres",
|
||||
"setrlimit", "pipe",
|
||||
"getdents64", "getdents",
|
||||
|
@ -35,6 +35,7 @@ var (
|
||||
timeLimit, realTimeLimit, memoryLimit, outputLimit, stackLimit uint64
|
||||
inputFileName, outputFileName, errorFileName, workPath, runt string
|
||||
|
||||
useCGroupFd bool
|
||||
pType, result string
|
||||
args []string
|
||||
)
|
||||
@ -65,6 +66,7 @@ func main() {
|
||||
flag.Var(&addRawReadable, "add-readable-raw", "Add a readable file (don't transform to its real path)")
|
||||
flag.Var(&addRawWritable, "add-writable-raw", "Add a writable file (don't transform to its real path)")
|
||||
flag.BoolVar(&useCGroup, "cgroup", false, "Use cgroup to colloct resource usage")
|
||||
flag.BoolVar(&useCGroupFd, "cgroupfd", false, "Use cgroup FD to clone3 (cgroup v2 & kernel > 5.7)")
|
||||
flag.BoolVar(&memfile, "memfd", false, "Use memfd as exec file")
|
||||
flag.StringVar(&runt, "runner", "ptrace", "Runner for the program (ptrace, ns, container)")
|
||||
flag.BoolVar(&cred, "cred", false, "Generate credential for containers (uid=10000)")
|
||||
@ -145,6 +147,8 @@ func start() (*runner.Result, error) {
|
||||
var (
|
||||
r runner.Runner
|
||||
cg cgroup.Cgroup
|
||||
cgDir *os.File
|
||||
cgroupFd uintptr
|
||||
err error
|
||||
execFile uintptr
|
||||
rt runner.Result
|
||||
@ -206,15 +210,28 @@ func start() (*runner.Result, error) {
|
||||
if err = cg.SetMemoryLimit(memoryLimit << 20); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
debug("cgroup:", cg)
|
||||
if useCGroupFd {
|
||||
debug("use cgroup fd")
|
||||
if t != cgroup.TypeV2 {
|
||||
return nil, fmt.Errorf("use cgroup fd cannot be enabled without cgroup v2")
|
||||
}
|
||||
if cgDir, err = cg.Open(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cgDir.Close()
|
||||
cgroupFd = cgDir.Fd()
|
||||
}
|
||||
}
|
||||
|
||||
syncFunc := func(pid int) error {
|
||||
if cg != nil {
|
||||
var syncFunc func(pid int) error
|
||||
if cg != nil {
|
||||
syncFunc = func(pid int) error {
|
||||
if err := cg.AddProc(pid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if memfile {
|
||||
@ -325,13 +342,15 @@ func start() (*runner.Result, error) {
|
||||
r = &containerRunner{
|
||||
Environment: m,
|
||||
ExecveParam: container.ExecveParam{
|
||||
Args: args,
|
||||
Env: []string{pathEnv},
|
||||
Files: fds,
|
||||
ExecFile: execFile,
|
||||
RLimits: rlims.PrepareRLimit(),
|
||||
Seccomp: filter,
|
||||
SyncFunc: syncFunc,
|
||||
Args: args,
|
||||
Env: []string{pathEnv},
|
||||
Files: fds,
|
||||
ExecFile: execFile,
|
||||
RLimits: rlims.PrepareRLimit(),
|
||||
Seccomp: filter,
|
||||
SyncFunc: syncFunc,
|
||||
CgroupFD: cgroupFd,
|
||||
SyncAfterExec: cg == nil || cgDir != nil,
|
||||
},
|
||||
}
|
||||
} else if runt == "ns" {
|
||||
@ -417,11 +436,18 @@ func start() (*runner.Result, error) {
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("cgroup memory: %v", err)
|
||||
}
|
||||
debug("cgroup: cpu: ", cpu, " memory: ", memory)
|
||||
procPeak, err := cg.ProcessPeak()
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return nil, fmt.Errorf("cgroup pid: %v", err)
|
||||
}
|
||||
debug("cgroup: cpu: ", cpu, " memory: ", memory, " procPeak: ", procPeak)
|
||||
rt.Time = time.Duration(cpu)
|
||||
if memory > 0 {
|
||||
rt.Memory = runner.Size(memory)
|
||||
}
|
||||
if procPeak > 0 {
|
||||
rt.ProcPeak = procPeak
|
||||
}
|
||||
debug("cgroup:", rt)
|
||||
}
|
||||
return &rt, nil
|
||||
|
@ -51,16 +51,74 @@ func BenchmarkContainer(b *testing.B) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestContainerSuccess(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := getEnv(t, nil)
|
||||
r := m.Execve(context.TODO(), ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
})
|
||||
if r.Status != runner.StatusNormal {
|
||||
t.Fatal(r.Status, r.Error, r)
|
||||
}
|
||||
type testCase struct {
|
||||
name string
|
||||
param ExecveParam
|
||||
expected runner.Status
|
||||
}
|
||||
|
||||
var err error = errors.New("test error")
|
||||
|
||||
var successParam = ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
}
|
||||
|
||||
var tests []testCase = []testCase{
|
||||
{
|
||||
name: "Success",
|
||||
param: successParam,
|
||||
expected: runner.StatusNormal,
|
||||
},
|
||||
{
|
||||
name: "SuccessWithSync",
|
||||
param: ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
SyncFunc: func(p int) error { return nil },
|
||||
},
|
||||
expected: runner.StatusNormal,
|
||||
},
|
||||
{
|
||||
name: "NotExists",
|
||||
param: ExecveParam{
|
||||
Args: []string{"not_exists"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
},
|
||||
expected: runner.StatusRunnerError,
|
||||
},
|
||||
{
|
||||
name: "NotExistsWithSync",
|
||||
param: ExecveParam{
|
||||
Args: []string{"not_exists"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
SyncFunc: func(p int) error { return nil },
|
||||
},
|
||||
expected: runner.StatusRunnerError,
|
||||
},
|
||||
{
|
||||
name: "SyncFuncFail",
|
||||
param: ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
SyncFunc: func(pid int) error {
|
||||
return err
|
||||
},
|
||||
},
|
||||
expected: runner.StatusRunnerError,
|
||||
},
|
||||
{
|
||||
name: "SyncFuncFailAfterExec",
|
||||
param: ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
SyncFunc: func(pid int) error {
|
||||
return err
|
||||
},
|
||||
SyncAfterExec: true,
|
||||
},
|
||||
expected: runner.StatusRunnerError,
|
||||
},
|
||||
}
|
||||
|
||||
type credgen struct{}
|
||||
@ -77,41 +135,31 @@ func TestContainerSetCred(t *testing.T) {
|
||||
if os.Getpid() != 1 {
|
||||
t.Skip("root required for this test")
|
||||
}
|
||||
m := getEnv(t, credgen{})
|
||||
r := m.Execve(context.TODO(), ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
})
|
||||
runTest(t, successParam, runner.StatusNormal, credgen{})
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, param ExecveParam, expected runner.Status, credGen CredGenerator) {
|
||||
t.Parallel()
|
||||
m := getEnv(t, credGen)
|
||||
r := m.Execve(context.TODO(), param)
|
||||
if r.Status != expected {
|
||||
t.Fatal(r.Status, r.Error, r)
|
||||
}
|
||||
if err := m.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// can also success once more (no protocol mismatch)
|
||||
r = m.Execve(context.TODO(), successParam)
|
||||
if r.Status != runner.StatusNormal {
|
||||
t.Fatal(r.Status, r.Error)
|
||||
t.Fatal(r.Status, r.Error, r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerNotExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := getEnv(t, nil)
|
||||
r := m.Execve(context.TODO(), ExecveParam{
|
||||
Args: []string{"not_exists"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
})
|
||||
if r.Status != runner.StatusRunnerError {
|
||||
t.Fatal(r.Status, r.Error)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerSyncFuncFail(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := getEnv(t, nil)
|
||||
err := errors.New("test error")
|
||||
r := m.Execve(context.TODO(), ExecveParam{
|
||||
Args: []string{"/bin/true"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
SyncFunc: func(pid int) error {
|
||||
return err
|
||||
},
|
||||
})
|
||||
if r.Status != runner.StatusRunnerError {
|
||||
t.Fatal(r.Status, r.Error)
|
||||
func TestCases(t *testing.T) {
|
||||
for _, c := range tests {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
runTest(t, c.param, c.expected, nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
var (
|
||||
files []uintptr
|
||||
execFile uintptr
|
||||
cgroupFd uintptr
|
||||
cred *syscall.Credential
|
||||
)
|
||||
if cmd == nil {
|
||||
@ -35,6 +36,14 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
execFile = files[0]
|
||||
files = files[1:]
|
||||
}
|
||||
// if cgroupFd, then the cgroupFd follows
|
||||
if cmd.FdCgroup {
|
||||
if len(files) == 0 {
|
||||
return c.sendErrorReply("handle: expected cgroup fd")
|
||||
}
|
||||
cgroupFd = files[0]
|
||||
files = files[1:]
|
||||
}
|
||||
|
||||
var env []string
|
||||
env = append(env, c.defaultEnv...)
|
||||
@ -48,7 +57,7 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
cmd.Argv[0] = exePath
|
||||
}
|
||||
|
||||
syncFunc := func(pid int) error {
|
||||
syncPid := func(pid int) error {
|
||||
msg := unixsocket.Msg{
|
||||
Cred: &syscall.Ucred{
|
||||
Pid: int32(pid),
|
||||
@ -68,6 +77,10 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var syncFunc func(pid int) error
|
||||
if !cmd.SyncAfter {
|
||||
syncFunc = syncPid
|
||||
}
|
||||
|
||||
if c.Cred {
|
||||
cred = &syscall.Credential{
|
||||
@ -95,6 +108,7 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
Credential: cred,
|
||||
CTTY: cmd.CTTY,
|
||||
Seccomp: seccomp,
|
||||
CgroupFd: cgroupFd,
|
||||
|
||||
UnshareCgroupAfterSync: c.UnshareCgroup,
|
||||
}
|
||||
@ -105,9 +119,12 @@ func (c *containerServer) handleExecve(cmd *execCmd, msg unixsocket.Msg) error {
|
||||
if len(cmd.Argv) > 0 {
|
||||
s = cmd.Argv[0]
|
||||
}
|
||||
c.sendErrorReply("start: %s: %v", s, err)
|
||||
c.recvCmd()
|
||||
return c.sendReply(reply{}, unixsocket.Msg{})
|
||||
return c.sendErrorReply("start: %s: %v", s, err)
|
||||
}
|
||||
if cmd.SyncAfter {
|
||||
if err := syncPid(1); err != nil {
|
||||
syscall.Kill(-1, syscall.SIGKILL)
|
||||
}
|
||||
}
|
||||
return c.handleExecveStarted(pid)
|
||||
}
|
||||
|
@ -66,6 +66,9 @@ type Builder struct {
|
||||
// ContainerUID & ContainerGID set the container uid / gid mapping
|
||||
ContainerUID int
|
||||
ContainerGID int
|
||||
|
||||
// UnshareCgroupBeforeExec calls unshare cgroup before execution
|
||||
UnshareCgroupBeforeExec bool
|
||||
}
|
||||
|
||||
// SymbolicLink defines symlinks to be created after mount
|
||||
@ -186,7 +189,7 @@ func (b *Builder) Build() (Environment, error) {
|
||||
Cred: b.CredGenerator != nil,
|
||||
ContainerUID: b.ContainerUID,
|
||||
ContainerGID: b.ContainerGID,
|
||||
UnshareCgroup: b.CloneFlags&unix.CLONE_NEWCGROUP == unix.CLONE_NEWCGROUP,
|
||||
UnshareCgroup: b.UnshareCgroupBeforeExec,
|
||||
}); err != nil {
|
||||
c.Destroy()
|
||||
return nil, err
|
||||
@ -250,7 +253,7 @@ func (b *Builder) startContainer() (*container, error) {
|
||||
unix.CAP_SYS_ADMIN,
|
||||
unix.CAP_SYS_RESOURCE,
|
||||
},
|
||||
Pdeathsig: syscall.SIGTERM,
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
},
|
||||
}
|
||||
if err = r.Start(); err != nil {
|
||||
|
@ -25,6 +25,9 @@ type ExecveParam struct {
|
||||
// ExecFile specifies file descriptor for executable file using fexecve
|
||||
ExecFile uintptr
|
||||
|
||||
// CgroupFD specifies file descriptor for cgroup V2
|
||||
CgroupFD uintptr
|
||||
|
||||
// RLimits specifies POSIX Resource limit through setrlimit
|
||||
RLimits []rlimit.RLimit
|
||||
|
||||
@ -36,6 +39,10 @@ type ExecveParam struct {
|
||||
|
||||
// SyncFunc calls with pid just before execve (for attach the process to cgroups)
|
||||
SyncFunc func(pid int) error
|
||||
|
||||
// SyncAfterExec makes syncFunc sync after the start of the execution
|
||||
// Thus, since pid is not guarantee to be exist (may exit early), it is not passed
|
||||
SyncAfterExec bool
|
||||
}
|
||||
|
||||
// Execve runs process inside container. It accepts context cancelation as time limit exceeded.
|
||||
@ -50,17 +57,22 @@ func (c *container) Execve(ctx context.Context, param ExecveParam) runner.Result
|
||||
if param.ExecFile > 0 {
|
||||
files = append(files, int(param.ExecFile))
|
||||
}
|
||||
if param.CgroupFD > 0 {
|
||||
files = append(files, int(param.CgroupFD))
|
||||
}
|
||||
files = append(files, uintptrSliceToInt(param.Files)...)
|
||||
msg := unixsocket.Msg{
|
||||
Fds: files,
|
||||
}
|
||||
execCmd := &execCmd{
|
||||
Argv: param.Args,
|
||||
Env: param.Env,
|
||||
RLimits: param.RLimits,
|
||||
Seccomp: param.Seccomp,
|
||||
FdExec: param.ExecFile > 0,
|
||||
CTTY: param.CTTY,
|
||||
Argv: param.Args,
|
||||
Env: param.Env,
|
||||
RLimits: param.RLimits,
|
||||
Seccomp: param.Seccomp,
|
||||
FdExec: param.ExecFile > 0,
|
||||
CTTY: param.CTTY,
|
||||
SyncAfter: param.SyncAfterExec,
|
||||
FdCgroup: param.CgroupFD > 0,
|
||||
}
|
||||
cm := cmd{
|
||||
Cmd: cmdExecve,
|
||||
@ -90,8 +102,6 @@ func (c *container) Execve(ctx context.Context, param ExecveParam) runner.Result
|
||||
if err := param.SyncFunc(int(msg.Cred.Pid)); err != nil {
|
||||
// tell sync function to exit and recv error
|
||||
c.execveSyncKill()
|
||||
// tell kill function to exit and sync
|
||||
c.execveSyncKill()
|
||||
return errResult("execve: syncfunc failed %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -36,12 +36,14 @@ type deleteCmd struct {
|
||||
|
||||
// execCmd stores execve parameter
|
||||
type execCmd struct {
|
||||
Argv []string // execve argv
|
||||
Env []string // execve env
|
||||
RLimits []rlimit.RLimit // execve posix rlimit
|
||||
Seccomp seccomp.Filter // seccomp filter
|
||||
FdExec bool // if use fexecve (fd[0] as exec)
|
||||
CTTY bool // if set CTTY
|
||||
Argv []string // execve argv
|
||||
Env []string // execve env
|
||||
RLimits []rlimit.RLimit // execve posix rlimit
|
||||
Seccomp seccomp.Filter // seccomp filter
|
||||
FdExec bool // if use fexecve (fd[0] as exec)
|
||||
FdCgroup bool // if use cgroupFd
|
||||
CTTY bool // if set CTTY
|
||||
SyncAfter bool // if sync function calls after execve returns
|
||||
}
|
||||
|
||||
// confCmd stores conf parameter
|
||||
|
6
go.mod
6
go.mod
@ -1,9 +1,9 @@
|
||||
module github.com/criyle/go-sandbox
|
||||
|
||||
go 1.22
|
||||
go 1.23
|
||||
|
||||
require (
|
||||
github.com/elastic/go-seccomp-bpf v1.5.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
)
|
||||
|
8
go.sum
8
go.sum
@ -6,9 +6,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
@ -33,6 +33,9 @@ type Cgroup interface {
|
||||
// MemoryMaxUsageInBytes reads max total memory usage. Not exist in cgroup v2 with kernel version < 5.19
|
||||
MemoryMaxUsage() (uint64, error)
|
||||
|
||||
// ProcessPeak reads maximum number of process ever existed in cgroup. Not exist in cgroup v1 or kernel < 6.1
|
||||
ProcessPeak() (uint64, error)
|
||||
|
||||
// SetCPUBandwidth sets the cpu bandwidth. Times in ns
|
||||
SetCPUBandwidth(quota, period uint64) error
|
||||
|
||||
@ -53,6 +56,9 @@ type Cgroup interface {
|
||||
|
||||
// Random creates a sub-cgroup based on the existing one but the name is randomly generated
|
||||
Random(string) (Cgroup, error)
|
||||
|
||||
// Open opens the cgroup directory on V2 (used for clone3)
|
||||
Open() (*os.File, error)
|
||||
}
|
||||
|
||||
// DetectedCgroupType defines the current cgroup type of the system
|
||||
|
@ -116,7 +116,9 @@ func DetectType() Type {
|
||||
|
||||
func remove(name string) error {
|
||||
if name != "" {
|
||||
return os.Remove(name)
|
||||
// os.Remove tried to Unlink, then Rmdir. Since we only delete directories, use
|
||||
// Rmdir directly
|
||||
return syscall.Rmdir(name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ type V1 struct {
|
||||
existing bool
|
||||
}
|
||||
|
||||
func (c *V1) Open() (*os.File, error) {
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
func (c *V1) String() string {
|
||||
names := make([]string, 0, numberOfControllers)
|
||||
for _, v := range []struct {
|
||||
@ -181,6 +185,11 @@ func (c *V1) MemoryMaxUsage() (uint64, error) {
|
||||
return c.memory.ReadUint("memory.max_usage_in_bytes")
|
||||
}
|
||||
|
||||
// ProcessPeak implements Cgroup.
|
||||
func (c *V1) ProcessPeak() (uint64, error) {
|
||||
return 0, ErrNotInitialized
|
||||
}
|
||||
|
||||
// SetMemoryLimit write memory.limit_in_bytes
|
||||
func (c *V1) SetMemoryLimit(i uint64) error {
|
||||
return c.memory.WriteUint("memory.limit_in_bytes", i)
|
||||
|
@ -21,6 +21,10 @@ type V2 struct {
|
||||
|
||||
var _ Cgroup = &V2{}
|
||||
|
||||
func (c *V2) Open() (*os.File, error) {
|
||||
return os.OpenFile(c.path, 0, dirPerm)
|
||||
}
|
||||
|
||||
func (c *V2) String() string {
|
||||
ct, _ := getAvailableControllerV2path(path.Join(c.path, cgroupControllers))
|
||||
return "v2(" + c.path + ")" + ct.String()
|
||||
@ -155,6 +159,14 @@ func (c *V2) MemoryMaxUsage() (uint64, error) {
|
||||
return c.ReadUint("memory.peak")
|
||||
}
|
||||
|
||||
// ProcessPeak reads pids.peak
|
||||
func (c *V2) ProcessPeak() (uint64, error) {
|
||||
if !c.control.Pids {
|
||||
return 0, ErrNotInitialized
|
||||
}
|
||||
return c.ReadUint("pids.peak")
|
||||
}
|
||||
|
||||
// SetCPUBandwidth set cpu.max quota period
|
||||
func (c *V2) SetCPUBandwidth(quota, period uint64) error {
|
||||
if !c.control.CPU {
|
||||
|
@ -20,10 +20,33 @@ var (
|
||||
)
|
||||
|
||||
func BenchmarkStdFork(b *testing.B) {
|
||||
f := openNull(b)
|
||||
defer f.Close()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
pid, err := syscall.ForkExec("/bin/echo", nil, &syscall.ProcAttr{
|
||||
Env: []string{"PATH=/bin"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
Files: []uintptr{f.Fd(), f.Fd(), f.Fd()},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
wait4(pid, b)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStdForkUser(b *testing.B) {
|
||||
f := openNull(b)
|
||||
defer f.Close()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
pid, err := syscall.ForkExec("/bin/echo", nil, &syscall.ProcAttr{
|
||||
Env: []string{"PATH=/bin"},
|
||||
Files: []uintptr{f.Fd(), f.Fd(), f.Fd()},
|
||||
Sys: &syscall.SysProcAttr{
|
||||
Cloneflags: syscall.CLONE_NEWUSER,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
17
pkg/forkexec/clone3_linux.go
Normal file
17
pkg/forkexec/clone3_linux.go
Normal file
@ -0,0 +1,17 @@
|
||||
package forkexec
|
||||
|
||||
// cloneArgs holds arguments for clone3 Linux syscall.
|
||||
// from src/syscall/exec_linux.go:196
|
||||
type cloneArgs struct {
|
||||
flags uint64 // Flags bit mask
|
||||
pidFD uint64 // Where to store PID file descriptor (int *)
|
||||
childTID uint64 // Where to store child TID, in child's memory (pid_t *)
|
||||
parentTID uint64 // Where to store child TID, in parent's memory (pid_t *)
|
||||
exitSignal uint64 // Signal to deliver to parent on child termination
|
||||
stack uint64 // Pointer to lowest byte of stack
|
||||
stackSize uint64 // Size of stack
|
||||
tls uint64 // Location of new TLS
|
||||
setTID uint64 // Pointer to a pid_t array (since Linux 5.5)
|
||||
setTIDSize uint64 // Number of elements in set_tid (since Linux 5.5)
|
||||
cgroup uint64 // File descriptor for target cgroup of child (since Linux 5.7)
|
||||
}
|
@ -1,19 +1,49 @@
|
||||
package forkexec
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/criyle/go-sandbox/pkg/forkexec/vfork"
|
||||
"github.com/criyle/go-sandbox/pkg/rlimit"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Reference to src/syscall/exec_linux.go
|
||||
//
|
||||
//go:noinline
|
||||
//go:norace
|
||||
//go:nocheckptr
|
||||
func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, hostname, domainname, pivotRoot *byte, p [2]int) (r1 uintptr, err1 syscall.Errno) {
|
||||
var (
|
||||
clone3 *cloneArgs
|
||||
pid uintptr
|
||||
err2 syscall.Errno
|
||||
unshareUser = r.CloneFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
||||
i int
|
||||
rlim rlimit.RLimit
|
||||
)
|
||||
pipe := p[1]
|
||||
|
||||
// similar to exec_linux, avoid side effect by shuffling around
|
||||
fd, nextfd := prepareFds(r.Files)
|
||||
|
||||
flag := r.CloneFlags & UnshareFlags
|
||||
if r.SyncFunc == nil && !(r.StopBeforeSeccomp || (r.Seccomp != nil && r.Ptrace)) && flag&syscall.CLONE_NEWUSER != syscall.CLONE_NEWUSER {
|
||||
flag |= syscall.CLONE_VM | syscall.CLONE_VFORK
|
||||
}
|
||||
|
||||
// use clone3 if cgroupFd specified
|
||||
if r.CgroupFd > 0 {
|
||||
clone3 = &cloneArgs{
|
||||
flags: uint64(flag) | unix.CLONE_INTO_CGROUP,
|
||||
exitSignal: uint64(syscall.SIGCHLD),
|
||||
cgroup: uint64(r.CgroupFd),
|
||||
}
|
||||
}
|
||||
flag |= uintptr(syscall.SIGCHLD)
|
||||
|
||||
// Acquire the fork lock so that no other threads
|
||||
// create new fds that are not yet close-on-exec
|
||||
// before we fork.
|
||||
@ -24,7 +54,16 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
beforeFork()
|
||||
|
||||
// UnshareFlags (new namespaces) is activated by clone syscall
|
||||
r1, _, err1 = syscall.RawSyscall6(syscall.SYS_CLONE, uintptr(syscall.SIGCHLD)|(r.CloneFlags&UnshareFlags), 0, 0, 0, 0, 0)
|
||||
if clone3 != nil {
|
||||
r1, err1 = vfork.RawVforkSyscall(unix.SYS_CLONE3, uintptr(unsafe.Pointer(clone3)), unsafe.Sizeof(*clone3), 0)
|
||||
} else {
|
||||
if runtime.GOARCH == "s390x" {
|
||||
// On Linux/s390, the first two arguments of clone(2) are swapped.
|
||||
r1, err1 = vfork.RawVforkSyscall(syscall.SYS_CLONE, 0, flag, 0)
|
||||
} else {
|
||||
r1, err1 = vfork.RawVforkSyscall(syscall.SYS_CLONE, flag, 0, 0)
|
||||
}
|
||||
}
|
||||
if err1 != 0 || r1 != 0 {
|
||||
// in parent process, immediate return
|
||||
return
|
||||
@ -34,13 +73,6 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
afterForkInChild()
|
||||
// Notice: cannot call any GO functions beyond this point
|
||||
|
||||
pipe := p[1]
|
||||
var (
|
||||
pid uintptr
|
||||
err2 syscall.Errno
|
||||
unshareUser = r.CloneFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
||||
)
|
||||
|
||||
// Close write end of pipe
|
||||
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[0]), 0, 0); err1 != 0 {
|
||||
childExitError(pipe, LocCloseWrite, err1)
|
||||
@ -126,7 +158,7 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
r.ExecFile = uintptr(nextfd)
|
||||
nextfd++
|
||||
}
|
||||
for i := 0; i < len(fd); i++ {
|
||||
for i = 0; i < len(fd); i++ {
|
||||
if fd[i] >= 0 && fd[i] < int(i) {
|
||||
// Avoid fd rewrite
|
||||
for nextfd == pipe || (r.ExecFile > 0 && nextfd == int(r.ExecFile)) {
|
||||
@ -142,7 +174,7 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
}
|
||||
}
|
||||
// Pass 2: fd[i] => i
|
||||
for i := 0; i < len(fd); i++ {
|
||||
for i = 0; i < len(fd); i++ {
|
||||
if fd[i] == -1 {
|
||||
syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(i), 0, 0)
|
||||
continue
|
||||
@ -296,7 +328,7 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
}
|
||||
|
||||
// Set limit
|
||||
for i, rlim := range r.RLimits {
|
||||
for i, rlim = range r.RLimits {
|
||||
// prlimit instead of setrlimit to avoid 32-bit limitation (linux > 3.2)
|
||||
_, _, err1 = syscall.RawSyscall6(syscall.SYS_PRLIMIT64, 0, uintptr(rlim.Res), uintptr(unsafe.Pointer(&rlim.Rlim)), 0, 0, 0)
|
||||
if err1 != 0 {
|
||||
@ -329,14 +361,16 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
// Enable Ptrace & sync with parent (since ptrace_me is a blocking operation)
|
||||
if r.Ptrace && r.Seccomp != nil {
|
||||
{
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncWrite, err1)
|
||||
}
|
||||
if r.SyncFunc != nil {
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncWrite, err1)
|
||||
}
|
||||
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncRead, err1)
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncRead, err1)
|
||||
}
|
||||
}
|
||||
|
||||
// unshare cgroup namespace
|
||||
@ -392,14 +426,16 @@ func forkAndExecInChild(r *Runner, argv0 *byte, argv, env []*byte, workdir, host
|
||||
// Before exec, sync with parent through pipe (configured as close_on_exec)
|
||||
if !r.Ptrace || r.Seccomp == nil {
|
||||
{
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncWrite, err1)
|
||||
}
|
||||
if r.SyncFunc != nil {
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncWrite, err1)
|
||||
}
|
||||
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncRead, err1)
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(pipe), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
if r1 == 0 || err1 != 0 {
|
||||
childExitError(pipe, LocSyncRead, err1)
|
||||
}
|
||||
}
|
||||
|
||||
// unshare cgroup namespace
|
||||
|
@ -65,6 +65,7 @@ func syncWithChild(r *Runner, p [2]int, pid int, err1 syscall.Errno) (int, error
|
||||
err error
|
||||
unshareUser = r.CloneFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
||||
childErr ChildError
|
||||
n int
|
||||
)
|
||||
|
||||
// sync with child
|
||||
@ -86,24 +87,24 @@ func syncWithChild(r *Runner, p [2]int, pid int, err1 syscall.Errno) (int, error
|
||||
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
}
|
||||
|
||||
n, err := readChildErr(p[0], &childErr)
|
||||
// child returned error code
|
||||
if (n != int(unsafe.Sizeof(err2)) && n != int(unsafe.Sizeof(childErr))) || childErr.Err != 0 || err != nil {
|
||||
childErr.Err = handlePipeError(n, childErr.Err)
|
||||
goto fail
|
||||
}
|
||||
|
||||
// if syncfunc return error, then fail child immediately
|
||||
// only sync if there is a syncFunc
|
||||
if r.SyncFunc != nil {
|
||||
n, err = readChildErr(p[0], &childErr)
|
||||
// child returned error code
|
||||
if (n != int(unsafe.Sizeof(err2)) && n != int(unsafe.Sizeof(childErr))) || childErr.Err != 0 || err != nil {
|
||||
childErr.Err = handlePipeError(n, childErr.Err)
|
||||
goto fail
|
||||
}
|
||||
if err = r.SyncFunc(int(pid)); err != nil {
|
||||
goto fail
|
||||
}
|
||||
// otherwise, ack child (err1 == 0)
|
||||
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(p[0]), uintptr(unsafe.Pointer(&err1)), uintptr(unsafe.Sizeof(err1)))
|
||||
}
|
||||
// otherwise, ack child (err1 == 0)
|
||||
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(p[0]), uintptr(unsafe.Pointer(&err1)), uintptr(unsafe.Sizeof(err1)))
|
||||
|
||||
// if stopped before execve by signal SIGSTOP or PTRACE_ME, then do not wait until execve
|
||||
if r.Ptrace || r.StopBeforeSeccomp {
|
||||
if r.StopBeforeSeccomp || (r.Seccomp != nil && r.Ptrace) {
|
||||
// let's wait it in another goroutine to avoid SIGPIPE
|
||||
go func() {
|
||||
readChildErr(p[0], &childErr)
|
||||
|
@ -61,6 +61,9 @@ type Runner struct {
|
||||
UIDMappings []syscall.SysProcIDMap
|
||||
GIDMappings []syscall.SysProcIDMap
|
||||
|
||||
// CgroupFd to use when clone3 with CLONE_INTO_CGROUP with kernel >=5.7 and cgroup v2
|
||||
CgroupFd uintptr
|
||||
|
||||
// Credential holds user and group identities to be assumed
|
||||
// by a child process started by StartProcess.
|
||||
Credential *syscall.Credential
|
||||
|
29
pkg/forkexec/vfork/asm_linux_386.s
Normal file
29
pkg/forkexec/vfork/asm_linux_386.s
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// See ../runtime/sys_linux_386.s for the reason why we always use int 0x80
|
||||
// instead of the glibc-specific "CALL 0x10(GS)".
|
||||
#define INVOKE_SYSCALL INT $0x80
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-24
|
||||
MOVL trap+0(FP), AX // syscall entry
|
||||
MOVL a1+4(FP), BX
|
||||
MOVL a2+8(FP), CX
|
||||
MOVL a3+12(FP), DX
|
||||
POPL SI // preserve return address
|
||||
INVOKE_SYSCALL
|
||||
PUSHL SI
|
||||
CMPL AX, $0xfffff001
|
||||
JLS ok
|
||||
MOVL $-1, r1+16(FP)
|
||||
NEGL AX
|
||||
MOVL AX, err+20(FP)
|
||||
RET
|
||||
ok:
|
||||
MOVL AX, r1+16(FP)
|
||||
MOVL $0, err+20(FP)
|
||||
RET
|
28
pkg/forkexec/vfork/asm_linux_amd64.s
Normal file
28
pkg/forkexec/vfork/asm_linux_amd64.s
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-48
|
||||
MOVQ a1+8(FP), DI
|
||||
MOVQ a2+16(FP), SI
|
||||
MOVQ a3+24(FP), DX
|
||||
MOVQ $0, R10
|
||||
MOVQ $0, R8
|
||||
MOVQ $0, R9
|
||||
MOVQ trap+0(FP), AX // syscall entry
|
||||
POPQ R12 // preserve return address
|
||||
SYSCALL
|
||||
PUSHQ R12
|
||||
CMPQ AX, $0xfffffffffffff001
|
||||
JLS ok2
|
||||
MOVQ $-1, r1+32(FP)
|
||||
NEGQ AX
|
||||
MOVQ AX, err+40(FP)
|
||||
RET
|
||||
ok2:
|
||||
MOVQ AX, r1+32(FP)
|
||||
MOVQ $0, err+40(FP)
|
||||
RET
|
26
pkg/forkexec/vfork/asm_linux_arm.s
Normal file
26
pkg/forkexec/vfork/asm_linux_arm.s
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-24
|
||||
MOVW trap+0(FP), R7 // syscall entry
|
||||
MOVW a1+4(FP), R0
|
||||
MOVW a2+8(FP), R1
|
||||
MOVW a3+12(FP), R2
|
||||
SWI $0
|
||||
MOVW $0xfffff001, R1
|
||||
CMP R1, R0
|
||||
BLS ok
|
||||
MOVW $-1, R1
|
||||
MOVW R1, r1+16(FP)
|
||||
RSB $0, R0, R0
|
||||
MOVW R0, err+20(FP)
|
||||
RET
|
||||
ok:
|
||||
MOVW R0, r1+16(FP)
|
||||
MOVW $0, R0
|
||||
MOVW R0, err+20(FP)
|
||||
RET
|
27
pkg/forkexec/vfork/asm_linux_arm64.s
Normal file
27
pkg/forkexec/vfork/asm_linux_arm64.s
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT,$0-48
|
||||
MOVD a1+8(FP), R0
|
||||
MOVD a2+16(FP), R1
|
||||
MOVD a3+24(FP), R2
|
||||
MOVD $0, R3
|
||||
MOVD $0, R4
|
||||
MOVD $0, R5
|
||||
MOVD trap+0(FP), R8 // syscall entry
|
||||
SVC
|
||||
CMN $4095, R0
|
||||
BCC ok
|
||||
MOVD $-1, R4
|
||||
MOVD R4, r1+32(FP) // r1
|
||||
NEG R0, R0
|
||||
MOVD R0, err+40(FP) // errno
|
||||
RET
|
||||
ok:
|
||||
MOVD R0, r1+32(FP) // r1
|
||||
MOVD ZR, err+40(FP) // errno
|
||||
RET
|
27
pkg/forkexec/vfork/asm_linux_loong64.s
Normal file
27
pkg/forkexec/vfork/asm_linux_loong64.s
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT,$0-48
|
||||
MOVV a1+8(FP), R4
|
||||
MOVV a2+16(FP), R5
|
||||
MOVV a3+24(FP), R6
|
||||
MOVV $0, R7
|
||||
MOVV $0, R8
|
||||
MOVV $0, R9
|
||||
MOVV trap+0(FP), R11 // syscall entry
|
||||
SYSCALL
|
||||
MOVW $-4096, R12
|
||||
BGEU R12, R4, ok
|
||||
MOVV $-1, R12
|
||||
MOVV R12, r1+32(FP) // r1
|
||||
SUBVU R4, R0, R4
|
||||
MOVV R4, err+40(FP) // errno
|
||||
RET
|
||||
ok:
|
||||
MOVV R4, r1+32(FP) // r1
|
||||
MOVV R0, err+40(FP) // errno
|
||||
RET
|
27
pkg/forkexec/vfork/asm_linux_mips64x.s
Normal file
27
pkg/forkexec/vfork/asm_linux_mips64x.s
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && (mips64 || mips64le)
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-48
|
||||
MOVV a1+8(FP), R4
|
||||
MOVV a2+16(FP), R5
|
||||
MOVV a3+24(FP), R6
|
||||
MOVV R0, R7
|
||||
MOVV R0, R8
|
||||
MOVV R0, R9
|
||||
MOVV trap+0(FP), R2 // syscall entry
|
||||
SYSCALL
|
||||
BEQ R7, ok
|
||||
MOVV $-1, R1
|
||||
MOVV R1, r1+32(FP) // r1
|
||||
MOVV R2, err+40(FP) // errno
|
||||
RET
|
||||
ok:
|
||||
MOVV R2, r1+32(FP) // r1
|
||||
MOVV R0, err+40(FP) // errno
|
||||
RET
|
24
pkg/forkexec/vfork/asm_linux_mipsx.s
Normal file
24
pkg/forkexec/vfork/asm_linux_mipsx.s
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && (mips || mipsle)
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-24
|
||||
MOVW a1+4(FP), R4
|
||||
MOVW a2+8(FP), R5
|
||||
MOVW a3+12(FP), R6
|
||||
MOVW trap+0(FP), R2 // syscall entry
|
||||
SYSCALL
|
||||
BEQ R7, ok
|
||||
MOVW $-1, R1
|
||||
MOVW R1, r1+16(FP) // r1
|
||||
MOVW R2, err+20(FP) // errno
|
||||
RET
|
||||
ok:
|
||||
MOVW R2, r1+16(FP) // r1
|
||||
MOVW R0, err+20(FP) // errno
|
||||
RET
|
27
pkg/forkexec/vfork/asm_linux_ppc64x.s
Normal file
27
pkg/forkexec/vfork/asm_linux_ppc64x.s
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux && (ppc64 || ppc64le)
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-48
|
||||
MOVD a1+8(FP), R3
|
||||
MOVD a2+16(FP), R4
|
||||
MOVD a3+24(FP), R5
|
||||
MOVD R0, R6
|
||||
MOVD R0, R7
|
||||
MOVD R0, R8
|
||||
MOVD trap+0(FP), R9 // syscall entry
|
||||
SYSCALL R9
|
||||
BVC ok
|
||||
MOVD $-1, R4
|
||||
MOVD R4, r1+32(FP) // r1
|
||||
MOVD R3, err+40(FP) // errno
|
||||
RET
|
||||
ok:
|
||||
MOVD R3, r1+32(FP) // r1
|
||||
MOVD R0, err+40(FP) // errno
|
||||
RET
|
27
pkg/forkexec/vfork/asm_linux_riscv64.s
Normal file
27
pkg/forkexec/vfork/asm_linux_riscv64.s
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-48
|
||||
MOV a1+8(FP), A0
|
||||
MOV a2+16(FP), A1
|
||||
MOV a3+24(FP), A2
|
||||
MOV ZERO, A3
|
||||
MOV ZERO, A4
|
||||
MOV ZERO, A5
|
||||
MOV trap+0(FP), A7 // syscall entry
|
||||
ECALL
|
||||
MOV $-4096, T0
|
||||
BLTU T0, A0, err
|
||||
MOV A0, r1+32(FP) // r1
|
||||
MOV ZERO, err+40(FP) // errno
|
||||
RET
|
||||
err:
|
||||
MOV $-1, T0
|
||||
MOV T0, r1+32(FP) // r1
|
||||
SUB A0, ZERO, A0
|
||||
MOV A0, err+40(FP) // errno
|
||||
RET
|
26
pkg/forkexec/vfork/asm_linux_s390x.s
Normal file
26
pkg/forkexec/vfork/asm_linux_s390x.s
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1, err uintptr)
|
||||
TEXT ·RawVforkSyscall(SB),NOSPLIT|NOFRAME,$0-48
|
||||
MOVD a1+8(FP), R2
|
||||
MOVD a2+16(FP), R3
|
||||
MOVD a3+24(FP), R4
|
||||
MOVD $0, R5
|
||||
MOVD $0, R6
|
||||
MOVD $0, R7
|
||||
MOVD trap+0(FP), R1 // syscall entry
|
||||
SYSCALL
|
||||
MOVD $0xfffffffffffff001, R8
|
||||
CMPUBLT R2, R8, ok2
|
||||
MOVD $-1, r1+32(FP)
|
||||
NEG R2, R2
|
||||
MOVD R2, err+40(FP) // errno
|
||||
RET
|
||||
ok2:
|
||||
MOVD R2, r1+32(FP)
|
||||
MOVD $0, err+40(FP) // errno
|
||||
RET
|
12
pkg/forkexec/vfork/syscall.go
Normal file
12
pkg/forkexec/vfork/syscall.go
Normal file
@ -0,0 +1,12 @@
|
||||
// Package vfork provides the mirror of the un-exported syscall.rawVforkSyscall.
|
||||
// The assembly code is copied from go1.24 syscall package
|
||||
package vfork
|
||||
|
||||
import "syscall"
|
||||
|
||||
// RawVforkSyscall provided the mirrored version from un-exported syscall.rawVforkSyscall
|
||||
// The go:linkname does not work for assembly function and it was suggested by the go team
|
||||
// to copy over the assembly functions
|
||||
//
|
||||
// See go.dev/issue/71892
|
||||
func RawVforkSyscall(trap, a1, a2, a3 uintptr) (r1 uintptr, err syscall.Errno)
|
@ -11,8 +11,9 @@ type Result struct {
|
||||
ExitStatus int // exit status (signal number if signalled)
|
||||
Error string // potential detailed error message (for program runner error)
|
||||
|
||||
Time time.Duration // used user CPU time (underlying type int64 in ns)
|
||||
Memory Size // used user memory (underlying type uint64 in bytes)
|
||||
Time time.Duration // used user CPU time (underlying type int64 in ns)
|
||||
Memory Size // used user memory (underlying type uint64 in bytes)
|
||||
ProcPeak uint64 // maximum processes
|
||||
|
||||
// metrics for the program runner
|
||||
SetUpTime time.Duration
|
||||
|
10
test_c/Makefile
Normal file
10
test_c/Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
all: helloworld/main.test nonzeroexit/main.test
|
||||
|
||||
helloworld/main.test: helloworld/main.cpp
|
||||
g++ helloworld/main.cpp -o helloworld/main.test -DNDEBUG -O3
|
||||
|
||||
nonzeroexit/main.test: nonzeroexit/main.cpp
|
||||
g++ nonzeroexit/main.cpp -o nonzeroexit/main.test -DNDEBUG -O3
|
||||
|
||||
clean:
|
||||
rm -rf helloworld/*.test nonzeroexit/*.test
|
6
test_c/helloworld/main.cpp
Normal file
6
test_c/helloworld/main.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include<iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Hello, world" << std::endl;
|
||||
return 0;
|
||||
}
|
3
test_c/nonzeroexit/main.cpp
Normal file
3
test_c/nonzeroexit/main.cpp
Normal file
@ -0,0 +1,3 @@
|
||||
int main() {
|
||||
return 100;
|
||||
}
|
Loading…
Reference in New Issue
Block a user