From c574865182e2c5dfa183b577f49ac602d16df5c0 Mon Sep 17 00:00:00 2001
From: Manoj Srivastava <srivasta@debian.org>
Date: Sun, 9 Feb 2014 16:09:49 +0000
Subject: Handle SELinux authorisation roles

Rejected upstream due to discomfort with magic usernames; a better approach
will need an SSH protocol change.  In the meantime, this came from Debian's
SELinux maintainer, so we'll keep it until we have something better.

Bug: https://bugzilla.mindrot.org/show_bug.cgi?id=1641
Bug-Debian: http://bugs.debian.org/394795
Last-Update: 2020-10-18

Patch-Name: selinux-role.patch
---
 auth.h                      |  1 +
 auth2.c                     | 10 ++++++++--
 monitor.c                   | 37 +++++++++++++++++++++++++++++++++----
 monitor.h                   |  2 ++
 monitor_wrap.c              | 27 ++++++++++++++++++++++++---
 monitor_wrap.h              |  3 ++-
 openbsd-compat/port-linux.c | 21 ++++++++++++++-------
 openbsd-compat/port-linux.h |  4 ++--
 platform.c                  |  4 ++--
 platform.h                  |  2 +-
 session.c                   | 10 +++++-----
 session.h                   |  2 +-
 sshd.c                      |  2 +-
 sshpty.c                    |  4 ++--
 sshpty.h                    |  2 +-
 15 files changed, 99 insertions(+), 32 deletions(-)

