commit 0f3295e2442f79c947b9c0e1a5ecd2aedd555f29
Author: Friedel Schön <[email protected]>
Date: Mon, 22 Apr 2024 10:49:55 +0200
initial commit
Diffstat:
A | LICENSE | 18 | ++++++++++++++++++ |
A | Makefile | 20 | ++++++++++++++++++++ |
A | arg.h | 19 | +++++++++++++++++++ |
A | readme.md | 70 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | weakbox.1 | 101 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | weakbox.c | 238 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
6 files changed, 466 insertions(+), 0 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -0,0 +1,17 @@
+Copyright (c) 2023 Friedel Schön <[email protected]>
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+3. This notice may not be removed or altered from any source distribution.
+\ No newline at end of file
diff --git a/Makefile b/Makefile
@@ -0,0 +1,19 @@
+CFLAGS += -O2 -Wall -Wextra
+PREFIX = /usr
+
+.PHONY: all install clean
+
+all: enter
+
+weakbox.o: weakbox.c arg.h
+ $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS)
+
+weakbox: weakbox.o
+ $(CC) $< -o $@ $(LDFLAGS)
+
+install: weakbox weakbox.1
+ cp weakbox $(PREFIX)/bin/
+ cp weakbox.1 $(PREFIX)/share/man/man1/
+
+clean:
+ rm -f weakbox weakbox.1
+\ No newline at end of file
diff --git a/arg.h b/arg.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#define SHIFT (argc--, argv++)
+
+#define ARGBEGIN \
+ for (SHIFT; *argv && *argv[0] == '-'; SHIFT) { \
+ if ((*argv)[1] == '-' && (*argv)[2] == '\0') { \
+ SHIFT; \
+ break; \
+ } \
+ for (char* opt = *argv + 1; *opt; opt++) {
+
+#define ARGEND \
+ } \
+ }
+
+#define OPT (*opt)
+#define ARGF (argv[1] ? (SHIFT, *argv) : NULL)
+#define EARGF(usage) (argv[1] ? ARGF : (printf("'-%c' requires an argument\n", *opt), usage, NULL))
diff --git a/readme.md b/readme.md
@@ -0,0 +1,70 @@
+# weakbox
+
+**weakbox** is a tool for Linux designed to create a weak (not secured) container for running programs from another Linux distribution. It is particularly useful for executing glibc-based programs (mostly closed-source software) under systems that are musl-based.
+
+## Features
+
+- Create a container environment for running programs from different Linux distributions.
+- Bind mount directories from the host system into the container.
+- Map user and group IDs inside the container.
+- Customizable root path for the container.
+- Option to run commands within the container as `root`.
+
+## Installation
+
+To install **weakbox**, simply clone the repository and compile the source code:
+
+```bash
+git clone https://github.com/friedelschoen/weakbox.git
+cd weakbox
+make
+sudo make install # which installs /usr/bin/weakbox and /usr/share/man/man1/weakbox.1
+sudo make PREFIX=... install # which installs $PREFIX/bin/weakbox and $PREFIX/share/man/man1/weakbox.1
+```
+
+## Usage
+
+Run **weakbox** with the desired options and command to execute within the container:
+
+```bash
+weakbox [options] command ...
+```
+
+### Options
+
+- `-h`: Display usage information.
+- `-s`: Run the specified command within the container as root.
+- `-v`: Enable verbose mode for debugging purposes.
+- `-r path`: Set the root path of the container to `path`.
+- `-b source[:target]`: Bind mount the specified source directory to the target directory within the container. Target is relative to `root`.
+- `-B source`: Remove the specified bind mount from the container.
+- `-u uid[:uid]`: Map user IDs inside the container.
+- `-g gid[:gid]`: Map group IDs inside the container.
+
+### Examples
+
+1. Run a program within the container:
+
+```bash
+weakbox -s /path/to/program
+```
+
+2. Create a container with custom root path and bind mount directories:
+
+```bash
+weakbox -r /custom/root -b /host/dir:/dir /path/to/program
+```
+
+3. Map user and group IDs inside the container:
+
+```bash
+weakbox -u 1000:1000 -g 1000:1000 /path/to/program
+```
+
+## Contributing
+
+Contributions are welcome! Feel free to submit bug reports, feature requests, or pull requests through GitHub issues and pull requests.
+
+## License
+
+This project is licensed under the zlib-license. See the [LICENSE](LICENSE) file for details.
diff --git a/weakbox.1 b/weakbox.1
@@ -0,0 +1,101 @@
+.TH WEAKBOX 1 "April 2024" "Version 0.1.0" "User Manuals"
+
+.SH NAME
+weakbox \- create a weak container for running programs from a different Linux distribution
+
+.SH SYNOPSIS
+.B weakbox
+[\-hs] [\fI\-r\fP path] [\fI\-b\fP source[:target]] [\fI\-B\fP source] [\fI\-u\fP uid[:uid]] [\fI\-g\fP gid[:gid]] command ...
+
+.SH DESCRIPTION
+\fBweakbox\fR is a tool for Linux that allows you to create a container environment suitable for running programs from a different Linux distribution, particularly useful for executing glibc-based programs (mostly closed-source software) under systems that are musl-based. The container created by \fBweakbox\fR is not secured and should not be considered as a secure isolation mechanism.
+
+.SH OPTIONS
+.TP
+\fB\-h\fP
+Display usage information and exit.
+.TP
+\fB\-s\fP
+Run the specified command within the container as \fIroot\fR.
+.TP
+\fB\-v\fP
+Enable verbose mode for debugging purposes.
+.TP
+\fB\-r\fP path
+Use a different root path of the container than \fBGLIBC_ROOT\fR. \fIpath\fR is relative to \fIcontainer-root\fR.
+.TP
+\fB\-b\fP source[:target]
+Bind mount the specified source directory to the target directory within the container. If \fItarget\fR is not provided, it defaults to the same as \fIsource\fR.
+.TP
+\fB\-B\fP source
+Remove the specified bind mount from the \fIdefault bindings\fR.
+.TP
+\fB\-u\fP uid[:uid]
+Map user IDs inside the container. If only one \fIuid\fR is provided, it will be mapped to the same ID inside the container.
+.TP
+\fB\-g\fP gid[:gid]
+Map group IDs inside the container. If only one \fIgid\fR is provided, it will be mapped to the same ID inside the container.
+
+.SH EXAMPLES
+.TP
+1. Run a program within the container:
+.B weakbox -s /path/to/program
+.TP
+2. Create a container with a custom root path and bind mount directories:
+.B weakbox -r /custom/root -b /host/dir:/dir /path/to/program
+.TP
+3. Map user and group IDs inside the container:
+.B weakbox -u 1000:1000 -g 1000:1000 /path/to/program
+
+.SH ENVIRONMENT VARIABLES
+\fBGLIBC_ROOT\fR
+Set the root path of the container if not provided via the \fI\-r\fR option.
+
+.SH DEFAULT MOUNTS
+.TP
+\fI/dev\fR
+directory containing all devices
+.TP
+\fI/home\fR
+home directories of users
+.TP
+\fI/proc\fR
+directories containing information about processes
+.TP
+\fI/sys\fR
+system directories for various devices
+.TP
+\fI/tmp\fR
+temporary directory
+.TP
+\fI/run\fR
+temporary directory for daemons and long-running programs
+.TP
+\fI/etc/resolv.conf\fR
+nameserver-resolution configuration
+.TP
+\fI/etc/passwd\fR
+file containing information about users
+.TP
+\fI/etc/group\fR
+file containing information about groups
+
+.SH EXIT STATUS
+.TP
+0
+Successful execution.
+.TP
+1
+An error occurred during execution.
+
+.SH SEE ALSO
+For more information, refer to the \fBweakbox\fR source code or documentation.
+
+.SH AUTHOR
+\fBweakbox\fR was written by Friedel Schon.
+
+.SH REPORTING BUGS
+Report bugs to the GitHub repository for \fBweakbox\fR.
+
+.SH COPYRIGHT
+\fBweakbox\fR is licensed under the zlib-license.
diff --git a/weakbox.c b/weakbox.c
@@ -0,0 +1,238 @@
+#define _GNU_SOURCE
+#include "arg.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#define LEN(arr) (sizeof(arr) / sizeof(*arr))
+#define mapping_t(type) \
+ struct { \
+ type source; \
+ type target; \
+ }
+
+#define DEBUG(...) (flagv ? fprintf(stderr, __VA_ARGS__) : 0)
+
+#define SET_MAPPING_WITH_DEMILITER(mapping, argf, temp, demiliter, source_, target_) \
+ if (((temp) = strchr(argf, demiliter))) { \
+ *(temp)++ = '\0'; \
+ (mapping).source = source_; \
+ (mapping).target = target_; \
+ } else { \
+ (mapping).source = source_; \
+ (mapping).target = source_; \
+ }
+
+#define PATH_PROC_UIDMAP "/proc/self/uid_map"
+#define PATH_PROC_GIDMAP "/proc/self/gid_map"
+#define PATH_PROC_SETGROUPS "/proc/self/setgroups"
+#define SHELL_DEFAULT "bash"
+#define MAX_BINDS 64
+#define MAX_USERMAP 8
+#define MAX_GROUPMAP 16
+
+
+static int open_printf(const char* file, const char* format, ...) {
+ FILE* fd;
+ va_list va;
+
+ if (!(fd = fopen(file, "w"))) {
+ return -1;
+ }
+
+ va_start(va, format);
+ vfprintf(fd, format, va);
+ va_end(va);
+ fclose(fd);
+ return 0;
+}
+
+static char* argv0;
+
+static __attribute__((noreturn)) void usage(int exitcode) {
+ printf("usage: %s [-hs] [-r path] [-b source[:target]] [-B source] [-u uid[:uid]] [-g gid[:gid]] command ...\n", argv0);
+ exit(exitcode);
+}
+
+static int bind_count = 9;
+static mapping_t(const char*) bind[MAX_BINDS] = {
+ { "/dev", "/dev" },
+ { "/home", "/home" },
+ { "/proc", "/proc" },
+ { "/sys", "/sys" },
+ { "/tmp", "/tmp" },
+ { "/run", "/run" },
+ { "/etc/resolv.conf", "/etc/resolv.conf" },
+ { "/etc/passwd", "/etc/passwd" },
+ { "/etc/group", "/etc/group" }
+};
+
+static int usermap_count = 0;
+static mapping_t(uid_t) usermap[MAX_USERMAP];
+
+static int groupmap_count = 0;
+static mapping_t(gid_t) groupmap[MAX_GROUPMAP];
+
+static int remove_bind(const char* path) {
+ int found = 0;
+ for (int i = 0; i < bind_count; i++) {
+ if (!strcmp(bind[i].source, path))
+ found++, bind_count--;
+ if (found) {
+ bind[i].source = bind[i + 1].source;
+ bind[i].target = bind[i + 1].target;
+ }
+ }
+ return found;
+}
+
+int main(int argc, char** argv) {
+ const char* root = getenv("GLIBC_ROOT");
+ const char* shell = getenv("SHELL");
+ int flagr = 0, flagv = 0;
+ char pwd[PATH_MAX];
+ char* temp;
+ char* argf;
+
+ (void) argc;
+
+ getcwd(pwd, sizeof(pwd));
+
+ argv0 = *argv;
+ ARGBEGIN
+ switch (OPT) {
+ case 'h':
+ usage(0);
+ case 'v':
+ flagv++;
+ break;
+ case 's':
+ flagr++;
+ break;
+ case 'r':
+ root = EARGF(usage(1));
+ break;
+ case 'b':
+ argf = EARGF(usage(1));
+ if (bind_count >= (int) LEN(bind)) {
+ printf("error: too many bindings\n");
+ return 1;
+ }
+ SET_MAPPING_WITH_DEMILITER(bind[bind_count], argf, temp, ':', argf, temp);
+ bind_count++;
+ break;
+ case 'u':
+ argf = EARGF(usage(1));
+ if (usermap_count >= (int) LEN(usermap)) {
+ printf("error: too many user-mappings\n");
+ return 1;
+ }
+
+ SET_MAPPING_WITH_DEMILITER(usermap[usermap_count], argf, temp, ':', atoi(argf), atoi(temp));
+ usermap_count++;
+ break;
+ case 'g':
+ argf = EARGF(usage(1));
+ if (groupmap_count >= (int) LEN(groupmap)) {
+ printf("error: too many group-mappings\n");
+ return 1;
+ }
+ SET_MAPPING_WITH_DEMILITER(groupmap[groupmap_count], argf, temp, ':', atoi(argf), atoi(temp));
+ groupmap_count++;
+ break;
+ case 'B':
+ argf = EARGF(usage(1));
+ if (!remove_bind(argf))
+ printf("warn: binding '%s' not found\n", argf);
+ break;
+ default:
+ printf("error: unknown option '-%c'\n", OPT);
+ usage(1);
+ }
+ ARGEND
+
+ usermap[usermap_count].source = flagr ? geteuid() : 0;
+ usermap[usermap_count++].target = getegid();
+ groupmap[groupmap_count].source = flagr ? getegid() : 0;
+ groupmap[groupmap_count++].target = getegid();
+
+ if (!root) {
+ fprintf(stderr, "error: $GLIBC_ROOT not set and option '-r' is not used\n");
+ return 1;
+ }
+
+ DEBUG("debug: unsharing filesystem-namespace and user-namespace\n");
+ if (unshare(CLONE_NEWNS | CLONE_NEWUSER)) {
+ fprintf(stderr, "error: unable to unshare for new filesystem and user-environment: %s\n", strerror(errno));
+ return 1;
+ }
+
+ DEBUG("debug: setting setgroups-policy\n");
+ if (open_printf(PATH_PROC_SETGROUPS, "deny") && errno != ENOENT) {
+ fprintf(stderr, "error: unable to set setgroups-policy: %s\n", strerror(errno));
+ return 1;
+ }
+
+ for (int i = 0; i < usermap_count; i++) {
+ DEBUG("debug: mapping user %d to %d\n", usermap[i].source, usermap[i].target);
+ if (open_printf(PATH_PROC_UIDMAP, "%u %u 1", usermap[i].target, usermap[i].source)) {
+ fprintf(stderr, "error: unable to map user %d to %d: %s\n", usermap[i].source, usermap[i].target, strerror(errno));
+ return 1;
+ }
+ }
+
+ for (int i = 0; i < groupmap_count; i++) {
+ DEBUG("debug: mapping group %d to %d\n", groupmap[i].source, groupmap[i].target);
+ if (open_printf(PATH_PROC_UIDMAP, "%u %u 1", groupmap[i].target, groupmap[i].source)) {
+ fprintf(stderr, "error: unable to map group %d to %d: %s\n", groupmap[i].source, groupmap[i].target, strerror(errno));
+ return 1;
+ }
+ }
+
+ char target[PATH_MAX];
+ for (int i = 0; i < bind_count; i++) {
+ snprintf(target, sizeof(target), "%s/%s", root, bind[i].target);
+ DEBUG("debug: mount '%s' to '%s'\n", bind[i].source, target);
+ if (mount(bind[i].source, target, NULL, MS_BIND | MS_REC, NULL)) {
+ fprintf(stderr, "error: unable to bind '%s' to '%s': %s\n", bind[i].source, target, strerror(errno));
+ return 1;
+ }
+ }
+
+ DEBUG("debug: change root to '%s'\n", root);
+ if (chroot(root)) {
+ fprintf(stderr, "error: unable to set root to '%s': %s\n", root, strerror(errno));
+ return 1;
+ }
+
+ // if chdir(pwd) fails, chdir("/") cannot fail
+ DEBUG("debug: change directory to '%s'\n", pwd);
+ if (chdir(pwd)) {
+ DEBUG("debug: ... which failed (%s), change directory to '/'\n", strerror(errno));
+ (void) chdir("/");
+ }
+
+ if (*argv) {
+ DEBUG("debug: executing '%s'...\n", *argv);
+ execvp(*argv, argv);
+ fprintf(stderr, "error: unable to execute '%s': %s\n", *argv, strerror(errno));
+ } else if (shell) {
+ DEBUG("debug: executing '%s'...\n", shell);
+ execlp(shell, shell, NULL);
+ fprintf(stderr, "error: unable to execute '%s': %s\n", shell, strerror(errno));
+ } else {
+ DEBUG("debug: executing '" SHELL_DEFAULT "'...\n");
+ execlp(SHELL_DEFAULT, SHELL_DEFAULT, NULL);
+ fprintf(stderr, "error: unable to execute '" SHELL_DEFAULT "': %s\n", strerror(errno));
+ }
+ return 1;
+}