├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── config.h ├── macros.h ├── test ├── testclient.c └── testserver.c ├── utils.c ├── utils.h ├── voidnsrun.c └── voidnsundo.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | .idea 3 | voidnsrun 4 | voidnsundo 5 | testclient 6 | testserver 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Evgeny Zinoviev 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | 3 | CFLAGS = -O2 -std=c99 -Wall -W 4 | LDFLAGS = 5 | 6 | INSTALL = /usr/bin/env install 7 | PREFIX = /usr/local 8 | 9 | all: 10 | @echo make run: build voidnsrun. 11 | @echo make install-run: install voidnsrun to $(PREFIX). 12 | @echo make undo: build voidnsundo. 13 | @echo make install-undo: install voidnsundo to $(PREFIX). 14 | 15 | test: testserver testclient 16 | 17 | run: voidnsrun.o utils.o 18 | $(CC) $(CFLAGS) -o voidnsrun $^ $(LDFLAGS) 19 | 20 | undo: voidnsundo.o utils.o 21 | $(CC) $(CFLAGS) -o voidnsundo $^ $(LDFLAGS) 22 | 23 | testserver: test/testserver.o utils.o 24 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 25 | 26 | testclient: test/testclient.o utils.o 27 | $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) 28 | 29 | install-run: run 30 | $(INSTALL) voidnsrun $(PREFIX)/bin 31 | chmod u+s $(PREFIX)/bin/voidnsrun 32 | 33 | install-undo: undo 34 | $(INSTALL) voidnsundo $(PREFIX)/bin 35 | chmod u+s $(PREFIX)/bin/voidnsundo 36 | 37 | clean: 38 | rm -f *.o test/*.o voidnsrun voidnsundo testserver testclient 39 | 40 | %.o: %.c 41 | $(CC) $(CFLAGS) -c $^ -I. -o $@ 42 | 43 | .PHONY: all run undo install-run install-undo clean -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voidnsrun 2 | 3 | **voidnsrun** is utility for launching programs in an isolated mount namespace 4 | with alternative `/usr` tree. Its primary goal is to run glibc programs in 5 | musl-libc Void Linux environments (or vice-versa, but who needs that?). 6 | 7 | It creates a new private mount namespace, transparently substitutes `/usr` and 8 | some other directories with directories from your glibc container using bind 9 | mounts, and launches your program. 10 | 11 | **voidnsundo**, to the contrary, is utility for launching programs in the parent 12 | mount namespace from within the mount namespace created by **voidnsrun**. 13 | 14 | ## Installation 15 | 16 | ### Creating glibc container 17 | 18 | As per the [Void documentation](https://docs.voidlinux.org/installation/musl.html#glibc-chroot), 19 | perform glibc base system installation to a separate new directory: 20 | ``` 21 | # mkdir /glibc 22 | # XBPS_ARCH=x86_64 xbps-install --repository=https://repo-default.voidlinux.org/current -r /glibc -S base-voidstrap 23 | ``` 24 | 25 | ### Installing voidnsrun 26 | 27 | Clone the repo, and then: 28 | 29 | ``` 30 | make run 31 | sudo make install-run 32 | ``` 33 | 34 | This will install **voidnsrun** to `/usr/local/bin`. 35 | 36 | Export path to the container: 37 | ``` 38 | export VOIDNSRUN_DIR=/glibc 39 | ``` 40 | 41 | Also export path to **voidnsundo**: 42 | ``` 43 | export VOIDNSUNDO_BIN=/usr/local/bin/voidnsundo 44 | ``` 45 | 46 | You may want to add these exports to your `~/.bashrc` or similar script. 47 | 48 | ### Installing voidnsundo 49 | 50 | **voidnsundo** is supposed to be used from within the glibc container, so it has 51 | to be linked with glibc. First, let's use just installed **voidnsrun** to install 52 | build dependencies into the container: 53 | ``` 54 | sudo voidnsrun -r /glibc xbps-install -Su 55 | sudo voidnsrun -r /glibc xbps-install make gcc 56 | ``` 57 | 58 | Then enter the container (the current working directory will be preserved by 59 | **voidnsrun** 1.2 or higher) and build, then install **voidnsundo**: 60 | ``` 61 | voidnsrun bash 62 | make clean 63 | make undo 64 | sudo make install-undo 65 | ``` 66 | 67 | This will install **voidnsundo** to `/usr/local/bin` in the container (which is 68 | `/glibc/usr/local/bin` in reality). 69 | 70 | Type `exit` or `Ctrl+D` to exit the container. 71 | 72 | ## Usage 73 | 74 | ### voidnsrun 75 | ``` 76 | Usage: voidnsrun [OPTIONS] PROGRAM [ARGS] 77 | 78 | Options: 79 | -r : Container path. When this option is not present, 80 | VOIDNSRUN_DIR environment variable is used. 81 | -m : Add bind mount. You can add up to 50 paths. 82 | -u : Add undo bind mount. You can add up to 50 paths. 83 | -d : Add /usr subdirectory bind mount. 84 | -U : Path to voidnsundo. When this option is not present, 85 | VOIDNSUNDO_BIN environment variable is used. 86 | -i: Don't treat missing source or target for added mounts as error. 87 | -V: Enable verbose output. 88 | -h: Print this help. 89 | -v: Print version. 90 | ``` 91 | 92 | **voidnsrun** needs to know the path to your glibc installation directory (or 93 | "container"), it can read it from the `VOIDNSRUN_DIR` environment variable or 94 | you can use `-r` argument to specify it. 95 | 96 | By default, **voidnsrun** binds only `/usr` from the container. But if you're 97 | launching `xbps-install`, `xbps-remove` or `xbps-reconfigure`and using 98 | **voidnsrun** version 1.1 or higher, it will bind `/usr`, `/var` and `/etc`. 99 | 100 | To bind something else, use the `-m` option. You can add up to 50 binds as of 101 | version 1.2. 102 | 103 | To bind a subdirectory from the host `/usr`, use the `-d` option (available 104 | since version 1.3). For example, instead of installing fonts into the container 105 | and therefore duplicating them and wasting your disk space, you can bind-mount 106 | `/usr/share/fonts` from the host. The rest of `/usr/` will be from the glibc 107 | container. 108 | 109 | There's also the `-u` option. It adds bind mounts of the **voidnsundo** binary 110 | inside the namespace. See more about this below in the **voidnsundo** bind mode 111 | section. Just like with the `-m` option, you can add up to 50 binds as of version 112 | 1.2. 113 | 114 | To bind the **voidnsundo** binary, **voidnsrun** has to know its path, and, like 115 | with the container's path, it reads it from the `VOIDNSUNDO_BIN` environment 116 | variable and from the `-U` option. 117 | 118 | ### voidnsundo 119 | 120 | ``` 121 | Usage: voidnsundo [OPTIONS] PROGRAM [ARGS] 122 | 123 | Options: 124 | -V: Enable verbose output. 125 | -h: Print this help. 126 | -v: Print version. 127 | ``` 128 | 129 | **voidnsundo** can be used in two modes. 130 | 131 | One is the **"normal" node**, when you invoke it like `voidnsundo [ARGS]` 132 | and your `PROGRAM` will be launched from and in the original mount namespace. 133 | 134 | For example, if you don't have a glibc version of firefox installed (so there's 135 | no `/usr/bin/firefox` in the container), but you want to launch the "real" (the 136 | one installed in your root musl system) firefox while being in the mount 137 | namespace, just do `voidnsundo /usr/bin/firefox`. 138 | 139 | The other mode is the **"bind" mode**. While in the container, and therefore in 140 | the new mount namespace, you can bind mount **voidnsundo** to any path (don't 141 | worry: it won't be visible outside the namespace), and, when invoked by that 142 | path, it will launch the corresponding executable in your parent (root) 143 | namespace. 144 | 145 | For example, being in the container, you can do this: 146 | ``` 147 | touch /usr/bin/firefox 148 | mount --bind /usr/local/bin/voidnsundo /usr/bin/firefox 149 | ``` 150 | and while there was no `/usr/bin/firefox` in the glibc container, after this, 151 | when you'll launch `/usr/bin/firefox`, the "real" firefox from the root musl 152 | system will be launched. 153 | 154 | The creation of this bind mounts of **voidnsundo** can be automated by using 155 | `-u` option of **voidnsrun**. 156 | 157 | ## Examples 158 | 159 | This section contains some real examples of how to use some proprietary glibc 160 | apps on your musl-libc Void Linux box. 161 | 162 | ### Vivaldi 163 | 164 | The first example is the Vivaldi browser. Let's assume you unpacked it to 165 | `/opt/vivaldi` (from rpm or deb package) and, obviously, it doesn't work. 166 | 167 | Try launching it with **voidnsrun**: 168 | ``` 169 | $ voidnsrun /opt/vivaldi/vivaldi 170 | ``` 171 | 172 | It won't work just yet, but it's a start: 173 | ``` 174 | /opt/vivaldi/vivaldi: error while loading shared libraries: libgobject-2.0.so.0: cannot open shared object file: No such file or directory 175 | ``` 176 | 177 | Now you need to install all its dependencies into your glibc installation. Use 178 | `xlocate` from `xtools` package to find a package responsible for a file (or 179 | just guess it): 180 | ``` 181 | $ xlocate libgobject-2.0.so.0 182 | Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so -> /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4 183 | Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0 -> /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4 184 | Signal-Desktop-1.38.1_1 /usr/lib/signal-desktop/resources/app.asar.unpacked/node_modules/sharp/vendor/lib/libgobject-2.0.so.0.5600.4 185 | glib-2.66.2_1 /usr/lib/libgobject-2.0.so.0 -> /usr/lib/libgobject-2.0.so.0.6600.2 186 | glib-2.66.2_1 /usr/lib/libgobject-2.0.so.0.6600.2 187 | glib-devel-2.66.2_1 /usr/share/gdb/auto-load/usr/lib/libgobject-2.0.so.0.6600.2-gdb.py 188 | libglib-devel-2.66.2_1 /usr/lib/libgobject-2.0.so -> /usr/lib/libgobject-2.0.so.0 189 | ``` 190 | 191 | Sync repos and install `glib`: 192 | ``` 193 | $ sudo voidnsrun -r /glibc xbps-install -Su 194 | $ sudo voidnsrun -r /glibc xbps-install glib 195 | ``` 196 | 197 | Try launching vivaldi again: 198 | ``` 199 | $ voidnsrun /opt/vivaldi/vivaldi 200 | /opt/vivaldi/vivaldi: error while loading shared libraries: libnss3.so: cannot open shared object file: No such file or directory 201 | ``` 202 | 203 | As you can see, it no longer complains about missing `libgobject-2.0.so.0`, now 204 | it's `libnss3.so`. Repeat steps above for all missing dependencies, and in the 205 | end, it will work. 206 | 207 | Note that, for some reason, it doesn't complain about missing font related 208 | libraries, such as freetype, so make sure to install them too, as well as some 209 | base fonts: 210 | ``` 211 | $ sudo voidnsrun -r /glibc xbps-install freetype fontconfig libXft xorg-fonts 212 | ``` 213 | 214 | If you're noticing performance issues with Vivaldi, check the `vivaldi://gpu` 215 | page. If it turns out that hardware acceleration is unavailable, you're missing 216 | some packages again. I don't know which ones exactly, but installing `xorg-minimal` 217 | should fix it. 218 | 219 | ### PhpStorm 220 | 221 | **PhpStorm** and other JetBrains IDEs should just work like this (of course, 222 | replace `/opt/PhpStorm` with real path on your machine): 223 | ``` 224 | voidnsrun /opt/PhpStorm/bin/phpstorm.sh 225 | ``` 226 | 227 | But it is only at first glance, everything works. After some time you may 228 | notice all kinds of weird stuff caused by the fact that it runs inside the 229 | "container" with different `/usr`. For instance, if you open built-in terminal 230 | window, it will work, but... it will not be the shell you expect, it will be 231 | glibc-linked shell from the container. Some programs that you have 232 | installed on your root musl system will not be available there (like, it won't be 233 | able to launch a browser because there's no browser), other may not work as 234 | expected. 235 | 236 | In general, all programs that launch other programs will suffer from this. To 237 | overcome this, the **voidnsundo** utility has been written and `-u` option added 238 | to **voidnsrun**. 239 | 240 | To fix the built-in PhpStorm's terminal and the ability to launch browser as shown 241 | in the above example, launch it like so: 242 | ``` 243 | voidnsrun -u /bin/bash -u /usr/bin/firefox /opt/PhpStorm/bin/phpstorm.sh 244 | ``` 245 | 246 | ## FAQ 247 | 248 | #### Q: `sudo voidnsrun xbps-install` exits with "environment variable VOIDNSRUN_DIR not found" error 249 | 250 | A: Add this line to `/etc/sudoers`: 251 | ``` 252 | Defaults env_keep += "VOIDNSRUN_DIR" 253 | ``` 254 | 255 | #### Q: Why applications launched with voidnsrun do not see my fonts? 256 | 257 | A: If you installed fonts on your main system, applications that run in the mount 258 | namespace can't see them because of custom `/usr` directory. You need to install 259 | them again into the container directory. 260 | 261 | Since 1.3, it's possible to bind-mount `/usr/share/fonts` or other directorires 262 | from the host to the mount namespace. Use the `-d` option for that. 263 | 264 | ## Security 265 | 266 | **voidnsrun** and **voidnsundo** are setuid applications, meaning they are 267 | actually started as root and then dropping privileges when they can. setuid is 268 | generally bad, it's a common attack vector that allows local privilege 269 | escalation by exploiting unsafe code of setuid programs. 270 | 271 | While these utilities have been written with this thought in mind, don't trust 272 | me. Read the code, it's not too big and it's commented. Place yourself in 273 | attacker's shoes and try to find a hole. For every new discovered vulnerability 274 | in these utilities that would allow privilege escalation or something similar I 275 | promise to pay $100 in Bitcoin. Contact me if you find something. 276 | 277 | ## Changelog 278 | 279 | #### 1.3.1 280 | 281 | - Bug fixes. 282 | 283 | #### 1.3 284 | 285 | - Added the `-d` option to bind mount subdirectories from the host `/usr`. 286 | 287 | #### 1.2.1 288 | 289 | - Minor code fixes, nothing serious. 290 | 291 | #### 1.2 292 | 293 | - Added **voidnsundo** utility for spawning programs in the parent mount 294 | namespace from within the namespace created by **voidnsrun**. 295 | - Restore current working directory after changing namespace. 296 | 297 | #### 1.1 298 | 299 | - Bind whole `/etc` and `/var` when launching `xbps-install`, `xbps-remove` or 300 | `xbps-reconfigure`. 301 | 302 | #### 1.0 303 | 304 | - Initial release. 305 | 306 | ## License 307 | 308 | BSD-2c 309 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef VOIDNSRUN_CONFIG_H 2 | #define VOIDNSRUN_CONFIG_H 3 | 4 | #define PROG_VERSION "1.3.2" 5 | 6 | #define USER_LISTS_MAX 50 7 | 8 | #define CONTAINER_DIR_VAR "VOIDNSRUN_DIR" 9 | #define UNDO_BIN_VAR "VOIDNSUNDO_BIN" 10 | #define VOIDNSUNDO_NAME "voidnsundo" 11 | #define OLDROOT "/oldroot" 12 | 13 | /* This path has not been made configurable and is hardcoded 14 | * here for security purposes. If you want to change it, change it 15 | * here and recompile and reinstall both utilities. */ 16 | #define SOCK_PATH "/run/voidnsrun/sock" 17 | 18 | #endif //VOIDNSRUN_CONFIG_H 19 | -------------------------------------------------------------------------------- /macros.h: -------------------------------------------------------------------------------- 1 | #ifndef VOIDNSRUN_MACROS_H 2 | #define VOIDNSRUN_MACROS_H 3 | 4 | #include 5 | #include 6 | 7 | extern bool g_verbose; 8 | 9 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 10 | #define UNUSED(x) (void)(x) 11 | #define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) 12 | #define DEBUG(f_, ...) if (g_verbose) { \ 13 | fprintf(stderr, "debug: "); \ 14 | fprintf(stderr, (f_), ##__VA_ARGS__); \ 15 | } 16 | 17 | #define ERROR_EXIT(f_, ...) { \ 18 | fprintf(stderr, (f_), ##__VA_ARGS__); \ 19 | goto end; \ 20 | } 21 | 22 | #endif //VOIDNSRUN_MACROS_H 23 | -------------------------------------------------------------------------------- /test/testclient.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "utils.h" 10 | 11 | #define ERROR_EXIT(f_, ...) { \ 12 | fprintf(stderr, (f_), ##__VA_ARGS__); \ 13 | return 1; \ 14 | } 15 | 16 | int main() 17 | { 18 | struct sockaddr_un addr = {0}; 19 | int sock; 20 | 21 | // Create and connect a unix domain socket 22 | sock = socket(AF_UNIX, SOCK_STREAM, 0); 23 | if (sock == -1) 24 | ERROR_EXIT("socket: %s\n", strerror(errno)); 25 | 26 | addr.sun_family = AF_UNIX; 27 | strcpy(&addr.sun_path[1], "/tmp/voidnsrun-test.sock"); 28 | 29 | if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) 30 | ERROR_EXIT("connect: %s\n", strerror(errno)); 31 | 32 | int fd = recv_fd(sock); 33 | close(sock); 34 | 35 | assert(fd != 0); 36 | 37 | struct stat st; 38 | if (fstat(fd, &st) == -1) 39 | ERROR_EXIT("stat: %s\n", strerror(errno)); 40 | 41 | printf("st_ino: %lu\n", st.st_ino); 42 | 43 | return 0; 44 | } -------------------------------------------------------------------------------- /test/testserver.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "utils.h" 12 | 13 | #define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__) 14 | #define UNUSED(x) (void)(x) 15 | #define ERROR_EXIT(f_, ...) { \ 16 | fprintf(stderr, (f_), ##__VA_ARGS__); \ 17 | return 1; \ 18 | } 19 | 20 | volatile sig_atomic_t term_caught = 0; 21 | void onterm(int sig) 22 | { 23 | printf("sigterm caught\n"); 24 | UNUSED(sig); 25 | term_caught = 1; 26 | } 27 | 28 | int main() 29 | { 30 | int result; 31 | int sock_fd, sock_conn; 32 | int nsfd; 33 | 34 | /* Get current namespace's file descriptor. */ 35 | nsfd = open("/proc/self/ns/mnt", O_RDONLY); 36 | if (nsfd == -1) 37 | ERROR_EXIT("error: failed to acquire mount namespace's fd.%s\n", 38 | strerror(errno)); 39 | 40 | /* Fork. */ 41 | pid_t ppid_before_fork = getpid(); 42 | pid_t pid = fork(); 43 | if (pid == -1) 44 | ERROR_EXIT("fork: %s\n", strerror(errno)); 45 | 46 | if (pid == 0) { 47 | /* Catch SIGTERM. */ 48 | struct sigaction sa = {0}; 49 | sa.sa_handler = onterm; 50 | sigaction(SIGTERM, &sa, NULL); 51 | 52 | /* Ignore SIGINT. */ 53 | signal(SIGINT, SIG_IGN); 54 | 55 | /* Set the child to die when parent thread dies. */ 56 | int r = prctl(PR_SET_PDEATHSIG, SIGTERM); 57 | if (r == -1) 58 | ERROR_EXIT("prctl: %s\n", strerror(errno)); 59 | if (getppid() != ppid_before_fork) 60 | ERROR_EXIT("error: parent has died already.\n"); 61 | 62 | /* Create unix socket. */ 63 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); 64 | if (sock_fd == -1) 65 | ERROR_EXIT("socket: %s.\n", strerror(errno)); 66 | 67 | struct sockaddr_un sock_addr = {0}; 68 | sock_addr.sun_family = AF_UNIX; 69 | strcpy(&sock_addr.sun_path[1], "/tmp/voidnsrun-test.sock"); 70 | 71 | if (bind(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) 72 | ERROR_EXIT("bind: %s\n", strerror(errno)); 73 | 74 | listen(sock_fd, 1); 75 | 76 | while (!term_caught) { 77 | sock_conn = accept(sock_fd, NULL, 0); 78 | if (sock_conn == -1) { 79 | ERROR("accept: %s\n", strerror(errno)); 80 | continue; 81 | } 82 | printf("accepted\n"); 83 | send_fd(sock_conn, nsfd); 84 | } 85 | printf("exiting\n"); 86 | } else { 87 | /* This is parent. Launch a program. */ 88 | char *argv[2] = {"/bin/sh", NULL}; 89 | 90 | result = execvp(argv[0], (char *const *)argv); 91 | if (result == -1) 92 | ERROR_EXIT("execvp: %s\n", strerror(errno)); 93 | } 94 | 95 | return 0; 96 | } -------------------------------------------------------------------------------- /utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "macros.h" 11 | #include "utils.h" 12 | 13 | bool isdir(const char *s) 14 | { 15 | struct stat st; 16 | int result = stat(s, &st); 17 | if (result == -1) 18 | ERROR("stat(%s): %s\n", s, strerror(errno)); 19 | return result == 0 && S_ISDIR(st.st_mode); 20 | } 21 | 22 | bool isexe(const char *s) 23 | { 24 | struct stat st; 25 | int result = stat(s, &st); 26 | if (result == -1) 27 | ERROR("stat(%s): %s\n", s, strerror(errno)); 28 | return result == 0 && !S_ISDIR(st.st_mode) && st.st_mode & S_IXUSR; 29 | } 30 | 31 | bool exists(const char *s) 32 | { 33 | struct stat st; 34 | return stat(s, &st) == 0; 35 | } 36 | 37 | bool mkfile(const char *s) 38 | { 39 | int fd; 40 | if ((fd = creat(s, 0700)) == -1) 41 | return false; 42 | close(fd); 43 | return true; 44 | } 45 | 46 | mode_t getmode(const char *s) 47 | { 48 | struct stat st; 49 | if (stat(s, &st) == -1) 50 | return 0; 51 | return st.st_mode; 52 | } 53 | 54 | bool startswith(const char *haystack, const char *needle) 55 | { 56 | return strncmp(haystack, needle, strlen(needle)) == 0; 57 | } 58 | 59 | int send_fd(int sock, int fd) 60 | { 61 | struct msghdr msg = {0}; 62 | struct iovec iov[1]; 63 | struct cmsghdr *cmsg = NULL; 64 | char ctrl_buf[CMSG_SPACE(sizeof(int))]; 65 | char data[1]; 66 | 67 | memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); 68 | 69 | data[0] = ' '; 70 | iov[0].iov_base = data; 71 | iov[0].iov_len = sizeof(data); 72 | 73 | msg.msg_name = NULL; 74 | msg.msg_namelen = 0; 75 | msg.msg_iov = iov; 76 | msg.msg_iovlen = 1; 77 | msg.msg_controllen = CMSG_SPACE(sizeof(int)); 78 | msg.msg_control = ctrl_buf; 79 | 80 | cmsg = CMSG_FIRSTHDR(&msg); 81 | cmsg->cmsg_level = SOL_SOCKET; 82 | cmsg->cmsg_type = SCM_RIGHTS; 83 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 84 | 85 | *((int *)CMSG_DATA(cmsg)) = fd; 86 | 87 | return sendmsg(sock, &msg, 0); 88 | } 89 | 90 | int recv_fd(int sock) 91 | { 92 | struct msghdr msg = {0}; 93 | struct iovec iov[1]; 94 | struct cmsghdr *cmsg = NULL; 95 | char ctrl_buf[CMSG_SPACE(sizeof(int))]; 96 | char data[1]; 97 | 98 | memset(ctrl_buf, 0, CMSG_SPACE(sizeof(int))); 99 | 100 | iov[0].iov_base = data; 101 | iov[0].iov_len = sizeof(data); 102 | 103 | msg.msg_name = NULL; 104 | msg.msg_namelen = 0; 105 | msg.msg_control = ctrl_buf; 106 | msg.msg_controllen = CMSG_SPACE(sizeof(int)); 107 | msg.msg_iov = iov; 108 | msg.msg_iovlen = 1; 109 | 110 | recvmsg(sock, &msg, 0); 111 | 112 | cmsg = CMSG_FIRSTHDR(&msg); 113 | 114 | return *((int *) CMSG_DATA(cmsg)); 115 | } 116 | 117 | bool isxbpscommand(const char *s) 118 | { 119 | const char *commands[] = { 120 | "/xbps-install", 121 | "/xbps-remove", 122 | "/xbps-reconfigure", 123 | "/xbps-query", 124 | "/xbps-pkgdb", 125 | }; 126 | for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { 127 | const char *command = commands[i]; 128 | if (!strcmp(s, command+1)) 129 | return true; 130 | char *slash = strrchr(s, '/'); 131 | if (slash && !strcmp(slash, command)) 132 | return true; 133 | } 134 | return false; 135 | } 136 | 137 | bool strarray_append(struct strarray *a, char *s) 138 | { 139 | if (a->end == a->size - 1) 140 | return false; 141 | else 142 | a->list[a->end++] = s; 143 | return true; 144 | } 145 | 146 | bool intarray_append(struct intarray *a, int i) 147 | { 148 | if (a->end == a->size - 1) 149 | return false; 150 | else 151 | a->list[a->end++] = i; 152 | return true; 153 | } 154 | 155 | void strarray_alloc(struct strarray *a, size_t size) 156 | { 157 | a->end = 0; 158 | a->size = size; 159 | a->list = malloc(sizeof(char *) * size); 160 | assert(a->list != NULL); 161 | } 162 | 163 | void intarray_alloc(struct intarray *i, size_t size) 164 | { 165 | i->end = 0; 166 | i->size = size; 167 | i->list = malloc(sizeof(int) * size); 168 | assert(i->list != NULL); 169 | } -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef VOIDNSRUN_UTILS_H 2 | #define VOIDNSRUN_UTILS_H 3 | 4 | #include 5 | #include "config.h" 6 | 7 | struct strarray { 8 | size_t end; 9 | size_t size; 10 | char **list; 11 | }; 12 | 13 | struct intarray { 14 | size_t end; 15 | size_t size; 16 | int *list; 17 | }; 18 | 19 | bool isdir(const char *s); 20 | bool isexe(const char *s); 21 | bool exists(const char *s); 22 | bool mkfile(const char *s); 23 | bool startswith(const char *haystack, const char *needle); 24 | mode_t getmode(const char *s); 25 | 26 | int send_fd(int sock, int fd); 27 | int recv_fd(int sock); 28 | 29 | bool isxbpscommand(const char *s); 30 | 31 | void strarray_alloc(struct strarray *a, size_t size); 32 | bool strarray_append(struct strarray *a, char *s); 33 | 34 | void intarray_alloc(struct intarray *i, size_t size); 35 | bool intarray_append(struct intarray *a, int i); 36 | 37 | #endif //VOIDNSRUN_UTILS_H 38 | -------------------------------------------------------------------------------- /voidnsrun.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "config.h" 23 | #include "utils.h" 24 | #include "macros.h" 25 | 26 | volatile sig_atomic_t term_caught = 0; 27 | bool g_verbose = false; 28 | 29 | void usage(const char *progname) 30 | { 31 | printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); 32 | printf("\n" 33 | "Options:\n" 34 | " -r : Container path. When this option is not present,\n" 35 | " " CONTAINER_DIR_VAR " environment variable is used.\n" 36 | " -m : Add bind mount. You can add up to %d paths.\n" 37 | " -u : Add undo bind mount. You can add up to %d paths.\n" 38 | " -d : Add /usr subdirectory bind mount.\n" 39 | " -U : Path to " VOIDNSUNDO_NAME ". When this option is not present,\n" 40 | " " UNDO_BIN_VAR " environment variable is used.\n" 41 | " -i: Don't treat missing source or target for added mounts as error.\n" 42 | " -V: Enable verbose output.\n" 43 | " -h: Print this help.\n" 44 | " -v: Print version.\n", 45 | USER_LISTS_MAX, USER_LISTS_MAX); 46 | } 47 | 48 | size_t mount_dirs(const char *source_prefix, 49 | size_t source_prefix_len, 50 | struct strarray *targets, 51 | struct intarray *created) 52 | { 53 | char buf[PATH_MAX]; 54 | int successful = 0; 55 | mode_t mode; 56 | for (size_t i = 0; i < targets->end; i++) { 57 | /* Check if it's safe to proceed. */ 58 | if (source_prefix_len + strlen(targets->list[i]) >= PATH_MAX) { 59 | ERROR("error: path %s%s is too large.\n", source_prefix, targets->list[i]); 60 | continue; 61 | } 62 | 63 | /* Should be safe as we just checked that total length of source_prefix 64 | * and targets->list[i] is no more than PATH_MAX-1. */ 65 | strcpy(buf, source_prefix); 66 | strcat(buf, targets->list[i]); 67 | 68 | if (!isdir(buf)) { 69 | ERROR("error: source mount dir %s does not exists.\n", buf); 70 | continue; 71 | } 72 | 73 | if (!exists(targets->list[i])) { 74 | if (created != NULL) { 75 | mode = getmode(buf); 76 | if (mode == 0) { 77 | ERROR("error: can't get mode for %s.\n", buf); 78 | continue; 79 | } 80 | 81 | if (mkdir(targets->list[i], mode) == -1) { 82 | ERROR("error: failed to create mountpotint at %s: %s.\n", 83 | targets->list[i], strerror(errno)); 84 | continue; 85 | } else 86 | intarray_append(created, i); 87 | } else { 88 | ERROR("error: mount dir %s does not exists.\n", buf); 89 | continue; 90 | } 91 | } 92 | 93 | if (!isdir(targets->list[i])) { 94 | ERROR("error: mount point %s is not a directory.\n", targets->list[i]); 95 | continue; 96 | } 97 | 98 | if (mount(buf, targets->list[i], NULL, MS_BIND|MS_REC, NULL) == -1) 99 | ERROR("mount: failed to mount %s: %s\n", targets->list[i], strerror(errno)); 100 | else 101 | successful++; 102 | } 103 | return successful; 104 | } 105 | 106 | size_t mount_undo(const char *source, 107 | const struct strarray *targets, 108 | struct intarray *created) 109 | { 110 | int successful = 0; 111 | for (size_t i = 0; i < targets->end; i++) { 112 | /* If the mount point does not exist, create an empty file, otherwise 113 | * mount() call will fail. In this case, remember which files we have 114 | * created to unlink() them before exit. */ 115 | if (!exists(targets->list[i])) { 116 | if (mkfile(targets->list[i])) 117 | intarray_append(created, i); 118 | else 119 | continue; 120 | } 121 | 122 | DEBUG("%s: source=%s, target=%s\n", __func__, source, targets->list[i]); 123 | if (mount(source, targets->list[i], NULL, MS_BIND, NULL) == -1) 124 | ERROR("mount: failed to mount %s to %s: %s", 125 | source, targets->list[i], strerror(errno)); 126 | else 127 | successful++; 128 | } 129 | return successful; 130 | } 131 | 132 | void onterm(int sig) 133 | { 134 | UNUSED(sig); 135 | term_caught = 1; 136 | } 137 | 138 | int main(int argc, char **argv) 139 | { 140 | if (argc < 2) { 141 | usage(argv[0]); 142 | return 0; 143 | } 144 | 145 | int nsfd = -1; 146 | char *dir = NULL; 147 | char buf[PATH_MAX*2]; 148 | char *undo_bin = NULL; 149 | int sock_fd = -1, sock_conn = -1; 150 | size_t dirlen; 151 | int c; 152 | int exit_code = 1; 153 | DIR *dirptr = NULL; 154 | bool ignore_missing = false; 155 | bool forked = false; 156 | pid_t pid = 0; 157 | char cwd[PATH_MAX]; 158 | 159 | struct strarray user_mounts; 160 | strarray_alloc(&user_mounts, USER_LISTS_MAX); 161 | 162 | struct strarray undo_mounts; 163 | strarray_alloc(&undo_mounts, USER_LISTS_MAX); 164 | 165 | /* List of user-specified /usr subdirectories to mount. */ 166 | struct strarray dir_mounts; 167 | strarray_alloc(&dir_mounts, USER_LISTS_MAX); 168 | 169 | /* List of indexes of items in the undo_mounts array. See comments in 170 | * mount_undo() function for more info. */ 171 | struct intarray created_undos; 172 | intarray_alloc(&created_undos, USER_LISTS_MAX); 173 | 174 | /* List of indexes of items in the dir_mounts array. */ 175 | struct intarray created_dirs; 176 | intarray_alloc(&created_dirs, USER_LISTS_MAX); 177 | 178 | while ((c = getopt(argc, argv, "vhm:r:u:U:iVd:")) != -1) { 179 | switch (c) { 180 | case 'v': 181 | printf("%s\n", PROG_VERSION); 182 | return 0; 183 | case 'h': 184 | usage(argv[0]); 185 | return 0; 186 | case 'i': 187 | ignore_missing = true; 188 | break; 189 | case 'r': 190 | dir = optarg; 191 | break; 192 | case 'U': 193 | undo_bin = optarg; 194 | break; 195 | case 'V': 196 | g_verbose = true; 197 | break; 198 | case 'm': 199 | if (!strarray_append(&user_mounts, optarg)) 200 | ERROR_EXIT("error: only up to %lu user mounts allowed.\n", 201 | user_mounts.size); 202 | break; 203 | case 'u': 204 | if (!strarray_append(&undo_mounts, optarg)) 205 | ERROR_EXIT("error: only up to %lu user mounts allowed.\n", 206 | undo_mounts.size); 207 | break; 208 | case 'd': 209 | if (!startswith(optarg, "/usr/")) 210 | ERROR_EXIT("only subdirectories of /usr are allowed for bind mounting this way.\n"); 211 | if (!strarray_append(&dir_mounts, optarg)) 212 | ERROR_EXIT("error: only up to %lu dir mounts allowed.\n", 213 | dir_mounts.size); 214 | break; 215 | case '?': 216 | return 1; 217 | } 218 | } 219 | 220 | if (!argv[optind]) { 221 | usage(argv[0]); 222 | return 1; 223 | } 224 | 225 | /* Get container path. */ 226 | if (!dir) 227 | dir = getenv(CONTAINER_DIR_VAR); 228 | if (!dir) 229 | ERROR_EXIT("error: environment variable %s not found.\n", 230 | CONTAINER_DIR_VAR); 231 | 232 | /* Validate it. */ 233 | if (!isdir(dir)) 234 | ERROR_EXIT("error: %s is not a directory.\n", dir); 235 | 236 | dirlen = strlen(dir); 237 | if (dirlen >= PATH_MAX) 238 | ERROR_EXIT("error: container's path is too long.\n"); 239 | 240 | DEBUG("dir=%s\n", dir); 241 | 242 | /* Get voidnsundo path, if needed. */ 243 | if (undo_mounts.end > 0) { 244 | if (!undo_bin) 245 | undo_bin = getenv(UNDO_BIN_VAR); 246 | if (!undo_bin) { 247 | ERROR_EXIT("error: environment variable %s not found.\n", 248 | UNDO_BIN_VAR); 249 | } 250 | 251 | size_t undo_bin_len = strlen(undo_bin); 252 | if (undo_bin_len >= PATH_MAX) 253 | ERROR_EXIT("error: undo binary path is too long.\n"); 254 | 255 | /* 256 | * Check that it exists and it is an executable. 257 | * These strcpy and strcat calls should be safe, as we already know that 258 | * both dir and undo_bin are no longer than PATH_MAX-1 and the buf's size 259 | * is PATH_MAX*2. 260 | */ 261 | strcpy(buf, dir); 262 | strcat(buf, undo_bin); 263 | if (!isexe(buf)) 264 | ERROR_EXIT("error: %s is not an executable.\n", undo_bin); 265 | 266 | DEBUG("undo_bin=%s\n", undo_bin); 267 | } 268 | 269 | /* Get current namespace's file descriptor. It may be needed later 270 | * for voidnsundo. */ 271 | nsfd = open("/proc/self/ns/mnt", O_RDONLY); 272 | if (nsfd == -1) 273 | ERROR_EXIT("error: failed to acquire mount namespace's fd.%s\n", 274 | strerror(errno)); 275 | 276 | /* Get current working directory. Will need to restore it later in the 277 | * new mount namespace. */ 278 | getcwd(cwd, PATH_MAX); 279 | DEBUG("cwd=%s\n", cwd); 280 | 281 | /* Create new mount namespace. */ 282 | if (unshare(CLONE_NEWNS) == -1) 283 | ERROR_EXIT("unshare: %s\n", strerror(errno)); 284 | 285 | /* Mount stuff from the container to the namespace. */ 286 | /* First, mount what user asked us to mount. */ 287 | if (mount_dirs(dir, dirlen, &user_mounts, NULL) < user_mounts.end && !ignore_missing) 288 | ERROR_EXIT("error: some mounts failed.\n"); 289 | 290 | /* Then preserve original /usr at /oldroot if needed. */ 291 | if (dir_mounts.end > 0) { 292 | mode_t mode = getmode("/usr"); 293 | if (mode == 0) 294 | ERROR_EXIT("error: failed to get mode of /usr.\n"); 295 | 296 | if (mount("tmpfs", OLDROOT, "tmpfs", 0, "size=4k,mode=0700,uid=0,gid=0") == -1) 297 | ERROR_EXIT("mount: error mounting tmpfs in %s.\n", OLDROOT); 298 | 299 | strcpy(buf, OLDROOT); 300 | strcat(buf, "/usr"); 301 | if (mkdir(buf, mode) == -1) 302 | ERROR_EXIT("error: failed to mkdir %s: %s.\n", buf, strerror(errno)); 303 | 304 | if (mount("/usr", buf, NULL, MS_BIND|MS_REC, NULL) == -1) 305 | ERROR_EXIT("error: failed to mount /usr at %s: %s.", 306 | buf, strerror(errno)); 307 | } 308 | 309 | /* Then the necessary stuff. */ 310 | struct strarray default_mounts; 311 | strarray_alloc(&default_mounts, 3); 312 | strarray_append(&default_mounts, "/usr"); 313 | if (isxbpscommand(argv[optind])) { 314 | strarray_append(&default_mounts, "/var"); 315 | strarray_append(&default_mounts, "/etc"); 316 | } 317 | if (mount_dirs(dir, dirlen, &default_mounts, NULL) < default_mounts.end) 318 | ERROR_EXIT("error: some necessary mounts failed.\n"); 319 | 320 | /* Mount /usr subdirectories if needed. */ 321 | if (dir_mounts.end > 0 322 | && mount_dirs(OLDROOT, strlen(OLDROOT), &dir_mounts, &created_dirs) < dir_mounts.end) 323 | ERROR_EXIT("error: some dir mounts failed.\n"); 324 | 325 | /* Now lets do bind mounts of voidnsundo (if needed). */ 326 | if (mount_undo(undo_bin, &undo_mounts, &created_undos) < undo_mounts.end 327 | && !ignore_missing) 328 | ERROR_EXIT("error: some undo mounts failed.\n"); 329 | 330 | /* Check socket directory. */ 331 | /* TODO: fix invalid permissions, or just die in that case. */ 332 | 333 | /* This should be safe, SOCK_PATH is hardcoded in config.h and it's definitely 334 | * smaller than buffer. */ 335 | strcpy(buf, SOCK_PATH); 336 | 337 | char *sock_dir = dirname(buf); 338 | if (access(sock_dir, F_OK) == -1) { 339 | if (mkdir(sock_dir, 0700) == -1) 340 | ERROR_EXIT("error: failed to create %s directory.\n", sock_dir); 341 | } else { 342 | if ((dirptr = opendir(sock_dir)) == NULL) 343 | ERROR_EXIT("error: %s is not a directory.\n", sock_dir); 344 | if (exists(SOCK_PATH) && unlink(SOCK_PATH) == -1) 345 | ERROR_EXIT("failed to unlink %s: %s", SOCK_PATH, strerror(errno)); 346 | } 347 | DEBUG("sock_dir=%s\n", sock_dir); 348 | 349 | /* Mount socket directory as tmpfs. It will only be visible in this namespace, 350 | * and the socket file will also be available from this namespace only.*/ 351 | if (mount("tmpfs", sock_dir, "tmpfs", 0, "size=4k,mode=0700,uid=0,gid=0") == -1) 352 | ERROR_EXIT("mount: error mounting tmpfs in %s: %s.\n", sock_dir, strerror(errno)); 353 | 354 | /* 355 | * Fork. We need it because we need to preserve file descriptor of the 356 | * original namespace. 357 | * 358 | * Linux doesn't allow to bind mount /proc/self/ns/mnt from the original 359 | * namespace in the child namespace because that would lead to dependency 360 | * loop. So I came up with another solution. 361 | * 362 | * Unix sockets are capable of passing file descriptors. We need to start a 363 | * server that will listen on a unix socket and pass the namespace's file 364 | * descriptor to connected clients over this socket. voidnsundo will connect 365 | * to the socket, receive the file descriptor and perform the setns() system 366 | * call. 367 | * 368 | * We also need to make sure the socket will only be accessible by root. 369 | * The path to the socket should be hardcoded. 370 | * 371 | * So we fork(), start the server in the child process, while the parent 372 | * drops root privileges and runs the programs it was asked to run. 373 | */ 374 | pid_t ppid_before_fork = getpid(); 375 | pid = fork(); 376 | if (pid == -1) 377 | ERROR_EXIT("fork: %s\n", strerror(errno)); 378 | 379 | forked = true; 380 | 381 | if (pid == 0) { 382 | /* This is the child process. 383 | * Catch SIGTERM: it will be sent here when parent dies. The signal will 384 | * interrupt the accept() call, so we can clean up and exit immediately. 385 | */ 386 | struct sigaction sa = {0}; 387 | sa.sa_handler = onterm; 388 | sigaction(SIGTERM, &sa, NULL); 389 | 390 | /* Ignore SIGINT. Otherwise it will be affected by Ctrl+C in the parent 391 | * process. */ 392 | signal(SIGINT, SIG_IGN); 393 | 394 | /* Set the child to get SIGTERM when parent thread dies. */ 395 | int r = prctl(PR_SET_PDEATHSIG, SIGTERM); 396 | if (r == -1) 397 | ERROR_EXIT("prctl: %s\n", strerror(errno)); 398 | 399 | /* Maybe it already has died? */ 400 | if (getppid() != ppid_before_fork) 401 | ERROR_EXIT("error: parent has died already.\n"); 402 | 403 | /* Create unix socket. */ 404 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); 405 | if (sock_fd == -1) 406 | ERROR_EXIT("socket: %s.\n", strerror(errno)); 407 | 408 | struct sockaddr_un sock_addr = {0}; 409 | sock_addr.sun_family = AF_UNIX; 410 | 411 | /* The size of sun_path is 108 bytes, our SOCK_PATH is definitely 412 | * smaller. */ 413 | strcpy(sock_addr.sun_path, SOCK_PATH); 414 | 415 | if (bind(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) 416 | ERROR_EXIT("bind: %s\n", strerror(errno)); 417 | 418 | listen(sock_fd, 1); 419 | 420 | /* Accept incoming connections until SIGTERM. */ 421 | while (!term_caught) { 422 | sock_conn = accept(sock_fd, NULL, 0); 423 | if (sock_conn == -1) 424 | continue; 425 | send_fd(sock_conn, nsfd); 426 | } 427 | } else { 428 | /* Parent process. Drop root rights. */ 429 | uid_t uid = getuid(); 430 | gid_t gid = getgid(); 431 | 432 | if (setreuid(uid, uid) == -1) 433 | ERROR_EXIT("setreuid: %s\n", strerror(errno)); 434 | 435 | if (setregid(gid, gid) == -1) 436 | ERROR_EXIT("setregid: %s\n", strerror(errno)); 437 | 438 | /* Restore working directory. */ 439 | if (chdir(cwd) == -1) 440 | DEBUG("chdir: %s\n", strerror(errno)); 441 | 442 | /* Launch program. */ 443 | if (execvp(argv[optind], (char *const *)argv+optind) == -1) 444 | ERROR_EXIT("execvp(%s): %s\n", argv[optind], strerror(errno)); 445 | } 446 | 447 | exit_code = 0; 448 | 449 | end: 450 | if (nsfd != -1) 451 | close(nsfd); 452 | 453 | if (sock_fd != -1) 454 | close(sock_fd); 455 | 456 | if (sock_conn != -1) 457 | close(sock_conn); 458 | 459 | if (dirptr != NULL) 460 | closedir(dirptr); 461 | 462 | if (!forked || pid == 0) { 463 | /* If we created some empty files to bind the voidnsundo utility, 464 | * delete them here. */ 465 | if (created_undos.end > 0) { 466 | for (size_t i = 0; i < created_undos.end; i++) { 467 | char *path = undo_mounts.list[created_undos.list[i]]; 468 | if (umount(path) == -1) 469 | DEBUG("umount(%s): %s\n", path, strerror(errno)); 470 | if (unlink(path) == -1) 471 | ERROR("unlink(%s): %s\n", path, strerror(errno)); 472 | else 473 | DEBUG("unlink(%s)\n", path); 474 | } 475 | } 476 | 477 | /* If we had to create mount tmpfs to /oldroot and do other 478 | * dirty hacks related to /usr subdirs bind mounting, clean up here. */ 479 | if (dir_mounts.end > 0) { 480 | for (size_t i = 0; i < dir_mounts.end; i++) { 481 | char *path = dir_mounts.list[i]; 482 | if (umount(path) == -1) 483 | ERROR("umount(%s): %s\n", path, strerror(errno)); 484 | } 485 | 486 | /* If we created some empty dirs to use them as mountpoints for 487 | * bind mounts, delete them here. */ 488 | if (created_dirs.end > 0) { 489 | for (size_t i = 0; i < created_dirs.end; i++) { 490 | char *path = dir_mounts.list[created_dirs.list[i]]; 491 | if (rmdir(path) == -1) 492 | ERROR("rmdir(%s): %s\n", path, strerror(errno)); 493 | else 494 | DEBUG("rmdir(%s)\n", path); 495 | } 496 | } 497 | 498 | strcpy(buf, OLDROOT); 499 | strcat(buf, "/usr"); 500 | if (umount(buf) == -1) 501 | ERROR("umount(%s): %s\n", buf, strerror(errno)); 502 | 503 | /* This call always fails with EBUSY and I don't know why. 504 | * We can safely ignore any errors here (I hope) because 505 | * the mount namespace will be destroyed as soon as there 506 | * will be no processes attached to it. */ 507 | umount(OLDROOT); 508 | /*if (umount(OLDROOT) == -1) 509 | ERROR("umount(%s): %s\n", OLDROOT, strerror(errno));*/ 510 | } 511 | } 512 | 513 | return exit_code; 514 | } 515 | -------------------------------------------------------------------------------- /voidnsundo.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "config.h" 17 | #include "utils.h" 18 | #include "macros.h" 19 | 20 | bool g_verbose = false; 21 | 22 | void usage(const char *progname) 23 | { 24 | printf("Usage: %s [OPTIONS] PROGRAM [ARGS]\n", progname); 25 | printf("\n" 26 | "Options:\n" 27 | " -V: Enable verbose output.\n" 28 | " -h: Print this help.\n" 29 | " -v: Print version.\n"); 30 | } 31 | 32 | int main(int argc, char **argv) 33 | { 34 | bool binded = strcmp(basename(argv[0]), VOIDNSUNDO_NAME) != 0; 35 | int c; 36 | int sock_fd = -1; 37 | int exit_code = 1; 38 | char realpath_buf[PATH_MAX]; 39 | char cwd[PATH_MAX]; 40 | if (!binded) { 41 | if (argc < 2) { 42 | usage(argv[0]); 43 | return 0; 44 | } 45 | 46 | while ((c = getopt(argc, argv, "vhs:V")) != -1) { 47 | switch (c) { 48 | case 'v': 49 | printf("%s\n", PROG_VERSION); 50 | return 0; 51 | case 'h': 52 | usage(argv[0]); 53 | return 0; 54 | case 'V': 55 | g_verbose = true; 56 | break; 57 | case '?': 58 | return 1; 59 | } 60 | } 61 | 62 | if (!argv[optind]) { 63 | usage(argv[0]); 64 | return 1; 65 | } 66 | } else { 67 | int bytes = readlink("/proc/self/exe", realpath_buf, PATH_MAX); 68 | realpath_buf[bytes] = '\0'; 69 | /* DEBUG("/proc/self/exe points to %s\n", realpath_buf); */ 70 | } 71 | 72 | /* Get current working directory. */ 73 | getcwd(cwd, PATH_MAX); 74 | DEBUG("cwd=%s\n", cwd); 75 | 76 | /* Get namespace's fd. */ 77 | sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); 78 | if (sock_fd == -1) 79 | ERROR_EXIT("socket: %s.\n", strerror(errno)); 80 | 81 | struct sockaddr_un sock_addr = {0}; 82 | sock_addr.sun_family = AF_UNIX; 83 | 84 | /* The size of sun_path is 108 bytes, our SOCK_PATH is definitely 85 | * smaller. */ 86 | strcpy(sock_addr.sun_path, SOCK_PATH); 87 | 88 | if (connect(sock_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1) 89 | ERROR_EXIT("connect: %s\n", strerror(errno)); 90 | 91 | int nsfd = recv_fd(sock_fd); 92 | if (!nsfd) 93 | ERROR_EXIT("error: failed to get nsfd.\n"); 94 | 95 | /* Change namespace. */ 96 | if (setns(nsfd, CLONE_NEWNS) == -1) 97 | ERROR_EXIT("setns: %s.\n", strerror(errno)); 98 | 99 | /* Drop root. */ 100 | uid_t uid = getuid(); 101 | gid_t gid = getgid(); 102 | 103 | if (setreuid(uid, uid) == -1) 104 | ERROR_EXIT("setreuid: %s\n", strerror(errno)); 105 | 106 | if (setregid(gid, gid) == -1) 107 | ERROR_EXIT("setregid: %s\n", strerror(errno)); 108 | 109 | /* Restore working directory. */ 110 | if (chdir(cwd) == -1) 111 | DEBUG("chdir: %s\n", strerror(errno)); 112 | 113 | /* Launch program. */ 114 | int argind = binded ? 0 : optind; 115 | if (binded) 116 | argv[0] = realpath_buf; 117 | if (execvp(argv[argind], (char *const *)argv+argind) == -1) 118 | ERROR_EXIT("execvp(%s): %s\n", argv[argind], strerror(errno)); 119 | 120 | exit_code = 0; 121 | 122 | end: 123 | if (sock_fd != -1) 124 | close(sock_fd); 125 | 126 | return exit_code; 127 | } --------------------------------------------------------------------------------