diff --git a/auth.h b/auth.h
index becc672b5..5da9fe75f 100644
--- a/auth.h
+++ b/auth.h
@@ -63,6 +63,7 @@ struct Authctxt {
 	char		*service;
 	struct passwd	*pw;		/* set if 'valid' */
 	char		*style;
+	char		*role;
 
 	/* Method lists for multiple authentication */
 	char		**auth_methods;	/* modified from server config */
diff --git a/auth2.c b/auth2.c
index 9fa1404b3..d8363bdba 100644
--- a/auth2.c
+++ b/auth2.c
@@ -265,7 +265,7 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
 {
 	Authctxt *authctxt = ssh->authctxt;
 	Authmethod *m = NULL;
-	char *user = NULL, *service = NULL, *method = NULL, *style = NULL;
+	char *user = NULL, *service = NULL, *method = NULL, *style = NULL, *role = NULL;
 	int r, authenticated = 0;
 	double tstart = monotime_double();
 
@@ -279,8 +279,13 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
 	debug("userauth-request for user %s service %s method %s", user, service, method);
 	debug("attempt %d failures %d", authctxt->attempt, authctxt->failures);
 
+	if ((role = strchr(user, '/')) != NULL)
+		*role++ = 0;
+
 	if ((style = strchr(user, ':')) != NULL)
 		*style++ = 0;
+	else if (role && (style = strchr(role, ':')) != NULL)
+		*style++ = '\0';
 
 	if (authctxt->attempt++ == 0) {
 		/* setup auth context */
@@ -307,8 +312,9 @@ input_userauth_request(int type, u_int32_t seq, struct ssh *ssh)
 		    use_privsep ? " [net]" : "");
 		authctxt->service = xstrdup(service);
 		authctxt->style = style ? xstrdup(style) : NULL;
+		authctxt->role = role ? xstrdup(role) : NULL;
 		if (use_privsep)
-			mm_inform_authserv(service, style);
+			mm_inform_authserv(service, style, role);
 		userauth_banner(ssh);
 		if (auth2_setup_methods_lists(authctxt) != 0)
 			ssh_packet_disconnect(ssh,
diff --git a/monitor.c b/monitor.c
index 11868952b..98362948f 100644
--- a/monitor.c
+++ b/monitor.c
@@ -118,6 +118,7 @@ int mm_answer_sign(struct ssh *, int, struct sshbuf *);
 int mm_answer_pwnamallow(struct ssh *, int, struct sshbuf *);
 int mm_answer_auth2_read_banner(struct ssh *, int, struct sshbuf *);
 int mm_answer_authserv(struct ssh *, int, struct sshbuf *);
+int mm_answer_authrole(struct ssh *, int, struct sshbuf *);
 int mm_answer_authpassword(struct ssh *, int, struct sshbuf *);
 int mm_answer_bsdauthquery(struct ssh *, int, struct sshbuf *);
 int mm_answer_bsdauthrespond(struct ssh *, int, struct sshbuf *);
@@ -198,6 +199,7 @@ struct mon_table mon_dispatch_proto20[] = {
     {MONITOR_REQ_SIGN, MON_ONCE, mm_answer_sign},
     {MONITOR_REQ_PWNAM, MON_ONCE, mm_answer_pwnamallow},
     {MONITOR_REQ_AUTHSERV, MON_ONCE, mm_answer_authserv},
+    {MONITOR_REQ_AUTHROLE, MON_ONCE, mm_answer_authrole},
     {MONITOR_REQ_AUTH2_READ_BANNER, MON_ONCE, mm_answer_auth2_read_banner},
     {MONITOR_REQ_AUTHPASSWORD, MON_AUTH, mm_answer_authpassword},
 #ifdef USE_PAM
@@ -820,6 +822,7 @@ mm_answer_pwnamallow(struct ssh *ssh, int sock, struct sshbuf *m)
 
 	/* Allow service/style information on the auth context */
 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHSERV, 1);
+	monitor_permit(mon_dispatch, MONITOR_REQ_AUTHROLE, 1);
 	monitor_permit(mon_dispatch, MONITOR_REQ_AUTH2_READ_BANNER, 1);
 
 #ifdef USE_PAM
@@ -853,16 +856,42 @@ mm_answer_authserv(struct ssh *ssh, int sock, struct sshbuf *m)
 	monitor_permit_authentications(1);
 
 	if ((r = sshbuf_get_cstring(m, &authctxt->service, NULL)) != 0 ||
-	    (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0)
+	    (r = sshbuf_get_cstring(m, &authctxt->style, NULL)) != 0 ||
+	    (r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
-	debug3("%s: service=%s, style=%s",
-	    __func__, authctxt->service, authctxt->style);
+	debug3("%s: service=%s, style=%s, role=%s",
+	    __func__, authctxt->service, authctxt->style, authctxt->role);
 
 	if (strlen(authctxt->style) == 0) {
 		free(authctxt->style);
 		authctxt->style = NULL;
 	}
 
+	if (strlen(authctxt->role) == 0) {
+		free(authctxt->role);
+		authctxt->role = NULL;
+	}
+
+	return (0);
+}
+
+int
+mm_answer_authrole(struct ssh *ssh, int sock, struct sshbuf *m)
+{
+	int r;
+
+	monitor_permit_authentications(1);
+
+	if ((r = sshbuf_get_cstring(m, &authctxt->role, NULL)) != 0)
+		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+	debug3("%s: role=%s",
+	    __func__, authctxt->role);
+
+	if (strlen(authctxt->role) == 0) {
+		free(authctxt->role);
+		authctxt->role = NULL;
+	}
+
 	return (0);
 }
 
@@ -1566,7 +1595,7 @@ mm_answer_pty(struct ssh *ssh, int sock, struct sshbuf *m)
 	res = pty_allocate(&s->ptyfd, &s->ttyfd, s->tty, sizeof(s->tty));
 	if (res == 0)
 		goto error;
-	pty_setowner(authctxt->pw, s->tty);
+	pty_setowner(authctxt->pw, s->tty, authctxt->role);
 
 	if ((r = sshbuf_put_u32(m, 1)) != 0 ||
 	    (r = sshbuf_put_cstring(m, s->tty)) != 0)
diff --git a/monitor.h b/monitor.h
index 2b1a2d590..4d87284aa 100644
--- a/monitor.h
+++ b/monitor.h
@@ -65,6 +65,8 @@ enum monitor_reqtype {
 
 	MONITOR_REQ_GSSSIGN = 150, MONITOR_ANS_GSSSIGN = 151,
 	MONITOR_REQ_GSSUPCREDS = 152, MONITOR_ANS_GSSUPCREDS = 153,
+
+	MONITOR_REQ_AUTHROLE = 154,
 };
 
 struct ssh;
diff --git a/monitor_wrap.c b/monitor_wrap.c
index 0e78cd006..d41d3949d 100644
--- a/monitor_wrap.c
+++ b/monitor_wrap.c
@@ -364,10 +364,10 @@ mm_auth2_read_banner(void)
 	return (banner);
 }
 
-/* Inform the privileged process about service and style */
+/* Inform the privileged process about service, style, and role */
 
 void
-mm_inform_authserv(char *service, char *style)
+mm_inform_authserv(char *service, char *style, char *role)
 {
 	struct sshbuf *m;
 	int r;
@@ -377,7 +377,8 @@ mm_inform_authserv(char *service, char *style)
 	if ((m = sshbuf_new()) == NULL)
 		fatal("%s: sshbuf_new failed", __func__);
 	if ((r = sshbuf_put_cstring(m, service)) != 0 ||
-	    (r = sshbuf_put_cstring(m, style ? style : "")) != 0)
+	    (r = sshbuf_put_cstring(m, style ? style : "")) != 0 ||
+	    (r = sshbuf_put_cstring(m, role ? role : "")) != 0)
 		fatal("%s: buffer error: %s", __func__, ssh_err(r));
 
 	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHSERV, m);
@@ -385,6 +386,26 @@ mm_inform_authserv(char *service, char *style)
 	sshbuf_free(m);
 }
 
+/* Inform the privileged process about role */
+
+void
+mm_inform_authrole(char *role)
+{
+	struct sshbuf *m;
+	int r;
+
+	debug3("%s entering", __func__);
+
+	if ((m = sshbuf_new()) == NULL)
+		fatal("%s: sshbuf_new failed", __func__);
+	if ((r = sshbuf_put_cstring(m, role ? role : "")) != 0)
+		fatal("%s: buffer error: %s", __func__, ssh_err(r));
+
+	mm_request_send(pmonitor->m_recvfd, MONITOR_REQ_AUTHROLE, m);
+
+	sshbuf_free(m);
+}
+
 /* Do the password authentication */
 int
 mm_auth_password(struct ssh *ssh, char *password)
diff --git a/monitor_wrap.h b/monitor_wrap.h
index 75aef1c74..c39e5dd8b 100644
--- a/monitor_wrap.h
+++ b/monitor_wrap.h
@@ -48,7 +48,8 @@ DH *mm_choose_dh(int, int, int);
 int mm_sshkey_sign(struct ssh *, struct sshkey *, u_char **, size_t *,
     const u_char *, size_t, const char *, const char *,
     const char *, u_int compat);
-void mm_inform_authserv(char *, char *);
+void mm_inform_authserv(char *, char *, char *);
+void mm_inform_authrole(char *);
 struct passwd *mm_getpwnamallow(struct ssh *, const char *);
 char *mm_auth2_read_banner(void);
 int mm_auth_password(struct ssh *, char *);
diff --git a/openbsd-compat/port-linux.c b/openbsd-compat/port-linux.c
index f46094faf..56f1d2c1e 100644
--- a/openbsd-compat/port-linux.c
+++ b/openbsd-compat/port-linux.c
@@ -56,7 +56,7 @@ ssh_selinux_enabled(void)
 
 /* Return the default security context for the given username */
 static security_context_t
-ssh_selinux_getctxbyname(char *pwname)
+ssh_selinux_getctxbyname(char *pwname, const char *role)
 {
 	security_context_t sc = NULL;
 	char *sename = NULL, *lvl = NULL;
@@ -71,9 +71,16 @@ ssh_selinux_getctxbyname(char *pwname)
 #endif
 
 #ifdef HAVE_GET_DEFAULT_CONTEXT_WITH_LEVEL
-	r = get_default_context_with_level(sename, lvl, NULL, &sc);
+	if (role != NULL && role[0])
+		r = get_default_context_with_rolelevel(sename, role, lvl, NULL,
+						       &sc);
+	else
+		r = get_default_context_with_level(sename, lvl, NULL, &sc);
 #else
-	r = get_default_context(sename, NULL, &sc);
+	if (role != NULL && role[0])
+		r = get_default_context_with_role(sename, role, NULL, &sc);
+	else
+		r = get_default_context(sename, NULL, &sc);
 #endif
 
 	if (r != 0) {
@@ -103,7 +110,7 @@ ssh_selinux_getctxbyname(char *pwname)
 
 /* Set the execution context to the default for the specified user */
 void
-ssh_selinux_setup_exec_context(char *pwname)
+ssh_selinux_setup_exec_context(char *pwname, const char *role)
 {
 	security_context_t user_ctx = NULL;
 
@@ -112,7 +119,7 @@ ssh_selinux_setup_exec_context(char *pwname)
 
 	debug3("%s: setting execution context", __func__);
 
-	user_ctx = ssh_selinux_getctxbyname(pwname);
+	user_ctx = ssh_selinux_getctxbyname(pwname, role);
 	if (setexeccon(user_ctx) != 0) {
 		switch (security_getenforce()) {
 		case -1:
@@ -134,7 +141,7 @@ ssh_selinux_setup_exec_context(char *pwname)
 
 /* Set the TTY context for the specified user */
 void
-ssh_selinux_setup_pty(char *pwname, const char *tty)
+ssh_selinux_setup_pty(char *pwname, const char *tty, const char *role)
 {
 	security_context_t new_tty_ctx = NULL;
 	security_context_t user_ctx = NULL;
@@ -146,7 +153,7 @@ ssh_selinux_setup_pty(char *pwname, const char *tty)
 
 	debug3("%s: setting TTY context on %s", __func__, tty);
 
-	user_ctx = ssh_selinux_getctxbyname(pwname);
+	user_ctx = ssh_selinux_getctxbyname(pwname, role);
 
 	/* XXX: should these calls fatal() upon failure in enforcing mode? */
 
diff --git a/openbsd-compat/port-linux.h b/openbsd-compat/port-linux.h
index 3c22a854d..c88129428 100644
--- a/openbsd-compat/port-linux.h
+++ b/openbsd-compat/port-linux.h
@@ -19,8 +19,8 @@
 
 #ifdef WITH_SELINUX
 int ssh_selinux_enabled(void);
-void ssh_selinux_setup_pty(char *, const char *);
-void ssh_selinux_setup_exec_context(char *);
+void ssh_selinux_setup_pty(char *, const char *, const char *);
+void ssh_selinux_setup_exec_context(char *, const char *);
 void ssh_selinux_change_context(const char *);
 void ssh_selinux_setfscreatecon(const char *);
 #endif
diff --git a/platform.c b/platform.c
index 44ba71dc5..2defe9425 100644
--- a/platform.c
+++ b/platform.c
@@ -143,7 +143,7 @@ platform_setusercontext(struct passwd *pw)
  * called if sshd is running as root.
  */
 void
-platform_setusercontext_post_groups(struct passwd *pw)
+platform_setusercontext_post_groups(struct passwd *pw, const char *role)
 {
 #if !defined(HAVE_LOGIN_CAP) && defined(USE_PAM)
 	/*
@@ -184,7 +184,7 @@ platform_setusercontext_post_groups(struct passwd *pw)
 	}
 #endif /* HAVE_SETPCRED */
 #ifdef WITH_SELINUX
-	ssh_selinux_setup_exec_context(pw->pw_name);
+	ssh_selinux_setup_exec_context(pw->pw_name, role);
 #endif
 }
 
diff --git a/platform.h b/platform.h
index ea4f9c584..60d72ffe7 100644
--- a/platform.h
+++ b/platform.h
@@ -25,7 +25,7 @@ void platform_post_fork_parent(pid_t child_pid);
 void platform_post_fork_child(void);
 int  platform_privileged_uidswap(void);
 void platform_setusercontext(struct passwd *);
-void platform_setusercontext_post_groups(struct passwd *);
+void platform_setusercontext_post_groups(struct passwd *, const char *);
 char *platform_get_krb5_client(const char *);
 char *platform_krb5_get_principal_name(const char *);
 int platform_sys_dir_uid(uid_t);
diff --git a/session.c b/session.c
index 857f17b3c..b1796a803 100644
--- a/session.c
+++ b/session.c
@@ -1364,7 +1364,7 @@ safely_chroot(const char *path, uid_t uid)
 
 /* Set login name, uid, gid, and groups. */
 void
-do_setusercontext(struct passwd *pw)
+do_setusercontext(struct passwd *pw, const char *role)
 {
 	char uidstr[32], *chroot_path, *tmp;
 
@@ -1392,7 +1392,7 @@ do_setusercontext(struct passwd *pw)
 		endgrent();
 #endif
 
-		platform_setusercontext_post_groups(pw);
+		platform_setusercontext_post_groups(pw, role);
 
 		if (!in_chroot && options.chroot_directory != NULL &&
 		    strcasecmp(options.chroot_directory, "none") != 0) {
@@ -1536,7 +1536,7 @@ do_child(struct ssh *ssh, Session *s, const char *command)
 
 	/* Force a password change */
 	if (s->authctxt->force_pwchange) {
-		do_setusercontext(pw);
+		do_setusercontext(pw, s->authctxt->role);
 		child_close_fds(ssh);
 		do_pwchange(s);
 		exit(1);
@@ -1554,7 +1554,7 @@ do_child(struct ssh *ssh, Session *s, const char *command)
 	/* When PAM is enabled we rely on it to do the nologin check */
 	if (!options.use_pam)
 		do_nologin(pw);
-	do_setusercontext(pw);
+	do_setusercontext(pw, s->authctxt->role);
 	/*
 	 * PAM session modules in do_setusercontext may have
 	 * generated messages, so if this in an interactive
@@ -1953,7 +1953,7 @@ session_pty_req(struct ssh *ssh, Session *s)
 		sshpkt_fatal(ssh, r, "%s: parse packet", __func__);
 
 	if (!use_privsep)
-		pty_setowner(s->pw, s->tty);
+		pty_setowner(s->pw, s->tty, s->authctxt->role);
 
 	/* Set window size from the packet. */
 	pty_change_window_size(s->ptyfd, s->row, s->col, s->xpixel, s->ypixel);
diff --git a/session.h b/session.h
index ce59dabd9..675c91146 100644
--- a/session.h
+++ b/session.h
@@ -77,7 +77,7 @@ void	 session_pty_cleanup2(Session *);
 Session	*session_new(void);
 Session	*session_by_tty(char *);
 void	 session_close(struct ssh *, Session *);
-void	 do_setusercontext(struct passwd *);
+void	 do_setusercontext(struct passwd *, const char *);
 
 const char	*session_get_remote_name_or_ip(struct ssh *, u_int, int);
 
diff --git a/sshd.c b/sshd.c
index a50ec3584..38d281ab4 100644
--- a/sshd.c
+++ b/sshd.c
@@ -594,7 +594,7 @@ privsep_postauth(struct ssh *ssh, Authctxt *authctxt)
 	reseed_prngs();
 
 	/* Drop privileges */
-	do_setusercontext(authctxt->pw);
+	do_setusercontext(authctxt->pw, authctxt->role);
 
  skip:
 	/* It is safe now to apply the key state */
diff --git a/sshpty.c b/sshpty.c
index bce09e255..308449b37 100644
--- a/sshpty.c
+++ b/sshpty.c
@@ -162,7 +162,7 @@ pty_change_window_size(int ptyfd, u_int row, u_int col,
 }
 
 void
-pty_setowner(struct passwd *pw, const char *tty)
+pty_setowner(struct passwd *pw, const char *tty, const char *role)
 {
 	struct group *grp;
 	gid_t gid;
@@ -186,7 +186,7 @@ pty_setowner(struct passwd *pw, const char *tty)
 		    strerror(errno));
 
 #ifdef WITH_SELINUX
-	ssh_selinux_setup_pty(pw->pw_name, tty);
+	ssh_selinux_setup_pty(pw->pw_name, tty, role);
 #endif
 
 	if (st.st_uid != pw->pw_uid || st.st_gid != gid) {
diff --git a/sshpty.h b/sshpty.h
index 9ec7e9a15..de7e000ae 100644
--- a/sshpty.h
+++ b/sshpty.h
@@ -24,5 +24,5 @@ int	 pty_allocate(int *, int *, char *, size_t);
 void	 pty_release(const char *);
 void	 pty_make_controlling_tty(int *, const char *);
 void	 pty_change_window_size(int, u_int, u_int, u_int, u_int);
-void	 pty_setowner(struct passwd *, const char *);
+void	 pty_setowner(struct passwd *, const char *, const char *);
 void	 disconnect_controlling_tty(void);
