xianlubird / mydocker

<<自己动手写docker>> 源码

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

error about User Namespace

lvxingzhe opened this issue · comments

使用root权限运行go run main.go,"main.go"如下所示:

//main.go
package main

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

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
	}
	cmd.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(1), Gid: uint32(1)}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	os.Exit(-1)
}

会出现下列报错

2017/07/30 01:27:36 fork/exec /bin/sh: operation not permitted
exit status 1

谁能解答一下?

请提供下你运行的操作系统的版本和内核的版本,我们复现下

Linux kernel在3.19以上的版本中对user namespace做了些修改,我怀疑跟这个有关,链接是:https://go-review.googlesource.com/c/10670/
我们当时开发验证的操作系统版本和内核版本是ubuntu 14.04kernel-3.13,可以切换成同样的环境验证,或者你如果有在高版本内核上兼容的实现方案,欢迎提交PR。

刚才在4.4的内核上测试了下那个代码,这样修改就可以在4.4内核正确运行了,修改后的代码是:

//main.go
package main

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

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
			syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1234,
				HostID:      0,
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 1234,
				HostID:      0,
				Size:        1,
			},
		},
	}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	os.Exit(-1)
}

请确认是否解决,我先关闭这个issue了,有问题可以随时在Open这个issue

我的环境:

# uname -a
Linux drjr-ThinkPad-T520 4.4.0-92-generic #115-Ubuntu SMP Thu Aug 10 09:04:33 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

我通过删除该代码成功运行了书中的示例。
被删除的代码
cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uiint32(1), Gid: uint32(1)}
运行展示

# go run main.go 
$ id
uid=65534(nobody) gid=65534(nogroup) 组=65534(nogroup)

代码如下:

package main

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

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
			syscall.CLONE_NEWUSER,
	}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	os.Exit(-1)
}

@luhuisicnu 是的,4.4的内核不在支持cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uiint32(1), Gid: uint32(1)}的方式

commented

@BSWANG 补充下,在ubuntu 16.04 - 4.13内核上,你上面给出的代码也不能通过了

@riclava 尴尬,kernel在更新时有些兼容性可能不能保证,你如果发现了在4.13上或者兼容所有的解决办法欢迎贡献代码给我们

commented

@BSWANG 尴尬尴尬😅 我的问题,不知什么时候退了sudo -s了;不过 golang 的 issue#10626 上面的代码可以借鉴(非root也能跑,当然部分Namespace没有权限):

UidMappings: []syscall.SysProcIDMap{
	{
		ContainerID: 1234,
		HostID:      syscall.Getuid(),
		Size:        1,
	},
},
GidMappings: []syscall.SysProcIDMap{
	{
		ContainerID: 1234,
		HostID:      syscall.Getgid(),
		Size:        1,
	},
},

效果:

ricl@ricl:~/code/playground$ make
go run main.go
$ id
uid=1234 gid=1234 groups=1234,65534(nogroup)
$

尴尬 ;-(

上面两种方法都试了,不行呢?

[root@15-pxe Docker]# uname -a
Linux 15-pxe 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

我的也是上面两种都试了 不行 Linux iZj6c5dly2y6k9y0thzjoqZ 3.10.0-693.2.2.el7.x86_64 #1 SMP Tue Sep 12 22:26:13 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

Linux VM_86_181_centos 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
fork/exec /usr/bin/sh: invalid argument
目前还未解决

@v1xingyue @Arbusz
centos默认的没有开启user namespace,参考链接
https://zhuanlan.zhihu.com/p/31871814
golang/go#16283

commented

grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
执行后要reboot

commented

centos7

[root@mydocker chapter2]# uname -a
Linux mydocker 3.10.0-957.10.1.el7.x86_64 #1 SMP Mon Mar 18 15:06:45 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

如果已经内核开启user_namespace依旧invalid argument的话
使用下面的命令fixed

echo 640 > /proc/sys/user/max_user_namespaces

https://unix.stackexchange.com/questions/479635/unable-to-create-user-namespace-in-rhel?rq=1

内核:4.15.0-47-generic
系统:Ubuntu 18.04.2 LTS

func main() {
	cmd := exec.Command("sh")
	cmd.SysProcAttr = &syscall.SysProcAttr{
		// Cloneflags: syscall.CLONE_NEWUTS,
		// Cloneflags: syscall.CLONE_NEWIPC,
		// Cloneflags: syscall.CLONE_NEWPID,
		// Cloneflags: syscall.CLONE_NEWNS,
		Cloneflags: syscall.CLONE_NEWUSER,
	}
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Fatal(err)
	}
	os.Exit(0)
}

除了CLONE_NEWUSER外,其他均无法用普通用户权限执行,但CLONE_NEWUSER | CLONE_XXXXX可以。

