Calling setns from Go returns EINVAL for the mnt namespace

C code works fine and correctly enters the namespace, but Go code always returns EINVAL from the setns call to enter the mnt namespace. I tried a few permutations (including C inline code using cgo and external .so ) on Go 1.2 , 1.3 and the current tip.

Running the code in gdb shows that both sequences call setns in libc exactly the same way (or so it seems to me).

I boiled what seemed to be related to the code below. What am I doing wrong?

Customization

I have a shell alias for running busybox fast containers:

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

After starting, startbb will start the container and display its PID.

lxc-checkconfig outputs:

 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 produces:

 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 

Work Code C

The following C code works fine:

 #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); } } 

After compiling with gcc -o checkns checkns.c output of 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 

Transition Code Error

Conversely, the following Go code (which should be identical) does not work either:

 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") } } } 

Instead, running sudo go run main.go <PID> calls:

 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 
+8
c linux go system-calls cgo
source share
1 answer

(the problem posed by the Go project )

So the answer to this question is that you are calling setns from a single-threaded context. This makes sense, since setns should attach the current thread to the namespace. Since Go is multithreaded, you need to make a setns call before the Go execution threads begin.

I think this is because the thread in which the syscall.RawSyscall call is syscall.RawSyscall is not the main thread - even with runtime.LockOSThread result is not what you expect (that is, the goroutine is "blocked" until the main thread C and therefore the equivalent of the constructor trick described below).

The answer I received after submitting the question suggested using the " cgo constructor cgo ". I could not find the “correct” documentation for this “trick”, but it is used in nsinit by Docker / Michael Crosby, and although I went through this code line by line, I did not try to run it this way (see below for frustration).

The “trick” is basically that you can get cgo to execute the C function before running Run run.

To do this, you add the __attribute__((constructor)) macro to decorate the function you want to run before Go starts:

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

Using this as a template, I modified checkns.go as follows:

 /* #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 ... 

This code works, but requires the PID be hard-coded, as it is not read properly from the command line input, but it illustrates the idea (and works if you provided the PID from the container running as described above).

This is disappointing because I wanted to call setns several times, but since this C code is executed before Run is run, Go code is missing.

Update:. Spanking on the kernel mailing lists provides this conversation link that documents this. I cannot find it in any published manpages, but here is a quote from the patch on setns(2) , confirmed by Eric Biederman:

A process cannot be re-associated with the new mount namespace if it is multi-threaded. Changing the mount namespace requires that the caller has both CAP_SYS_CHROOT and CAP_SYS_ADMIN capabilities in its own username space and CAP_SYS_ADMIN in the target namespace.

+5
source share

All Articles