Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

syscall: CLONE_NEWUSER from unprivileged user #10626

Closed
LK4D4 opened this issue Apr 30, 2015 · 21 comments
Closed

syscall: CLONE_NEWUSER from unprivileged user #10626

LK4D4 opened this issue Apr 30, 2015 · 21 comments
Milestone

Comments

@LK4D4
Copy link
Contributor

@LK4D4 LK4D4 commented Apr 30, 2015

It should be possible to use CLONE_NEWUSER from unprivileged user, but somehow it isn't. Code:

package main

import (
        "log"
        "os"
        "os/exec"
        "syscall"
)

func main() {
        cmd := exec.Command(os.Args[1], os.Args[2:]...)
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr
        cmd.Stdin = os.Stdin
        cmd.SysProcAttr = &syscall.SysProcAttr{}
        cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
        cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{
                {ContainerID: 0, HostID: 1000, Size: 1},
        }
        cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{
                {ContainerID: 0, HostID: 1000, Size: 1},
        }
        if err := cmd.Run(); err != nil {
                log.Fatal(err)
        }
}

On ./unshare /bin/zsh returns

2015/04/29 20:14:55 fork/exec /bin/zsh: operation not permitted

Strace shows:

[pid 27482] open("/proc/27481/uid_map", O_RDWR <unfinished ...>
[pid 27480] <... select resumed> )      = 0 (Timeout)
[pid 27482] <... open resumed> )        = 5
[pid 27480] select(0, NULL, NULL, NULL, {0, 20} <unfinished ...>
[pid 27482] write(5, "0 1000 1\n\0", 10) = 10
[pid 27482] close(5)                    = 0
[pid 27480] <... select resumed> )      = 0 (Timeout)
[pid 27482] open("/proc/27481/gid_map", O_RDWR <unfinished ...>
[pid 27480] select(0, NULL, NULL, NULL, {0, 20} <unfinished ...>
[pid 27482] <... open resumed> )        = 5
[pid 27482] write(5, "0 1000 1\n\0", 10) = -1 EPERM (Operation not permitted)

Code similar to forkAndExecInChild from syscall/exec_linux.go is in man user_namespaces: https://gist.github.com/31920b19eb18cf4b507d
I compiled it with clang clone.c -o clone and run as ./clone -Uz /bin/zsh. It works from unprivileged user and mapping uids/gids inside namespace.
It is pretty cool feature, because it allows unprivileged users to create own namespaces.
ping @mrunalp as author of UidMappings.

For reproducing I used go from tag go1.4.2 on x86_64 Gentoo linux with 4.0.0 kernel.

@minux
Copy link
Member

@minux minux commented Apr 30, 2015

Are you sure your uid/gid are both 1000?

$ cat issue10626.go
package main

import (
"log"
"os"
"os/exec"
"syscall"
)

func main() {
cmd := exec.Command(os.Args[1], os.Args[2:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{
{ContainerID: 0, HostID: syscall.Getuid(), Size: 1},
}
cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{
{ContainerID: 0, HostID: syscall.Getgid(), Size: 1},
}
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
}
$ go build issue10626.go
$ ./issue10626 /bin/bash

id

uid=0(root) gid=0(root) groups=0(root),65534(nobody)

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Apr 30, 2015

@minux Yeah, I'm changed code as you suggested(Getuid/Getgid), result is same. What kernel do you use?

@minux
Copy link
Member

@minux minux commented Apr 30, 2015

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Apr 30, 2015

Actually, since 3.19 kernel code from forkAndExecInChild can't work with writing map_gid because of /proc/self/setgroups. You can find reference in newest man user_namespace. But I can't reach even that point :(

@mrunalp
Copy link
Contributor

@mrunalp mrunalp commented Apr 30, 2015

I am seeing the same issue as @LK4D4 (on 3.19.4) and I am thinking that this might be probably related to the setgroups code torvalds/linux@9cc4651

@mrunalp
Copy link
Contributor

@mrunalp mrunalp commented Apr 30, 2015

@LK4D4 yes, at minimum the go code has to add to check for the presence of setgroups and write deny into it.

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Apr 30, 2015

@mrunalp Actually I tried to insert setgroups code to syscall/exec_linux.go and get permission denied on writing there. Also, C code without setgroups failing on gid_map, not uid_map.
But still very possible that related, because seems like it broken since 3.19.

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Apr 30, 2015

Also I tried to reimplement this with unshare. I used unshare hack from #10051 (comment) and /proc/self/setgroups was writable! But uid_map still wasn't.

@mdempsky mdempsky changed the title CLONE_NEWUSER from unprivileged user syscall: CLONE_NEWUSER from unprivileged user Apr 30, 2015
@ianlancetaylor ianlancetaylor added this to the Go1.5Maybe milestone Jun 3, 2015
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 3, 2015

It would be nice to fix this problem, but I don't understand what is causing it. Can somebody running kernel 3.19 or later figure this out? For example, if somebody can demonstrate a working C program, we may be able to modify the syscall package in the same way.

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Jun 3, 2015

@ianlancetaylor There is working C program in http://man7.org/linux/man-pages/man7/user_namespaces.7.html
But as I see we doing exactly the same apart from setgroups. I'll try to bisect kernel to find which commit brought this problem.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 3, 2015

Thanks. It seems clear from the man page that we must write "deny" to /proc/PID/setgroups before writing to /proc/PID/gid_map. That seems consistent with the problem in the initial issue report. But I see your earlier comment that when you tried that, it failed. Can you share the patch that you tried?

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Jun 3, 2015

@ianlancetaylor Yup, in 30 minutes.

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Jun 3, 2015

@ianlancetaylor Hmm, sorry for confusion. It works for me now, maybe I did something wrong first time or maybe I updated kernel(now I use 4.0.4 and there was a lot of changes in 4.0.2).
Will it be ok to send this patch https://gist.github.com/anonymous/4a12b43dbce0007bfbef to golang now?

@gopherbot
Copy link

@gopherbot gopherbot commented Jun 3, 2015

CL https://golang.org/cl/10670 mentions this issue.

@aclements
Copy link
Member

@aclements aclements commented Jun 13, 2015

The fix for this seems to have broken the syscall test on my machine.

$ go test syscall
--- FAIL: TestCloneNEWUSERAndRemapNoRootDisableSetgroups-4 (0.00s)
    exec_linux_test.go:42: Cmd failed with err fork/exec /usr/bin/whoami: operation not permitted, output: 
FAIL
FAIL    syscall 0.029s
$ uname -a
Linux austin-glaptop 3.13.0-52-generic #86-Ubuntu SMP Mon May 4 04:32:59 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
$ id
uid=12166(austin) gid=5000(eng) groups=5000(eng),...
@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 13, 2015

Interesting. It fails on my system too. The test successfully opens /proc/PID/setgroups and writes "deny" to it. It then successfully opens /proc/PID/gid_map. However, the attempt to write to gid_map fails with EPERM.

@ianlancetaylor
Copy link
Contributor

@ianlancetaylor ianlancetaylor commented Jun 13, 2015

@mikioh mikioh modified the milestones: Go1.5, Go1.5Maybe Jun 16, 2015
@HackToday
Copy link

@HackToday HackToday commented Dec 4, 2015

Hi @LK4D4
for your comment:

@HackToday
Copy link

@HackToday HackToday commented Dec 4, 2015

Hi @LK4D4
for your comment:
@ianlancetaylor There is working C program in http://man7.org/linux/man-pages/man7/user_namespaces.7.html
But as I see we doing exactly the same apart from setgroups. I'll try to bisect kernel to find which commit brought this problem.

For your above comments,
I did not find it can work

Linux 3.19.0-30-generic
ubuntu@testtmp:$ id -u
1000
ubuntu@testtmp:
$ id -g
1000
ubuntu@testtmp:$ ./userns_child_exec -p -m -U -M '0 1000 1' -G '0 1000 1' bash
ERROR: write /proc/5629/gid_map: Operation not permitted
About to exec bash
ubuntu@testtmp:
$ root@testtmp:~#

Did you get that work in your env ?

@LK4D4
Copy link
Contributor Author

@LK4D4 LK4D4 commented Dec 4, 2015

@HackToday yeah, it should work with latest go versions.

@HackToday
Copy link

@HackToday HackToday commented Dec 4, 2015

@LK4D4 I tried the c program, it not worked, That was the guide issue, as said need to add
You must first be denied by writing "deny" to the /proc/[pid]/setgroups

And besides, I noticed, even I added this, the example in
http://manpages.ubuntu.com/manpages/wily/man7/user_namespaces.7.html

it would not have this result:
bash$cat /proc/$$/status | egrep '^[UG]id'
Uid: 0 0 0 0
Gid: 0 0 0 0
bash$ cat /proc/$$/status | egrep '^Cap(Prm|Inh|Eff)'
CapInh: 0000000000000000
CapPrm: 0000001fffffffff
CapEff: 0000001fffffffff

The result is as this

#cat /proc/$$/status | egrep '^[UG]id'
Uid: 65534 65534 65534 65534
Gid: 65534 65534 65534 65534
root@dockerexper:~# cat /proc/$$/status | egrep '^Cap(Prm|Inh|Eff)'
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff

Do you know why it is not mapped ?

@golang golang locked and limited conversation to collaborators Dec 14, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
8 participants
You can’t perform that action at this time.