fixed unshared user namespace

This commit is contained in:
criyle 2019-07-14 16:45:50 -07:00
parent 4a120f9a3d
commit d260bf4657
5 changed files with 95 additions and 88 deletions

View File

@ -35,9 +35,11 @@ Packages:
- seccomp: provides utility function that wrappers libseccomp - seccomp: provides utility function that wrappers libseccomp
- mount: provides utility function that wrappers mount syscall - 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 - 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
- runconfig: defines arch & language specified trace condition - runconfig: defines arch & language specified trace condition
Executable: Executable:

View File

@ -24,8 +24,10 @@ func afterForkInChild()
//go:norace //go:norace
func (r *Runner) Start() (int, error) { func (r *Runner) Start() (int, error) {
var ( var (
err1 syscall.Errno err1, err2 syscall.Errno
r1 uintptr r1 uintptr
p [2]int
unshareUser = r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER
) )
argv0, argv, envv, err := prepareExec(r.Args, r.Env) argv0, argv, envv, err := prepareExec(r.Args, r.Env)
@ -51,8 +53,12 @@ func (r *Runner) Start() (int, error) {
return 0, nil return 0, nil
} }
// prepare set uid / gid map files if unshareUser {
files := prepareIDMap(r.UnshareFlags&unix.CLONE_NEWUSER == unix.CLONE_NEWUSER) err = unix.Pipe2(p[:], unix.O_CLOEXEC)
if err != nil {
return 0, err
}
}
// similar to exec_linux, avoid side effect by shuffling around // similar to exec_linux, avoid side effect by shuffling around
fd, nextfd := prepareFds(r.Files) fd, nextfd := prepareFds(r.Files)
@ -71,8 +77,19 @@ 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
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 { if err1 != 0 {
return int(pid), syscall.Errno(err1) return int(pid), syscall.Errno(err1)
} }
@ -83,6 +100,32 @@ func (r *Runner) Start() (int, error) {
afterForkInChild() afterForkInChild()
// Notice: cannot call any GO functions beyond this point // 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 // Get pid of child
pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0) pid, _, err1 = syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0)
if err1 != 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 // mount tmpfs & chdir to new root before performing mounts
if pivotRoot != nil { if pivotRoot != nil {
// mount("tmpfs", root, "tmpfs", 0, "") // 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 // at this point, runner is successfully attached for seccomp trap filter
// or execve traped without seccomp filter // or execve traped without seccomp filter
// time to exec // time to exec

View File

@ -41,13 +41,9 @@ type Runner struct {
// right before the calls to seccomp. It is automatically enabled when seccomp // right before the calls to seccomp. It is automatically enabled when seccomp
// filter and ptrace are provided since kill might not be avaliable after // filter and ptrace are provided since kill might not be avaliable after
// seccomp and execve might be traced by ptrace // seccomp and execve might be traced by ptrace
// cannot stop after seccomp since kill might not be allowed by seccomp filter
StopBeforeSeccomp bool 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 // unshare flag to create linux namespace, effective when clone child
// since unshare syscall does not join the new pid group // since unshare syscall does not join the new pid group
UnshareFlags uintptr UnshareFlags uintptr

View File

@ -6,37 +6,36 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
const ( // writeUidGidMappings writes User ID and Group ID mappings for user namespaces
fileOption = unix.O_RDWR // for a process and it is called from the parent process.
filePerm = 0755 func writeIDMaps(pid int) error {
) pidStr := strconv.Itoa(pid)
uidStr := strconv.Itoa(unix.Geteuid())
var ( gidStr := strconv.Itoa(unix.Getegid())
uidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'u', 'i', 'd', '_', 'm', 'a', 'p', 0} if err := writeFile("/proc/"+pidStr+"/uid_map", []byte("0 "+uidStr+" 1")); err != nil {
gidMap = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 'g', 'i', 'd', '_', 'm', 'a', 'p', 0} return err
setGroups = [...]byte{'/', 'p', 'r', 'o', 'c', '/', 's', 'e', 'l', 'f', '/', 's', 'e', 't', 'g', 'r', 'o', 'u', 'p', 's', 0} }
) if err := writeFile("/proc/"+pidStr+"/setgroups", []byte("deny")); err != nil {
return err
type fileWriteSyscall struct { }
fileName *byte if err := writeFile("/proc/"+pidStr+"/gid_map", []byte("0 "+gidStr+" 1")); err != nil {
fileContent []byte return err
}
return nil
} }
func prepareIDMap(userNs bool) []fileWriteSyscall { // writeFile writes file
ret := make([]fileWriteSyscall, 0, 3) func writeFile(path string, content []byte) error {
if userNs { fd, err := unix.Open(path, unix.O_RDWR, 0)
ret = append(ret, fileWriteSyscall{ if err != nil {
fileName: &uidMap[0], return err
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"),
})
} }
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
} }

View File

@ -42,7 +42,7 @@ func (r *RunUnshared) Start() (rt tracer.TraceResult, err error) {
WorkDir: r.WorkDir, WorkDir: r.WorkDir,
Seccomp: bpf, Seccomp: bpf,
NoNewPrivs: true, NoNewPrivs: true,
StopBeforeExec: true, StopBeforeSeccomp: true,
UnshareFlags: UnshareFlags, UnshareFlags: UnshareFlags,
Mounts: r.Mounts, Mounts: r.Mounts,
PivotRoot: r.Root, PivotRoot: r.Root,