SMF/process contracts in Solaris 10

Chad Mynhier mynhier at cs.utk.edu
Wed Aug 2 00:38:35 EST 2006


I'm including a non-Sun-sshd-derived patch for this problem below.

To restate the problem, sshd doesn't put child processes into separate
process contracts, so every process for which sshd was ever an
ancestor (including processes that have otherwise been daemonized)
are in the same process contract.  This means that if sshd was started
via the service management facility (SMF), any normal or abnormal
termination of sshd via SMF will cause all of the child sshd processes
(and any process that was ever started from within an ssh session,
regardless of whether that ssh session still exists) to be terminated.

The output below shows the wrong behavior, i.e., that the child
processes are in process contract 562 with the parent sshd process:

server>ps
   PID TTY         TIME CMD
 16778 pts/2       0:00 tcsh
 16849 pts/2       0:00 ps
server>ptree -c 16778
[process contract 1]
  1     /sbin/init
    [process contract 4]
      7     /lib/svc/bin/svc.startd
        [process contract 562]
          16771 /usr/local/sbin/sshd
            16774 /usr/local/sbin/sshd -R
              16776 /usr/local/sbin/sshd -R
                16778 -tcsh
                  16850 ptree -c 16778
server>

In the above case, a "svcadm disable ssh" will terminate all sshd
child processes.  The output below shows the correct
behavior, i.e., that the child process is in a separate process
contract (565):

server>ps
   PID TTY         TIME CMD
  1195 pts/2       0:00 tcsh
  1256 pts/2       0:00 ps
server>ptree -c 1195
[process contract 565]
  1191  /usr/local/sbin/sshd -R
    1193  /usr/local/sbin/sshd -R
      1195  -tcsh
        1257  ptree -c 1195
server>

In this case, a "svcadm disable ssh" will not terminate existing
ssh connections.

Chad Mynhier


diff -Naur --exclude=RCS openssh-4.3p2/configure.ac openssh-4.3p2-process-contracts/configure.ac
--- openssh-4.3p2/configure.ac	Wed Feb  8 11:11:06 2006
+++ openssh-4.3p2-process-contracts/configure.ac	Tue Jul 25 12:56:49 2006
@@ -1,4 +1,4 @@
-# $Id: configure.ac,v 1.322.2.6 2006/02/08 11:11:06 dtucker Exp $
+# $Id: configure.ac,v 1.8 2006/07/25 12:56:33 mynhierc Exp $
 #
 # Copyright (c) 1999-2004 Damien Miller
 #
@@ -15,7 +15,7 @@
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 AC_INIT(OpenSSH, Portable, openssh-unix-dev at mindrot.org)
-AC_REVISION($Revision: 1.322.2.6 $)
+AC_REVISION($Revision: 1.8 $)
 AC_CONFIG_SRCDIR([ssh.c])
 
 AC_CONFIG_HEADER(config.h)
