Skip to content

Commit 76697e3

Browse files
committed
Working socket-based qrexec
This commit includes working socket-based qrexec. The main unimplemented feature is passing metadata about the connection to the remote command. This will be implemented in a future commit. qrexec-client needed to be changed to use shutdown() instead of close().
1 parent 980fdcb commit 76697e3

12 files changed

+437
-109
lines changed

agent/qrexec-agent-data.c

+40-14
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,29 @@
1919
*
2020
*/
2121

22+
#define _POSIX_C_SOURCE 200809L
23+
#define _GNU_SOURCE 1
24+
#include <limits.h>
25+
#include <stdint.h>
2226
#include <stdio.h>
2327
#include <stdlib.h>
2428
#include <signal.h>
25-
#include <unistd.h>
2629
#include <errno.h>
2730
#include <inttypes.h>
2831
#include <string.h>
32+
#include <stddef.h>
33+
#include <assert.h>
34+
#include <stdbool.h>
35+
36+
#include <unistd.h>
2937
#include <sys/types.h>
3038
#include <sys/stat.h>
3139
#include <sys/wait.h>
3240
#include <sys/select.h>
3341
#include <sys/socket.h>
42+
#include <sys/un.h>
3443
#include <fcntl.h>
3544
#include <libvchan.h>
36-
#include <assert.h>
37-
#include <limits.h>
3845

