aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2020-11-29 03:42:28 +0300
committerEvgeny Zinoviev <me@ch1p.io>2020-11-29 03:42:28 +0300
commit4b7af666f2abdf5e3cc76e21b11273deccaec169 (patch)
tree860c20bafc18ccbb2e4bdc1bbe8c1540bfbd7634
initial
-rw-r--r--Makefile27
-rw-r--r--glibcrun.c101
2 files changed, 128 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4e8c506
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+CC := gcc
+
+PROGRAM = glibcrun
+CFLAGS = -O2 -std=c99 -Wall -W
+LDFLAGS =
+
+INSTALL = /usr/bin/env install
+PREFIX = /usr/local
+
+OBJS = glibcrun.o
+
+all: $(PROGRAM)
+
+$(PROGRAM): $(OBJS)
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+install: $(PROGRAM)
+ $(INSTALL) $(PROGRAM) $(PREFIX)/bin
+ chmod u+s $(PREFIX)/bin/${PROGRAM}
+
+clean:
+ rm -f $(OBJS) $(PROGRAM)
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c $^ -I. -o $@
+
+.PHONY: all install clean distclean
diff --git a/glibcrun.c b/glibcrun.c
new file mode 100644
index 0000000..efedba1
--- /dev/null
+++ b/glibcrun.c
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2020 Evgeny Zinoviev <me@ch1p.io>.
+ *
+ * This program is licensed under the BSD 2-Clause License.
+ * https://opensource.org/licenses/BSD-2-Clause
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sched.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+
+const char *var_name = "GLIBCRUN_DIR";
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#define ERROR(f_, ...) fprintf(stderr, (f_), ##__VA_ARGS__)
+
+int main(int argc, char *argv[]) {
+ int result;
+
+ /* Get glibc base path */
+ const char *dir = getenv(var_name);
+ if (!dir) {
+ ERROR("error: environment variable %s not found.\n", var_name);
+ return 1;
+ }
+
+ /* Validate it */
+ struct stat st;
+ result = stat(dir, &st);
+ if (result != 0 || !S_ISDIR(st.st_mode)) {
+ ERROR("error: %s is not a directory.\n", dir);
+ return 1;
+ }
+
+ /* Get shell from current env to reuse it if needed */
+ const char *shell = getenv("SHELL");
+ if (!shell)
+ shell = "/bin/sh";
+
+ /* Do the unshare magic */
+ result = unshare(CLONE_NEWNS);
+ if (result == -1) {
+ ERROR("unshare: %s\n", strerror(errno));
+ return 1;
+ }
+
+ /* Mount glibc stuff to our private namespace */
+ const char *const mountpoints[] = {"/usr", "/var/db/xbps"};
+ char buf[PATH_MAX];
+
+ for (int i = 0; i < (int)ARRAY_SIZE(mountpoints); i++) {
+ strcpy(buf, dir);
+ strcat(buf, mountpoints[i]);
+ result = mount(buf, mountpoints[i], NULL, MS_BIND|MS_REC, NULL);
+ if (result == -1) {
+ ERROR("mount(%s): %s\n", mountpoints[i], strerror(errno));
+ return 1;
+ }
+ }
+
+ /* Drop root */
+ uid_t uid = getuid();
+ gid_t gid = getgid();
+
+ result = setreuid(uid, uid);
+ if (result == -1) {
+ ERROR("setreuid: %s\n", strerror(errno));
+ return 1;
+ }
+
+ result = setregid(gid, gid);
+ if (result == -1) {
+ ERROR("setregid: %s\n", strerror(errno));
+ return 1;
+ }
+
+ /* Launch program or shell */
+ const char *exec_cmd;
+ char *const *exec_args = NULL;
+ if (argc < 2)
+ exec_cmd = shell;
+ else {
+ exec_cmd = argv[1];
+ exec_args = (char *const *)argv+1;
+ }
+
+ result = execvp(exec_cmd, exec_args);
+ if (result == -1) {
+ ERROR("execvp(%s): %s\n", exec_cmd, strerror(errno));
+ return 1;
+ }
+
+ return 0;
+}