├── .gitignore ├── Makefile ├── doin ├── README.md └── doin.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | .*.sw[po] 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | PREFIX ?= /usr/local 3 | 4 | all: doin.so 5 | 6 | doin.so: doin.c 7 | $(CC) $(CFLAGS) -shared -fPIC -Wall -o $@ $< -ldl 8 | 9 | clean: 10 | $(RM) doin.so 11 | 12 | install: doin.so 13 | install -Dm755 doin.so $(DESTDIR)$(PREFIX)/bin/doin.so 14 | install -Dm755 doin $(DESTDIR)$(PREFIX)/bin/doin 15 | 16 | .PHONY: install 17 | -------------------------------------------------------------------------------- /doin: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | set -e 3 | 4 | die () { 5 | if [ -n "$1" ] ; then 6 | echo "$0: $1" 1>&2 7 | else 8 | echo "Usage: $(basename "$0") PID command..." 1>&2 9 | fi 10 | exit 1 11 | } 12 | 13 | if ! [ "x${UID}" = "x0" ] ; then 14 | if ! [ "x${EUID}" = "x0" ] ; then 15 | die "You need to be 'root' to use this program" 16 | fi 17 | fi 18 | [ -n "$1" ] || die 19 | [ -n "$2" ] || die 20 | kill -0 "$1" &> /dev/null || die "Invalid PID '$1'" 21 | 22 | export LD_PRELOAD="$(dirname "$0")/doin.so" 23 | export __DOIN_ATTACH_PID=$1 24 | shift 25 | exec "$@" 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | doin 2 | ==== 3 | 4 | Executes *any* binary in *any* namespace-based Linux container. 5 | Without platform-dependant kludges. 6 | 7 | Tested and working with: 8 | 9 | * [LXC](https://linuxcontainers.org/) 10 | * [Docker](http://www.docker.com/) 11 | 12 | (If you test `doin` with other namespace-based container system working, 13 | please post a PR to update this `README` file!) 14 | 15 | 16 | Usage 17 | ===== 18 | 19 | ```sh 20 | # Build doin.so (does not need root) 21 | make 22 | 23 | # Now as "root": 24 | PID=$(docker inspect --format='{{.State.Pid}}' my-minimal-container) 25 | ./doin $PID tree / 26 | ``` 27 | 28 | The latest line above is roughly equivalent to: 29 | 30 | ```sh 31 | LD_PRELOAD=$(pwd)/doin.so __DOIN_ATTACH_PID=${PID} tree / 32 | ``` 33 | 34 | doin vs. … 35 | ========== 36 | 37 | Infilter 38 | -------- 39 | 40 | [Infilter](https://github.com/yadutaf/infilter) uses the 41 | [ptrace()](http://linux.die.net/man/2/ptrace) system call to be notified when 42 | the program makes it first system calls, and modify CPU registers directly 43 | to inject calls to the `setns()` system call. On the other hand, `doin` works 44 | independently of the platform, as it does not access hardware features 45 | directly. 46 | 47 | 48 | How does it work? 49 | ================= 50 | 51 | The `doin.so` shared object is loaded by the dynamic linker before the rest of 52 | shared objects needed by the binary being executed — including the C library. 53 | The C library provides the 54 | [__libc_start_main](http://refspecs.linuxbase.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/baselib---libc-start-main-.html), 55 | which is called by the `_start` function of every program (which is where 56 | execution really starts, this code resides in `crt1.o` and is linked in every 57 | program) which `doin.so` overrides to arrange calls to 58 | [setns()](http://linux.die.net/man/2/setns) *before* calling the original 59 | `__libc_start_main` provided by the C library, which in turn will call the 60 | `main()` function in the program. 61 | 62 | 63 | License 64 | ======= 65 | 66 | [MIT](http://opensource.org/licenses/mit) 67 | 68 | -------------------------------------------------------------------------------- /doin.c: -------------------------------------------------------------------------------- 1 | /* 2 | * doin.c 3 | * Copyright (C) 2015 Adrian Perez 4 | * 5 | * Distributed under terms of the MIT license. 6 | */ 7 | 8 | #define _GNU_SOURCE 1 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #define LENGTH_OF(_v) (sizeof (_v) / sizeof ((_v)[0])) 20 | 21 | 22 | static const struct { 23 | const char *name; 24 | int flag; 25 | } s_namespaces[] = { 26 | { "pid", CLONE_NEWPID }, 27 | { "ipc", CLONE_NEWIPC }, 28 | { "net", CLONE_NEWNET }, 29 | { "uts", CLONE_NEWUTS }, 30 | { "mnt", CLONE_NEWNS }, 31 | { "user", CLONE_NEWUSER }, 32 | }; 33 | 34 | 35 | static int 36 | doin_error_main (int argc, char **argv, char **envp) 37 | { 38 | (void) argc; /* unused */; 39 | (void) argv; /* unused */; 40 | (void) envp; /* unused */; 41 | 42 | fprintf (stderr, "%s: %s '%s': %s\n", argv[0], argv[1], argv[2], argv[3]); 43 | return EXIT_FAILURE; 44 | } 45 | 46 | 47 | typedef int (*start_main_func) (int (*main) (int, char**, char**), 48 | int argc, char **ubp_av, 49 | void (*init) (void), 50 | void (*fini) (void), 51 | void (*rtld_fini) (void), 52 | void (*stack_end)); 53 | 54 | int __libc_start_main(int (*main) (int, char**, char**), 55 | int argc, char ** ubp_av, 56 | void (*init) (void), 57 | void (*fini) (void), 58 | void (*rtld_fini) (void), 59 | void (*stack_end)) 60 | { 61 | start_main_func orig_libc_start_main = dlsym (RTLD_NEXT, "__libc_start_main"); 62 | char *error_argv[] = { "doin", "error-kind", "error-file", "error-message", NULL }; 63 | 64 | const char *pid_string = getenv ("__DOIN_ATTACH_PID"); 65 | if (!pid_string) { 66 | error_argv[1] = "getenv"; 67 | error_argv[2] = "__DOIN_ATTACH_PID"; 68 | error_argv[3] = "no environment variable"; 69 | ubp_av = error_argv; 70 | main = doin_error_main; 71 | goto call_libc_start_main; 72 | } 73 | 74 | char path[PATH_MAX + 1]; 75 | for (unsigned i = 0; i < LENGTH_OF (s_namespaces); i++) { 76 | snprintf (path, PATH_MAX, "/proc/%s/ns/%s", pid_string, s_namespaces[i].name); 77 | 78 | /* 79 | * For error handling, we have to make sure that our function can 80 | * exit() properly, which means it has to be done inside main(). 81 | * We have to overwrite main with our own, which does the error 82 | * reporting. 83 | */ 84 | int fd = open (path, O_RDONLY); 85 | if (fd < 0) { 86 | if (errno == ENOENT) { 87 | /* Write with Unix syscalls, fprintf() cannot be used here. */ 88 | static const char warn_prefix[] = ": cannot join '"; 89 | static const char warn_suffix[] = "' namespace (unsupported by kernel)\n"; 90 | write (STDERR_FILENO, ubp_av[0], strlen (ubp_av[0])); 91 | write (STDERR_FILENO, warn_prefix, LENGTH_OF (warn_prefix)); 92 | write (STDERR_FILENO, s_namespaces[i].name, strlen (s_namespaces[i].name)); 93 | write (STDERR_FILENO, warn_suffix, LENGTH_OF (warn_suffix)); 94 | continue; 95 | } 96 | error_argv[1] = "cannot open"; 97 | error_argv[2] = path; 98 | error_argv[3] = strerror (errno); 99 | ubp_av = error_argv; 100 | main = doin_error_main; 101 | break; 102 | } 103 | if (setns (fd, s_namespaces[i].flag) < 0) { 104 | error_argv[1] = "setns()"; 105 | error_argv[2] = (char*) s_namespaces[i].name; 106 | error_argv[3] = strerror (errno); 107 | close (fd); 108 | ubp_av = error_argv; 109 | main = doin_error_main; 110 | break; 111 | } 112 | } 113 | 114 | call_libc_start_main: 115 | return (*orig_libc_start_main) (main, argc, ubp_av, init, fini, rtld_fini, stack_end); 116 | } 117 | --------------------------------------------------------------------------------