3946
#include "qrexec.h"
4047
#include "libqrexec-utils.h"
@@ -146,14 +153,20 @@ static int handle_just_exec(char *cmdline)
146153
{
147154
int fdn, pid;
148155

156+
char *end_username = strchr(cmdline, ':');
157+
if (!end_username) {
158+
fprintf(stderr, "No colon in command from dom0\n");
159+
return -1;
160+
}
161+
*end_username++ = '\0';
149162
switch (pid = fork()) {
150163
case -1:
151164
perror("fork");
152165
return -1;
153166
case 0:
154167
fdn = open("/dev/null", O_RDWR);
155168
fix_fds(fdn, fdn, fdn);
156-
do_exec(cmdline);
169+
do_exec(end_username, cmdline);
157170
default:;
158171
}
159172
fprintf(stderr, "executed (nowait) %s pid %d\n", cmdline, pid);
@@ -354,7 +367,7 @@ static int process_child_io(libvchan_t *data_vchan,
354367
fd_set rdset, wrset;
355368
int vchan_fd;
356369
sigset_t selectmask;
357-
int child_process_status = child_process_pid ? -1 : 0;
370+
int child_process_status = child_process_pid > 0 ? -1 : 0;
358371
int remote_process_status = -1;
359372
int ret, max_fd;
360373
struct timespec zero_timeout = { 0, 0 };
@@ -378,7 +391,7 @@ static int process_child_io(libvchan_t *data_vchan,
378391
while (1) {
379392
if (child_exited) {
380393
int status;
381-
if (child_process_pid &&
394+
if (child_process_pid > 0 &&
382395
waitpid(child_process_pid, &status, WNOHANG) > 0) {
383396
if (WIFSIGNALED(status))
384397
child_process_status = 128 + WTERMSIG(status);
@@ -590,11 +603,6 @@ static int handle_new_process_common(int type, int connect_domain, int connect_p
590603
pid_t pid;
591604
int data_protocol_version;
592605

593-
if (type != MSG_SERVICE_CONNECT) {
594-
assert(cmdline != NULL);
595-
cmdline[cmdline_len-1] = 0;
596-
}
597-
598606
if (buffer_size == 0)
599607
buffer_size = VCHAN_BUFFER_SIZE;
600608

@@ -604,6 +612,18 @@ static int handle_new_process_common(int type, int connect_domain, int connect_p
604612
if (data_vchan)
605613
libvchan_wait(data_vchan);
606614
} else {
615+
if (cmdline == NULL) {
616+
fputs("internal qrexec error: NULL cmdline passed to a non-MSG_SERVICE_CONNECT call\n", stderr);
617+
abort();
618+
} else if (cmdline_len == 0) {
619+
fputs("internal qrexec error: zero-length command line passed to a non-MSG_SERVICE_CONNECT call\n", stderr);
620+
abort();
621+
} else if (cmdline_len > MAX_QREXEC_CMD_LEN) {
622+
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
623+
fprintf(stderr, "Bad command from dom0: command line too long: length %zu\n", cmdline_len);
624+
abort();
625+
}
626+
cmdline[cmdline_len-1] = 0;
607627
data_vchan = libvchan_client_init(connect_domain, connect_port);
608628
}
609629
if (!data_vchan) {
@@ -620,7 +640,8 @@ static int handle_new_process_common(int type, int connect_domain, int connect_p
620640
send_exit_code(data_vchan, handle_just_exec(cmdline));
621641
break;
622642
case MSG_EXEC_CMDLINE:
623-
do_fork_exec(cmdline, &pid, &stdin_fd, &stdout_fd, &stderr_fd);
643+
if (execute_qubes_rpc_command(cmdline, &pid, &stdin_fd, &stdout_fd, &stderr_fd, !qrexec_is_fork_server) < 0)
644+
fputs("failed to spawn process\n", stderr);
624645
fprintf(stderr, "executed %s pid %d\n", cmdline, pid);
625646
child_process_pid = pid;
626647
exit_code = process_child_io(
@@ -664,8 +685,6 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port,
664685
-1, -1, -1, 0);
665686

666687
exit(exit_code);
667-
/* suppress warning */
668-
return 0;
669688
}
670689

671690
/* Returns exit code of remote process */
@@ -680,3 +699,10 @@ int handle_data_client(int type, int connect_domain, int connect_port,
680699
NULL, 0, stdin_fd, stdout_fd, stderr_fd, buffer_size);
681700
return exit_code;
682701
}
702+
703+
/* Local Variables: */
704+
/* mode: c */
705+
/* indent-tabs-mode: nil */
706+
/* c-basic-offset: 4 */
707+
/* coding: utf-8-unix */
708+
/* End: */

agent/qrexec-agent.c

+25-40
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@
2626
#include <sys/un.h>
2727
#include <stdio.h>
2828
#include <stdlib.h>
29+
#include <stdbool.h>
2930
#include <signal.h>
3031
#include <unistd.h>
32+
#include <stddef.h>
3133
#include <errno.h>
3234
#include <err.h>
3335
#include <sys/wait.h>
@@ -78,12 +80,7 @@ static int meminfo_write_started = 0;
7880

7981
static void handle_server_exec_request_do(int type, int connect_domain, int connect_port, char *cmdline);
8082

81-
static _Noreturn void no_colon_in_cmd(void)
82-
{
83-
fprintf(stderr,
84-
"cmdline is supposed to be in user:command form\n");
85-
exit(1);
86-
}
83+
const bool qrexec_is_fork_server = false;
8784

8885
#ifdef HAVE_PAM
8986
static int pam_conv_callback(int num_msg, const struct pam_message **msg,
@@ -118,7 +115,6 @@ static struct pam_conv conv = {
118115
NULL
119116
};
120117
#endif
121-
122118
/* Start program requested by dom0 in already prepared process
123119
* (stdin/stdout/stderr already set, etc)
124120
* Called in two cases:
@@ -136,9 +132,8 @@ static struct pam_conv conv = {
136132
* If dom0 sends overly long cmd, it will probably crash qrexec-agent (unless
137133
* process can allocate up to 4GB on both stack and heap), sorry.
138134
*/
139-
_Noreturn void do_exec(char *cmd)
135+
_Noreturn void do_exec(char *cmd, const char *user)
140136
{
141-
char *realcmd = strchr(cmd, ':'), *user;
142137
#ifdef HAVE_PAM
143138
int retval, status;
144139
pam_handle_t *pamh=NULL;
@@ -151,14 +146,9 @@ _Noreturn void do_exec(char *cmd)
151146
char *shell_basename;
152147
#endif
153148

154-
if (!realcmd)
155-
no_colon_in_cmd();
156-
/* mark end of username and move to command */
157-
user=strndup(cmd,(size_t)(realcmd-cmd));
158-
realcmd++;
159149
/* ignore "nogui:" prefix in linux agent */
160-
if (strncmp(realcmd, NOGUI_CMD_PREFIX, NOGUI_CMD_PREFIX_LEN) == 0)
161-
realcmd += NOGUI_CMD_PREFIX_LEN;
150+
if (strncmp(cmd, NOGUI_CMD_PREFIX, NOGUI_CMD_PREFIX_LEN) == 0)
151+
cmd += NOGUI_CMD_PREFIX_LEN;
162152

163153
signal(SIGCHLD, SIG_DFL);
164154
signal(SIGPIPE, SIG_DFL);
@@ -258,10 +248,10 @@ _Noreturn void do_exec(char *cmd)
258248
warn("chdir(%s)", pw->pw_dir);
259249

260250
/* call QUBESRPC if requested */
261-
exec_qubes_rpc_if_requested(realcmd, env);
251+
exec_qubes_rpc_if_requested(cmd, env);
262252

263253
/* otherwise exec shell */
264-
execle(pw->pw_shell, arg0, "-c", realcmd, (char*)NULL, env);
254+
execle(pw->pw_shell, arg0, "-c", cmd, (char*)NULL, env);
265255
exit(127);
266256
default:
267257
/* parent */
@@ -296,10 +286,10 @@ _Noreturn void do_exec(char *cmd)
296286
exit(1);
297287
#else
298288
/* call QUBESRPC if requested */
299-
exec_qubes_rpc_if_requested(realcmd, environ);
289+
exec_qubes_rpc_if_requested(cmd, environ);
300290

301291
/* otherwise exec shell */
302-
execl("/bin/su", "su", "-", user, "-c", realcmd, NULL);
292+
execl("/bin/su", "su", "-", user, "-c", cmd, NULL);
303293
perror("execl");
304294
exit(1);
305295
#endif
@@ -380,7 +370,7 @@ static void wake_meminfo_writer(void)
380370
/* wake meminfo-writer only once */
381371
return;
382372

383-
f = fopen(MEMINFO_WRITER_PIDFILE, "r");
373+
f = fopen(MEMINFO_WRITER_PIDFILE, "re");
384374
if (f == NULL) {
385375
/* no meminfo-writer found, ignoring */
386376
return;
@@ -407,10 +397,10 @@ static int try_fork_server(int type, int connect_domain, int connect_port,
407397
char *cmdline, size_t cmdline_len) {
408398
char *colon;
409399
char *fork_server_socket_path;
410-
int s;
400+
int s = -1;
411401
struct sockaddr_un remote;
412402
struct qrexec_cmd_info info;
413-
if (cmdline_len > (1ULL << 20))
403+
if (cmdline_len > MAX_QREXEC_CMD_LEN)
414404
return -1;
415405
char *username = malloc(cmdline_len);
416406
if (!username) {
@@ -420,16 +410,14 @@ static int try_fork_server(int type, int connect_domain, int connect_port,
420410
memcpy(username, cmdline, cmdline_len);
421411
colon = strchr(username, ':');
422412
if (!colon)
423-
return -1;
413+
goto fail;
424414
*colon = '\0';
425415

426416
if (asprintf(&fork_server_socket_path, QREXEC_FORK_SERVER_SOCKET, username) < 0) {
427417
fprintf(stderr, "Memory allocation failed\n");
428-
free(username);
429-
return -1;
418+
goto fail;
430419
}
431420

432-
_Static_assert(sizeof(remote.sun_path) > 64, "bad size of sun_path");
433421
remote.sun_path[sizeof(remote.sun_path) - 1] = '\0';
434422
remote.sun_family = AF_UNIX;
435423
strncpy(remote.sun_path, fork_server_socket_path,
@@ -438,42 +426,39 @@ static int try_fork_server(int type, int connect_domain, int connect_port,
438426

439427
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
440428
perror("socket");
441-
free(username);
442-
return -1;
429+
goto fail;
443430
}
444431
size_t len = strlen(remote.sun_path) + sizeof(remote.sun_family);
445432
if (connect(s, (struct sockaddr *) &remote, (socklen_t)len) == -1) {
446433
if (errno != ECONNREFUSED && errno != ENOENT)
447434
perror("connect");
448-
close(s);
449-
free(username);
450-
return -1;
435+
goto fail;
451436
}
452437

453438
memset(&info, 0, sizeof info);
454439
info.type = type;
455440
info.connect_domain = connect_domain;
456441
info.connect_port = connect_port;
457442
size_t username_len = strlen(username);
458-
assert(username_len < SIZE_MAX);
459443
assert(cmdline_len <= INT_MAX);
460444
assert(cmdline_len > username_len);
461445
info.cmdline_len = (int)(cmdline_len - (username_len + 1));
462446
if (!write_all(s, &info, sizeof(info))) {
463447
perror("write");
464-
close(s);
465-
free(username);
466-
return -1;
448+
goto fail;
467449
}
468-
free(username);
469-
username = NULL;
470450
if (!write_all(s, colon+1, info.cmdline_len)) {
471451
perror("write");
472-
close(s);
473-
return -1;
452+
goto fail;
474453
}
475454

455+
free(username);
476456
return s;
457+
fail:
458+
if (s >= 0)
459+
close(s);
460+
free(username);
461+
return -1;
477462
}
478463

479464

agent/qrexec-agent.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,19 @@
2929
// support only very small configuration files,
3030
#define MAX_CONFIG_SIZE 4096
3131

32+
#include <stdbool.h>
3233
int handle_handshake(libvchan_t *ctrl);
3334
void handle_vchan_error(const char *op);
34-
_Noreturn void do_exec(char *cmd);
35+
_Noreturn void do_exec(char *cmd, const char *user);
3536
/* call before fork() for service handling process (either end) */
3637
void prepare_child_env(void);
3738

3839
// whether qrexec-client should replace problematic bytes with _ before printing the output
3940
extern int replace_chars_stdout;
4041
extern int replace_chars_stderr;
42+
#include <stdbool.h>
43+
/* true in qrexec-fork-server, false in qrexec-agent */
44+
extern const bool qrexec_is_fork_server;
4145

4246
pid_t handle_new_process(int type,
4347
int connect_domain, int connect_port,

agent/qrexec-client-vm.c

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,17 @@
3131
#include "qrexec.h"
3232
#include "qrexec-agent.h"
3333

34+
const bool qrexec_is_fork_server = false;
35+
3436
_Noreturn void handle_vchan_error(const char *op)
3537
{
3638
fprintf(stderr, "Error while vchan %s, exiting\n", op);
3739
exit(1);
3840
}
3941

40-
void do_exec(char *const cmd __attribute__((__unused__))) {
42+
_Noreturn void do_exec(char *cmd __attribute__((unused)), char const* user __attribute__((__unused__))) {
4143
fprintf(stderr, "BUG: do_exec function shouldn't be called!\n");
42-
exit(1);
44+
abort();
4345
}
4446

4547
static int connect_unix_socket(char *path)

agent/qrexec-fork-server.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@
3434
#include "qrexec-agent.h"
3535

3636
extern char **environ;
37+
const bool qrexec_is_fork_server = true;
3738

38-
void do_exec(char *cmd)
39+
void do_exec(char *cmd, const char *user __attribute__((unused)))
3940
{
4041
char *shell;
4142

0 commit comments

Comments
 (0)