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

I’m trying to debug an init script on a Linux system; I’m trying to pass init=/bin/sh to the kernel to make it start sh without starting init so I can run through the init sequence manually.

What I’ve found is that the kernel is starting init anyway. During bootup, one of the printk messages is the command line, and that is showing that the line is being set properly; in addition, I can affect other things using the kernel command line. I have checked to make sure the path exists; it does.

This is a busybox system, and init is a symlink to busybox; so to make sure busybox doesn’t do strange magic when its PID is 1, I also tried running a non-busybox program as init; that didn’t work, either. It seems that no matter what I do, init is run.

What could be causing this behavior?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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?

Method 2

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

Method 3

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.

Method 4

You can custom your Linux kernel and recompile it. For 4.9 kernel, Edit the function “kernel_init” in init/main.c and try to run the following line first:

try_to_run_init_process("/bin/sh")

In addition, It might be caused by the kernel parameters passed by BootLoader.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments