diff svr-main.c @ 1861:2b3a8026a6ce

Add re-exec for server This allows ASLR to re-randomize the address space for every connection, preventing some vulnerabilities from being exploitable by repeated probing. Overhead (memory and time) is yet to be confirmed. At present this is only enabled on Linux. Other BSD platforms with fexecve() would probably also work though have not been tested.
author Matt Johnston <matt@ucc.asn.au>
date Sun, 30 Jan 2022 10:14:56 +0800
parents 6ea18ca8fc03
children adfcdfb161a4
line wrap: on
line diff
--- a/svr-main.c	Thu Jan 27 15:09:29 2022 +0800
+++ b/svr-main.c	Sun Jan 30 10:14:56 2022 +0800
@@ -35,12 +35,8 @@
 static void sigchld_handler(int dummy);
 static void sigsegv_handler(int);
 static void sigintterm_handler(int fish);
-#if INETD_MODE
 static void main_inetd(void);
-#endif
-#if NON_INETD_MODE
-static void main_noinetd(void);
-#endif
+static void main_noinetd(int argc, char ** argv);
 static void commonsetup(void);
 
 #if defined(DBMULTI_dropbear) || !DROPBEAR_MULTI
@@ -55,6 +51,10 @@
 
 	disallow_core();
 
+	if (argc < 1) {
+		dropbear_exit("Bad argc");
+	}
+
 	/* get commandline options */
 	svr_getopts(argc, argv);
 
@@ -66,8 +66,21 @@
 	}
 #endif
 
+#if DROPBEAR_DO_REEXEC
+	if (svr_opts.reexec_child) {
+#ifdef PR_SET_NAME
+		/* Fix the "Name:" in /proc/pid/status, otherwise it's
+		a FD number from fexecve.
+		Failure doesn't really matter, it's mostly aesthetic */
+		prctl(PR_SET_NAME, basename(argv[0]), 0, 0);
+#endif
+		main_inetd();
+		/* notreached */
+	}
+#endif
+
 #if NON_INETD_MODE
-	main_noinetd();
+	main_noinetd(argc, argv);
 	/* notreached */
 #endif
 
@@ -76,7 +89,7 @@
 }
 #endif
 
-#if INETD_MODE
+#if INETD_MODE || DROPBEAR_DO_REEXEC
 static void main_inetd() {
 	char *host, *port = NULL;
 
@@ -85,23 +98,18 @@
 
 	seedrandom();
 
-#if DEBUG_TRACE
-	if (debug_trace) {
-		/* -v output goes to stderr which would get sent over the inetd network socket */
-		dropbear_exit("Dropbear inetd mode is incompatible with debug -v");
-	}
-#endif
+	if (!svr_opts.reexec_child) {
+		/* In case our inetd was lax in logging source addresses */
+		get_socket_address(0, NULL, NULL, &host, &port, 0);
+			dropbear_log(LOG_INFO, "Child connection from %s:%s", host, port);
+		m_free(host);
+		m_free(port);
 
-	/* In case our inetd was lax in logging source addresses */
-	get_socket_address(0, NULL, NULL, &host, &port, 0);
-	dropbear_log(LOG_INFO, "Child connection from %s:%s", host, port);
-	m_free(host);
-	m_free(port);
-
-	/* Don't check the return value - it may just fail since inetd has
-	 * already done setsid() after forking (xinetd on Darwin appears to do
-	 * this */
-	setsid();
+		/* Don't check the return value - it may just fail since inetd has
+		 * already done setsid() after forking (xinetd on Darwin appears to do
+		 * this */
+		setsid();
+	}
 
 	/* Start service program 
 	 * -1 is a dummy childpipe, just something we can close() without 
@@ -113,7 +121,7 @@
 #endif /* INETD_MODE */
 
 #if NON_INETD_MODE
-static void main_noinetd() {
+static void main_noinetd(int argc, char ** argv) {
 	fd_set fds;
 	unsigned int i, j;
 	int val;
@@ -121,6 +129,7 @@
 	int listensocks[MAX_LISTEN_ADDR];
 	size_t listensockcount = 0;
 	FILE *pidfile = NULL;
+	int execfd = -1;
 
 	int childpipes[MAX_UNAUTH_CLIENTS];
 	char * preauth_addrs[MAX_UNAUTH_CLIENTS];
@@ -128,6 +137,9 @@
 	int childsock;
 	int childpipe[2];
 
+	(void)argc;
+	(void)argv;
+
 	/* Note: commonsetup() must happen before we daemon()ise. Otherwise
 	   daemon() will chdir("/"), and we won't be able to find local-dir
 	   hostkeys. */
@@ -138,7 +150,7 @@
 		childpipes[i] = -1;
 	}
 	memset(preauth_addrs, 0x0, sizeof(preauth_addrs));
-	
+
 	/* Set up the listening sockets */
 	listensockcount = listensockets(listensocks, MAX_LISTEN_ADDR, &maxsock);
 	if (listensockcount == 0)
@@ -150,6 +162,14 @@
 		FD_SET(listensocks[i], &fds);
 	}
 
+#if DROPBEAR_DO_REEXEC
+	execfd = open(argv[0], O_CLOEXEC|O_RDONLY);
+	if (execfd < 0) {
+		/* Just fallback to straight fork */
+		TRACE(("Couldn't open own binary %s, disabling re-exec: %s", argv[0], strerror(errno)))
+	}
+#endif
+
 	/* fork */
 	if (svr_opts.forkbg) {
 		int closefds = 0;
@@ -181,7 +201,7 @@
 	for(;;) {
 
 		DROPBEAR_FD_ZERO(&fds);
-		
+
 		/* listening sockets */
 		for (i = 0; i < listensockcount; i++) {
 			FD_SET(listensocks[i], &fds);
@@ -201,7 +221,7 @@
 			unlink(svr_opts.pidfile);
 			dropbear_exit("Terminated by signal");
 		}
-		
+
 		if (val == 0) {
 			/* timeout reached - shouldn't happen. eh */
 			continue;
@@ -286,7 +306,7 @@
 			}
 
 			addrandom((void*)&fork_ret, sizeof(fork_ret));
-			
+
 			if (fork_ret > 0) {
 
 				/* parent */
@@ -316,6 +336,27 @@
 
 				m_close(childpipe[0]);
 
+				if (execfd >= 0) {
+#if DROPBEAR_DO_REEXEC
+					/* Add "-2" to the args and re-execute ourself */
+					char **new_argv = m_malloc(sizeof(char*) * (argc+1));
+					memcpy(new_argv, argv, sizeof(char*) * argc);
+					new_argv[argc] = "-2";
+
+					if ((dup2(childsock, STDIN_FILENO) < 0)) {
+						dropbear_exit("dup2 failed: %s", strerror(errno));
+					}
+					m_close(childsock);
+					/* Re-execute ourself */
+					fexecve(execfd, new_argv, environ);
+					/* Not reached on success */
+
+					/* Fall back on plain fork otherwise */
+					TRACE(("fexecve failed, disabling re-exec: %s", strerror(errno)))
+					m_free(new_argv);
+#endif /* DROPBEAR_DO_REEXEC */
+				}
+
 				/* start the session */
 				svr_session(childsock, childpipe[1]);
 				/* don't return */