git: 4c9f64885262 - main - init(8): extract reroot transient code into reroot_seed

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Tue, 14 Jul 2026 15:44:18 UTC
The branch main has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=4c9f648852629d757165796bb3cd97bb84a0e483

commit 4c9f648852629d757165796bb3cd97bb84a0e483
Author:     Konstantin Belousov <kib@FreeBSD.org>
AuthorDate: 2026-07-10 19:02:54 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2026-07-14 15:43:45 +0000

    init(8): extract reroot transient code into reroot_seed
    
    Since init become dynamically linked, reroot appeared to be broken
    because init copies itself into a transient tmpfs mount to continue
    controlling execution right after the reboot(REROOT) syscall.  Because
    the binary is dynamically linked, it cannot be properly executed.
    
    Provide a minimal static binary 'reroot_seed' embedded into the init as
    byte stream, which performs what the 'init -r' did, namely, the second
    phase reroot.
    
    For the static build of init as part of the /rescue crunch, keep the
    inline reroot code.
    
    Reported and tested by: markj
    Sponsored by:   The FreeBSD Foundation
    MFC after:      1 week
    Differential revision:  https://reviews.freebsd.org/D58164
---
 sbin/Makefile                  |   2 +
 sbin/init/Makefile             |   9 +++-
 sbin/init/init.c               |  91 +++++++++++++++++++++++++++-------
 sbin/init/reroot_seed.embed.s  |   4 ++
 sbin/reroot_seed/Makefile      |   6 +++
 sbin/reroot_seed/reroot_seed.c | 109 +++++++++++++++++++++++++++++++++++++++++
 6 files changed, 203 insertions(+), 18 deletions(-)

diff --git a/sbin/Makefile b/sbin/Makefile
index ef953e80ba1e..f936a9646fa9 100644
--- a/sbin/Makefile
+++ b/sbin/Makefile
@@ -25,6 +25,8 @@ SUBDIR=adjkerntz \
 	ggate \
 	growfs \
 	ifconfig \
+	reroot_seed \
+	.WAIT \
 	init \
 	kldconfig \
 	kldload \
diff --git a/sbin/init/Makefile b/sbin/init/Makefile
index 342df4596a72..1e319f1bb828 100644
--- a/sbin/init/Makefile
+++ b/sbin/init/Makefile
@@ -2,9 +2,16 @@ CONFGROUPS=	CONFTTYS
 PACKAGE=runtime
 PROG=	init
 MAN=	init.8
+SRCS=	init.c
 PRECIOUSPROG=
 INSTALLFLAGS=-b -B.bak
-CFLAGS+=-DDEBUGSHELL -DSECURE -DLOGIN_CAP -DCOMPAT_SYSV_INIT
+CFLAGS.init.c+=-DDEBUGSHELL -DSECURE -DLOGIN_CAP -DCOMPAT_SYSV_INIT
+.ifdef RESCUE
+CFLAGS.init.c+=-DRESCUE
+.else
+SRCS+=	reroot_seed.embed.s
+ACFLAGS.reroot_seed.embed.s+=-I${OBJTOP}/sbin/reroot_seed
+.endif
 LIBADD=	util crypt
 
 CONFTTYSNAME=	ttys
diff --git a/sbin/init/init.c b/sbin/init/init.c
index db680c7f73bd..1e07ca1d09c0 100644
--- a/sbin/init/init.c
+++ b/sbin/init/init.c
@@ -116,7 +116,9 @@ static state_func_t catatonia(void);
 static state_func_t death(void);
 static state_func_t death_single(void);
 static state_func_t reroot(void);
+#ifdef RESCUE
 static state_func_t reroot_phase_two(void);
+#endif
 
 static state_func_t run_script(const char *);
 
@@ -269,7 +271,11 @@ invalid:
 	 * This code assumes that we always get arguments through flags,
 	 * never through bits set in some random machine register.
 	 */
-	while ((c = getopt(argc, argv, "dsfr")) != -1)
+	while ((c = getopt(argc, argv, "dsf"
+#ifdef RESCUE
+	    "r"
+#endif
+	    )) != -1)
 		switch (c) {
 		case 'd':
 			devfs = true;
@@ -280,9 +286,11 @@ invalid:
 		case 'f':
 			runcom_mode = FASTBOOT;
 			break;
+#ifdef RESCUE
 		case 'r':
 			initial_transition = reroot_phase_two;
 			break;
+#endif
 		default:
 			warning("unrecognized flag '-%c'", c);
 			break;
@@ -388,7 +396,10 @@ invalid:
 			free(s);
 	}
 
-	if (initial_transition != reroot_phase_two) {
+#ifdef RESCUE
+	if (initial_transition != reroot_phase_two)
+#endif
+	{
 		/*
 		 * Unmount reroot leftovers.  This runs after init(8)
 		 * gets reexecuted after reroot_phase_two() is done.
@@ -628,6 +639,7 @@ write_stderr(const char *message)
 	write(STDERR_FILENO, message, strlen(message));
 }
 
+#ifdef RESCUE
 static int
 read_file(const char *path, void **bufp, size_t *bufsizep)
 {
@@ -678,6 +690,7 @@ read_file(const char *path, void **bufp, size_t *bufsizep)
 
 	return (0);
 }
+#endif /* RESCUE */
 
 static int
 create_file(const char *path, const void *buf, size_t bufsize)
@@ -738,16 +751,14 @@ mount_tmpfs(const char *fspath)
 	return (0);
 }
 
+#ifndef RESCUE
+extern char reroot_seed_start[], reroot_seed_end[];
+
 static state_func_t
 reroot(void)
 {
-	void *buf;
-	size_t bufsize;
 	int error;
 
-	buf = NULL;
-	bufsize = 0;
-
 	revoke_ttys();
 	runshutdown();
 
@@ -762,32 +773,27 @@ reroot(void)
 		goto out;
 	}
 
-	/*
-	 * Copy the init binary into tmpfs, so that we can unmount
-	 * the old rootfs without committing suicide.
-	 */
-	error = read_file(init_path_argv0, &buf, &bufsize);
-	if (error != 0)
-		goto out;
 	error = mount_tmpfs(_PATH_REROOT);
 	if (error != 0)
 		goto out;
-	error = create_file(_PATH_REROOT_INIT, buf, bufsize);
+	error = create_file(_PATH_REROOT_INIT, reroot_seed_start,
+	    reroot_seed_end - reroot_seed_start);
 	if (error != 0)
 		goto out;
 
 	/*
 	 * Execute the temporary init.
 	 */
-	execl(_PATH_REROOT_INIT, _PATH_REROOT_INIT, "-r", NULL);
+	execl(_PATH_REROOT_INIT, _PATH_REROOT_INIT, NULL);
 	emergency("cannot exec %s: %m", _PATH_REROOT_INIT);
 
 out:
 	emergency("reroot failed; going to single user mode");
-	free(buf);
 	return (state_func_t) single_user;
 }
 
+#else /* !RESCUE */
+
 static state_func_t
 reroot_phase_two(void)
 {
@@ -839,6 +845,57 @@ out:
 	return (state_func_t) single_user;
 }
 
+static state_func_t
+reroot(void)
+{
+	void *buf;
+	size_t bufsize;
+	int error;
+
+	buf = NULL;
+	bufsize = 0;
+
+	revoke_ttys();
+	runshutdown();
+
+	/*
+	 * Make sure nobody can interfere with our scheme.
+	 * Ignore ESRCH, which can apparently happen when
+	 * there are no processes to kill.
+	 */
+	error = kill(-1, SIGKILL);
+	if (error != 0 && errno != ESRCH) {
+		emergency("kill(2) failed: %m");
+		goto out;
+	}
+
+	/*
+	 * Copy the init binary into tmpfs, so that we can unmount
+	 * the old rootfs without committing suicide.
+	 */
+	error = read_file(init_path_argv0, &buf, &bufsize);
+	if (error != 0)
+		goto out;
+	error = mount_tmpfs(_PATH_REROOT);
+	if (error != 0)
+		goto out;
+	error = create_file(_PATH_REROOT_INIT, buf, bufsize);
+	if (error != 0)
+		goto out;
+
+	/*
+	 * Execute the temporary init.
+	 */
+	execl(_PATH_REROOT_INIT, _PATH_REROOT_INIT, "-r", NULL);
+	emergency("cannot exec %s: %m", _PATH_REROOT_INIT);
+
+out:
+	emergency("reroot failed; going to single user mode");
+	return (state_func_t) single_user;
+}
+
+#endif /* !RESCUE */
+
 /*
  * Bring the system up single user.
  */
diff --git a/sbin/init/reroot_seed.embed.s b/sbin/init/reroot_seed.embed.s
new file mode 100644
index 000000000000..5871c41f06d0
--- /dev/null
+++ b/sbin/init/reroot_seed.embed.s
@@ -0,0 +1,4 @@
+	.globl	reroot_seed_start, reroot_seed_end
+reroot_seed_start:
+	.incbin	"reroot_seed"
+reroot_seed_end:
diff --git a/sbin/reroot_seed/Makefile b/sbin/reroot_seed/Makefile
new file mode 100644
index 000000000000..584775aa287f
--- /dev/null
+++ b/sbin/reroot_seed/Makefile
@@ -0,0 +1,6 @@
+PROG=	reroot_seed
+MAN=
+NO_SHARED=	YES
+INTERNALPROG=	YES
+
+.include <bsd.prog.mk>
diff --git a/sbin/reroot_seed/reroot_seed.c b/sbin/reroot_seed/reroot_seed.c
new file mode 100644
index 000000000000..9ba8516ba70f
--- /dev/null
+++ b/sbin/reroot_seed/reroot_seed.c
@@ -0,0 +1,109 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Donn Seeley at Berkeley Software Design, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/mount.h>
+#include <sys/reboot.h>
+#include <sys/sysctl.h>
+
+#include <err.h>
+#include <kenv.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+static void emergency(const char *, ...) __printflike(1, 2);
+
+static void
+emergency(const char *message, ...)
+{
+	va_list ap;
+	va_start(ap, message);
+
+	vsyslog(LOG_EMERG, message, ap);
+	va_end(ap);
+}
+
+int
+main(void)
+{
+	char init_path[PATH_MAX], *path, *path_component;
+	size_t init_path_len;
+	int nbytes, error;
+
+	openlog("init", LOG_CONS, LOG_AUTH);
+
+	/*
+	 * Ask the kernel to mount the new rootfs.
+	 */
+	error = reboot(RB_REROOT);
+	if (error != 0) {
+		emergency("RB_REBOOT failed: %m");
+		_exit(1);
+	}
+
+	/*
+	 * Figure out where the destination init(8) binary is.  Note that
+	 * the path could be different than what we've started with.  Use
+	 * the value from kenv, if set, or the one from sysctl otherwise.
+	 * The latter defaults to a hardcoded value, but can be overridden
+	 * by a build time option.
+	 */
+	nbytes = kenv(KENV_GET, "init_path", init_path, sizeof(init_path));
+	if (nbytes <= 0) {
+		init_path_len = sizeof(init_path);
+		error = sysctlbyname("kern.init_path",
+		    init_path, &init_path_len, NULL, 0);
+		if (error != 0) {
+			emergency("failed to retrieve kern.init_path: %m");
+			_exit(1);
+		}
+	}
+
+	/*
+	 * Repeat the init search logic from sys/kern/init_path.c
+	 */
+	path_component = init_path;
+	while ((path = strsep(&path_component, ":")) != NULL) {
+		/*
+		 * Execute init(8) from the new rootfs.
+		 */
+		execl(path, path, NULL);
+	}
+	emergency("cannot exec init from %s: %m", init_path);
+	_exit(1);
+}