9ce4ea8
From eb6d8d221b34a93e57c22cefa47d924350251c4c Mon Sep 17 00:00:00 2001
9ce4ea8
From: Ray Strode <rstrode@redhat.com>
9ce4ea8
Date: Thu, 15 Oct 2015 14:37:33 -0400
9ce4ea8
Subject: [PATCH] daemon: fork before threads are spawned
9ce4ea8
9ce4ea8
It's not really a good idea to fork after glib has initialized,
9ce4ea8
since it has helper threads that may have taken locks etc.
9ce4ea8
9ce4ea8
This commit forks really early to prevent locks from leaking
9ce4ea8
and causing deadlock.
9ce4ea8
---
9ce4ea8
 daemon/gkd-main.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++---------
9ce4ea8
 1 file changed, 63 insertions(+), 12 deletions(-)
9ce4ea8
9ce4ea8
diff --git a/daemon/gkd-main.c b/daemon/gkd-main.c
9ce4ea8
index f567633..4cc8552 100644
9ce4ea8
--- a/daemon/gkd-main.c
9ce4ea8
+++ b/daemon/gkd-main.c
9ce4ea8
@@ -98,60 +98,61 @@ EGG_SECURE_DECLARE (daemon_main);
9ce4ea8
 #	else
9ce4ea8
 #		define DEFAULT_COMPONENTS  GKD_COMP_PKCS11 "," GKD_COMP_SECRETS
9ce4ea8
 #	endif
9ce4ea8
 #endif
9ce4ea8
 
9ce4ea8
 /*
9ce4ea8
  * If --login is used and then daemon is not initialized within LOGIN_TIMEOUT
9ce4ea8
  * seconds, then we exit. See on_login_timeout() below.
9ce4ea8
  */
9ce4ea8
 
9ce4ea8
 #define LOGIN_TIMEOUT 120
9ce4ea8
 
9ce4ea8
 static gchar* run_components = DEFAULT_COMPONENTS;
9ce4ea8
 static gboolean pkcs11_started = FALSE;
9ce4ea8
 static gboolean secrets_started = FALSE;
9ce4ea8
 static gboolean ssh_started = FALSE;
9ce4ea8
 static gboolean dbus_started = FALSE;
9ce4ea8
 
9ce4ea8
 static gboolean run_foreground = FALSE;
9ce4ea8
 static gboolean run_daemonized = FALSE;
9ce4ea8
 static gboolean run_version = FALSE;
9ce4ea8
 static gboolean run_for_login = FALSE;
9ce4ea8
 static gboolean perform_unlock = FALSE;
9ce4ea8
 static gboolean run_for_start = FALSE;
9ce4ea8
 static gboolean run_for_replace = FALSE;
9ce4ea8
 static gchar* login_password = NULL;
9ce4ea8
 static gchar* control_directory = NULL;
9ce4ea8
 static guint timeout_id = 0;
9ce4ea8
 static gboolean initialization_completed = FALSE;
9ce4ea8
 static GMainLoop *loop = NULL;
9ce4ea8
+static int parent_wakeup_fd = -1;
9ce4ea8
 
9ce4ea8
 static GOptionEntry option_entries[] = {
9ce4ea8
 	{ "start", 's', 0, G_OPTION_ARG_NONE, &run_for_start,
9ce4ea8
 	  "Start a dameon or initialize an already running daemon." },
9ce4ea8
 	{ "replace", 'r', 0, G_OPTION_ARG_NONE, &run_for_replace,
9ce4ea8
 	  "Replace the daemon for this desktop login environment." },
9ce4ea8
 	{ "foreground", 'f', 0, G_OPTION_ARG_NONE, &run_foreground,
9ce4ea8
 	  "Run in the foreground", NULL },
9ce4ea8
 	{ "daemonize", 'd', 0, G_OPTION_ARG_NONE, &run_daemonized,
9ce4ea8
 	  "Run as a daemon", NULL },
9ce4ea8
 	{ "login", 'l', 0, G_OPTION_ARG_NONE, &run_for_login,
9ce4ea8
 	  "Run by PAM for a user login. Read login password from stdin", NULL },
9ce4ea8
 	{ "unlock", 0, 0, G_OPTION_ARG_NONE, &perform_unlock,
9ce4ea8
 	  "Prompt for login keyring password, or read from stdin", NULL },
9ce4ea8
 	{ "components", 'c', 0, G_OPTION_ARG_STRING, &run_components,
9ce4ea8
 	  "The optional components to run", DEFAULT_COMPONENTS },
9ce4ea8
 	{ "control-directory", 'C', 0, G_OPTION_ARG_FILENAME, &control_directory,
9ce4ea8
 	  "The directory for sockets and control data", NULL },
9ce4ea8
 	{ "version", 'V', 0, G_OPTION_ARG_NONE, &run_version,
9ce4ea8
 	  "Show the version number and exit.", NULL },
9ce4ea8
 	{ NULL }
9ce4ea8
 };
