mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
syscall: correctly set up uid/gid mappings in user namespaces
Before this CL, uid/gid mapping was always set up from the parent process, which is a privileged operation. When using unprivileged user namespaces, a process can modify its uid/gid mapping after the unshare(2) call (but setting the uid/gid mapping from another process is NOT possible). Fixes #29789 Change-Id: I8c96a03f5da23fe80bbb83ef051ad89cf185d750 Reviewed-on: https://go-review.googlesource.com/c/go/+/158298 Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org>
This commit is contained in:
parent
30cc8a46c4
commit
2bd28cee23
2 changed files with 122 additions and 19 deletions
|
|
@ -82,10 +82,13 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
|
||||||
|
|
||||||
if sys.UidMappings != nil || sys.GidMappings != nil {
|
if sys.UidMappings != nil || sys.GidMappings != nil {
|
||||||
Close(p[0])
|
Close(p[0])
|
||||||
err := writeUidGidMappings(pid, sys)
|
|
||||||
var err2 Errno
|
var err2 Errno
|
||||||
if err != nil {
|
// uid/gid mappings will be written after fork and unshare(2) for user
|
||||||
err2 = err.(Errno)
|
// namespaces.
|
||||||
|
if sys.Unshareflags&CLONE_NEWUSER == 0 {
|
||||||
|
if err := writeUidGidMappings(pid, sys); err != nil {
|
||||||
|
err2 = err.(Errno)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RawSyscall(SYS_WRITE, uintptr(p[1]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
|
RawSyscall(SYS_WRITE, uintptr(p[1]), uintptr(unsafe.Pointer(&err2)), unsafe.Sizeof(err2))
|
||||||
Close(p[1])
|
Close(p[1])
|
||||||
|
|
@ -142,12 +145,32 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
|
||||||
// Declare all variables at top in case any
|
// Declare all variables at top in case any
|
||||||
// declarations require heap allocation (e.g., err1).
|
// declarations require heap allocation (e.g., err1).
|
||||||
var (
|
var (
|
||||||
err2 Errno
|
err2 Errno
|
||||||
nextfd int
|
nextfd int
|
||||||
i int
|
i int
|
||||||
caps caps
|
caps caps
|
||||||
|
fd1 uintptr
|
||||||
|
puid, psetgroups, pgid []byte
|
||||||
|
uidmap, setgroups, gidmap []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sys.UidMappings != nil {
|
||||||
|
puid = []byte("/proc/self/uid_map\000")
|
||||||
|
uidmap = formatIDMappings(sys.UidMappings)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sys.GidMappings != nil {
|
||||||
|
psetgroups = []byte("/proc/self/setgroups\000")
|
||||||
|
pgid = []byte("/proc/self/gid_map\000")
|
||||||
|
|
||||||
|
if sys.GidMappingsEnableSetgroups {
|
||||||
|
setgroups = []byte("allow\000")
|
||||||
|
} else {
|
||||||
|
setgroups = []byte("deny\000")
|
||||||
|
}
|
||||||
|
gidmap = formatIDMappings(sys.GidMappings)
|
||||||
|
}
|
||||||
|
|
||||||
// Record parent PID so child can test if it has died.
|
// Record parent PID so child can test if it has died.
|
||||||
ppid, _ := rawSyscallNoError(SYS_GETPID, 0, 0, 0)
|
ppid, _ := rawSyscallNoError(SYS_GETPID, 0, 0, 0)
|
||||||
|
|
||||||
|
|
@ -264,6 +287,46 @@ func forkAndExecInChild1(argv0 *byte, argv, envv []*byte, chroot, dir *byte, att
|
||||||
if err1 != 0 {
|
if err1 != 0 {
|
||||||
goto childerror
|
goto childerror
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sys.Unshareflags&CLONE_NEWUSER != 0 && sys.GidMappings != nil {
|
||||||
|
dirfd := int(_AT_FDCWD)
|
||||||
|
if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&psetgroups[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&setgroups[0])), uintptr(len(setgroups)))
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
|
||||||
|
if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&pgid[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&gidmap[0])), uintptr(len(gidmap)))
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sys.Unshareflags&CLONE_NEWUSER != 0 && sys.UidMappings != nil {
|
||||||
|
dirfd := int(_AT_FDCWD)
|
||||||
|
if fd1, _, err1 = RawSyscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(&puid[0])), uintptr(O_WRONLY), 0, 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
r1, _, err1 = RawSyscall(SYS_WRITE, uintptr(fd1), uintptr(unsafe.Pointer(&uidmap[0])), uintptr(len(uidmap)))
|
||||||
|
if err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
if _, _, err1 = RawSyscall(SYS_CLOSE, uintptr(fd1), 0, 0); err1 != 0 {
|
||||||
|
goto childerror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The unshare system call in Linux doesn't unshare mount points
|
// The unshare system call in Linux doesn't unshare mount points
|
||||||
// mounted with --shared. Systemd mounts / with --shared. For a
|
// mounted with --shared. Systemd mounts / with --shared. For a
|
||||||
// long discussion of the pros and cons of this see debian bug 739593.
|
// long discussion of the pros and cons of this see debian bug 739593.
|
||||||
|
|
@ -480,6 +543,14 @@ func forkExecPipe(p []int) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatIDMappings(idMap []SysProcIDMap) []byte {
|
||||||
|
var data []byte
|
||||||
|
for _, im := range idMap {
|
||||||
|
data = append(data, []byte(itoa(im.ContainerID)+" "+itoa(im.HostID)+" "+itoa(im.Size)+"\n")...)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
// writeIDMappings writes the user namespace User ID or Group ID mappings to the specified path.
|
// writeIDMappings writes the user namespace User ID or Group ID mappings to the specified path.
|
||||||
func writeIDMappings(path string, idMap []SysProcIDMap) error {
|
func writeIDMappings(path string, idMap []SysProcIDMap) error {
|
||||||
fd, err := Open(path, O_RDWR, 0)
|
fd, err := Open(path, O_RDWR, 0)
|
||||||
|
|
@ -487,18 +558,7 @@ func writeIDMappings(path string, idMap []SysProcIDMap) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
data := ""
|
if _, err := Write(fd, formatIDMappings(idMap)); err != nil {
|
||||||
for _, im := range idMap {
|
|
||||||
data = data + itoa(im.ContainerID) + " " + itoa(im.HostID) + " " + itoa(im.Size) + "\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := ByteSliceFromString(data)
|
|
||||||
if err != nil {
|
|
||||||
Close(fd)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := Write(fd, bytes); err != nil {
|
|
||||||
Close(fd)
|
Close(fd)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -434,6 +434,49 @@ func TestUnshareMountNameSpaceChroot(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnshareUidGidMappingHelper(*testing.T) {
|
||||||
|
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Exit(0)
|
||||||
|
if err := syscall.Chroot(os.TempDir()); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for Issue 29789: unshare fails when uid/gid mapping is specified
|
||||||
|
func TestUnshareUidGidMapping(t *testing.T) {
|
||||||
|
if os.Getuid() == 0 {
|
||||||
|
t.Skip("test exercises unprivileged user namespace, fails with privileges")
|
||||||
|
}
|
||||||
|
checkUserNS(t)
|
||||||
|
cmd := exec.Command(os.Args[0], "-test.run=TestUnshareUidGidMappingHelper")
|
||||||
|
cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
|
||||||
|
GidMappingsEnableSetgroups: false,
|
||||||
|
UidMappings: []syscall.SysProcIDMap{
|
||||||
|
{
|
||||||
|
ContainerID: 0,
|
||||||
|
HostID: syscall.Getuid(),
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GidMappings: []syscall.SysProcIDMap{
|
||||||
|
{
|
||||||
|
ContainerID: 0,
|
||||||
|
HostID: syscall.Getgid(),
|
||||||
|
Size: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cmd failed with err %v, output: %s", err, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type capHeader struct {
|
type capHeader struct {
|
||||||
version uint32
|
version uint32
|
||||||
pid int32
|
pid int32
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue