Sfoglia il codice sorgente

qrexec: use PAM directly instead of calling su to setup the session

Instead of calling 'su' to switch the user, use own implementation of
this. Thanks to PAM it's pretty simple. The main reason is to have
control over process waiting for session termination (to call
pam_close_sesion/pam_end). Especially we don't want it to keep std* fds
open, which would prevent qrexec-agent from receiving EOF when one of
them will be closed.
Also, this will preserve QREXEC_AGENT_PID environment variable.

Fixes QubesOS/qubes-issues#2851
Marek Marczykowski-Górecki 9 anni fa
parent
commit
3af55c5cb3

+ 1 - 0
debian/control

@@ -3,6 +3,7 @@ Section: admin
 Priority: extra
 Maintainer: unman <unman@thirdeyesecurity.org>
 Build-Depends:
+    libpam0g-dev,
     libqrexec-utils-dev,
     libqubes-rpc-filecopy-dev (>= 3.1.3),
     libvchan-xen-dev,

+ 1 - 0
debian/qubes-core-agent-qrexec.install

@@ -1,3 +1,4 @@
+etc/pam.d/qrexec
 lib/systemd/system/qubes-qrexec-agent.service
 usr/bin/qrexec-client-vm
 usr/bin/qrexec-fork-server

+ 2 - 1
qrexec/Makefile

