mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
fixed unshared user namespace
This commit is contained in:
parent
4a120f9a3d
commit
d260bf4657
@ -35,9 +35,11 @@ Packages:
|
||||
|
||||
- seccomp: provides utility function that wrappers libseccomp
|
||||
- mount: provides utility function that wrappers mount syscall
|
||||
- forkexec: fork-exec provides unshare, ptrace, seccomp before exec
|
||||
- rlimit: provides utility function that defines rlimit syscall
|
||||
- forkexec: fork-exec provides mount, unshare, ptrace, seccomp, capset before exec
|
||||
- tracer: ptrace tracer and provides syscall trap filter context
|
||||
- runprogram: wrapper to call forkexec and trecer
|
||||
- rununshared: wrapper to call forkexec and unshared namespaces
|
||||
- runconfig: defines arch & language specified trace condition
|
||||
|
||||
Executable:
|
||||
|
||||
@ -24,8 +24,10 @@ func afterForkInChild()
|
||||
//go:norace
|
||||
func (r *Runner) Start() (int, error) {
|
||||
var (
|
||||
err1 syscall.Errno
|
||||
err1, err2 syscall.Errno
|
||||
r1 uintptr
|
||||
p [2]int
|
||||
unshareUser = r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
|
||||
)
|
||||
|
||||
argv0, argv, envv, err := prepareExec(r.Args, r.Env)
|
||||
@ -51,8 +53,12 @@ func (r *Runner) Start() (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// prepare set uid / gid map files
|
||||
files := prepareIDMap(r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER)
|
||||
if unshareUser {
|
||||
err = unix.Pipe2(p[:], unix.O_CLOEXEC)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// similar to exec_linux, avoid side effect by shuffling around
|
||||
fd, nextfd := prepareFds(r.Files)
|
||||
@ -71,8 +77,19 @@ func (r *Runner) Start() (int, error) {
|
||||
if err1 != 0 || pid != 0 {
|
||||
// restore all signals
|
||||
afterFork()
|
||||
syscall.ForkLock.Unlock()
|
||||
|
||||
// synchronize with child for uid / gid map
|
||||
if unshareUser {
|
||||
unix.Close(p[0])
|
||||
err = writeIDMaps(int(pid))
|
||||
if err != nil {
|
||||
err2 = err.(syscall.Errno)
|
||||
}
|
||||
syscall.RawSyscall(syscall.SYS_WRITE, uintptr(p[1]), uintptr(unsafe.Pointer(&err2)), uintptr(unsafe.Sizeof(err2)))
|
||||
unix.Close(p[1])
|
||||
}
|
||||
|
||||
syscall.ForkLock.Unlock()
|
||||
if err1 != 0 {
|
||||
return int(pid), syscall.Errno(err1)
|
||||
}
|
||||
@ -83,6 +100,32 @@ func (r *Runner) Start() (int, error) {
|
||||
afterForkInChild()
|
||||
// Notice: cannot call any GO functions beyond this point
|
||||
|
||||
// If usernamespace is unshared, uid map and gid map is required to create folders
|
||||
// and files
|
||||
// We need parent to setup uid_map / gid_map for us since we do not have capabilities
|
||||
// in the original namespace
|
||||
// At the same time, socket pair / pipe sychronization is required as well
|
||||
if unshareUser {
|
||||
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[1]), 0, 0); err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
r1, _, err1 = syscall.RawSyscall(syscall.SYS_READ, uintptr(p[0]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
if r1 != unsafe.Sizeof(err2) {
|
||||
err1 = syscall.EINVAL
|
||||
goto childerror
|
||||
}
|
||||
if err2 != 0 {
|
||||
err1 = err2
|
||||
goto childerror
|
||||
}
|
||||
if _, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, uintptr(p[0]), 0, 0); err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// Get pid of child
|
||||
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
|
||||
if err1 != 0 {
|
||||
@ -141,31 +184,6 @@ func (r *Runner) Start() (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// If usernamespace is unshared, uid map and gid map is required to create folders
|
||||
// and files
|
||||
// Notice: This is not working right now since unshare user namespace drops all
|
||||
// capabilities, thus this operation will fail to do this
|
||||
// Thus, we need parent to setup uid_map / gid_map for us
|
||||
// At the same time, socket pair / pipe sychronization is required as well
|
||||
for _, f := range files {
|
||||
r1, _, err1 = syscall.RawSyscall6(syscall.SYS_OPENAT, uintptr(_AT_FDCWD),
|
||||
uintptr(unsafe.Pointer(f.fileName)), uintptr(fileOption), uintptr(filePerm), 0, 0)
|
||||
if err1 == syscall.ENOENT { // Kernel > 3.19 for setgroups
|
||||
continue
|
||||
} else if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_WRITE, r1, uintptr(unsafe.Pointer(&f.fileContent[0])),
|
||||
uintptr(len(f.fileContent)))
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_CLOSE, r1, 0, 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// mount tmpfs & chdir to new root before performing mounts
|
||||
if pivotRoot != nil {
|
||||
// mount("tmpfs", root, "tmpfs", 0, "")
|
||||
@ -301,14 +319,6 @@ func (r *Runner) Start() (int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// stop before execve syscall
|
||||
if r.StopBeforeExec {
|
||||
_, _, err1 = syscall.RawSyscall(syscall.SYS_KILL, pid, uintptr(syscall.SIGSTOP), 0)
|
||||
if err1 != 0 {
|
||||
goto childerror
|
||||
}
|
||||
}
|
||||
|
||||
// at this point, runner is successfully attached for seccomp trap filter
|
||||
// or execve traped without seccomp filter
|
||||
// time to exec
|
||||
|
||||
@ -41,13 +41,9 @@ type Runner struct {
|
||||
// right before the calls to seccomp. It is automatically enabled when seccomp
|
||||
// filter and ptrace are provided since kill might not be avaliable after
|
||||
// seccomp and execve might be traced by ptrace
|
||||
// cannot stop after seccomp since kill might not be allowed by seccomp filter
|
||||
StopBeforeSeccomp bool
|
||||
|
||||
// stop before exec calls kill(getpid(), SIGSTOP) to wait for tracer to continue
|
||||
// right before the final calls to execve. It acts as a synchronize
|
||||
// for parent process
|
||||
StopBeforeExec bool
|
||||
|
||||
// unshare flag to create linux namespace, effective when clone child
|
||||
// since unshare syscall does not join the new pid group
|
||||
UnshareFlags uintptr
|
||||
|
||||
@ -6,37 +6,36 @@ import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const (
|
||||
fileOption = unix.O_RDWR
|
||||
filePerm = 0755
|
||||
)
|
||||
|
||||
var (
|
||||
uidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'u', 'i', 'd', '_', 'm', 'a', 'p', 0}
|
||||
gidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'g', 'i', 'd', '_', 'm', 'a', 'p', 0}
|
||||
setGroups = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 's', 'e', 't', 'g', 'r', 'o', 'u', 'p', 's', 0}
|
||||
)
|
||||
|
||||
type fileWriteSyscall struct {
|
||||
fileName *byte
|
||||
fileContent []byte
|
||||
// writeUidGidMappings writes User ID and Group ID mappings for user namespaces
|
||||
// for a process and it is called from the parent process.
|
||||
func writeIDMaps(pid int) error {
|
||||
pidStr := strconv.Itoa(pid)
|
||||
uidStr := strconv.Itoa(unix.Geteuid())
|
||||
gidStr := strconv.Itoa(unix.Getegid())
|
||||
if err := writeFile("/proc/"+pidStr+"/uid_map", []byte("0 "+uidStr+" 1")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile("/proc/"+pidStr+"/setgroups", []byte("deny")); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := writeFile("/proc/"+pidStr+"/gid_map", []byte("0 "+gidStr+" 1")); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareIDMap(userNs bool) []fileWriteSyscall {
|
||||
ret := make([]fileWriteSyscall, 0, 3)
|
||||
if userNs {
|
||||
ret = append(ret, fileWriteSyscall{
|
||||
fileName: &uidMap[0],
|
||||
fileContent: []byte("0 " + strconv.Itoa(unix.Geteuid()) + " 1"),
|
||||
})
|
||||
ret = append(ret, fileWriteSyscall{
|
||||
fileName: &gidMap[0],
|
||||
fileContent: []byte("0 " + strconv.Itoa(unix.Getegid()) + " 1"),
|
||||
})
|
||||
ret = append(ret, fileWriteSyscall{
|
||||
fileName: &setGroups[0],
|
||||
fileContent: []byte("deny"),
|
||||
})
|
||||
// writeFile writes file
|
||||
func writeFile(path string, content []byte) error {
|
||||
fd, err := unix.Open(path, unix.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ret
|
||||
if _, err := unix.Write(fd, content); err != nil {
|
||||
unix.Close(fd)
|
||||
return err
|
||||
}
|
||||
if err := unix.Close(fd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
|
||||
WorkDir: r.WorkDir,
|
||||
Seccomp: bpf,
|
||||
NoNewPrivs: true,
|
||||
StopBeforeExec: true,
|
||||
StopBeforeSeccomp: true,
|
||||
UnshareFlags: UnshareFlags,
|
||||
Mounts: r.Mounts,
|
||||
PivotRoot: r.Root,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user