此外,关于UID和GID的设置,在我的测试系统中有如下发现:

	cmd.SysProcAttr = &syscall.SysProcAttr{
		Cloneflags: syscall.CLONE_NEWUSER,
		/*
			以下两种情况,会导致UidMappings/GidMappings中设置了非当前进程所属UID和GID的相关数值:
			1. HostID非本进程所有(与Getuid()和Getgid()不等)
			2. Size大于1 (则肯定包含非当前进程的UID和GID)
			则需要Host机使用Root权限才能正常执行此段代码。
		*/
		UidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 10086,
				HostID:      syscall.Getuid(),
				Size:        1,
			},
			{
				ContainerID: 10010,
				HostID:      syscall.Getgid() + 1,
				Size:        1,
			},
		},
		GidMappings: []syscall.SysProcIDMap{
			{
				ContainerID: 10086,
				HostID:      syscall.Getgid(),
				Size:        1,
			},
			{
				ContainerID: 10010,
				HostID:      syscall.Getgid() + 1,
				Size:        1,
			},
		},
	}

以上规则应该是处于安全的考虑

uname -a
Linux ubuntu 3.19.0-80-generic #88~14.04.1-Ubuntu SMP Fri Jan 13 14:54:07 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

把原书中cmd.SysProcAttr.Credential = &syscall.Credential{Uid: uint32(1), Gid: uint32(1)}这行去掉跑出了结果,其他方案都不行

我的是centos7.3 ,Linux localhost.localdomain 3.10.0-514.el7.x86_64 #1 SMP Tue Nov 22 16:42:41 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux,执行usernamespace的报错

@wangpu123 centos 7是不支持user namespace的

echo 640 > /proc/sys/user/max_user_namespaces

靠谱,centos 7上验证可行

系统版本: Linux VM-0-10-centos 3.10.0-1127.13.1.el7.x86_64 #1 SMP Tue Jun 23 15:46:38 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
按照此修改后可用

sh-4.2$ id
uid=1234 gid=1234 组=1234

系统:linux 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
go: 1.12
两个版本都可以用。。。

commented

grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
执行后要reboot

阿里云 ECS 上 cent os 7.x 的 userns 打开过程很奇怪,显示执行一下指令:

grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"

后报错:

grubby: failed to open /etc/mtab: No such file or directory

提交工单后,重启了一下,居然就好了......

之后的执行过程就很顺利了。

这个问题在CentoOS 7上的解决方案

go version: go1.15.2 linux/amd64

CentOS 7 kernelLinux work 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux默认没有开启UserNamespace.

下述指令以及代码皆使用root用户运行

  • 开启UserNamespace

       # grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
       # reboot
       # echo 640 > /proc/sys/user/max_user_namespaces
    
  • 关闭UserNamespace

     # grubby --remove-args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
     # reboot
    
  • 代码

package main

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

func main() {
        cmd := exec.Command("sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
                        syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
                UidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      0,
                                Size:        1,
                        },
                },
                GidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      0,
                                Size:        1,
                        },
                },
        }
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr

        if err := cmd.Run(); err != nil {
                log.Fatal(err)
        }
        os.Exit(-1)
}

这个问题在CentoOS 7上的解决方案

go version: go1.15.2 linux/amd64

CentOS 7 kernelLinux work 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux默认没有开启UserNamespace.

下述指令以及代码皆使用root用户运行

  • 开启UserNamespace
       # grubby --args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
       # reboot
       # echo 640 > /proc/sys/user/max_user_namespaces
    
  • 关闭UserNamespace
     # grubby --remove-args="user_namespace.enable=1" --update-kernel="$(grubby --default-kernel)"
     # reboot
    
  • 代码
package main

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

func main() {
        cmd := exec.Command("sh")
        cmd.SysProcAttr = &syscall.SysProcAttr{
                Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID |
                        syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
                UidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      0,
                                Size:        1,
                        },
                },
                GidMappings: []syscall.SysProcIDMap{
                        {
                                ContainerID: 0,
                                HostID:      0,
                                Size:        1,
                        },
                },
        }
        cmd.Stdin = os.Stdin
        cmd.Stdout = os.Stdout
        cmd.Stderr = os.Stderr

        if err := cmd.Run(); err != nil {
                log.Fatal(err)
        }
        os.Exit(-1)
}

Linux izwz957vusqd7tgu3cd2eyz 3.10.0-514.26.2.el7.x86_64 #1 SMP Tue Jul 4 15:04:05 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
CentOS Linux release 7.3.1611 (Core)
go version : go version go1.13.1 linux/amd64

如果要执行以上代码需要加上:

	cmd.SysProcAttr.Credential = &syscall.Credential{
		Uid: uint32(0),
		Gid: uint32(0),
	}

不然会报错operation not permitted

@v1xingyue @Arbusz
centos默认的没有开启user namespace,参考链接
https://zhuanlan.zhihu.com/p/31871814
golang/go#16283

你好,我当前项目中也碰到这个问题。请问 "fork/exec /bin/bash: invalid argument" 错误和开启 user_namespace 有什么文章依据吗?目前由于文献依据,团队leader不是很认可这个解决方案。

Exec Error: fork/exec /usr/bin/sh: invalid argument

Code:
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC | syscall.CLONE_NEWPID | syscall.CLONE_NEWNS |
syscall.CLONE_NEWUSER,
}

System: Linux 3.10.0-1160.71.1.el7.x86_64 #1 SMP Tue Jun 28 15:37:28 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

The command "echo 640 > /proc/sys/user/max_user_namespaces" can resolve the exec error.