[PATCH 1/2] init: ensure that /dev/console is (nearly) always available in initramfs

Askar Safin
2026-02-19T21:04:20Z

If we generate external initramfs as normal user using "cpio"
command, then we cannot put /dev/console there.

Fortunately, in this case default builtin initramfs will
contain /dev/console (before this commit).

But if we generate builtin initramfs instead, then we will
not have /dev/console at all. Thus the kernel will be unable to
open /dev/console, and PID 1 will have stdin, stdout and stderr
closed.

This problem can be solved by using gen_init_cpio.

But I think that proper solution is to ensure that /dev/console
is always available, no matter what. This is quality-of-implementation
feature. This will reduce number of possible failure modes. And
this will make easier for developers to get early boot right.
(Early boot issues are very hard to debug.)

So I put to the beginning of function "do_populate_rootfs" a code, which
creates nodes /dev, /dev/console and /root. This ensures that
they are always available even if both builtin and external
initramfses don't contain them.

The kernel itself relies on presence of these nodes. It uses
/dev/console in "console_on_rootfs" and /root in "do_mount_root".
So, ensuring that they are always available is right thing to do.

But then code in the beginning of "do_populate_rootfs" becomes
very similar to the code in "default_rootfs". So I extract
a common helper named "create_basic_rootfs".

Also I replace S_IRUSR | S_IWUSR with 0600 (suggested by checkpatch.pl).

Then nodes in usr/default_cpio_list become not needed,
so I make this file empty.

(In fact, this patch doesn't ensure that /dev/console is truly
always available. You can still break it if you create
bad /dev/console in builtin or external initramfs. So I
update comment on "console_on_rootfs".)

This patch makes default builtin initramfs truly empty.
This happens to match description in ramfs-rootfs-initramfs.rst.
But that description says that size of this initramfs is 134 bytes.
This is wrong. I checked: its size is 512 bytes (and default
builtin initramfs is always uncompressed). So I just remove that
number. While I'm there, I remove word "gzipped",
because builtin initramfs may be also uncompressed or compressed
using different algorithm.

Signed-off-by: Askar Safin <safinaskar@gmail.com>
---
.../filesystems/ramfs-rootfs-initramfs.rst | 4 +--
init/do_mounts.c | 8 ++++++
init/do_mounts.h | 2 ++
init/initramfs.c | 2 ++
init/main.c | 6 +++-
init/noinitramfs.c | 28 +++----------------
usr/Makefile | 2 +-
usr/default_cpio_list | 6 +---
8 files changed, 25 insertions(+), 33 deletions(-)

diff --git a/Documentation/filesystems/ramfs-rootfs-initramfs.rst b/Documentation/filesystems/ramfs-rootfs-initramfs.rst
index 165117a721ce..c6451692294f 100644
--- a/Documentation/filesystems/ramfs-rootfs-initramfs.rst
+++ b/Documentation/filesystems/ramfs-rootfs-initramfs.rst
@@ -133,9 +133,9 @@ All this differs from the old initrd in several ways:
Populating initramfs:
---------------------

-The 2.6 kernel build process always creates a gzipped cpio format initramfs
+The kernel build process always creates a cpio format initramfs
archive and links it into the resulting kernel binary. By default, this
-archive is empty (consuming 134 bytes on x86).
+archive is empty.

The config option CONFIG_INITRAMFS_SOURCE (in General Setup in menuconfig,
and living in usr/Kconfig) can be used to specify a source for the
diff --git a/init/do_mounts.c b/init/do_mounts.c
index 55ed3ac0b70f..f911280a348e 100644
--- a/init/do_mounts.c
+++ b/init/do_mounts.c
@@ -519,3 +519,11 @@ void __init init_rootfs(void)
is_tmpfs = true;
}
}
+
+void __init create_basic_rootfs(void)
+{
+ WARN_ON_ONCE(init_mkdir("/dev", 0755) != 0);
+ WARN_ON_ONCE(init_mknod("/dev/console", S_IFCHR | 0600,
+ new_encode_dev(MKDEV(5, 1))) != 0);
+ WARN_ON_ONCE(init_mkdir("/root", 0700) != 0);
+}
diff --git a/init/do_mounts.h b/init/do_mounts.h
index a386ee5314c9..907e9af77464 100644
--- a/init/do_mounts.h
+++ b/init/do_mounts.h
@@ -16,6 +16,8 @@ void mount_root_generic(char *name, char *pretty_name, int flags);
void mount_root(char *root_device_name);
extern int root_mountflags;

