Skip to content

Commit 163597e

Browse files
committed
Report correct statuses for service execution failure
"Service not found" must be reported with status 127, and "service cannot be executed" must be reported with status 125. However, the code had lots of bugs and would often report wrong status codes in these error conditions. This fixes all of the problems I could find, and adds tests to ensure that many cases stay fixed. This breaks the libqrexec ABI, but thankfully the ABI that was broken is not in any release yet.
1 parent a91949a commit 163597e

13 files changed

+160
-125
lines changed

agent/qrexec-agent-data.c

+18-12
Original file line numberDiff line numberDiff line change
@@ -141,21 +141,24 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd)
141141
int fdn, pid;
142142

143143
if (cmd == NULL)
144-
return 127;
144+
return QREXEC_EXIT_PROBLEM;
145145

146146
if (cmd->service_descriptor) {
147147
int socket_fd;
148148
struct buffer stdin_buffer;
149149
buffer_init(&stdin_buffer);
150-
if (!find_qrexec_service(cmd, &socket_fd, &stdin_buffer))
151-
return 127;
150+
int status = find_qrexec_service(cmd, &socket_fd, &stdin_buffer);
151+
if (status == -1)
152+
return QREXEC_EXIT_SERVICE_NOT_FOUND;
153+
if (status != 0)
154+
return QREXEC_EXIT_PROBLEM;
152155
if (socket_fd != -1)
153-
return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : 127;
156+
return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : QREXEC_EXIT_PROBLEM;
154157
}
155158
switch (pid = fork()) {
156159
case -1:
157160
PERROR("fork");
158-
return 127;
161+
return QREXEC_EXIT_PROBLEM;
159162
case 0:
160163
fdn = open("/dev/null", O_RDWR);
161164
fix_fds(fdn, fdn, fdn);
@@ -226,23 +229,26 @@ static int handle_new_process_common(
226229
libvchan_close(data_vchan);
227230
return 0;
228231
case MSG_EXEC_CMDLINE:
232+
exit_code = QREXEC_EXIT_PROBLEM;
229233
buffer_init(&stdin_buf);
230-
if (cmd == NULL ||
231-
execute_parsed_qubes_rpc_command(cmd, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &stdin_buf) < 0) {
232-
struct msg_header hdr = {
233-
.type = MSG_DATA_STDOUT,
234-
.len = 0,
235-
};
234+
if (cmd != NULL) {
235+
int status = execute_parsed_qubes_rpc_command(cmd, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &stdin_buf);
236+
if (status == -1)
237+
exit_code = QREXEC_EXIT_SERVICE_NOT_FOUND;
238+
else if (status == 0)
239+
exit_code = 0;
240+
}
241+
if (exit_code != 0) {
236242
LOG(ERROR, "failed to spawn process");
237243
/* Send stdout+stderr EOF first, since the service is expected to send
238244
* one before exit code in case of MSG_EXEC_CMDLINE. Ignore
239245
* libvchan_send error if any, as we're going to terminate soon
240246
* anyway.
241247
*/
248+
struct msg_header hdr = { .type = MSG_DATA_STDOUT, .len = 0 };
242249
libvchan_send(data_vchan, &hdr, sizeof(hdr));
243250
hdr.type = MSG_DATA_STDERR;
244251
libvchan_send(data_vchan, &hdr, sizeof(hdr));
245-
exit_code = 127;
246252
send_exit_code(data_vchan, exit_code);
247253
libvchan_close(data_vchan);
248254
return exit_code;

agent/qrexec-agent.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ _Noreturn void do_exec(const char *cmd, const char *user)
272272
/* child */
273273

274274
if (setgid (pw->pw_gid))
275-
exit(126);
275+
_exit(QREXEC_EXIT_PROBLEM);
276276
if (setuid (pw->pw_uid))
277-
exit(126);
277+
_exit(QREXEC_EXIT_PROBLEM);
278278
setsid();
279279
/* This is a copy but don't care to free as we exec later anyway. */
280280
env = pam_getenvlist (pamh);
@@ -289,7 +289,7 @@ _Noreturn void do_exec(const char *cmd, const char *user)
289289

290290
/* otherwise exec shell */
291291
execle(pw->pw_shell, arg0, "-c", cmd, (char*)NULL, env);
292-
exit(127);
292+
_exit(QREXEC_EXIT_PROBLEM);
293293
default:
294294
/* parent */
295295
/* close std*, so when child process closes them, qrexec-agent will receive EOF */

agent/qrexec-client-vm.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ int main(int argc, char **argv)
249249
ret = read(trigger_fd, &exec_params, sizeof(exec_params));
250250
if (ret == 0) {
251251
fprintf(stderr, "Request refused\n");
252-
exit(126);
252+
exit(QREXEC_EXIT_REQUEST_REFUSED);
253253
}
254254
if (ret < 0 || ret != sizeof(exec_params)) {
255255
PERROR("read");

agent/qrexec-client-vm.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ way to learn exit code of remote service in this case.
7676
In both cases, if process (local or remote) was terminated by a signal, exit
7777
status is 128+signal number.
7878

79-
If service call is denied by dom0, ``qrexec-client-vm`` exit with status 126.
79+
If service call is denied by dom0, ``qrexec-client-vm`` exits with status 126.
80+
If invoking the service fails for some other reason, such as resource exhaustion
81+
or a system configuration problem, ``qrexec-client-vm`` exits with status 125.
8082

8183
AUTHORS
8284
=======

daemon/qrexec-client.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ int main(int argc, char **argv)
175175
bool replace_chars_stdout = false;
176176
bool replace_chars_stderr = false;
177177
bool exit_with_code = true;
178-
int rc = 126;
178+
int rc = QREXEC_EXIT_PROBLEM;
179179

180180
if (argc < 3) {
181181
// certainly too few arguments
@@ -301,7 +301,7 @@ int main(int argc, char **argv)
301301
struct qrexec_parsed_command *command =
302302
parse_qubes_rpc_command(local_cmdline, false);
303303
if (!command)
304-
prepare_ret = 127;
304+
prepare_ret = QREXEC_EXIT_PROBLEM;
305305
else {
306306
prepare_ret = prepare_local_fds(command, &stdin_buffer);
307307
destroy_qrexec_parsed_command(command);

daemon/qrexec-daemon-common.c

+11-13
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,14 @@ int prepare_local_fds(struct qrexec_parsed_command *command, struct buffer *stdi
277277
};
278278
sigemptyset(&action.sa_mask);
279279
if (sigaction(SIGCHLD, &action, NULL))
280-
return 126;
280+
return QREXEC_EXIT_PROBLEM;
281281
return execute_parsed_qubes_rpc_command(command, &local_pid, &local_stdin_fd, &local_stdout_fd,
282282
NULL, stdin_buffer);
283283
}
284284

285285
// See also qrexec-agent/qrexec-agent-data.c
286-
__attribute__((warn_unused_result))
287-
static int handle_failed_exec(libvchan_t *data_vchan, bool is_service)
286+
static void handle_failed_exec(libvchan_t *data_vchan, bool is_service, int exit_code)
288287
{
289-
int exit_code = 127;
290288
struct msg_header hdr = {
291289
.type = MSG_DATA_STDOUT,
292290
.len = 0,
@@ -308,7 +306,6 @@ static int handle_failed_exec(libvchan_t *data_vchan, bool is_service)
308306
libvchan_send(data_vchan, &hdr, sizeof(hdr));
309307
send_exit_code(data_vchan, exit_code);
310308
}
311-
return exit_code;
312309
}
313310

314311
static int select_loop(struct handshake_params *params)
@@ -351,25 +348,25 @@ int run_qrexec_to_dom0(const struct service_params *svc_params,
351348
set_remote_domain(src_domain_name);
352349
s = connect_unix_socket_by_id(src_domain_id);
353350
if (s < 0)
354-
return 126;
351+
return QREXEC_EXIT_PROBLEM;
355352
if (!negotiate_connection_params(s,
356353
0, /* dom0 */
357354
MSG_SERVICE_CONNECT,
358355
svc_params,
359356
sizeof(*svc_params),
360357
&data_domain,
361358
&data_port))
362-
return 126;
359+
return QREXEC_EXIT_PROBLEM;
363360

364361
struct buffer stdin_buffer;
365362
buffer_init(&stdin_buffer);
366363
struct qrexec_parsed_command *command =
367364
parse_qubes_rpc_command(remote_cmdline, false);
368365
if (command == NULL) {
369-
prepare_ret = -1;
366+
prepare_ret = -2;
370367
} else if (!wait_for_session_maybe(command)) {
371368
LOG(ERROR, "Cannot load service configuration, or forking process failed");
372-
prepare_ret = -1;
369+
prepare_ret = -2;
373370
} else {
374371
prepare_ret = prepare_local_fds(command, &stdin_buffer);
375372
}
@@ -401,15 +398,16 @@ int handshake_and_go(struct handshake_params *params)
401398
{
402399
if (params->data_vchan == NULL || !libvchan_is_open(params->data_vchan)) {
403400
LOG(ERROR, "Failed to open data vchan connection");
404-
return 126;
401+
return QREXEC_EXIT_PROBLEM;
405402
}
406403
int rc;
407404
int data_protocol_version = handle_agent_handshake(params->data_vchan,
408405
params->remote_send_first);
409406
if (data_protocol_version < 0) {
410-
rc = 126;
411-
} else if (params->prepare_ret < 0) {
412-
rc = handle_failed_exec(params->data_vchan, params->remote_send_first);
407+
rc = QREXEC_EXIT_PROBLEM;
408+
} else if (params->prepare_ret != 0) {
409+
rc = params->prepare_ret == -1 ? QREXEC_EXIT_SERVICE_NOT_FOUND : QREXEC_EXIT_PROBLEM;
410+
handle_failed_exec(params->data_vchan, params->remote_send_first, rc);
413411
} else {
414412
params->data_protocol_version = data_protocol_version;
415413
rc = select_loop(params);

daemon/qrexec-daemon.c

+22-22
Original file line numberDiff line numberDiff line change
@@ -924,7 +924,7 @@ static void send_request_to_daemon(
924924
service_name);
925925
if (command_size < 0) {
926926
PERROR("failed to construct request");
927-
daemon__exit(126);
927+
daemon__exit(QREXEC_EXIT_PROBLEM);
928928
}
929929

930930
for (int i = 0; i < command_size; i += bytes_sent) {
@@ -934,7 +934,7 @@ static void send_request_to_daemon(
934934
if (bytes_sent < 0) {
935935
assert(bytes_sent == -1);
936936
PERROR("send to socket failed");
937-
daemon__exit(126);
937+
daemon__exit(QREXEC_EXIT_PROBLEM);
938938
}
939939
}
940940
free(command);
@@ -945,7 +945,7 @@ static _Noreturn void null_exit(void)
945945
#ifdef COVERAGE
946946
__gcov_dump();
947947
#endif
948-
_exit(126);
948+
_exit(QREXEC_EXIT_PROBLEM);
949949
}
950950

951951
/*
@@ -970,7 +970,7 @@ static enum policy_response connect_daemon_socket(
970970
int daemon_socket = socket(AF_UNIX, SOCK_STREAM, 0);
971971
if (daemon_socket < 0) {
972972
PERROR("socket creation failed");
973-
daemon__exit(126);
973+
daemon__exit(QREXEC_EXIT_PROBLEM);
974974
}
975975

976976
int connect_result = connect(daemon_socket,
@@ -1001,21 +1001,21 @@ static enum policy_response connect_daemon_socket(
10011001
int fds[2];
10021002
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds)) {
10031003
PERROR("socketpair()");
1004-
daemon__exit(126);
1004+
daemon__exit(QREXEC_EXIT_PROBLEM);
10051005
}
10061006
daemon_socket = fds[0];
10071007

10081008
pid = fork();
10091009
switch (pid) {
10101010
case -1:
10111011
LOG(ERROR, "Could not fork!");
1012-
daemon__exit(126);
1012+
daemon__exit(QREXEC_EXIT_PROBLEM);
10131013
case 0:
10141014
if (close(fds[0]))
1015-
_exit(126);
1015+
daemon__exit(QREXEC_EXIT_PROBLEM);
10161016
if (dup2(fds[1], 0) != 0 || dup2(fds[1], 1) != 1) {
10171017
PERROR("dup2()");
1018-
daemon__exit(126);
1018+
daemon__exit(QREXEC_EXIT_PROBLEM);
10191019
}
10201020
if (close(fds[1]))
10211021
abort();
@@ -1034,7 +1034,7 @@ static enum policy_response connect_daemon_socket(
10341034
} else {
10351035
PERROR("snprintf");
10361036
}
1037-
daemon__exit(126);
1037+
daemon__exit(QREXEC_EXIT_PROBLEM);
10381038
default:
10391039
if (close(fds[1]))
10401040
abort();
@@ -1045,12 +1045,12 @@ static enum policy_response connect_daemon_socket(
10451045
do {
10461046
if (waitpid(pid, &status, 0) != pid) {
10471047
PERROR("waitpid");
1048-
daemon__exit(126);
1048+
daemon__exit(QREXEC_EXIT_PROBLEM);
10491049
}
10501050
} while (!WIFEXITED(status));
10511051
if (WEXITSTATUS(status) != 0) {
10521052
LOG(ERROR, "qrexec-policy-exec failed");
1053-
daemon__exit(126);
1053+
daemon__exit(QREXEC_EXIT_PROBLEM);
10541054
}
10551055
// This leaks 'result', but as the code execs later anyway this isn't a problem.
10561056
// 'result' cannot be freed as 'user', 'target', and 'requested_target' point into
@@ -1077,7 +1077,7 @@ static _Noreturn void do_exec(const char *prog, const char *username __attribute
10771077
/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
10781078
execl("/bin/bash", "bash", "-c", prog, NULL);
10791079
PERROR("exec bash");
1080-
_exit(126);
1080+
_exit(QREXEC_EXIT_PROBLEM);
10811081
}
10821082

10831083
_Noreturn static void handle_execute_service_child(
@@ -1098,7 +1098,7 @@ _Noreturn static void handle_execute_service_child(
10981098
&user, &target, &requested_target, &autostart);
10991099

11001100
if (policy_response != RESPONSE_ALLOW)
1101-
daemon__exit(126);
1101+
daemon__exit(QREXEC_EXIT_REQUEST_REFUSED);
11021102

11031103
/* Replace the target domain with the version normalized by the policy engine */
11041104
target_domain = requested_target;
@@ -1130,7 +1130,7 @@ _Noreturn static void handle_execute_service_child(
11301130
remote_domain_name,
11311131
type,
11321132
target_domain) <= 0)
1133-
daemon__exit(126);
1133+
daemon__exit(QREXEC_EXIT_PROBLEM);
11341134
register_exec_func(&do_exec);
11351135
daemon__exit(run_qrexec_to_dom0(request_id,
11361136
remote_domain_id,
@@ -1144,7 +1144,7 @@ _Noreturn static void handle_execute_service_child(
11441144
disposable = true;
11451145
buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len);
11461146
if (!buf) // error already printed by qubesd_call
1147-
daemon__exit(126);
1147+
daemon__exit(QREXEC_EXIT_PROBLEM);
11481148
if (memcmp(buf, "0", 2) == 0) {
11491149
/* we exec later so memory leaks do not matter */
11501150
target = buf + 2;
@@ -1154,42 +1154,42 @@ _Noreturn static void handle_execute_service_child(
11541154
} else {
11551155
LOG(ERROR, "invalid response to admin.vm.CreateDisposable");
11561156
}
1157-
daemon__exit(126);
1157+
daemon__exit(QREXEC_EXIT_PROBLEM);
11581158
}
11591159
}
11601160
if (asprintf(&cmd, "%s:QUBESRPC %s%s %s",
11611161
user,
11621162
service_name,
11631163
trailer,
11641164
remote_domain_name) <= 0)
1165-
daemon__exit(126);
1165+
daemon__exit(QREXEC_EXIT_PROBLEM);
11661166
if (autostart) {
11671167
buf = qubesd_call(target, "admin.vm.Start", "", &resp_len);
11681168
if (!buf) // error already printed by qubesd_call
1169-
daemon__exit(126);
1169+
daemon__exit(QREXEC_EXIT_PROBLEM);
11701170
if (!((memcmp(buf, "0", 2) == 0) ||
11711171
(resp_len >= 24 && memcmp(buf, "2\0QubesVMNotHaltedError", 24) == 0))) {
11721172
if (memcmp(buf, "2", 2) == 0) {
11731173
LOG(ERROR, "qubesd could not start VM %s: %s", target, buf + 2);
11741174
} else {
11751175
LOG(ERROR, "invalid response to admin.vm.Start");
11761176
}
1177-
daemon__exit(126);
1177+
daemon__exit(QREXEC_EXIT_PROBLEM);
11781178
}
11791179
free(buf);
11801180
}
11811181
int s = connect_unix_socket(target);
11821182
int data_domain;
11831183
int data_port;
1184-
int rc = 126;
1184+
int rc = QREXEC_EXIT_PROBLEM;
11851185
if (!negotiate_connection_params(s,
11861186
remote_domain_id,
11871187
MSG_EXEC_CMDLINE,
11881188
cmd,
11891189
compute_service_length(cmd),
11901190
&data_domain,
11911191
&data_port))
1192-
daemon__exit(126);
1192+
daemon__exit(QREXEC_EXIT_PROBLEM);
11931193
int wait_connection_end = -1;
11941194
if (disposable) {
11951195
wait_connection_end = s;
@@ -1237,7 +1237,7 @@ static void handle_execute_service(
12371237
exit(1);
12381238
case 0:
12391239
if (atexit(null_exit))
1240-
_exit(126);
1240+
_exit(QREXEC_EXIT_PROBLEM);
12411241
handle_execute_service_child(remote_domain_id, remote_domain_name,
12421242
target_domain, service_name, request_id);
12431243
abort();

0 commit comments

Comments
 (0)