Recently, I have begun messing with the Linux kernel, modifying some code, printing out some ascii art and making a very tiny initramfs without busybox for fun. This has made me aware of the fact that your init system resides in (or is bootstrapped from) a single binary! So, as one typically does, I decided to mess around with the things you can do when running as the init system.
Ah yes, irresponsibly modifying your system files, the killer of immutable distros...
Do not do this on a real system, this will brick it!
So... how would one replace their init system? Well, to test this, I wrote a simple C program to print out hello world...
#include <stdlib.h>
int main() {
printf("Hello world! This is init speaking to you from the linux kernel.\n");
return 0;
}
I compiled it with gcc test.c
, ran it by adding init=<path to binary> to my kernel parameters, and... it failed?
[ 1.547957] Failed to execute /test (error -2)
[ 1.549016] Default init /sbin/init failed (error -2)
[ 1.550202] Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance.
[ 1.550792] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.6.62_1 #1
[ 1.551034] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
[ 1.551468] Call Trace:
[ 1.551602] <TASK>
[ 1.551921] dump_stack_lvl+0x47/0x60
[ 1.552280] panic+0x180/0x340
[ 1.552392] ? __pfx_kernel_init+0x10/0x10
[ 1.552509] kernel_init+0x18d/0x1f0
[ 1.552611] ret_from_fork+0x34/0x50
[ 1.552719] ? __pfx_kernel_init+0x10/0x10
[ 1.552834] ret_from_fork_asm+0x1b/0x30
[ 1.552978] </TASK>
[ 1.553421] Kernel Offset: 0x1d000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 1.553901] ---[ end Kernel panic - not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance. ]---
Oh, right... my test environment doesn't include any libraries, it's just a cpio image with the binary at /test! Oh well, we can fix that! I added -static
to the GCC flags to include the libraries with the binary, and...
Hello world! This is init speaking to you from the linux kernel.
[ 1.615461] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
[ 1.616336] CPU: 0 PID: 1 Comm: init Not tainted 6.6.62_1 #1
[ 1.616712] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.3-0-ga6ed6b701f0a-prebuilt.qemu.org 04/01/2014
[ 1.617199] Call Trace:
[ 1.617343] <TASK>
[ 1.617494] dump_stack_lvl+0x47/0x60
[ 1.617882] panic+0x180/0x340
[ 1.617987] do_exit+0x9c1/0xb40
[ 1.618097] do_group_exit+0x31/0x80
[ 1.618205] __x64_sys_exit_group+0x18/0x20
[ 1.618326] do_syscall_64+0x5a/0x80
[ 1.618457] ? __count_memcg_events+0x73/0xc0
[ 1.618595] ? count_memcg_events.constprop.0+0x1a/0x30
[ 1.618749] ? handle_mm_fault+0xa2/0x360
[ 1.618865] ? preempt_count_add+0x4b/0xa0
[ 1.618978] ? up_read+0x3b/0x80
[ 1.619087] ? do_user_addr_fault+0x30b/0x620
[ 1.619211] ? fpregs_assert_state_consistent+0x26/0x50
[ 1.619353] ? exit_to_user_mode_prepare+0x40/0x1b0
[ 1.619489] entry_SYSCALL_64_after_hwframe+0x78/0xe2
[ 1.619785] RIP: 0033:0x4172b5
[ 1.620186] Code: ff ff ff c3 48 c7 c0 c0 ff ff ff 64 c7 00 26 00 00 00 eb ea 90 48 c7 c6 c0 ff ff ff ba e7 00 00 00 eb 03 66 90 f4 89 d0 0f 05 <48> 3d 00 f0 ff ff 76 f3 f7 d8 64 89 06 eb0
[ 1.620764] RSP: 002b:00007fffe87e5058 EFLAGS: 00000206 ORIG_RAX: 00000000000000e7
[ 1.621005] RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00000000004172b5
[ 1.621210] RDX: 00000000000000e7 RSI: ffffffffffffffc0 RDI: 0000000000000000
[ 1.621407] RBP: 0000000000000000 R08: 00007fffe87e4f58 R09: 000000000000001c
[ 1.621627] R10: 0000000000000007 R11: 0000000000000206 R12: 0000000000000001
[ 1.621830] R13: 0000000000000000 R14: 000000000049d100 R15: 000000000049f3e0
[ 1.622077] </TASK>
[ 1.622537] Kernel Offset: 0x22000000 from 0xffffffff81000000 (relocation range: 0xffffffff80000000-0xffffffffbfffffff)
[ 1.623075] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000 ]---
It worked! We have successfully created our own init system, if you will.
Right, so now we have our own, working "init system", but what if we want to do this on a real system, while still being able to boot? It's simple, really! Make your init system redirect execution to the "real" init system. We can do this by executing the execve syscall at the end of our C program, passing /sbin/init as the argument.
From the execve man page:
execve() executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialized stack, heap, and (initialized and uninitialized) data segments.
My simple C program now looks like this:
#include <stdlib.h>
int main() {
printf("Hello world! This is init speaking to you from the linux kernel.\n");
execve("/sbin/init", NULL, NULL); // redirect execution to the real init system
return 0;
}
And... it now boots!
This section should be safe to do on a "real" system, but could still brick it. I am not responsible for any damage you cause to your system.
What everyone wants of course! Some ascii art! For this, I have made a simple C program that slowly reads out a file on the root filesystem and centers it.
I have gone through many revisions, but here is the final (janky) program I came up with.
#include <math.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mount.h>
#include <unistd.h>
#include <asm/termbits.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/kd.h>
int main(void)
{
FILE *fptr;
char c;
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1) {
printf("fuck the terminal size");
}
fptr = fopen("/sequence", "r");
if (fptr == NULL) {
printf("Couldn't find file!");
return 1;
}
char line[256];
memset(line, 0, sizeof(line)); // clear line string
while ((c = fgetc(fptr)) != EOF) {
int len = strlen(line);
line[len] = c; // append char to string
if (c == '\n') {
int width = round(
len +
(ws.ws_col - len) /
2); // calculate appropriate width to add to start of line
char line_no_padding[sizeof(
line)]; // copy line without the padding for use in formatting
memcpy(line_no_padding, line,
sizeof(line_no_padding)); // copy old line var to line_no_padding
sprintf(line, "%*s", width,
line_no_padding); // recycle old line var for new line with padding
// slowly print out each char
for (int i = 0; i < strlen(line) + 1; i++) {
printf("%c", line[i]); // print char
if (line[i] != ' ')
usleep(25 *
1000); // sleep if char is not a space
fflush(stdout); // flush tty
}
memset(line, 0,
sizeof(line)); // clear the line to make room for the next line
}
}
fclose(fptr);
execve("/sbin/init", NULL, NULL);
return 0;
}
Compile with gcc -static main.c
and move the resulting binary to /splash, and then put your ascii art into /sequence
. Here's mine:
#######################################################################################
# #
# ..####....####...######...####............####....####.. |\---/| #
# .##......##..##....##....##..##..........##..##..##..... | ,_, | #
# .##.###..######....##....##..##..........##..##...####.. \_`_/-..----. #
# .##..##..##..##....##....##..##..........##..##......##. ___/ ` ' ,""+ \ meow #
# ..####...##..##....##.....####............####....####.. (__...' __\ |`.___.'; #
# ........................................................ (_,...'(_,.`__)/'.....+ #
# #
#######################################################################################
doing some toolfoolery...
Now the last thing you need to do is modify your bootloader to use /splash, this can be done by changing the init parameter to /splash. If you use grub, append init=/splash
to GRUB_CMDLINE_LINUX_DEFAULT, and run update-grub
.
Tada! You now have some fancy ascii art at boot.
-- hex