├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── src └── main.c /.gitignore: -------------------------------------------------------------------------------- 1 | docker-boot 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: docker-boot 2 | 3 | docker-boot: src/main.c 4 | $(CC) $^ -o $@ -O2 -Wall 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-boot 2 | 3 | https://github.com/purplesyringa/docker-boot/assets/16370781/0f2a24e1-7c5f-47a6-9730-479f853af25a 4 | 5 | > Like `execve`, but for userspace. 6 | 7 | docker-boot replaces your current running system with an in-memory root filesystem constructed from a Docker image. 8 | 9 | Example with GUI: 10 | 11 | **Dockerfile** 12 | 13 | ```dockerfile 14 | FROM ubuntu 15 | RUN apt update 16 | RUN apt-get install -y software-properties-common && add-apt-repository ppa:mozillateam/ppa 17 | RUN DEBIAN_FRONTEND=noninteractive apt-get install -y sudo htop systemd sddm kde-plasma-desktop firefox-esr 18 | RUN useradd --create-home --shell /bin/bash --groups sudo --password "$(perl -e "print crypt('cutie', 'sa');")" --user-group purplesyringa 19 | RUN echo "InputMethod=" >/etc/sddm.conf 20 | ``` 21 | 22 | ```shell 23 | $ docker build . -t workstation 24 | <...> 25 | 26 | $ sudo docker-boot workstation /bin/systemd 27 | 28 | ``` 29 | 30 | Example without GUI: 31 | 32 | **Dockerfile** 33 | 34 | ```shell 35 | $ sudo docker-boot ubuntu /bin/bash -c "mount -t proc proc proc; mount -t sysfs sys sys; exec bash -i" 36 | 37 | ``` 38 | 39 | (Or make `systemd` mount the filesystems for you if you're feeling adventurous.) 40 | 41 | 42 | ## Building 43 | 44 | Just do `make`. 45 | 46 | You're going to need `docker`, `swapoff`, `tar`, and `dd` installed. 47 | 48 | 49 | ## Why? 50 | 51 | 1. I'm a Nix contrarian, so naturally I wanted *something* to be to Docker like NixOS is to Nix. docker-boot fills this niche. 52 | 2. Injecting into `init` is based, I've always wanted to do that; this project is my excuse. 53 | 3. If you need to move partitions on your boot disk, you probably want to run a system off RAM. This is typically accomplished by creating a tmpfs, `debootstrap`ing an OS into it, `pivot_root`ing and killing services that use the real disk. That's a bit ridiculous of a manual; this project attempts to reduce the gap. 54 | 55 | 56 | ## How it works 57 | 58 | First, `docker-boot` creates a directory at `/run/dboot/root`, mounts `tmpfs` on top of it, and exports the Docker image there. Other useful metadata, such as the program to launch as `init` from the image, is stored in other files in `/run/dboot`. 59 | 60 | `docker-boot` then attaches to PID 1 via ptrace, just like a debugger would. It waits for init (typically `systemd`) to start a syscall and interrupts the process just before the syscall is executed. Processor registers are then updated in the process to execute another syscall in place of the one intended by real `init`. 61 | 62 | The substituted syscall is `execve("/proc//exe", {"dboot", NULL}, NULL)`, where the `` is the PID of the `docker-boot` process. The path `/proc//exe` refers to the executable of the corresponding process, so this is an easy way to replace PID 1 with a copy of `docker-boot` regardless of (the length of) the path to `docker-boot`. The strings `/proc//exe` and `dboot` are stored below the red zone, i.e. 128 bytes below `rsp`. That is a region of stack that is almost always safe to overwrite. 63 | 64 | `docker-boot` then asks the kernel to execute exactly one instruction in the `init` process and checks whether the `errno` is `0`. If not, the `execve` is assumed to have failed, and the processor registers are restored to the state before `execve`, so that the system can keep functioning. 65 | 66 | On success, `docker-boot` continues execution in PID 1. It switches graphics to TTY 3, kills all processes, and switches the root filesystem to `/run/dboot/root` via `pivot_root`, which is a more robust and modern alternative to `chroot` that allows `docker-boot` to safely unmount the host filesystem after changing roots. Finally, the process uses `execve` to execute the process requested by the user, typically systemd or a shell. 67 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 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 | #include 22 | 23 | void wait_for_pid1() { 24 | int wstatus; 25 | if (waitpid(1, &wstatus, 0) == -1) { 26 | perror("Failed to wait for PID 1"); 27 | exit(2); 28 | } 29 | 30 | if (!WIFSTOPPED(wstatus)) { 31 | fputs("Expected PID 1 to be stopped, got another wait status instead\n", stderr); 32 | exit(2); 33 | } 34 | } 35 | 36 | [[noreturn]] void init() { 37 | // If a failure occurs here, we don't want the kernel to panic so that the user can check error 38 | // messages. 39 | 40 | int console_fd = open("/dev/console", O_RDWR); 41 | if (console_fd == -1) { 42 | perror("Failed to open /dev/console"); 43 | goto err; 44 | } 45 | 46 | // Use tty 3, as tty 1/2 are commonly used by DEs and display managers 47 | if (ioctl(console_fd, VT_ACTIVATE, 3) == -1) { 48 | perror("Failed to activate virtual terminal"); 49 | goto err; 50 | } 51 | 52 | if (ioctl(console_fd, KDSETMODE, KD_TEXT) == -1) { 53 | perror("Failed to switch virtual terminal to text mode"); 54 | goto err; 55 | } 56 | 57 | close(console_fd); 58 | 59 | int tty_fd = open("/dev/tty3", O_RDWR); 60 | if (tty_fd == -1) { 61 | perror("Failed to open /dev/tty3"); 62 | goto err; 63 | } 64 | 65 | for (int fd = 0; fd < 3; fd++) { 66 | if (dup2(tty_fd, fd) == -1) { 67 | perror("Failed to redirect stdio to VT"); 68 | goto err; 69 | } 70 | } 71 | 72 | close(tty_fd); 73 | 74 | // We are now attached to the virtual terminal the user supposedly has access to. 75 | 76 | fputs("dboot: docker-boot is now online\n", stderr); 77 | fflush(stderr); 78 | 79 | // Close all fds, include non-cloexec 80 | if (syscall(SYS_close_range, 3, ~0, 0) == -1) { 81 | perror("dboot: Failed to close fds"); 82 | goto err; 83 | } 84 | 85 | int argv_fd = open("/run/dboot/argv", O_RDONLY); 86 | if (argv_fd == -1) { 87 | perror("Failed to open /run/dboot/argv"); 88 | goto err; 89 | } 90 | char argv_buffer[65537]; 91 | size_t argv_offset = 0; 92 | for (;;) { 93 | size_t n_read = read(argv_fd, argv_buffer + argv_offset, sizeof(argv_buffer) - 1 - argv_offset); 94 | if (n_read == -1) { 95 | perror("Failed to read /run/dboot/argv"); 96 | goto err; 97 | } 98 | if (n_read == 0) { 99 | break; 100 | } 101 | argv_offset += n_read; 102 | } 103 | argv_buffer[argv_offset] = '\0'; 104 | close(argv_fd); 105 | 106 | char *argv_pointers[256]; 107 | char *p = argv_buffer; 108 | int i = 0; 109 | while (i < 255 && p != argv_buffer + argv_offset) { 110 | argv_pointers[i++] = p; 111 | p += strlen(p) + 1; 112 | } 113 | argv_pointers[i] = NULL; 114 | if (p != argv_buffer + argv_offset) { 115 | fputs("Invalid init command line\n", stderr); 116 | goto err; 117 | } 118 | 119 | fputs("dboot: Sending SIGTERM to all processes\n", stderr); 120 | fflush(stderr); 121 | if (kill(-1, SIGTERM) == -1) { 122 | perror("dboot: Failed to send SIGTERM"); 123 | goto err; 124 | } 125 | 126 | // Sleep for 3 seconds 127 | usleep(3000000); 128 | 129 | fputs("dboot: Sending SIGKILL to all processes\n", stderr); 130 | fflush(stderr); 131 | if (kill(-1, SIGKILL) == -1) { 132 | perror("Failed to send SIGKILL"); 133 | goto err; 134 | } 135 | 136 | int wstatus; 137 | while (wait(&wstatus) != -1) {} 138 | if (errno != ECHILD) { 139 | perror("Failed to wait for children to terminate"); 140 | goto err; 141 | } 142 | 143 | // We are now the only process alive. 144 | 145 | if (chdir("/run/dboot/root") == -1) { 146 | perror("Failed to chdir to /run/dboot/root"); 147 | goto err; 148 | } 149 | if (syscall(SYS_pivot_root, ".", ".") == -1) { 150 | perror("Failed to pivot_root"); 151 | goto err; 152 | } 153 | if (umount2(".", MNT_DETACH) == -1) { 154 | perror("Failed to unmount old root"); 155 | goto err; 156 | } 157 | if (chdir("/") == -1) { 158 | perror("Failed to chdir to /"); 159 | goto err; 160 | } 161 | 162 | char *envp[] = {"TERM=linux", NULL}; 163 | execve(argv_pointers[0], argv_pointers, envp); 164 | perror("Failed to exec init"); 165 | 166 | err: 167 | fputs("dboot: Entering recovery shell\n", stderr); 168 | fflush(stdout); 169 | fflush(stderr); 170 | 171 | execle("/bin/sh", "sh", NULL, envp); 172 | perror("Failed to execute /bin/sh"); 173 | fflush(stderr); 174 | 175 | for (;;) { 176 | pause(); 177 | } 178 | } 179 | 180 | int main(int argc, char **argv) { 181 | if (getpid() == 1) { 182 | init(); 183 | } 184 | 185 | // This interface is visible to the user. 186 | if (argc < 3) { 187 | fputs("dboot: Replace running system with a system from a Docker image\n", stderr); 188 | fprintf(stderr, "Usage: %s \n", argv[0]); 189 | return 1; 190 | } 191 | if (argv[2][0] != '/') { 192 | fputs("init command must have an absolute path\n", stderr); 193 | return 1; 194 | } 195 | 196 | if (getuid() != 0) { 197 | fputs("You are not root, aborting\n", stderr); 198 | return 1; 199 | } 200 | 201 | if (system("swapoff --all") != 0) { 202 | fputs("Failed to disable swap", stderr); 203 | return 2; 204 | } 205 | 206 | char hostname[256]; 207 | if (gethostname(hostname, sizeof(hostname) - 3) == -1) { 208 | perror("Failed to get hostname"); 209 | } 210 | hostname[sizeof(hostname) - 1] = '\0'; 211 | 212 | if (mkdir("/run/dboot", 0700) == -1 && errno != EEXIST) { 213 | perror("Failed to create /run/dboot"); 214 | return 2; 215 | } 216 | if (mkdir("/run/dboot/root", 0700) == -1 && errno != EEXIST) { 217 | perror("Failed to create /run/dboot/root"); 218 | return 2; 219 | } 220 | if (mount(NULL, "/run/dboot/root", "tmpfs", 0, NULL) == -1) { 221 | perror("Failed to mount tmpfs on /run/dboot/root"); 222 | return 2; 223 | } 224 | 225 | int argv_fd = open("/run/dboot/argv", O_WRONLY | O_CREAT, 0600); 226 | if (argv_fd == -1) { 227 | perror("Failed to open /run/dboot/argv"); 228 | return 2; 229 | } 230 | if (argc > 256) { 231 | fputs("Too many arguments\n", stderr); 232 | return 1; 233 | } 234 | size_t total_length = 0; 235 | for (int i = 2; i < argc; i++) { 236 | size_t count_written = 0; 237 | size_t count = strlen(argv[i]) + 1; 238 | total_length += count; 239 | while (count_written < count) { 240 | ssize_t result = write(argv_fd, argv[i] + count_written, count - count_written); 241 | if (result == -1) { 242 | perror("Failed to write to /run/dboot/argv"); 243 | return 2; 244 | } 245 | count_written += result; 246 | } 247 | } 248 | if (total_length > 65536) { 249 | fputs("argv too long\n", stderr); 250 | return 1; 251 | } 252 | if (close(argv_fd) == -1) { 253 | perror("Failed to close /run/dboot/argv"); 254 | return 2; 255 | } 256 | 257 | int fds[2]; 258 | if (pipe(fds) == -1) { 259 | perror("Failed to create pipe"); 260 | umount("/run/dboot/root"); 261 | return 2; 262 | } 263 | pid_t child_pid = fork(); 264 | if (child_pid == -1) { 265 | perror("Failed to fork"); 266 | umount("/run/dboot/root"); 267 | return 2; 268 | } 269 | if (child_pid == 0) { 270 | close(fds[0]); 271 | if (dup2(fds[1], 1) == -1) { 272 | perror("Failed to redirect stdout"); 273 | return 2; 274 | } 275 | execlp("docker", "docker", "create", argv[1], NULL); 276 | perror("Failed to exec docker create"); 277 | return 2; 278 | } 279 | close(fds[1]); 280 | int wstatus; 281 | if (waitpid(child_pid, &wstatus, 0) == -1) { 282 | perror("Failed to wait for child"); 283 | umount("/run/dboot/root"); 284 | return 2; 285 | } 286 | if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { 287 | fputs("docker create failed\n", stderr); 288 | umount("/run/dboot/root"); 289 | return 2; 290 | } 291 | char container_id[256]; 292 | ssize_t container_id_length = read(fds[0], container_id, sizeof(container_id) - 1); 293 | if (container_id_length == -1) { 294 | perror("Failed to receive container ID from docker create"); 295 | umount("/run/dboot/root"); 296 | return 2; 297 | } 298 | close(fds[0]); 299 | while (container_id_length > 0 && isspace((int)container_id[container_id_length - 1])) { 300 | container_id_length--; 301 | } 302 | container_id[container_id_length] = '\0'; 303 | 304 | fputs("Copying root filesystem to tmpfs...\n", stderr); 305 | fflush(stderr); 306 | 307 | char cmdline[4096]; 308 | sprintf(cmdline, "docker cp %s:/ - | dd status=progress bs=8M | tar xf /dev/stdin -C /run/dboot/root", container_id); 309 | int cp_is_success = system(cmdline) == 0; 310 | 311 | sprintf(cmdline, "docker rm %s >/dev/null", container_id); 312 | if (system(cmdline) != 0) { 313 | fputs("docker rm failed\n", stderr); 314 | umount("/run/dboot/root"); 315 | return 2; 316 | } 317 | 318 | if (!cp_is_success) { 319 | fputs("Copying root to ramfs failed\n", stderr); 320 | umount("/run/dboot/root"); 321 | return 2; 322 | } 323 | 324 | int etc_hostname_fd = open("/run/dboot/root/etc/hostname", O_WRONLY | O_CREAT, 0644); 325 | if (etc_hostname_fd == -1) { 326 | perror("Failed to open /run/dboot/root/etc/hostname"); 327 | umount("/run/dboot/root"); 328 | return 2; 329 | } 330 | errno = 0; 331 | if (write(etc_hostname_fd, hostname, strlen(hostname)) != strlen(hostname)) { 332 | perror("Failed to write hostname"); 333 | umount("/run/dboot/root"); 334 | return 2; 335 | } 336 | close(etc_hostname_fd); 337 | 338 | int etc_hosts_fd = open("/run/dboot/root/etc/hosts", O_WRONLY | O_CREAT, 0644); 339 | if (etc_hosts_fd == -1) { 340 | perror("Failed to open /run/dboot/root/etc/hosts"); 341 | umount("/run/dboot/root"); 342 | return 2; 343 | } 344 | char hosts[4096]; 345 | sprintf(hosts, "127.0.0.1 localhost\n127.0.0.1 %s\n::1 ip6-localhost ip6-loopback\nfe00::0 ip6-localnet\nff00::0 ip6-mcastprefix\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", hostname); 346 | errno = 0; 347 | if (write(etc_hosts_fd, hosts, strlen(hosts)) != strlen(hosts)) { 348 | perror("Failed to write hosts"); 349 | umount("/run/dboot/root"); 350 | return 2; 351 | } 352 | close(etc_hosts_fd); 353 | 354 | int etc_resolv_conf_fd = open("/run/dboot/root/etc/resolv.conf", O_WRONLY | O_CREAT, 0644); 355 | if (etc_resolv_conf_fd == -1) { 356 | perror("Failed to open /run/dboot/root/etc/resolv.conf"); 357 | umount("/run/dboot/root"); 358 | return 2; 359 | } 360 | errno = 0; 361 | if (write(etc_resolv_conf_fd, "nameserver 8.8.8.8\n", 19) != 19) { 362 | perror("Failed to write resolv.conf"); 363 | umount("/run/dboot/root"); 364 | return 2; 365 | } 366 | close(etc_resolv_conf_fd); 367 | 368 | // Sanity checks 369 | int root_fd = open("/run/dboot/root", __O_PATH); 370 | if (root_fd == -1) { 371 | perror("Failed to open /run/dboot/root"); 372 | umount("/run/dboot/root"); 373 | return 2; 374 | } 375 | int init_fd = openat(root_fd, argv[2] + 1, O_RDONLY); 376 | if (init_fd == -1) { 377 | perror("init path is invalid"); 378 | close(root_fd); 379 | umount("/run/dboot/root"); 380 | return 1; 381 | } 382 | close(root_fd); 383 | close(init_fd); 384 | int console_fd = open("/dev/console", O_RDWR); 385 | if (console_fd == -1) { 386 | perror("Failed to open /dev/console"); 387 | umount("/run/dboot/root"); 388 | return 2; 389 | } 390 | close(console_fd); 391 | 392 | if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) == -1) { 393 | perror("Failed to remount all filesystems private"); 394 | umount("/run/dboot/root"); 395 | return 2; 396 | } 397 | 398 | if (ptrace(PTRACE_ATTACH, 1) == -1) { 399 | perror("Failed to attach to PID 1"); 400 | umount("/run/dboot/root"); 401 | return 2; 402 | } 403 | 404 | wait_for_pid1(); 405 | 406 | // init is stopped by SIGSTOP now. 407 | 408 | if (ptrace(PTRACE_SYSCALL, 1, NULL, 0) == -1) { 409 | // If we can't manage to PTRACE_SYSCALL PID 1, we aren't going to be able to continue it 410 | // either, so no smart error handling here. 411 | perror("Failed to keep PID 1 running until a syscall"); 412 | umount("/run/dboot/root"); 413 | return 2; 414 | } 415 | 416 | wait_for_pid1(); 417 | 418 | struct user_regs_struct registers; 419 | struct iovec registers_iov = { 420 | .iov_base = ®isters, 421 | .iov_len = sizeof(registers), 422 | }; 423 | if (ptrace(PTRACE_GETREGSET, 1, NT_PRSTATUS, ®isters_iov) == -1) { 424 | perror("Failed to get registers of PID 1"); 425 | // Best-effort: keep init running. 426 | ptrace(PTRACE_CONT, 1, NULL, 0); 427 | umount("/run/dboot/root"); 428 | return 2; 429 | } 430 | 431 | unsigned long base_address = registers.rsp - 256; 432 | 433 | char exe_path[24]; 434 | sprintf(exe_path, "/proc/%d/exe", getpid()); 435 | unsigned long pathname_address = base_address; 436 | base_address += sizeof(exe_path); 437 | for (size_t i = 0; i < sizeof(exe_path); i += sizeof(long)) { 438 | long word; 439 | memcpy(&word, exe_path + i, sizeof(long)); 440 | if (ptrace(PTRACE_POKEDATA, 1, pathname_address + i, word) == -1) { 441 | perror("Failed to write \"/proc//exe\" to PID 1 memory"); 442 | // Best-effort: keep init running. 443 | ptrace(PTRACE_CONT, 1, NULL, 0); 444 | umount("/run/dboot/root"); 445 | return 2; 446 | } 447 | } 448 | 449 | long word; 450 | memcpy(&word, "dboot", 6); 451 | unsigned long argv0_address = base_address; 452 | base_address += 8; 453 | if (ptrace(PTRACE_POKEDATA, 1, argv0_address, word) == -1) { 454 | perror("Failed to write \"dboot\" to PID 1 memory"); 455 | // Best-effort: keep init running. 456 | ptrace(PTRACE_CONT, 1, NULL, 0); 457 | umount("/run/dboot/root"); 458 | return 2; 459 | } 460 | 461 | unsigned long argv_address = base_address; 462 | base_address += sizeof(long); 463 | if (ptrace(PTRACE_POKEDATA, 1, argv_address, argv0_address) == -1) { 464 | perror("Failed to write argv to PID 1 memory"); 465 | // Best-effort: keep init running. 466 | ptrace(PTRACE_CONT, 1, NULL, 0); 467 | umount("/run/dboot/root"); 468 | return 2; 469 | } 470 | 471 | unsigned long envp_address = base_address; 472 | base_address += sizeof(long); 473 | // This NULL also acts as an argv terminator 474 | if (ptrace(PTRACE_POKEDATA, 1, envp_address, NULL) == -1) { 475 | perror("Failed to write envp to PID 1 memory"); 476 | // Best-effort: keep init running. 477 | ptrace(PTRACE_CONT, 1, NULL, 0); 478 | umount("/run/dboot/root"); 479 | return 2; 480 | } 481 | 482 | unsigned long old_orig_rax = registers.orig_rax; 483 | unsigned long old_rdi = registers.rdi; 484 | unsigned long old_rsi = registers.rsi; 485 | unsigned long old_rdx = registers.rdx; 486 | 487 | registers.orig_rax = SYS_execve; 488 | registers.rdi = pathname_address; 489 | registers.rsi = argv_address; 490 | registers.rdx = envp_address; 491 | 492 | if (ptrace(PTRACE_SETREGSET, 1, NT_PRSTATUS, ®isters_iov) == -1) { 493 | perror("Failed to set registers of PID 1"); 494 | // Best-effort: keep init running. 495 | ptrace(PTRACE_CONT, 1, NULL, 0); 496 | umount("/run/dboot/root"); 497 | return 2; 498 | } 499 | 500 | // We have just asked PID 1 to perform a syscall. If execve fails, we need to execute the 501 | // syscall that we have just replaced. 502 | 503 | if (ptrace(PTRACE_SYSCALL, 1, NULL, 0) == -1) { 504 | perror("Failed to wait for PID 1 to perform a syscall"); 505 | // If we can't manage to PTRACE_SYSCALL PID 1, we aren't going to be able to continue it 506 | // either, so no smart error handling here. 507 | umount("/run/dboot/root"); 508 | return 2; 509 | } 510 | 511 | wait_for_pid1(); 512 | 513 | struct user_regs_struct registers_new; 514 | struct iovec registers_new_iov = { 515 | .iov_base = ®isters_new, 516 | .iov_len = sizeof(registers_new), 517 | }; 518 | if (ptrace(PTRACE_GETREGSET, 1, NT_PRSTATUS, ®isters_new_iov) == -1) { 519 | perror("Failed to get registers of PID 1"); 520 | // Best-effort: keep init running. 521 | ptrace(PTRACE_CONT, 1, NULL, 0); 522 | umount("/run/dboot/root"); 523 | return 2; 524 | } 525 | 526 | errno = -registers_new.rax; 527 | if (errno != 0) { 528 | perror("Failed to make PID 1 do an execve"); 529 | umount("/run/dboot/root"); 530 | 531 | registers.orig_rax = old_orig_rax; 532 | registers.rdi = old_rdi; 533 | registers.rsi = old_rsi; 534 | registers.rdx = old_rdx; 535 | 536 | if (ptrace(PTRACE_SETREGSET, 1, NT_PRSTATUS, ®isters_iov) == -1) { 537 | perror("Failed to restore registers of PID 1"); 538 | // Best-effort: keep init running. 539 | ptrace(PTRACE_CONT, 1, NULL, 0); 540 | return 2; 541 | } 542 | 543 | if (ptrace(PTRACE_CONT, 1, NULL, 0) == -1) { 544 | perror("Failed to continue PID 1"); 545 | return 2; 546 | } 547 | 548 | return 2; 549 | } 550 | 551 | // Successful execve 552 | 553 | if (ptrace(PTRACE_CONT, 1, NULL, 0) == -1) { 554 | perror("Failed to continue PID 1"); 555 | return 2; 556 | } 557 | 558 | return 2; 559 | } 560 | --------------------------------------------------------------------------------