What can make passing init=/path/to/program to the kernel not start program as init?

initrd shenanigans

If you are using initrd or initramfs, keep in mind the following:

  • rdinit= is used instead of init=

  • if rdinit= is not given, the attempted default paths are: /sbin/init, /etc/init, /bin/init and /bin/sh but not /init

    When not using initrd, /init is the first path tried, followed by the others.

v4.15 RTFS: everything is contained inside the https://github.com/torvalds/linux/blob/v4.15/init/main.c file.

First we learn that:

  • execute_comand is whatever is passed to: init=
  • ramdisk_execute_command is whatever is passed to: rdinit=

as can be seen from:

static int __init init_setup(char *str)
{
    unsigned int i;

    execute_command = str;
    /*
    * In case LILO is going to boot us with default command line,
    * it prepends "auto" before the whole cmdline which makes
    * the shell think it should execute a script with such name.
    * So we ignore all arguments entered _before_ init=... [MJ]
    */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("init=", init_setup);

static int __init rdinit_setup(char *str)
{
    unsigned int i;

    ramdisk_execute_command = str;
    /* See "auto" comment in init_setup */
    for (i = 1; i < MAX_INIT_ARGS; i++)
        argv_init[i] = NULL;
    return 1;
}
__setup("rdinit=", rdinit_setup);

where __setup is a magic way of handling up command line parameters.

start_kernel, the kernel "entry point", calls rest_init, which "calls" kernel_init on a thread:

pid = kernel_thread(kernel_init, NULL, CLONE_FS);

Then, kernel_init does:

static int __ref kernel_init(void *unused)
{
    int ret;

    kernel_init_freeable();

    [...]

    if (ramdisk_execute_command) {
        ret = run_init_process(ramdisk_execute_command);
        if (!ret)
            return 0;
        pr_err("Failed to execute %s (error %d)\n",
            ramdisk_execute_command, ret);
    }

    [...]

    if (execute_command) {
        ret = run_init_process(execute_command);
        if (!ret)
            return 0;
        panic("Requested init %s failed (error %d).",
            execute_command, ret);
    }
    if (!try_to_run_init_process("/sbin/init") ||
        !try_to_run_init_process("/etc/init") ||
        !try_to_run_init_process("/bin/init") ||
        !try_to_run_init_process("/bin/sh"))
        return 0;

    panic("No working init found.  Try passing init= option to kernel. "
        "See Linux Documentation/admin-guide/init.rst for guidance.");
}

and kernel_init_freeable does:

static noinline void __init kernel_init_freeable(void)
{

    [...]

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

TODO: understand sys_access.

Also note that there are further differences between ram inits and non-ram inits, e.g. console handling: Difference in execution of init with embedded vs. external initramfs?


On

https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt

I found:

When debugging a normal root filesystem, it's nice to be able to boot with "init=/bin/sh". The initramfs equivalent is "rdinit=/bin/sh", and it's just as useful.

So probably try ridinit=/bin/sh


Looking at Linux kernel source, I see that if the file /init exists, the kernel will always attempt to run it on the assumption that it's doing a ramdisk boot. Check your system to see if /init exists, if it does, then that's probably your problem.

Tags:

Linux

Init