@@ -414,6 +414,15 @@
 		AC_DEFINE(DISABLE_UTMP)
 		AC_DEFINE(DISABLE_WTMP, 1,
 			[Define if you don't want to use wtmp])
+	else
+		AC_MSG_RESULT(no)
+	fi
+	AC_MSG_CHECKING(for process contracts)
+	if test "$sol2ver" -ge 10; then
+		AC_MSG_RESULT(yes)
+		AC_DEFINE(WITH_SOLARIS_PROCESS_CONTRACTS, 1,
+			[Define if you have Solaris process contracts])
+		LIBS="$LIBS -lcontract"
 	else
 		AC_MSG_RESULT(no)
 	fi
diff -Naur --exclude=RCS openssh-4.3p2/sshd.c openssh-4.3p2-process-contracts/sshd.c
--- openssh-4.3p2/sshd.c	Sat Dec 24 03:59:12 2005
+++ openssh-4.3p2-process-contracts/sshd.c	Tue Jul 25 20:48:02 2006
@@ -86,6 +86,12 @@
 #include "monitor_wrap.h"
 #include "monitor_fdpass.h"
 
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+#include <libcontract.h>
+#include <sys/contract/process.h>
+#include <sys/ctfs.h>
+#endif /* WITH_SOLARIS_PROCESS_CONTRACTS */
+
 #ifdef LIBWRAP
 #include <tcpd.h>
 #include <syslog.h>
@@ -866,6 +872,144 @@
 	debug3("%s: done", __func__);
 }
 
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+/*
+ * Returns the file descriptor for the process contract template on success,
+ * -1 on failure.
+ */
+int
+pre_fork_contract_processing()
+{
+	int		tmpl_fd=-1;
+
+	if ((tmpl_fd = open64(CTFS_ROOT "/process/template", O_RDWR)) == -1) {
+		error("Error opening  " CTFS_ROOT "/process/template: %.100s",strerror(errno));
+		goto cleanup_pre_fork_contract_processing;
+	}
+	/*
+	 * We have to set certain attributes before activating the template.
+	 */
+	if (ct_pr_tmpl_set_fatal(tmpl_fd, CT_PR_EV_HWERR|CT_PR_EV_SIGNAL) != 0) {
+		error("Error setting process contract template fatal events: %.100s",strerror(errno));
+		goto cleanup_pre_fork_contract_processing;
+	}
+	if (ct_tmpl_set_critical(tmpl_fd, CT_PR_EV_HWERR) != 0) {
+		error("Error setting process contract template critical events: %.100s",strerror(errno));
+		goto cleanup_pre_fork_contract_processing;
+	}
+	/*
+	 * Now make this the active template for this process.
+	 */
+	if (ct_tmpl_activate(tmpl_fd) != 0) {
+		error("Error activating process contract template: %.100s",strerror(errno));
+		goto cleanup_pre_fork_contract_processing;
+	}
+
+	return tmpl_fd;
+
+cleanup_pre_fork_contract_processing:
+	/*
+	 * Certain failure modes could lead to file descriptor leakage
+	 * if we don't clean up after ourselves.
+	 */
+	if (tmpl_fd > 0) {
+		close(tmpl_fd);
+	}
+	return -1;
+}
+
+void
+post_fork_contract_processing(int tmpl_fd,int pid)
+{
+	char		ctl_path[MAXPATHLEN];
+	ctid_t		ctid;
+	ct_stathdl_t	stathdl;
+	int		ctl_fd=-1;
+	int		pathlen;
+	int		stat_fd=-1;
+
+	/*
+	 * First clear the active template.
+	 */
+	if (ct_tmpl_clear(tmpl_fd) != 0) {
+		error("Error clearing active process contract template: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	close(tmpl_fd);
+
+	/*
+	 * If the fork didn't succeed (pid < 0), or if we're the child
+	 * (pid == 0), we have nothing more to do.
+	 */
+	if (pid <= 0) {
+		return;
+	}
+
+	/*
+	 * Now abandon the contract we've created.  This involves the
+	 * following steps:
+	 * - Get the contract id (ct_status_read(), ct_status_get_id())
+	 * - Get an fd for the ctl file for this contract 
+	 *   (/proc/all/contracts/<ctid>/ctl)
+	 * - Abandon the contract (ct_ctl_abandon(fd))
+	 */
+	if ((stat_fd = open64(CTFS_ROOT "/process/latest", O_RDONLY)) == -1) {
+		error("Error opening 'latest' process contract: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	if (ct_status_read(stat_fd, CTD_COMMON, &stathdl) != 0) {
+		error("Error reading process contract status: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	if ((ctid = ct_status_get_id(stathdl)) < 0) {
+		error("Error getting process contract id: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	ct_status_free(stathdl);
+	close(stat_fd);
+
+	pathlen = snprintf(ctl_path, MAXPATHLEN, CTFS_ROOT "/process/%ld/ctl",ctid);
+	if (pathlen > MAXPATHLEN) {
+		/*
+		 * Note:  Even though this case is unlikely, this could
+		 * theoretically lead to contract id leakage, as it would
+		 * prevent us from abandoning the contract.  If we were to 
+		 * run long enough with enough leakage, we would bump
+		 * up against process resource limits.
+		 */
+		error("Won't attempt to open process contract ctl file: %.100s",strerror(ENAMETOOLONG));
+		goto cleanup_post_fork_contract_processing;
+	}
+	if ((ctl_fd = open64(ctl_path, O_WRONLY)) < 0) {
+		error("Error opening process contract ctl file: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	if (ct_ctl_abandon(ctl_fd) < 0) {
+		error("Error abandoning process contract: %.100s",strerror(errno));
+		goto cleanup_post_fork_contract_processing;
+	}
+	close(ctl_fd);
+	return;
+
+cleanup_post_fork_contract_processing:
+	/*
+	 * Certain failure modes could lead to file descriptor leakage
+	 * if we don't clean up after ourselves.
+	 */
+	if (tmpl_fd > 0) {
+		close(tmpl_fd);
+	}
+	if (stat_fd > 0) {
+		close(stat_fd);
+	}
+	if (ctl_fd > 0) {
+		close(ctl_fd);
+	}
+	return;
+}
+
+#endif /* WITH_SOLARIS_PROCESS_CONTRACTS */
+
 /*
  * Main program for the daemon.
  */
@@ -893,6 +1037,9 @@
 	Authctxt *authctxt;
 	int ret, key_used = 0;
 	Buffer cfg;
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+	int contract_fd=-1;
+#endif
 
 #ifdef HAVE_SECUREWARE
 	(void)set_auth_parameters(ac, av);
@@ -1503,6 +1650,9 @@
 					 * the child process the connection. The
 					 * parent continues listening.
 					 */
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+					contract_fd = pre_fork_contract_processing();
+#endif /* WITH_SOLARIS_PROCESS_CONTRACTS */
 					if ((pid = fork()) == 0) {
 						/*
 						 * Child.  Close the listening and max_startup
@@ -1511,6 +1661,9 @@
 						 * changed).  We break out of the loop to handle
 						 * the connection.
 						 */
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+						post_fork_contract_processing(contract_fd,pid);
+#endif /* WITH_SOLARIS_PROCESS_CONTRACTS */
 						startup_pipe = startup_p[1];
 						close_startup_pipes();
 						close_listen_socks();
@@ -1523,6 +1676,9 @@
 					}
 				}
 
+#ifdef WITH_SOLARIS_PROCESS_CONTRACTS
+				post_fork_contract_processing(contract_fd,pid);
+#endif /* WITH_SOLARIS_PROCESS_CONTRACTS */
 				/* Parent.  Stay in the loop. */
 				if (pid < 0)
 					error("fork: %.100s", strerror(errno));



More information about the openssh-unix-dev mailing list