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
This commit is contained in:
parent
f49042211b
commit
3af55c5cb3
1
debian/control
vendored
1
debian/control
vendored
@ -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
debian/qubes-core-agent-qrexec.install
vendored
1
debian/qubes-core-agent-qrexec.install
vendored
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
qrexec/qrexec.pam
Normal file
9
qrexec/qrexec.pam
Normal file
@ -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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user