this repo has no description
at main 10 kB view raw
1#define _GNU_SOURCE 2#define _FILE_OFFSET_BITS 64 3#include <linux/sched.h> 4 5#include <sys/stat.h> 6#include <sys/types.h> 7#include <sys/eventfd.h> 8#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24 9#include <sys/random.h> 10#endif 11#include <sys/syscall.h> 12#include <sys/wait.h> 13#include <sys/mount.h> 14#include <limits.h> 15#include <errno.h> 16#include <dirent.h> 17#include <fcntl.h> 18#include <signal.h> 19#include <unistd.h> 20#include <string.h> 21 22 23#define CAML_INTERNALS 24#include <caml/mlvalues.h> 25#include <caml/unixsupport.h> 26#include <caml/memory.h> 27#include <caml/custom.h> 28#include <caml/signals.h> 29#include <caml/fail.h> 30#undef CAML_INTERNALS 31 32// From Eio 33#include <include/fork_action.h> 34 35#ifndef SYS_pidfd_send_signal 36#define SYS_pidfd_send_signal 424 37#endif 38 39// struct clone_args isn't defined in linux-lts headers, so define it here 40// Note that this struct is versioned by size. See linux/sched.h for details 41struct caml_void_clone_args 42{ 43 uint64_t flags; 44 uint64_t pidfd; 45 uint64_t child_tid; 46 uint64_t parent_tid; 47 uint64_t exit_signal; 48 uint64_t stack; 49 uint64_t stack_size; 50 uint64_t tls; 51}; 52 53static int 54pidfd_send_signal (int pidfd, int sig, siginfo_t *info, unsigned int flags) 55{ 56 return syscall (SYS_pidfd_send_signal, pidfd, sig, info, flags); 57} 58 59CAMLprim value 60caml_void_pidfd_send_signal (value v_pidfd, value v_signal) 61{ 62 CAMLparam0 (); 63 int res; 64 65 res = 66 pidfd_send_signal (Int_val (v_pidfd), 67 caml_convert_signal_number (Int_val (v_signal)), NULL, 68 0); 69 if (res == -1) 70 uerror ("pidfd_send_signal", Nothing); 71 CAMLreturn (Val_unit); 72} 73 74static pid_t 75clone3_no_fallback (struct caml_void_clone_args *cl_args) 76{ 77 int *pidfd = (int *) (uintptr_t) cl_args->pidfd; 78 pid_t child_pid = 79 syscall (SYS_clone3, cl_args, sizeof (struct caml_void_clone_args)); 80 81 if (child_pid >= 0) 82 return child_pid; /* Success! */ 83 84 if (errno != ENOSYS && errno != EPERM) 85 { 86 uerror ("clone3", Nothing); /* Unknown error */ 87 } 88 89 uerror ("clone3", Nothing); 90} 91 92CAMLprim value 93caml_void_clone3 (value v_errors, value v_flags, value v_actions) 94{ 95 CAMLparam1 (v_actions); 96 CAMLlocal1 (v_result); 97 pid_t child_pid; 98 int pidfd = -1; /* Is automatically close-on-exec */ 99 100 struct caml_void_clone_args cl_args = { 101 .flags = Int_val (v_flags), 102 .pidfd = (uintptr_t) & pidfd, 103 .exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */ 104 .stack = (uintptr_t) NULL, /* Use copy-on-write parent stack */ 105 .stack_size = 0, 106 }; 107 108 child_pid = clone3_no_fallback (&cl_args); 109 if (child_pid == 0) 110 { 111 /* Run child actions (doesn't return) */ 112 eio_unix_run_fork_actions (Int_val (v_errors), v_actions); 113 } 114 115 v_result = caml_alloc_tuple (2); 116 Store_field (v_result, 0, Val_long (child_pid)); 117 Store_field (v_result, 1, Val_int (pidfd)); 118 119 CAMLreturn (v_result); 120} 121 122 123// Actions 124 125// MOUNT/UNMOUNT 126static void 127action_mount (int errors, value v_config) 128{ 129 value v_src = Field (v_config, 1); 130 value v_tgt = Field (v_config, 2); 131 value v_type = Field (v_config, 3); 132 value v_flags = Field (v_config, 4); 133 134 int r; 135 136 r = 137 mount (String_val (v_src), String_val (v_tgt), String_val (v_type), 138 Int_val (v_flags), NULL); 139 140 if (r != 0) 141 { 142 eio_unix_fork_error (errors, "mount", strerror (errno)); 143 _exit (1); 144 } 145} 146 147CAMLprim value 148void_fork_mount (value v_unit) 149{ 150 return Val_fork_fn (action_mount); 151} 152 153// Writes a single line to a file 154static int 155put_line (const char *filename, const char *line) 156{ 157 int fd; 158 int written; 159 160 fd = open (filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644); 161 162 if (fd < 0) 163 { 164 return fd; 165 } 166 167 written = write (fd, line, strlen (line)); 168 169 close (fd); 170 171 if (written != strlen (line)) 172 { 173 return -1; 174 } 175 176 return 0; 177} 178 179// MAP UID/GID to root 180static void 181action_map_uid_gid (int errors, value v_config) 182{ 183 value v_uid = Field (v_config, 1); 184 value v_gid = Field (v_config, 2); 185 int result; 186 char uid_line[30]; 187 char gid_line[30]; 188 189 // We map root onto the calling UID 190 snprintf (uid_line, sizeof (uid_line), "0 %i 1\n", Int_val (v_uid)); 191 result = put_line ("/proc/self/uid_map", uid_line); 192 193 if (result < 0) 194 { 195 eio_unix_fork_error (errors, "map_uid_gid-uid", strerror (errno)); 196 _exit (1); 197 } 198 199 /* From user_namespaces(7) 200 * 201 * Writing "deny" to the /proc/pid/setgroups file before writing to 202 * /proc/pid/gid_map will permanently disable setgroups(2) in a user 203 * namespace and allow writing to /proc/pid/gid_map without having 204 * the CAP_SETGID capability in the parent user namespace. 205 * 206 * See also: https://lwn.net/Articles/626665/ */ 207 208 put_line ("/proc/self/setgroups", "deny\n"); 209 210 if (result < 0) 211 { 212 eio_unix_fork_error (errors, "map_uid_gid-setgroups", strerror (errno)); 213 _exit (1); 214 } 215 216 result = 217 snprintf (gid_line, sizeof (gid_line), "0 %i 1\n", Int_val (v_gid)); 218 put_line ("/proc/self/gid_map", gid_line); 219 220 if (result < 0) 221 { 222 eio_unix_fork_error (errors, "map_uid_gid-gid", strerror (errno)); 223 _exit (1); 224 } 225} 226 227 228CAMLprim value 229void_fork_map_uid_gid (value v_unit) 230{ 231 return Val_fork_fn (action_map_uid_gid); 232} 233 234static void 235action_setuid (int errors, value v_config) 236{ 237 value v_uid = Field(v_config, 1); 238 239 // if (setuid(1000)) { 240 // eio_unix_fork_error (errors, "setuid", strerror (errno)); 241 // _exit (1); 242 // } 243} 244 245// SETUID 246CAMLprim value 247void_fork_setuid (value v_unit) 248{ 249 return Val_fork_fn (action_setuid); 250} 251 252// PIVOT ROOT 253// 254static int 255pivot_root (const char *new_root, const char *put_old) 256{ 257 return syscall (SYS_pivot_root, new_root, put_old); 258} 259 260// Is there too much OCaml stuff going on here for a fork_action ? 261static void 262action_pivot_root (int errors, value v_config) 263{ 264 value v_new_root = Field (v_config, 1); 265 value v_root_flags = Field (v_config, 2); 266 value v_no_root = Field (v_config, 3); 267 value v_mounts = Field (v_config, 4); 268 char path[PATH_MAX]; 269 char old_root_path[PATH_MAX]; 270 char *new_root = String_val (v_new_root); 271 const char *put_old = ".old_root"; 272 273 // From pivot_root example: We want to change the propagation type 274 // of root to be private so we can pivot it. 275 if (mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1) 276 { 277 eio_unix_fork_error (errors, "pivot_root-private", strerror (errno)); 278 _exit (1); 279 } 280 281 // If no pivot_root was given, then we tmpfs the tmpdir we assume was passed. 282 if (Bool_val (v_no_root)) 283 { 284 // Make a temporary directory... can't because it allocates ? 285 //if (mkdtemp(new_root) != NULL) { 286 // eio_unix_fork_error(errors, new_root, strerror(errno)); 287 // _exit(1); 288 //} 289 290 if (mount ("tmpfs", new_root, "tmpfs", 0, NULL) <= -1) 291 { 292 eio_unix_fork_error (errors, "pivot_root-tmpfs", strerror (errno)); 293 _exit (1); 294 } 295 } 296 else 297 { 298 // From pivot_root example: we check that new_root is indeed a mountpoint 299 if (mount (new_root, new_root, NULL, MS_BIND, NULL) <= -1) 300 { 301 eio_unix_fork_error (errors, "pivot_root-new_root", 302 strerror (errno)); 303 _exit (1); 304 } 305 } 306 307 // Make the place to pivot the old root too, under the new root 308 snprintf (old_root_path, sizeof (path), "%s/%s", new_root, put_old); 309 310 if (mkdir (old_root_path, 0777) == -1) 311 { 312 eio_unix_fork_error (errors, "pivot_root-mkdir-put_old", 313 strerror (errno)); 314 _exit (1); 315 } 316 317 // Pivot the root 318 if (pivot_root (new_root, old_root_path)) 319 { 320 eio_unix_fork_error (errors, "pivot_root", strerror (errno)); 321 _exit (1); 322 } 323 324 // Add mounts 325 value current_mount = v_mounts; 326 int mount_result; 327 int mode; 328 while (current_mount != Val_emptylist) 329 { 330 // TODO: Mode for mounting 331 mode = Int_val (Field (Field (current_mount, 0), 2)); 332 333 // A mount is a record {src; tgt; mode}, we first create the mount point 334 // directory target 335 if (mkdir (String_val (Field (Field (current_mount, 0), 1)), 0777) == 336 -1) 337 { 338 eio_unix_fork_error (errors, "pivot_root-mkdir-mount", 339 strerror (errno)); 340 _exit (1); 341 } 342 343 mount_result = mount (String_val (Field (Field (current_mount, 0), 0)), 344 String_val (Field (Field (current_mount, 0), 1)), 345 NULL, MS_REC | MS_BIND, NULL); 346 347 // Fail early if a mount fails... 348 if (mount_result < 0) 349 { 350 char error[PATH_MAX]; 351 snprintf (error, sizeof (error), "mount failed: (%s->%s)", 352 String_val (Field (Field (current_mount, 0), 0)), 353 String_val (Field (Field (current_mount, 0), 1))); 354 eio_unix_fork_error (errors, error, strerror (errno)); 355 _exit (1); 356 } 357 358 // After mounting for the first time, we can come back and add any 359 // extra modes that may have been specified, for example RDONLY. 360 if (mode != 0) 361 { 362 mount_result = 363 mount (String_val (Field (Field (current_mount, 0), 0)), 364 String_val (Field (Field (current_mount, 0), 1)), NULL, 365 MS_REMOUNT | MS_BIND | mode, NULL); 366 367 if (mount_result < 0) 368 { 369 eio_unix_fork_error (errors, "remount for mode", 370 strerror (errno)); 371 _exit (1); 372 } 373 } 374 375 // Next mount in the list 376 current_mount = Field (current_mount, 1); 377 } 378 379 380 // Change to the 'new' root 381 if (chdir ("/") == -1) 382 { 383 eio_unix_fork_error (errors, "pivot_root-chdir", strerror (errno)); 384 _exit (1); 385 } 386 387 // Unmount the old root and remove it 388 if (umount2 (put_old, MNT_DETACH) == -1) 389 { 390 eio_unix_fork_error (errors, put_old, strerror (errno)); 391 _exit (1); 392 } 393 394 // Remove the old root 395 if (rmdir (put_old) == -1) 396 { 397 eio_unix_fork_error (errors, put_old, strerror (errno)); 398 _exit (1); 399 } 400 401 402 // Apply any flags to the new root, e.g. RDONLY 403 if (Int_val (v_root_flags)) 404 { 405 if (mount 406 ("/", "/", NULL, (MS_REMOUNT | MS_BIND | Int_val (v_root_flags)), 407 NULL) <= -1) 408 { 409 eio_unix_fork_error (errors, "pivot_root-rootflags", 410 strerror (errno)); 411 _exit (1); 412 } 413 } 414} 415 416CAMLprim value 417void_fork_pivot_root (value v_unit) 418{ 419 return Val_fork_fn (action_pivot_root); 420}