+void __init create_basic_rootfs(void);
+
static inline __init int create_dev(char *name, dev_t dev)
{
init_unlink(name);
diff --git a/init/initramfs.c b/init/initramfs.c
index 6ddbfb17fb8f..678b5050a7ac 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -717,6 +717,8 @@ static void __init populate_initrd_image(char *err)

static void __init do_populate_rootfs(void *unused, async_cookie_t cookie)
{
+ create_basic_rootfs();
+
/* Load the built in initramfs */
char *err = unpack_to_rootfs(__initramfs_start, __initramfs_size);
if (err)
diff --git a/init/main.c b/init/main.c
index 1cb395dd94e4..76f5320d3d4c 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1645,7 +1645,11 @@ static int __ref kernel_init(void *unused)
"See Linux Documentation/admin-guide/init.rst for guidance.");
}

-/* Open /dev/console, for stdin/stdout/stderr, this should never fail */
+/*
+ * Open /dev/console, for stdin/stdout/stderr, this should never fail,
+ * unless you intentionally make it fail, for example, by supplying
+ * wrong /dev/console in initramfs
+ */
void __init console_on_rootfs(void)
{
struct file *file = filp_open("/dev/console", O_RDWR, 0);
diff --git a/init/noinitramfs.c b/init/noinitramfs.c
index d1d26b93d25c..00bcf59cf05d 100644
--- a/init/noinitramfs.c
+++ b/init/noinitramfs.c
@@ -6,37 +6,17 @@
* Author: Jean-Paul Saman <jean-paul.saman@nxp.com>
*/
#include <linux/init.h>
-#include <linux/stat.h>
-#include <linux/kdev_t.h>
-#include <linux/syscalls.h>
-#include <linux/init_syscalls.h>
#include <linux/umh.h>

+#include "do_mounts.h"
+
/*
- * Create a simple rootfs that is similar to the default initramfs
+ * Create a simple rootfs
*/
static int __init default_rootfs(void)
{
- int err;
-
usermodehelper_enable();
- err = init_mkdir("/dev", 0755);
- if (err < 0)
- goto out;
-
- err = init_mknod("/dev/console", S_IFCHR | S_IRUSR | S_IWUSR,
- new_encode_dev(MKDEV(5, 1)));
- if (err < 0)
- goto out;
-
- err = init_mkdir("/root", 0700);
- if (err < 0)
- goto out;
-
+ create_basic_rootfs();
return 0;
-
-out:
- printk(KERN_WARNING "Failed to create a rootfs\n");
- return err;
}
rootfs_initcall(default_rootfs);
diff --git a/usr/Makefile b/usr/Makefile
index e8f42478a0b7..de1ee4e78ef4 100644
--- a/usr/Makefile
+++ b/usr/Makefile
@@ -22,7 +22,7 @@ ramfs-input := $(CONFIG_INITRAMFS_SOURCE)
cpio-data :=

# If CONFIG_INITRAMFS_SOURCE is empty, generate a small initramfs with the
-# default contents.
+# contents, specified in default_cpio_list, which is currently empty.
ifeq ($(ramfs-input),)
ramfs-input := $(src)/default_cpio_list
endif
diff --git a/usr/default_cpio_list b/usr/default_cpio_list
index 37b3864066e8..1df1ef08e504 100644
--- a/usr/default_cpio_list
+++ b/usr/default_cpio_list
@@ -1,6 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
-# This is a very simple, default initramfs
-
-dir /dev 0755 0 0
-nod /dev/console 0600 0 0 c 5 1
-dir /root 0700 0 0
+# Default initramfs is empty
--
2.47.3