4 #include <glib/gstdio.h>
10 #include <sys/mount.h>
12 #include <sys/types.h>
14 #include <sys/syscall.h>
16 #define fail(s, err) g_error("%s: %s: %s", __func__, s, g_strerror(err))
17 #define fail_if(expr) \
21 const gchar
*bind_blacklist
[] = {"bin", "etc", "host", "real-host", "usr", "lib", "lib64", "lib32", "sbin", "opt", NULL
};
23 int pivot_root(const char *new_root
, const char *put_old
) {
24 return syscall(SYS_pivot_root
, new_root
, put_old
);
27 void mount_tmpfs(const gchar
*target
) {
28 fail_if(mount("none", target
, "tmpfs", 0, NULL
));
31 void bind_mount(const gchar
*source
, const gchar
*target
) {
32 fail_if(g_mkdir(target
, 0755));
33 fail_if(mount(source
, target
, NULL
, MS_BIND
| MS_REC
, NULL
));
36 const gchar
*create_tmpdir() {
38 g_build_filename(g_get_tmp_dir(), "chrootenvXXXXXX", NULL
);
39 fail_if(!g_mkdtemp_full(prefix
, 0755));
43 void pivot_host(const gchar
*guest
) {
44 g_autofree gchar
*point
= g_build_filename(guest
, "host", NULL
);
45 fail_if(g_mkdir(point
, 0755));
46 fail_if(pivot_root(guest
, point
));
49 void bind_mount_item(const gchar
*host
, const gchar
*guest
, const gchar
*name
) {
50 g_autofree gchar
*source
= g_build_filename(host
, name
, NULL
);
51 g_autofree gchar
*target
= g_build_filename(guest
, name
, NULL
);
53 if (G_LIKELY(g_file_test(source
, G_FILE_TEST_IS_DIR
)))
54 bind_mount(source
, target
);
57 void bind(const gchar
*host
, const gchar
*guest
) {
62 g_autofree gchar
*host_dir
= g_build_filename("/host", host
, NULL
);
64 g_autoptr(GError
) err
= NULL
;
65 g_autoptr(GDir
) dir
= g_dir_open(host_dir
, 0, &err
);
68 fail("g_dir_open", errno
);
72 while ((item
= g_dir_read_name(dir
)))
73 if (!g_strv_contains(bind_blacklist
, item
))
74 bind_mount_item(host_dir
, "/", item
);
77 void spit(const char *path
, char *fmt
, ...) {
81 FILE *f
= g_fopen(path
, "w");
84 fail("g_fopen", errno
);
86 g_vfprintf(f
, fmt
, args
);
90 int main(gint argc
, gchar
**argv
) {
91 const gchar
*self
= *argv
++;
94 g_message("%s command [arguments...]", self
);
98 g_autofree
const gchar
*prefix
= create_tmpdir();
105 else if (cpid
== 0) {
106 uid_t uid
= getuid();
107 gid_t gid
= getgid();
109 int namespaces
= CLONE_NEWNS
;
111 namespaces
|= CLONE_NEWUSER
;
113 if (unshare(namespaces
) < 0) {
114 int unshare_errno
= errno
;
116 g_message("Requires Linux version >= 3.19 built with CONFIG_USER_NS");
117 if (g_file_test("/proc/sys/kernel/unprivileged_userns_clone",
119 g_message("Run: sudo sysctl -w kernel.unprivileged_userns_clone=1");
121 fail("unshare", unshare_errno
);
124 // hide all mounts we do from the parent
125 fail_if(mount(0, "/", 0, MS_SLAVE
| MS_REC
, 0));
128 spit("/proc/self/setgroups", "deny");
129 spit("/proc/self/uid_map", "%d %d 1", uid
, uid
);
130 spit("/proc/self/gid_map", "%d %d 1", gid
, gid
);
133 // If there is a /host directory, assume this is nested chrootenv and use it as host instead.
134 gboolean nested_host
= g_file_test("/host", G_FILE_TEST_EXISTS
| G_FILE_TEST_IS_DIR
);
135 g_autofree
const gchar
*host
= nested_host
? "/host" : "/";
139 // Replace /host by an actual (inner) /host.
141 fail_if(g_mkdir("/real-host", 0755));
142 fail_if(mount("/host/host", "/real-host", NULL
, MS_BIND
| MS_REC
, NULL
));
143 // For some reason umount("/host") returns EBUSY even immediately after
144 // pivot_root. We detach it at least to keep `/proc/mounts` from blowing
145 // up in nested cases.
146 fail_if(umount2("/host", MNT_DETACH
));
147 fail_if(mount("/real-host", "/host", NULL
, MS_MOVE
, NULL
));
148 fail_if(rmdir("/real-host"));
152 fail_if(execvp(*argv
, argv
));
158 fail_if(waitpid(cpid
, &status
, 0) != cpid
);
159 fail_if(rmdir(prefix
));
161 if (WIFEXITED(status
))
162 return WEXITSTATUS(status
);
164 else if (WIFSIGNALED(status
))
165 kill(getpid(), WTERMSIG(status
));