qrexec: add simple "fork server" to spawn new processes inside user session
This process should be started from user session (most likely qubes-session). New processes (of that user) will be created as children of that session making logind and such crap happy. This should also solve problems with EOF transmission (no additional "su" process) and prevent loading all the environment multiple times.
This commit is contained in:
parent
4b5960daa3
commit
700c240d37
@ -2,9 +2,11 @@ CC=gcc
|
||||
CFLAGS+=-I. -g -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)`
|
||||
LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils
|
||||
|
||||
all: qrexec-agent qrexec-client-vm
|
||||
all: qrexec-agent qrexec-client-vm qrexec-fork-server
|
||||
qrexec-agent: qrexec-agent.o qrexec-agent-data.o
|
||||
$(CC) -pie -g -o qrexec-agent qrexec-agent.o qrexec-agent-data.o $(LIBS)
|
||||
qrexec-fork-server: qrexec-fork-server.o qrexec-agent-data.o
|
||||
$(CC) -pie -g -o qrexec-fork-server qrexec-fork-server.o qrexec-agent-data.o $(LIBS)
|
||||
qrexec-client-vm: qrexec-client-vm.o
|
||||
$(CC) -pie -g -o qrexec-client-vm qrexec-client-vm.o
|
||||
clean:
|
||||
@ -12,9 +14,10 @@ clean:
|
||||
|
||||
install:
|
||||
install -d $(DESTDIR)/etc/qubes-rpc
|
||||
install -d $(DESTDIR)/usr/lib/qubes
|
||||
install -d $(DESTDIR)/usr/lib/qubes $(DESTDIR)/usr/bin
|
||||
install qrexec-agent $(DESTDIR)/usr/lib/qubes
|
||||
install qrexec-client-vm $(DESTDIR)/usr/lib/qubes
|
||||
ln -s 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
|
||||
|
||||
|
@ -19,7 +19,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
@ -38,7 +41,8 @@
|
||||
#include "qrexec-agent.h"
|
||||
|
||||
struct _connection_info {
|
||||
int pid;
|
||||
int pid; /* pid of child process handling the data */
|
||||
int fd; /* socket to process handling the data (wait for EOF here) */
|
||||
int connect_domain;
|
||||
int connect_port;
|
||||
};
|
||||
@ -147,13 +151,70 @@ void wake_meminfo_writer()
|
||||
meminfo_write_started = 1;
|
||||
}
|
||||
|
||||
void register_vchan_connection(pid_t pid, int domain, int port)
|
||||
int try_fork_server(int type, int connect_domain, int connect_port,
|
||||
char *cmdline, int cmdline_len) {
|
||||
char username[cmdline_len];
|
||||
char *colon;
|
||||
char *fork_server_socket_path;
|
||||
int s, len;
|
||||
struct sockaddr_un remote;
|
||||
struct qrexec_cmd_info info;
|
||||
|
||||
strncpy(username, cmdline, cmdline_len);
|
||||
colon = index(username, ':');
|
||||
if (!colon)
|
||||
return -1;
|
||||
*colon = '\0';
|
||||
|
||||
if (asprintf(&fork_server_socket_path, QREXEC_FORK_SERVER_SOCKET, username) < 0) {
|
||||
fprintf(stderr, "Memory allocation failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
remote.sun_family = AF_UNIX;
|
||||
strncpy(remote.sun_path, fork_server_socket_path,
|
||||
sizeof(remote.sun_path));
|
||||
free(fork_server_socket_path);
|
||||
|
||||
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
len = strlen(remote.sun_path) + sizeof(remote.sun_family);
|
||||
if (connect(s, (struct sockaddr *) &remote, len) == -1) {
|
||||
if (errno != ECONNREFUSED)
|
||||
perror("connect");
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
info.type = type;
|
||||
info.connect_domain = connect_domain;
|
||||
info.connect_port = connect_port;
|
||||
info.cmdline_len = cmdline_len-(strlen(username)+1);
|
||||
if (!write_all(s, &info, sizeof(info))) {
|
||||
perror("write");
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
if (!write_all(s, colon+1, info.cmdline_len)) {
|
||||
perror("write");
|
||||
close(s);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
void register_vchan_connection(pid_t pid, int fd, int domain, int port)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_FDS; i++) {
|
||||
if (connection_info[i].pid == 0) {
|
||||
connection_info[i].pid = pid;
|
||||
connection_info[i].fd = fd;
|
||||
connection_info[i].connect_domain = domain;
|
||||
connection_info[i].connect_port = port;
|
||||
return;
|
||||
@ -176,11 +237,23 @@ void handle_server_exec_request(struct msg_header *hdr)
|
||||
if (libvchan_recv(ctrl_vchan, buf, hdr->len-sizeof(params)) < 0)
|
||||
handle_vchan_error("read exec cmd");
|
||||
|
||||
if ((hdr->type == MSG_EXEC_CMDLINE || hdr->type == MSG_JUST_EXEC) &&
|
||||
!strstr(buf, ":nogui:")) {
|
||||
int child_socket = try_fork_server(hdr->type,
|
||||
params.connect_domain, params.connect_port,
|
||||
buf, hdr->len-sizeof(params));
|
||||
if (child_socket >= 0) {
|
||||
register_vchan_connection(-1, child_socket,
|
||||
params.connect_domain, params.connect_port);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
child_agent = handle_new_process(hdr->type,
|
||||
params.connect_domain, params.connect_port,
|
||||
buf, hdr->len-sizeof(params));
|
||||
|
||||
register_vchan_connection(child_agent,
|
||||
register_vchan_connection(child_agent, -1,
|
||||
params.connect_domain, params.connect_port);
|
||||
}
|
||||
|
||||
@ -280,6 +353,7 @@ void reap_children()
|
||||
int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
|
||||
{
|
||||
int max = -1;
|
||||
int i;
|
||||
FD_ZERO(rdset);
|
||||
FD_ZERO(wrset);
|
||||
|
||||
@ -289,6 +363,14 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset)
|
||||
FD_SET(passfd_socket, rdset);
|
||||
if (passfd_socket > max)
|
||||
max = passfd_socket;
|
||||
|
||||
for (i = 0; i < MAX_FDS; i++) {
|
||||
if (connection_info[i].pid != 0 && connection_info[i].fd != -1) {
|
||||
FD_SET(connection_info[i].fd, rdset);
|
||||
if (connection_info[i].fd > max)
|
||||
max = connection_info[i].fd;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@ -329,6 +411,24 @@ void handle_trigger_io()
|
||||
}
|
||||
}
|
||||
|
||||
void handle_terminated_fork_client(fd_set *rdset) {
|
||||
int i, ret;
|
||||
char buf[2];
|
||||
|
||||
for (i = 0; i < MAX_FDS; i++) {
|
||||
if (connection_info[i].pid && connection_info[i].fd >= 0 &&
|
||||
FD_ISSET(connection_info[i].fd, rdset)) {
|
||||
ret = read(connection_info[i].fd, buf, sizeof(buf));
|
||||
if (ret == 0 || (ret == -1 && errno == ECONNRESET)) {
|
||||
close(connection_info[i].fd);
|
||||
release_connection(i);
|
||||
} else {
|
||||
fprintf(stderr, "Unexpected read on fork-server connection: %d(%s)\n", ret, strerror(errno));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
fd_set rdset, wrset;
|
||||
@ -362,5 +462,7 @@ int main()
|
||||
|
||||
if (FD_ISSET(trigger_fd, &rdset))
|
||||
handle_trigger_io();
|
||||
|
||||
handle_terminated_fork_client(&rdset);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#define QREXEC_FORK_SERVER_SOCKET "/var/run/qubes/qrexec-server.%s.sock"
|
||||
|
||||
int handle_handshake(libvchan_t *ctrl);
|
||||
void handle_vchan_error(const char *op);
|
||||
void do_exec(const char *cmd);
|
||||
@ -26,3 +28,11 @@ void do_exec(const char *cmd);
|
||||
pid_t handle_new_process(int type,
|
||||
int connect_domain, int connect_port,
|
||||
char *cmdline, int cmdline_len);
|
||||
|
||||
struct qrexec_cmd_info {
|
||||
int type;
|
||||
int connect_domain;
|
||||
int connect_port;
|
||||
int cmdline_len;
|
||||
char cmdline[0];
|
||||
};
|
||||
|
110
qrexec/qrexec-fork-server.c
Normal file
110
qrexec/qrexec-fork-server.c
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* The Qubes OS Project, http://www.qubes-os.org
|
||||
*
|
||||
* Copyright (C) 2015 Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include "qrexec.h"
|
||||
#include <libvchan.h>
|
||||
#include "libqrexec-utils.h"
|
||||
#include "qrexec-agent.h"
|
||||
|
||||
void do_exec(const char *cmd)
|
||||
{
|
||||
char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - strlen(RPC_REQUEST_COMMAND) + 1];
|
||||
/* replace magic RPC cmd with RPC multiplexer path */
|
||||
if (strncmp(cmd, RPC_REQUEST_COMMAND " ", strlen(RPC_REQUEST_COMMAND)+1)==0) {
|
||||
strcpy(buf, QUBES_RPC_MULTIPLEXER_PATH);
|
||||
strcpy(buf + strlen(QUBES_RPC_MULTIPLEXER_PATH), cmd + strlen(RPC_REQUEST_COMMAND));
|
||||
cmd = buf;
|
||||
}
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
|
||||
execl("/bin/sh", "sh", "-c", cmd, NULL);
|
||||
perror("execl");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void handle_vchan_error(const char *op)
|
||||
{
|
||||
fprintf(stderr, "Error while vchan %s, exiting\n", op);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void handle_single_command(int fd, struct qrexec_cmd_info *info) {
|
||||
char cmdline[info->cmdline_len+1];
|
||||
|
||||
if (!read_all(fd, cmdline, info->cmdline_len))
|
||||
return;
|
||||
cmdline[info->cmdline_len] = 0;
|
||||
|
||||
handle_new_process(info->type, info->connect_domain,
|
||||
info->connect_port,
|
||||
cmdline, info->cmdline_len);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int s, fd;
|
||||
char *socket_path;
|
||||
struct qrexec_cmd_info info;
|
||||
struct sockaddr_un peer;
|
||||
unsigned int addrlen;
|
||||
|
||||
|
||||
if (argc == 2) {
|
||||
socket_path = argv[1];
|
||||
} else if (argc == 1) {
|
||||
/* this will be leaked, but we don't care as the process will then terminate */
|
||||
if (asprintf(&socket_path, QREXEC_FORK_SERVER_SOCKET, getenv("USER")) < 0) {
|
||||
fprintf(stderr, "Memory allocation failed\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Usage: %s [socket path]\n", argv[0]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s = get_server_socket(socket_path);
|
||||
if (fcntl(s, F_SETFD, O_CLOEXEC) < 0) {
|
||||
perror("fcntl");
|
||||
exit(1);
|
||||
}
|
||||
signal(SIGCHLD, SIG_IGN);
|
||||
register_exec_func(do_exec);
|
||||
|
||||
while ((fd = accept(s, (struct sockaddr *) &peer, &addrlen)) >= 0) {
|
||||
if (read_all(fd, &info, sizeof(info))) {
|
||||
handle_single_command(fd, &info);
|
||||
}
|
||||
close(fd);
|
||||
addrlen = sizeof(peer);
|
||||
}
|
||||
close(s);
|
||||
unlink(socket_path);
|
||||
return 0;
|
||||
}
|
@ -400,6 +400,7 @@ rm -f %{name}-%{version}
|
||||
/usr/bin/qvm-mru-entry
|
||||
/usr/bin/xenstore-watch-qubes
|
||||
/usr/bin/qubes-desktop-run
|
||||
/usr/bin/qrexec-fork-server
|
||||
%dir /usr/lib/qubes
|
||||
/usr/lib/qubes/vusb-ctl.py*
|
||||
/usr/lib/qubes/dispvm-prerun.sh
|
||||
|
Loading…
Reference in New Issue
Block a user