9ce4ea8
 
9ce4ea8
 static void
9ce4ea8
 parse_arguments (int *argc, char** argv[])
9ce4ea8
 {
9ce4ea8
 	GError *err = NULL;
9ce4ea8
 	GOptionContext *context;
9ce4ea8
 
9ce4ea8
 	context = g_option_context_new ("- The Gnome Keyring Daemon");
9ce4ea8
@@ -474,60 +475,110 @@ read_login_password (int fd)
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	egg_secure_free (buf);
9ce4ea8
 	return ret;
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
 static void
9ce4ea8
 cleanup_and_exit (int code)
9ce4ea8
 {
9ce4ea8
 	egg_cleanup_perform ();
9ce4ea8
 	exit (code);
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
 static void
9ce4ea8
 clear_login_password (void)
9ce4ea8
 {
9ce4ea8
 	if(login_password)
9ce4ea8
 		egg_secure_strfree (login_password);
9ce4ea8
 	login_password = NULL;
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
 static void
9ce4ea8
 print_environment (void)
9ce4ea8
 {
9ce4ea8
 	const gchar **env;
9ce4ea8
 	for (env = gkd_util_get_environment (); *env; ++env)
9ce4ea8
 		printf ("%s\n", *env);
9ce4ea8
 	fflush (stdout);
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
+
9ce4ea8
+static void
9ce4ea8
+print_environment_from_fd (int fd)
9ce4ea8
+{
9ce4ea8
+	char *output;
9ce4ea8
+	gsize output_size;
9ce4ea8
+	gsize bytes_read;
9ce4ea8
+
9ce4ea8
+	bytes_read = read (fd, &output_size, sizeof (output_size));
9ce4ea8
+
9ce4ea8
+	if (bytes_read < sizeof (output_size))
9ce4ea8
+		exit (1);
9ce4ea8
+
9ce4ea8
+	output = g_malloc0 (output_size);
9ce4ea8
+	bytes_read = read (fd, output, output_size);
9ce4ea8
+
9ce4ea8
+	if (bytes_read < output_size)
9ce4ea8
+		exit (1);
9ce4ea8
+
9ce4ea8
+	printf ("%s\n", output);
9ce4ea8
+	fflush (stdout);
9ce4ea8
+	g_free (output);
9ce4ea8
+}
9ce4ea8
+
9ce4ea8
+static void
9ce4ea8
+send_environment_and_finish_parent (int fd)
9ce4ea8
+{
9ce4ea8
+	char *output;
9ce4ea8
+	gsize output_size;
9ce4ea8
+	gsize bytes_written;
9ce4ea8
+
9ce4ea8
+	if (fd < 0) {
9ce4ea8
+		print_environment ();
9ce4ea8
+		return;
9ce4ea8
+	}
9ce4ea8
+
9ce4ea8
+	output = g_strjoinv ("\n", (gchar **) gkd_util_get_environment ());
9ce4ea8
+	output_size = strlen (output) + 1;
9ce4ea8
+	bytes_written = write (fd, &output_size, sizeof (output_size));
9ce4ea8
+
9ce4ea8
+	if (bytes_written < sizeof (output_size))
9ce4ea8
+		exit (1);
9ce4ea8
+
9ce4ea8
+	bytes_written = write (fd, output, output_size);
9ce4ea8
+	if (bytes_written < output_size)
9ce4ea8
+		exit (1);
9ce4ea8
+
9ce4ea8
+	g_free (output);
9ce4ea8
+}
9ce4ea8
+
9ce4ea8
 static gboolean
9ce4ea8
 initialize_daemon_at (const gchar *directory)
9ce4ea8
 {
9ce4ea8
 	gchar **ourenv, **daemonenv, **e;
9ce4ea8
 
9ce4ea8
 	/* Exchange environment variables, and try to initialize daemon */
9ce4ea8
 	ourenv = gkd_util_build_environment (GKD_UTIL_IN_ENVIRONMENT);
9ce4ea8
 	daemonenv = gkd_control_initialize (directory, run_components,
9ce4ea8
 	                                    (const gchar**)ourenv);
9ce4ea8
 	g_strfreev (ourenv);
9ce4ea8
 
9ce4ea8
 	/* Initialization failed, start this process up as a daemon */
9ce4ea8
 	if (!daemonenv)
9ce4ea8
 		return FALSE;
9ce4ea8
 
9ce4ea8
 	/* Setup all the environment variables we were passed */
9ce4ea8
 	for (e = daemonenv; *e; ++e)
9ce4ea8
 		gkd_util_push_environment_full (*e);
9ce4ea8
 	g_strfreev (daemonenv);
9ce4ea8
 
9ce4ea8
 	return TRUE;
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
 static gboolean
9ce4ea8
 replace_daemon_at (const gchar *directory)
9ce4ea8
 {
9ce4ea8
 	gboolean ret;
9ce4ea8
 
9ce4ea8
 	/*
9ce4ea8
 	 * The first control_directory is the environment one, always
9ce4ea8
@@ -577,136 +628,134 @@ discover_other_daemon (DiscoverFunc callback, gboolean acquire)
9ce4ea8
 
9ce4ea8
 	/* Or the default location when no evironment variable */
9ce4ea8
 	control_env = g_getenv ("XDG_RUNTIME_DIR");
9ce4ea8
 	if (control_env) {
9ce4ea8
 		control = g_build_filename (control_env, "keyring", NULL);
9ce4ea8
 		ret = (callback) (control);
9ce4ea8
 		g_free (control);
9ce4ea8
 		if (ret == TRUE)
9ce4ea8
 			return TRUE;
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	/* See if we can contact a daemon running, that didn't set an env variable */
9ce4ea8
 	if (acquire && !gkd_dbus_singleton_acquire (&acquired))
9ce4ea8
 		return FALSE;
9ce4ea8
 
9ce4ea8
 	/* We're the main daemon */
9ce4ea8
 	if (acquired)
9ce4ea8
 		return FALSE;
9ce4ea8
 
9ce4ea8
 	control = gkd_dbus_singleton_control ();
9ce4ea8
 	if (control) {
9ce4ea8
 		ret = (callback) (control);
9ce4ea8
 		g_free (control);
9ce4ea8
 		if (ret == TRUE)
9ce4ea8
 			return TRUE;
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	return FALSE;
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
-static void
9ce4ea8
+static int
9ce4ea8
 fork_and_print_environment (void)
9ce4ea8
 {
9ce4ea8
 	int status;
9ce4ea8
 	pid_t pid;
9ce4ea8
 	int fd, i;
9ce4ea8
+	int wakeup_fds[2] = { -1, -1 };
9ce4ea8
 
9ce4ea8
-	if (run_foreground) {
9ce4ea8
-		print_environment ();
9ce4ea8
-		return;
9ce4ea8
-	}
9ce4ea8
+	g_unix_open_pipe (wakeup_fds, FD_CLOEXEC, NULL);
9ce4ea8
 
9ce4ea8
 	pid = fork ();
9ce4ea8
 
9ce4ea8
 	if (pid != 0) {
9ce4ea8
-
9ce4ea8
 		/* Here we are in the initial process */
9ce4ea8
 
9ce4ea8
 		if (run_daemonized) {
9ce4ea8
 
9ce4ea8
 			/* Initial process, waits for intermediate child */
9ce4ea8
 			if (pid == -1)
9ce4ea8
 				exit (1);
9ce4ea8
 
9ce4ea8
 			waitpid (pid, &status, 0);
9ce4ea8
 			if (WEXITSTATUS (status) != 0)
9ce4ea8
 				exit (WEXITSTATUS (status));
9ce4ea8
 
9ce4ea8
 		} else {
9ce4ea8
 			/* Not double forking */
9ce4ea8
-			print_environment ();
9ce4ea8
+			print_environment_from_fd (wakeup_fds[0]);
9ce4ea8
 		}
9ce4ea8
 
9ce4ea8
 		/* The initial process exits successfully */
9ce4ea8
 		exit (0);
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	if (run_daemonized) {
9ce4ea8
 
9ce4ea8
 		/*
9ce4ea8
 		 * Become session leader of a new session, process group leader of a new
9ce4ea8
 		 * process group, and detach from the controlling TTY, so that SIGHUP is
9ce4ea8
 		 * not sent to this process when the previous session leader dies
9ce4ea8
 		 */
9ce4ea8
 		setsid ();
9ce4ea8
 
9ce4ea8
 		/* Double fork if need to daemonize properly */
9ce4ea8
 		pid = fork ();
9ce4ea8
 
9ce4ea8
 		if (pid != 0) {
9ce4ea8
-
9ce4ea8
 			/* Here we are in the intermediate child process */
9ce4ea8
 
9ce4ea8
 			/*
9ce4ea8
 			 * This process exits, so that the final child will inherit
9ce4ea8
 			 * init as parent to avoid zombies
9ce4ea8
 			 */
9ce4ea8
 			if (pid == -1)
9ce4ea8
 				exit (1);
9ce4ea8
 
9ce4ea8
 			/* We've done two forks. */
9ce4ea8
-			print_environment ();
9ce4ea8
+			print_environment_from_fd (wakeup_fds[0]);
9ce4ea8
 
9ce4ea8
 			/* The intermediate child exits */
9ce4ea8
 			exit (0);
9ce4ea8
 		}
9ce4ea8
 
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	/* Here we are in the resulting daemon or background process. */
9ce4ea8
 
9ce4ea8
 	for (i = 0; i < 3; ++i) {
9ce4ea8
 		fd = open ("/dev/null", O_RDONLY);
9ce4ea8
 		sane_dup2 (fd, i);
9ce4ea8
 		close (fd);
9ce4ea8
 	}
9ce4ea8
+
9ce4ea8
+	return wakeup_fds[1];
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
 static gboolean
9ce4ea8
 gkr_daemon_startup_steps (const gchar *components)
9ce4ea8
 {
9ce4ea8
 	g_assert (components);
9ce4ea8
 
9ce4ea8
 	/*
9ce4ea8
 	 * Startup that must run before forking.
9ce4ea8
 	 * Note that we set initialized flags early so that two
9ce4ea8
 	 * initializations don't overlap
9ce4ea8
 	 */
9ce4ea8
 
9ce4ea8
 #ifdef WITH_SSH
9ce4ea8
 	if (strstr (components, GKD_COMP_SSH)) {
9ce4ea8
 		if (ssh_started) {
9ce4ea8
 			g_message ("The SSH agent was already initialized");
9ce4ea8
 		} else {
9ce4ea8
 			ssh_started = TRUE;
9ce4ea8
 			if (!gkd_daemon_startup_ssh ()) {
9ce4ea8
 				ssh_started = FALSE;
9ce4ea8
 				return FALSE;
9ce4ea8
 			}
9ce4ea8
 		}
9ce4ea8
 	}
9ce4ea8
 #endif
9ce4ea8
 
9ce4ea8
 	return TRUE;
9ce4ea8
 }
9ce4ea8
 
9ce4ea8
@@ -849,112 +898,114 @@ main (int argc, char *argv[])
9ce4ea8
 #ifdef HAVE_LOCALE_H
9ce4ea8
 	/* internationalisation */
9ce4ea8
 	setlocale (LC_ALL, "");
9ce4ea8
 #endif
9ce4ea8
 
9ce4ea8
 #ifdef HAVE_GETTEXT
9ce4ea8
 	bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
9ce4ea8
 	textdomain (GETTEXT_PACKAGE);
9ce4ea8
 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
9ce4ea8
 #endif
9ce4ea8
 
9ce4ea8
 	egg_libgcrypt_initialize ();
9ce4ea8
 
9ce4ea8
 	/* Send all warning or error messages to syslog */
9ce4ea8
 	prepare_logging ();
9ce4ea8
 
9ce4ea8
 	parse_arguments (&argc, &argv);
9ce4ea8
 
9ce4ea8
 	/* The --version option. This is machine parseable output */
9ce4ea8
 	if (run_version) {
9ce4ea8
 		g_print ("gnome-keyring-daemon: %s\n", VERSION);
9ce4ea8
 		g_print ("testing: %s\n",
9ce4ea8
 #ifdef WITH_DEBUG
9ce4ea8
 		         "enabled");
9ce4ea8
 #else
9ce4ea8
 		         "disabled");
9ce4ea8
 #endif
9ce4ea8
 		exit (0);
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
+	/* The whole forking and daemonizing dance starts here. */
9ce4ea8
+	parent_wakeup_fd = fork_and_print_environment();
9ce4ea8
+
9ce4ea8
 	/* The --start option */
9ce4ea8
 	if (run_for_start) {
9ce4ea8
 		if (discover_other_daemon (initialize_daemon_at, TRUE)) {
9ce4ea8
 			/*
9ce4ea8
 			 * Another daemon was initialized, print out environment
9ce4ea8
 			 * for any callers, and quit or go comatose.
9ce4ea8
 			 */
9ce4ea8
-			print_environment ();
9ce4ea8
+			send_environment_and_finish_parent (parent_wakeup_fd);
9ce4ea8
 			if (run_foreground)
9ce4ea8
 				while (sleep(0x08000000) == 0);
9ce4ea8
 			cleanup_and_exit (0);
9ce4ea8
 		}
9ce4ea8
 
9ce4ea8
 	/* The --replace option */
9ce4ea8
 	} else if (run_for_replace) {
9ce4ea8
 		discover_other_daemon (replace_daemon_at, FALSE);
9ce4ea8
 		if (control_directory)
9ce4ea8
 			g_message ("Replacing daemon, using directory: %s", control_directory);
9ce4ea8
 		else
9ce4ea8
 			g_message ("Could not find daemon to replace, staring normally");
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	/* Initialize the main directory */
9ce4ea8
 	gkd_util_init_master_directory (control_directory);
9ce4ea8
 
9ce4ea8
 	/* Initialize our daemon main loop and threading */
9ce4ea8
 	loop = g_main_loop_new (NULL, FALSE);
9ce4ea8
 
9ce4ea8
 	/* Initialize our control socket */
9ce4ea8
 	if (!gkd_control_listen ())
9ce4ea8
 		return FALSE;
9ce4ea8
 
9ce4ea8
 	if (perform_unlock) {
9ce4ea8
 		login_password = read_login_password (STDIN);
9ce4ea8
 		atexit (clear_login_password);
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	/* The --login option. Delayed initialization */
9ce4ea8
 	if (run_for_login) {
9ce4ea8
 		timeout_id = g_timeout_add_seconds (LOGIN_TIMEOUT, (GSourceFunc) on_login_timeout, NULL);
9ce4ea8
 
9ce4ea8
 	/* Not a login daemon. Startup stuff now.*/
9ce4ea8
 	} else {
9ce4ea8
 		/* These are things that can run before forking */
9ce4ea8
 		if (!gkr_daemon_startup_steps (run_components))
9ce4ea8
 			cleanup_and_exit (1);
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	signal (SIGPIPE, SIG_IGN);
9ce4ea8
 
9ce4ea8
-	/* The whole forking and daemonizing dance starts here. */
9ce4ea8
-	fork_and_print_environment();
9ce4ea8
+	send_environment_and_finish_parent (parent_wakeup_fd);
9ce4ea8
 
9ce4ea8
 	g_unix_signal_add (SIGTERM, on_signal_term, loop);
9ce4ea8
 	g_unix_signal_add (SIGHUP, on_signal_term, loop);
9ce4ea8
 	g_unix_signal_add (SIGUSR1, on_signal_usr1, loop);
9ce4ea8
 
9ce4ea8
 	/* Prepare logging a second time, since we may be in a different process */
9ce4ea8
 	prepare_logging();
9ce4ea8
 
9ce4ea8
 	/* Remainder initialization after forking, if initialization not delayed */
9ce4ea8
 	if (!run_for_login) {
9ce4ea8
 		gkr_daemon_initialize_steps (run_components);
9ce4ea8
 
9ce4ea8
 		/*
9ce4ea8
 		 * Close stdout and so that the caller knows that we're
9ce4ea8
 		 * all initialized, (when run in foreground mode).
9ce4ea8
 		 *
9ce4ea8
 		 * However since some logging goes to stdout, redirect that
9ce4ea8
 		 * to stderr. We don't want the caller confusing that with
9ce4ea8
 		 * valid output anyway.
9ce4ea8
 		 */
9ce4ea8
 		if (dup2 (2, 1) < 1)
9ce4ea8
 			g_warning ("couldn't redirect stdout to stderr");
9ce4ea8
 
9ce4ea8
 		g_debug ("initialization complete");
9ce4ea8
 	}
9ce4ea8
 
9ce4ea8
 	g_main_loop_run (loop);
9ce4ea8
 
9ce4ea8
 	/* This wraps everything up in order */
9ce4ea8
 	egg_cleanup_perform ();
9ce4ea8
-- 
9ce4ea8
2.5.0
9ce4ea8