diff --git a/debian/control b/debian/control index 18545c4..3ec505a 100644 --- a/debian/control +++ b/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, diff --git a/debian/qubes-core-agent-qrexec.install b/debian/qubes-core-agent-qrexec.install index 1c61677..0696b42 100644 --- a/debian/qubes-core-agent-qrexec.install +++ b/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 diff --git a/qrexec/Makefile b/qrexec/Makefile index 96c9c78..40ddc3a 100644 --- a/qrexec/Makefile +++ b/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 diff --git a/qrexec/qrexec-agent.c b/qrexec/qrexec-agent.c index fba2f47..6a5a139 100644 --- a/qrexec/qrexec-agent.c +++ b/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) diff --git a/qrexec/qrexec.pam b/qrexec/qrexec.pam new file mode 100644 index 0000000..c6896bc --- /dev/null +++ b/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 diff --git a/rpm_spec/core-agent.spec b/rpm_spec/core-agent.spec index 3136883..07e1c8c 100644 --- a/rpm_spec/core-agent.spec +++ b/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