@@ -1,7 +1,7 @@
 CC=gcc
 CFLAGS+=-I. -g -O2 -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)`
 LDFLAGS=-pie
-LDLIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils
+LDLIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils -lpam
 
 all: qrexec-agent qrexec-client-vm qrexec-fork-server
 qrexec-agent: qrexec-agent.o qrexec-agent-data.o
@@ -19,4 +19,5 @@ install:
 	ln -s ../../bin/qrexec-client-vm $(DESTDIR)/usr/lib/qubes/qrexec_client_vm
 	install qrexec-fork-server $(DESTDIR)/usr/bin
 	install qubes-rpc-multiplexer $(DESTDIR)/usr/lib/qubes
+	install -D -m 0644 qrexec.pam $(DESTDIR)/etc/pam.d/qrexec
 

+ 144 - 0
qrexec/qrexec-agent.c

@@ -20,6 +20,8 @@
  */
 
 #define _GNU_SOURCE
+#define HAVE_PAM
+
 #include <sys/select.h>
 #include <sys/socket.h>
 #include <sys/un.h>
@@ -35,6 +37,9 @@
 #include <grp.h>
 #include <sys/stat.h>
 #include <assert.h>
+#ifdef HAVE_PAM
+#include <security/pam_appl.h>
+#endif
 #include "qrexec.h"
 #include <libvchan.h>
 #include "libqrexec-utils.h"
@@ -65,6 +70,36 @@ void no_colon_in_cmd()
     exit(1);
 }
 
+#ifdef HAVE_PAM
+int pam_conv_callback(int num_msg, const struct pam_message **msg,
+        struct pam_response **resp, void *appdata_ptr __attribute__((__unused__)))
+{
+    int i;
+    struct pam_response *resp_array =
+        calloc(sizeof(struct pam_response), num_msg);
+
+    if (resp_array == NULL)
+        return PAM_BUF_ERR;
+
+    for (i=0; i<num_msg; i++) {
+        if (msg[i]->msg_style == PAM_ERROR_MSG)
+            fprintf(stderr, "%s", msg[i]->msg);
+        if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF ||
+                msg[i]->msg_style == PAM_PROMPT_ECHO_ON) {
+            resp_array[i].resp = strdup("");
+            resp_array[i].resp_retcode = 0;
+        }
+    }
+    *resp = resp_array;
+    return PAM_SUCCESS;
+}
+
+static struct pam_conv conv = {
+    pam_conv_callback,
+    NULL
+};
+#endif
+
 /* Start program requested by dom0 in already prepared process
  * (stdin/stdout/stderr already set, etc)
  * Called in two cases:
@@ -86,6 +121,16 @@ void do_exec(const char *cmd)
 {
     char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - RPC_REQUEST_COMMAND_LEN + 1];
     char *realcmd = index(cmd, ':'), *user;
+#ifdef HAVE_PAM
+    int retval, status;
+    pam_handle_t *pamh=NULL;
+    struct passwd *pw;
+    struct passwd pw_copy;
+    pid_t child, pid;
+    char **env;
+    char pid_s[32];
+#endif
+
     if (!realcmd)
         no_colon_in_cmd();
     /* mark end of username and move to command */
@@ -103,9 +148,108 @@ void do_exec(const char *cmd)
     signal(SIGCHLD, SIG_DFL);
     signal(SIGPIPE, SIG_DFL);
 
+#ifdef HAVE_PAM
+    pw = getpwnam (user);
+    if (! (pw && pw->pw_name && pw->pw_name[0] && pw->pw_dir && pw->pw_dir[0]
+                && pw->pw_passwd)) {
+        fprintf(stderr, "user %s does not exist", user);
+        exit(1);
+    }
+
+    /* Make a copy of the password information and point pw at the local
+     * copy instead.  Otherwise, some systems (e.g. Linux) would clobber
+     * the static data through the getlogin call.
+     */
+    pw_copy = *pw;
+    pw = &pw_copy;
+    pw->pw_name = strdup(pw->pw_name);
+    pw->pw_passwd = strdup(pw->pw_passwd);
+    pw->pw_dir = strdup(pw->pw_dir);
+    pw->pw_shell = strdup(pw->pw_shell);
+    endpwent();
+
+    retval = pam_start("qrexec", user, &conv, &pamh);
+    if (retval != PAM_SUCCESS)
+        goto error;
+
+    retval = pam_authenticate(pamh, 0);
+    if (retval != PAM_SUCCESS)
+        goto error;
+
+    retval = initgroups(pw->pw_name, pw->pw_gid);
+    if (retval == -1) {
+        perror("initgroups");
+        goto error;
+    }
+
+    retval = pam_setcred(pamh, PAM_ESTABLISH_CRED);
+    if (retval != PAM_SUCCESS)
+        goto error;
+
+    retval = pam_open_session(pamh, 0);
+    if (retval != PAM_SUCCESS)
+        goto error;
+
+    /* provide this variable to child process */
+    snprintf(pid_s, sizeof(pid_s), "QREXEC_AGENT_PID=%d", getppid());
+    retval = pam_putenv(pamh, pid_s);
+    if (retval != PAM_SUCCESS)
+        goto error;
+
+    /* FORK HERE */
+    child = fork ();
+
+    switch (child) {
+        case -1:
+            goto error;
+        case 0:
+            /* child */
+            if (setgid (pw->pw_gid))
+                exit(126);
+            if (setuid (pw->pw_uid))
+                exit(126);
+            setsid();
+            /* This is a copy but don't care to free as we exec later anyways.  */
+            env = pam_getenvlist (pamh);
+            execle("/bin/sh", "sh", "-c", realcmd, (char*)NULL, env);
+            exit(127);
+        default:
+            /* parent */
+            /* close std*, so when child process closes them, qrexec-agent will receive EOF */
+            /* this is the main purpose of this reimplementation of /bin/su... */
+            close(0);
+            close(1);
+            close(2);
+    }
+
+    /* reachable only in parent */
+    pid = waitpid (child, &status, 0);
+    if (pid != (pid_t)-1) {
+        if (WIFSIGNALED (status))
+            status = WTERMSIG (status) + 128;
+        else
+            status = WEXITSTATUS (status);
+    } else
+        status = 1;
+
+    retval = pam_close_session (pamh, 0);
+
+    retval = pam_setcred (pamh, PAM_DELETE_CRED | PAM_SILENT);
+
+    if (pam_end(pamh, retval) != PAM_SUCCESS) {     /* close Linux-PAM */
+        pamh = NULL;
+        exit(1);
+    }
+    exit(status);
+error:
+    pam_end(pamh, PAM_ABORT);
+    exit(1);
+#else
     execl("/bin/su", "su", "-", user, "-c", realcmd, NULL);
     perror("execl");
     exit(1);
+#endif
+
 }
 
 void handle_vchan_error(const char *op)

+ 9 - 0
qrexec/qrexec.pam

@@ -0,0 +1,9 @@
+#%PAM-1.0
+auth		sufficient	pam_rootok.so
+auth		substack	system-auth
+auth		include		postlogin
+account		sufficient	pam_succeed_if.so uid = 0 use_uid quiet
+account		include		system-auth
+password	include		system-auth
+session		include		system-auth
+session		include		postlogin

+ 2 - 0
rpm_spec/core-agent.spec

@@ -157,6 +157,7 @@ BuildRequires: xen-devel
 BuildRequires: libX11-devel
 BuildRequires: qubes-utils-devel >= 3.1.3
 BuildRequires: qubes-libvchan-%{backend_vmm}-devel
+BuildRequires: pam-devel
 
 %description
 The Qubes core files for installation inside a Qubes VM.
@@ -611,6 +612,7 @@ rm -f %{name}-%{version}
 %{python3_sitelib}/dnf-plugins/*
 
 %files qrexec
+%config(noreplace) /etc/pam.d/qrexec
 /usr/bin/qrexec-fork-server
 /usr/bin/qrexec-client-vm
 /usr/lib/qubes/qrexec-agent