gpt4 book ai didi

从 Go 调用 setns 为 mnt 命名空间返回 EINVAL

转载 作者:IT老高 更新时间:2023-10-28 13:09:28 26 4
gpt4 key购买 nike

C 代码工作正常并正确进入命名空间,但 Go 代码似乎总是从 setns 返回 EINVAL调用电话进入mnt命名空间。我在 Go .so 上尝试了许多排列(包括带有 cgo 和外部 1.2 的嵌入式 C 代码) , 1.3和当前的提示。

单步执行gdb 中的代码表明两个序列都在调用 setnslibc完全相同的方式(或者在我看来)。

我将问题归结为下面的代码。我做错了什么?

设置

我有一个用于启动快速bu​​sybox容器的shell别名:

alias startbb='docker inspect --format "{{ .State.Pid }}" $(docker run -d busybox sleep 1000000)'

运行后,startbb将启动一个容器并输出它的 PID。

lxc-checkconfig输出:

Found kernel config file /boot/config-3.8.0-44-generic
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
Pid namespace: enabled
User namespace: missing
Network namespace: enabled
Multiple /dev/pts instances: enabled

--- Control groups ---
Cgroup: enabled
Cgroup clone_children flag: enabled
Cgroup device: enabled
Cgroup sched: enabled
Cgroup cpu account: enabled
Cgroup memory controller: missing
Cgroup cpuset: enabled

--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
File capabilities: enabled

uname -a产生:

Linux gecko 3.8.0-44-generic #66~precise1-Ubuntu SMP Tue Jul 15 04:01:04 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

工作 C 代码

以下 C 代码可以正常工作:

#include <errno.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

main(int argc, char* argv[]) {
int i;
char nspath[1024];
char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" };

if (geteuid()) { fprintf(stderr, "%s\n", "abort: you want to run this as root"); exit(1); }

if (argc != 2) { fprintf(stderr, "%s\n", "abort: you must provide a PID as the sole argument"); exit(2); }

for (i=0; i<5; i++) {
sprintf(nspath, "/proc/%s/ns/%s", argv[1], namespaces[i]);
int fd = open(nspath, O_RDONLY);

if (setns(fd, 0) == -1) {
fprintf(stderr, "setns on %s namespace failed: %s\n", namespaces[i], strerror(errno));
} else {
fprintf(stdout, "setns on %s namespace succeeded\n", namespaces[i]);
}

close(fd);
}
}

使用 gcc -o checkns checkns.c 编译后,sudo ./checkns <PID> 的输出是:

setns on ipc namespace succeeded
setns on uts namespace succeeded
setns on net namespace succeeded
setns on pid namespace succeeded
setns on mnt namespace succeeded

失败的 Go 代码

相反,以下 Go 代码(应该是相同的)不能很好地工作:

package main

import (
"fmt"
"os"
"path/filepath"
"syscall"
)

func main() {
if syscall.Geteuid() != 0 {
fmt.Println("abort: you want to run this as root")
os.Exit(1)
}

if len(os.Args) != 2 {
fmt.Println("abort: you must provide a PID as the sole argument")
os.Exit(2)
}

namespaces := []string{"ipc", "uts", "net", "pid", "mnt"}

for i := range namespaces {
fd, _ := syscall.Open(filepath.Join("/proc", os.Args[1], "ns", namespaces[i]), syscall.O_RDONLY, 0644)
err, _, msg := syscall.RawSyscall(308, uintptr(fd), 0, 0) // 308 == setns

if err != 0 {
fmt.Println("setns on", namespaces[i], "namespace failed:", msg)
} else {
fmt.Println("setns on", namespaces[i], "namespace succeeded")
}

}
}

相反,运行 sudo go run main.go <PID>产生:

setns on ipc namespace succeeded
setns on uts namespace succeeded
setns on net namespace succeeded
setns on pid namespace succeeded
setns on mnt namespace failed: invalid argument

最佳答案

(有an issue filed on the Go project)

所以,这个问题的答案是您必须从单线程上下文中调用 setns。这是有道理的,因为 setns 应该将当前线程加入命名空间。由于 Go 是多线程的,因此您需要在 Go 运行时线程启动之前进行 setns 调用。

认为这是因为执行syscall.RawSyscall调用的线程不是主线程——甚至withruntime.LockOSThread 结果不是你所期望的(即,goroutine 被“锁定”到主 C 线程,因此相当于下面解释的构造函数技巧)。

我在提交问题后得到的回复建议使用“cgo 构造函数技巧”。我在这个“技巧”上找不到任何“正确”的文档,但是 Docker/Michael Crosby 在 nsinit 中使用了它,即使我逐行查看了该代码,但我没有尝试以这种方式运行它(请参阅下面的挫败感)。

“诀窍”基本上是您可以让 cgo 在启动 Go 运行时之前执行 C 函数。

为此,您添加 __attribute__((constructor)) 宏来装饰您要在 Go 启动之前运行的函数:

/*
__attribute__((constructor)) void init() {
// this code will execute before Go starts up
// in runs in a single-threaded C context
// before Go's threads start running
}
*/
import "C"

使用这个作为模板,我修改了 checkns.go 如下:

/*
#include <sched.h>
#include <stdio.h>
#include <fcntl.h>

__attribute__((constructor)) void enter_namespace(void) {
setns(open("/proc/<PID>/ns/mnt", O_RDONLY, 0644), 0);
}
*/
import "C"

... rest of file is unchanged ...

此代码有效,但需要对 PID 进行硬编码,因为它没有从命令行输入正确读取,但它说明了这个想法(如果您提供 PID 来自如上所述启动的容器)。

这很令人沮丧,因为我想多次调用 setns 但由于此 C 代码在 Go 运行时开始之前执行,因此没有可用的 Go 代码。

更新: 在内核邮件列表中闲逛提供了 this link到记录这一点的对话。我似乎无法在任何实际发布的联机帮助页中找到它,但这是 setns(2) 补丁中的引述,由 Eric Biederman 确认:

A process may not be reassociated with a new mount namespace if it is multi-threaded. Changing the mount namespace requires that the caller possess both CAP_SYS_CHROOT and CAP_SYS_ADMIN capabilities in its own user namespace and CAP_SYS_ADMIN in the target mount namespace.

关于从 Go 调用 setns 为 mnt 命名空间返回 EINVAL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25704661/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com