Ubuntu18.04运行Go实现容器代码报fork/exec权限错误排查
问题背景
- 学习场景:跟随Liz Rice在GOTO 2018分享的《Containers From Scratch》教程,本地测试开源仓库lizrice/containers-from-scratch源码,理解容器底层实现原理
- 测试环境:Ubuntu 18.04
- 测试代码
main1.go内容如下:
package main import ( "fmt" "os" "os/exec" "syscall" ) // go run main.go run <cmd> <args> func main() { switch os.Args[1] { case "run": run() case "child": child() default: panic("help") } } func run() { fmt.Printf("In <Run> Running %v \n", os.Args[2:]) cmd := exec.Command("/proc/self/exe", append([]string{"child"}, os.Args[2:]...)...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.SysProcAttr = &syscall.SysProcAttr{ Cloneflags: syscall.CLONE_NEWUTS, } must(cmd.Run()) } func child() { fmt.Printf("In <child> Running %v \n", os.Args[2:]) cmd := exec.Command(os.Args[2], os.Args[3:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr syscall.Sethostname([]byte("container")) must(cmd.Run()) } func must(err error) { if err != nil { panic(err) } }
故障现象
在普通用户权限下执行命令go run main1.go run /bin/bash,触发如下报错:
In <Run> Running [/bin/bash] panic: fork/exec /proc/self/exe: operation not permitted goroutine 1 [running]: main.must(...) /home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:72 main.run() /home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:44 +0x27f main.main() /home/jia/cs_study_plan/docker/containers-from-scratch/main1.go:17 +0x4d exit status 2
已完成排查操作
- 尝试先编译二进制再提权执行,可正常运行,操作流程如下(原步骤4存在笔误,
gp build应为go build):
# 进入main1.go所在目录 # 初始化go模块(模块名与文件名保持一致) go mod init main1 # 拉取依赖(可选步骤) go mod tidy # 编译生成二进制文件 go build # 提权执行二进制 sudo ./main1 run /bin/bash
执行成功后输出:
In <Run> Running [/bin/bash] In <child> Running [/bin/bash]
此时执行ps查看进程列表输出如下:
PID TTY TIME CMD 15774 pts/0 00:00:00 sudo 15775 pts/0 00:00:00 main1 15781 pts/0 00:00:00 exe 15786 pts/0 00:00:00 bash 16047 pts/0 00:00:00 ps
- 尝试停用系统AppArmor服务排除拦截影响,停用后直接执行
go run main1.go run echo hello仍返回相同的operation not permitted错误。
问题根因
- 核心权限要求:代码中调用
clone系统调用时携带syscall.CLONE_NEWUTS标记创建独立UTS命名空间,属于Linux内核定义的特权操作,要求执行进程持有CAP_SYS_ADMIN权能,普通用户默认不具备该权限。该错误是内核权能检查阶段直接返回的,不属于AppArmor等安全模块的拦截范围,因此停用AppArmor无法解决问题。 go run执行逻辑差异:go run不会在当前目录输出永久可执行文件,而是先将源码编译到/tmp目录下的随机临时路径,再以执行go run命令的当前用户身份启动该临时二进制,全程不会自动提权。普通用户身份启动的进程没有创建命名空间所需的特权,因此必然报错。而手动编译二进制后通过sudo执行时,进程以root身份运行,持有全部所需权能,因此可以正常创建命名空间、fork子进程。
直接使用
go run执行的修复方案 - 最简便稳妥的方案:执行
go run时直接加sudo提权即可,命令示例:
该方式下编译、执行全流程均以root身份运行,持有sudo go run main1.go run /bin/bashCAP_SYS_ADMIN权能,可正常完成命名空间创建操作,无额外系统配置改动,测试完成后不会留下安全配置残留。 - 不推荐的测试方案:修改系统内核参数允许非特权用户创建命名空间,该操作会降低系统安全边界,仅建议在完全隔离的虚机测试环境中使用,生产环境和日常办公环境禁止配置。
内容的提问来源于stack exchange,提问作者Jia
相关产品推荐
相关产品推荐

