/src/qubes-os/qubes-core-qrexec/daemon/qrexec-daemon.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The Qubes OS Project, http://www.qubes-os.org |
3 | | * |
4 | | * Copyright (C) 2010 Rafal Wojtczuk <rafal@invisiblethingslab.com> |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU General Public License |
8 | | * as published by the Free Software Foundation; either version 2 |
9 | | * of the License, or (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU General Public License |
17 | | * along with this program; if not, write to the Free Software |
18 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | | * |
20 | | */ |
21 | | |
22 | | #include <inttypes.h> |
23 | | #include <stdarg.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include <sys/syscall.h> |
27 | | #include <unistd.h> |
28 | | #include <signal.h> |
29 | | #include <errno.h> |
30 | | #include <fcntl.h> |
31 | | #include <sys/stat.h> |
32 | | #include <sys/wait.h> |
33 | | #include <sys/socket.h> |
34 | | #include <sys/un.h> |
35 | | #include <err.h> |
36 | | #include <string.h> |
37 | | #include <assert.h> |
38 | | #include <getopt.h> |
39 | | #include "qrexec.h" |
40 | | #include "libqrexec-utils.h" |
41 | | #include "../libqrexec/ioall.h" |
42 | | #include "qrexec-daemon-common.h" |
43 | | |
44 | 0 | #define QREXEC_MIN_VERSION QREXEC_PROTOCOL_V2 |
45 | 0 | #define QREXEC_SOCKET_PATH "/run/qubes/policy.sock" |
46 | | |
47 | | #ifdef COVERAGE |
48 | | void __gcov_dump(void); |
49 | | void __gcov_reset(void); |
50 | | #endif |
51 | 0 | static _Noreturn void daemon__exit(int status) { |
52 | | #ifdef COVERAGE |
53 | | __gcov_dump(); |
54 | | #endif |
55 | 0 | _exit(status); |
56 | 0 | } |
57 | | |
58 | | enum client_state { |
59 | | CLIENT_INVALID = 0, // table slot not used |
60 | | CLIENT_HELLO, // waiting for client hello |
61 | | CLIENT_CMDLINE, // waiting for cmdline from client |
62 | | CLIENT_RUNNING // waiting for client termination (to release vchan port) |
63 | | }; |
64 | | |
65 | | enum vchan_port_state { |
66 | | VCHAN_PORT_UNUSED = -1 |
67 | | }; |
68 | | |
69 | | struct _client { |
70 | | int state; // enum client_state |
71 | | }; |
72 | | |
73 | | enum policy_response { |
74 | | RESPONSE_PENDING, |
75 | | RESPONSE_ALLOW, |
76 | | RESPONSE_DENY, |
77 | | RESPONSE_MALFORMED, |
78 | | }; |
79 | | |
80 | | struct _policy_pending { |
81 | | pid_t pid; |
82 | | struct service_params params; |
83 | | enum policy_response response_sent; |
84 | | }; |
85 | | |
86 | 332 | #define VCHAN_BASE_DATA_PORT (VCHAN_BASE_PORT+1) |
87 | | |
88 | | /* |
89 | | The "clients" array is indexed by client's fd. |
90 | | Thus its size must be equal MAX_FDS; defining MAX_CLIENTS for clarity. |
91 | | */ |
92 | | |
93 | 10.3k | #define MAX_CLIENTS MAX_FDS |
94 | | static struct _client clients[MAX_CLIENTS]; // data on all qrexec_client connections |
95 | | |
96 | | static struct _policy_pending policy_pending[MAX_CLIENTS]; |
97 | | static int policy_pending_max = -1; |
98 | | |
99 | | /* indexed with vchan port number relative to VCHAN_BASE_DATA_PORT; stores |
100 | | * either VCHAN_PORT_* or remote domain id for used port */ |
101 | | static int used_vchan_ports[MAX_CLIENTS]; |
102 | | |
103 | | /* notify client (close its connection) when connection initiated by it was |
104 | | * terminated - used by qrexec-policy to cleanup (disposable) VM; indexed with |
105 | | * vchan port number relative to VCHAN_BASE_DATA_PORT; stores fd of given |
106 | | * client or -1 if none requested */ |
107 | | static int vchan_port_notify_client[MAX_CLIENTS]; |
108 | | |
109 | | static int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table |
110 | | static int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor |
111 | | static const char *default_user = "user"; |
112 | | static const char default_user_keyword[] = "DEFAULT:"; |
113 | 0 | #define default_user_keyword_len_without_colon (sizeof(default_user_keyword)-2) |
114 | | |
115 | | static int opt_quiet = 0; |
116 | | |
117 | | static const char *policy_program = QREXEC_POLICY_PROGRAM; |
118 | | |
119 | | #ifdef __GNUC__ |
120 | | # define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) |
121 | | #else |
122 | | # define UNUSED(x) UNUSED_ ## x |
123 | | #endif |
124 | | |
125 | | static volatile int child_exited; |
126 | | static volatile int terminate_requested; |
127 | | |
128 | | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
129 | | #include "../fuzz/fuzz.h" |
130 | | #else |
131 | | static |
132 | | #endif |
133 | | libvchan_t *vchan; |
134 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
135 | | static |
136 | | #endif |
137 | | int protocol_version; |
138 | | |
139 | | static const char *remote_domain_name; // guess what |
140 | | static const char *remote_domain_uuid; |
141 | | static int remote_domain_id; |
142 | | |
143 | | static void unlink_or_exit(const char *path) |
144 | 0 | { |
145 | 0 | int v = unlink(path); |
146 | 0 | if (v != 0 && !(v == -1 && errno == ENOENT)) |
147 | 0 | err(1, "unlink(%s)", path); |
148 | 0 | } |
149 | | |
150 | | static char __attribute__((format(printf, 1, 2))) *xasprintf(const char *fmt, ...) |
151 | 0 | { |
152 | 0 | va_list x; |
153 | 0 | char *res; |
154 | 0 | va_start(x, fmt); |
155 | 0 | int r = vasprintf(&res, fmt, x); |
156 | 0 | va_end(x); |
157 | 0 | if (r < 0) |
158 | 0 | abort(); |
159 | 0 | return res; |
160 | 0 | } |
161 | | |
162 | | static void unlink_qrexec_socket(void) |
163 | 0 | { |
164 | 0 | char *socket_name; |
165 | 0 | const char *p[2] = {remote_domain_name, remote_domain_uuid}; |
166 | 0 | int i; |
167 | |
|
168 | 0 | for (i = 0; i < 2; ++i) { |
169 | 0 | char *link_to_socket_name = xasprintf("qrexec.%s%s", i > 0 ? "uuid:" : "", p[i]); |
170 | 0 | unlink_or_exit(link_to_socket_name); |
171 | 0 | free(link_to_socket_name); |
172 | 0 | } |
173 | 0 | if (asprintf(&socket_name, "qrexec.%d", remote_domain_id) < 0) |
174 | 0 | abort(); |
175 | 0 | unlink_or_exit(socket_name); |
176 | 0 | free(socket_name); |
177 | 0 | } |
178 | | |
179 | | static void handle_vchan_error(const char *op) |
180 | 67 | { |
181 | 67 | LOG(ERROR, "Error while vchan %s, exiting", op); |
182 | 67 | exit(1); |
183 | 67 | } |
184 | | |
185 | | static int create_qrexec_socket(int domid, const char *domname, const char *domuuid) |
186 | 0 | { |
187 | | /* When running as root, make the socket accessible; perms on /var/run/qubes still apply */ |
188 | 0 | umask(0); |
189 | |
|
190 | 0 | const char *p[2] = { domuuid, domname }; |
191 | 0 | char *socket_address = xasprintf("qrexec.%d", domid); |
192 | 0 | for (int i = 0; i < 2; ++i) { |
193 | 0 | if (p[i] == NULL) |
194 | 0 | continue; |
195 | 0 | char *link_to_socket_name = xasprintf("qrexec.%s%s", i ? "" : "uuid:", p[i]); |
196 | 0 | unlink_or_exit(link_to_socket_name); |
197 | 0 | if (symlink(socket_address, link_to_socket_name)) { |
198 | 0 | PERROR("symlink(%s,%s)", socket_address, link_to_socket_name); |
199 | 0 | } |
200 | 0 | free(link_to_socket_name); |
201 | 0 | } |
202 | 0 | int fd = get_server_socket(socket_address); |
203 | 0 | umask(0077); |
204 | 0 | return fd; |
205 | 0 | } |
206 | | |
207 | 0 | #define MAX_STARTUP_TIME_DEFAULT 60 |
208 | | |
209 | | static void incompatible_protocol_error_message( |
210 | | const char *domain_name, int remote_version) |
211 | 0 | { |
212 | 0 | char text[1024]; |
213 | 0 | int ret; |
214 | 0 | struct stat buf; |
215 | 0 | ret=stat("/usr/bin/kdialog", &buf); |
216 | 0 | #define KDIALOG_CMD "kdialog --title 'Qrexec daemon' --sorry " |
217 | 0 | #define ZENITY_CMD "zenity --title 'Qrexec daemon' --warning --text " |
218 | 0 | snprintf(text, sizeof(text), |
219 | 0 | "%s" |
220 | 0 | "'Domain %s uses incompatible qrexec protocol (%d instead of %d). " |
221 | 0 | "You need to update either dom0 or VM packages.\n" |
222 | 0 | "To access this VM console do not close this error message and run:\n" |
223 | 0 | "sudo xl console -t pv %s'", |
224 | 0 | ret==0 ? KDIALOG_CMD : ZENITY_CMD, |
225 | 0 | domain_name, remote_version, QREXEC_PROTOCOL_VERSION, domain_name); |
226 | 0 | #undef KDIALOG_CMD |
227 | 0 | #undef ZENITY_CMD |
228 | | /* silence -Wunused-result */ |
229 | 0 | ret = system(text); |
230 | 0 | } |
231 | | |
232 | | static int handle_agent_hello(libvchan_t *ctrl, const char *domain_name) |
233 | 0 | { |
234 | 0 | struct msg_header hdr; |
235 | 0 | struct peer_info info; |
236 | 0 | int actual_version; |
237 | |
|
238 | 0 | if (libvchan_recv(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
239 | 0 | LOG(ERROR, "Failed to read agent HELLO hdr"); |
240 | 0 | return -1; |
241 | 0 | } |
242 | | |
243 | 0 | if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) { |
244 | 0 | LOG(ERROR, "Invalid HELLO packet received: type %d, len %d", hdr.type, hdr.len); |
245 | 0 | return -1; |
246 | 0 | } |
247 | | |
248 | 0 | if (libvchan_recv(ctrl, &info, sizeof(info)) != sizeof(info)) { |
249 | 0 | LOG(ERROR, "Failed to read agent HELLO body"); |
250 | 0 | return -1; |
251 | 0 | } |
252 | | |
253 | 0 | actual_version = info.version < QREXEC_PROTOCOL_VERSION ? info.version : QREXEC_PROTOCOL_VERSION; |
254 | |
|
255 | 0 | if (actual_version < QREXEC_MIN_VERSION) { |
256 | 0 | LOG(ERROR, "Incompatible agent protocol version (remote %d, local %d)", info.version, QREXEC_PROTOCOL_VERSION); |
257 | 0 | incompatible_protocol_error_message(domain_name, info.version); |
258 | 0 | return -1; |
259 | 0 | } |
260 | | |
261 | | /* send own HELLO */ |
262 | | /* those messages are the same as received from agent, but set it again for |
263 | | * readability */ |
264 | 0 | hdr.type = MSG_HELLO; |
265 | 0 | hdr.len = sizeof(info); |
266 | 0 | info.version = QREXEC_PROTOCOL_VERSION; |
267 | |
|
268 | 0 | if (libvchan_send(ctrl, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
269 | 0 | LOG(ERROR, "Failed to send HELLO hdr to agent"); |
270 | 0 | return -1; |
271 | 0 | } |
272 | | |
273 | 0 | if (libvchan_send(ctrl, &info, sizeof(info)) != sizeof(info)) { |
274 | 0 | LOG(ERROR, "Failed to send HELLO hdr to agent"); |
275 | 0 | return -1; |
276 | 0 | } |
277 | | |
278 | 0 | return actual_version; |
279 | 0 | } |
280 | | |
281 | | static void signal_handler(int sig); |
282 | | |
283 | | /* do the preparatory tasks, needed before entering the main event loop */ |
284 | | static void init(int xid, bool opt_direct) |
285 | 0 | { |
286 | 0 | char qrexec_error_log_name[256]; |
287 | 0 | int logfd; |
288 | 0 | int i; |
289 | 0 | pid_t pid; |
290 | 0 | int startup_timeout = MAX_STARTUP_TIME_DEFAULT; |
291 | 0 | const char *startup_timeout_str = NULL; |
292 | |
|
293 | 0 | if (xid <= 0) { |
294 | 0 | LOG(ERROR, "domain id=0?"); |
295 | 0 | exit(1); |
296 | 0 | } |
297 | 0 | startup_timeout_str = getenv("QREXEC_STARTUP_TIMEOUT"); |
298 | 0 | if (startup_timeout_str) { |
299 | 0 | startup_timeout = atoi(startup_timeout_str); |
300 | 0 | if (startup_timeout <= 0) |
301 | | // invalid or negative number |
302 | 0 | startup_timeout = MAX_STARTUP_TIME_DEFAULT; |
303 | 0 | } |
304 | |
|
305 | 0 | int pipes[2] = { -1, -1 }; |
306 | 0 | bool have_timeout = getenv("QREXEC_STARTUP_NOWAIT") == NULL; |
307 | 0 | if (!opt_direct) { |
308 | 0 | if (have_timeout && pipe2(pipes, O_CLOEXEC)) |
309 | 0 | err(1, "pipe2()"); |
310 | 0 | switch (pid=fork()) { |
311 | 0 | case -1: |
312 | 0 | PERROR("fork"); |
313 | 0 | exit(1); |
314 | 0 | case 0: |
315 | 0 | if (have_timeout) |
316 | 0 | close(pipes[0]); |
317 | 0 | break; |
318 | 0 | default: |
319 | 0 | if (!have_timeout) |
320 | 0 | exit(0); |
321 | 0 | close(pipes[1]); |
322 | 0 | if (!opt_quiet) |
323 | 0 | LOG(ERROR, "Waiting for VM's qrexec agent."); |
324 | 0 | struct pollfd fds[1] = {{ .fd = pipes[0], .events = POLLIN | POLLHUP, .revents = 0 }}; |
325 | 0 | for (;;) { |
326 | 0 | int res = poll(fds, 1, 1000); |
327 | 0 | if (res < 0) |
328 | 0 | err(1, "poll()"); |
329 | 0 | if (res) { |
330 | 0 | char buf[1]; |
331 | 0 | ssize_t bytes = read(pipes[0], buf, sizeof buf); |
332 | 0 | if (bytes < 0) |
333 | 0 | err(1, "read()"); |
334 | 0 | if (bytes == 0) { |
335 | 0 | LOG(ERROR, "Connection to the VM failed"); |
336 | 0 | exit(1); |
337 | 0 | } |
338 | 0 | switch (buf[0]) { |
339 | 0 | case 0: |
340 | 0 | if (!opt_quiet) |
341 | 0 | LOG(INFO, "Connected to VM"); |
342 | 0 | exit(0); |
343 | 0 | case 1: |
344 | 0 | LOG(ERROR, "Cannot connect to '%s' qrexec agent for %d seconds, giving up", remote_domain_name, startup_timeout); |
345 | 0 | exit(3); |
346 | 0 | default: |
347 | 0 | abort(); |
348 | 0 | } |
349 | 0 | } |
350 | 0 | if (!opt_quiet) |
351 | 0 | fprintf(stderr, "."); |
352 | 0 | } |
353 | 0 | } |
354 | 0 | } |
355 | | |
356 | | |
357 | 0 | if (chdir(socket_dir) < 0) { |
358 | 0 | PERROR("chdir %s failed", socket_dir); |
359 | 0 | exit(1); |
360 | 0 | } |
361 | |
|
362 | 0 | if (!opt_direct) { |
363 | 0 | if ((unsigned)snprintf(qrexec_error_log_name, sizeof(qrexec_error_log_name), |
364 | 0 | "/var/log/qubes/qrexec.%s.log", remote_domain_name) >= |
365 | 0 | sizeof(qrexec_error_log_name)) |
366 | 0 | errx(1, "remote domain name too long"); |
367 | 0 | umask(0007); // make the log readable by the "qubes" group |
368 | 0 | logfd = |
369 | 0 | open(qrexec_error_log_name, O_WRONLY | O_CREAT | O_TRUNC, |
370 | 0 | 0660); |
371 | |
|
372 | 0 | if (logfd < 0) { |
373 | 0 | PERROR("open"); |
374 | 0 | exit(1); |
375 | 0 | } |
376 | |
|
377 | 0 | if (dup2(logfd, 1) != 1 || dup2(logfd, 2) != 2) |
378 | 0 | err(1, "dup2()"); |
379 | 0 | if (logfd > 2) |
380 | 0 | close(logfd); |
381 | |
|
382 | 0 | if (setsid() < 0) { |
383 | 0 | PERROR("setsid()"); |
384 | 0 | exit(1); |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | 0 | int wait_fd; |
389 | 0 | if (have_timeout) { |
390 | 0 | vchan = libvchan_client_init_async(xid, VCHAN_BASE_PORT, &wait_fd); |
391 | 0 | if (vchan != NULL && qubes_wait_for_vchan_connection_with_timeout( |
392 | 0 | vchan, wait_fd, false, startup_timeout) < 0) { |
393 | 0 | if (!opt_direct && write(pipes[1], "\1", 1)) {} |
394 | 0 | LOG(ERROR, "qrexec connection timeout"); |
395 | 0 | exit(3); |
396 | 0 | } |
397 | 0 | } else { |
398 | | /* No timeout in this case */ |
399 | 0 | vchan = libvchan_client_init(xid, VCHAN_BASE_PORT); |
400 | 0 | } |
401 | 0 | if (!vchan) { |
402 | 0 | LOG(ERROR, "Cannot create data vchan connection"); |
403 | 0 | exit(3); |
404 | 0 | } |
405 | 0 | protocol_version = handle_agent_hello(vchan, remote_domain_name); |
406 | 0 | if (protocol_version < 0) { |
407 | 0 | exit(1); |
408 | 0 | } |
409 | |
|
410 | 0 | if (setgid(getgid()) < 0) { |
411 | 0 | PERROR("setgid()"); |
412 | 0 | exit(1); |
413 | 0 | } |
414 | 0 | if (setuid(getuid()) < 0) { |
415 | 0 | PERROR("setuid()"); |
416 | 0 | exit(1); |
417 | 0 | } |
418 | | |
419 | | /* initialize clients state arrays */ |
420 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
421 | 0 | clients[i].state = CLIENT_INVALID; |
422 | 0 | policy_pending[i].pid = 0; |
423 | 0 | used_vchan_ports[i] = VCHAN_PORT_UNUSED; |
424 | 0 | vchan_port_notify_client[i] = VCHAN_PORT_UNUSED; |
425 | 0 | } |
426 | |
|
427 | 0 | atexit(unlink_qrexec_socket); |
428 | 0 | qrexec_daemon_unix_socket_fd = |
429 | 0 | create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid); |
430 | |
|
431 | 0 | struct sigaction sigchld_action = { |
432 | 0 | .sa_handler = signal_handler, |
433 | 0 | .sa_flags = SA_NOCLDSTOP, |
434 | 0 | }; |
435 | 0 | struct sigaction sigterm_action = { |
436 | 0 | .sa_handler = signal_handler, |
437 | 0 | .sa_flags = 0, |
438 | 0 | }; |
439 | 0 | sigemptyset(&sigchld_action.sa_mask); |
440 | 0 | sigemptyset(&sigterm_action.sa_mask); |
441 | 0 | if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) |
442 | 0 | err(1, "signal"); |
443 | 0 | if (sigaction(SIGCHLD, &sigchld_action, NULL)) |
444 | 0 | err(1, "sigaction"); |
445 | 0 | if (sigaction(SIGTERM, &sigterm_action, NULL)) |
446 | 0 | err(1, "sigaction"); |
447 | 0 | if (have_timeout && !opt_direct) { |
448 | 0 | if (write(pipes[1], "", 1) != 1) |
449 | 0 | err(1, "write(pipe)"); |
450 | 0 | close(pipes[1]); |
451 | 0 | } |
452 | 0 | } |
453 | | |
454 | | static int send_client_hello(int fd) |
455 | 0 | { |
456 | 0 | struct msg_header hdr; |
457 | 0 | struct peer_info info; |
458 | |
|
459 | 0 | hdr.type = MSG_HELLO; |
460 | 0 | hdr.len = sizeof(info); |
461 | 0 | info.version = QREXEC_PROTOCOL_VERSION; |
462 | |
|
463 | 0 | if (!write_all(fd, &hdr, sizeof(hdr))) { |
464 | 0 | LOG(ERROR, "Failed to send MSG_HELLO hdr to client %d", fd); |
465 | 0 | return -1; |
466 | 0 | } |
467 | 0 | if (!write_all(fd, &info, sizeof(info))) { |
468 | 0 | LOG(ERROR, "Failed to send MSG_HELLO to client %d", fd); |
469 | 0 | return -1; |
470 | 0 | } |
471 | 0 | return 0; |
472 | 0 | } |
473 | | |
474 | | static int allocate_vchan_port(int connect_domain) |
475 | 0 | { |
476 | | /* |
477 | | Make sure the allocated ports numbers are unique for a given {domX, domY} |
478 | | set. |
479 | | |
480 | | For domX-domY connections, both daemons can allocate ports. If they both |
481 | | allocate the same port number, this can cause trouble: |
482 | | - We might receive MSG_CONNECTION_TERMINATED for the wrong connection. |
483 | | - Although vchan connections in both directions can exist independently, |
484 | | the direction (client-server or server-client) is not always |
485 | | the same, so collision is still possible. |
486 | | |
487 | | To prevent that from happening, for X < Y allow the daemon for X to |
488 | | allocate only odd port numbers, and the daemon for Y to allocate only |
489 | | even port numbers. |
490 | | |
491 | | (This does not apply if we are connecting to/from dom0, as there is no |
492 | | separate daemon running for dom0). |
493 | | */ |
494 | |
|
495 | 0 | int i, step; |
496 | 0 | if (connect_domain == 0) { |
497 | 0 | i = 0; |
498 | 0 | step = 1; |
499 | 0 | } else { |
500 | 0 | i = connect_domain > remote_domain_id ? 1 : 0; |
501 | 0 | step = 2; |
502 | 0 | } |
503 | |
|
504 | 0 | for (; i < MAX_CLIENTS; i += step) { |
505 | 0 | if (used_vchan_ports[i] == VCHAN_PORT_UNUSED) { |
506 | 0 | used_vchan_ports[i] = connect_domain; |
507 | 0 | return VCHAN_BASE_DATA_PORT+i; |
508 | 0 | } |
509 | 0 | } |
510 | 0 | return 0; |
511 | 0 | } |
512 | | |
513 | | static void handle_new_client(void) |
514 | 0 | { |
515 | 0 | int fd = do_accept(qrexec_daemon_unix_socket_fd); |
516 | 0 | if (fd >= MAX_CLIENTS) { |
517 | 0 | LOG(ERROR, "too many clients ?"); |
518 | 0 | exit(1); |
519 | 0 | } |
520 | |
|
521 | 0 | if (send_client_hello(fd) < 0) { |
522 | 0 | close(fd); |
523 | 0 | clients[fd].state = CLIENT_INVALID; |
524 | 0 | return; |
525 | 0 | } |
526 | | |
527 | 0 | clients[fd].state = CLIENT_HELLO; |
528 | 0 | if (fd > max_client_fd) |
529 | 0 | max_client_fd = fd; |
530 | 0 | } |
531 | | |
532 | | static void terminate_client(int fd) |
533 | 1 | { |
534 | 1 | int port; |
535 | 1 | clients[fd].state = CLIENT_INVALID; |
536 | 1 | close(fd); |
537 | | /* if client requested vchan connection end notify, cancel it */ |
538 | 257 | for (port = 0; port < MAX_CLIENTS; port++) { |
539 | 256 | if (vchan_port_notify_client[port] == fd) |
540 | 256 | vchan_port_notify_client[port] = VCHAN_PORT_UNUSED; |
541 | 256 | } |
542 | 1 | } |
543 | | |
544 | | static void release_vchan_port(int port, int expected_remote_id) |
545 | 50 | { |
546 | | /* release only if was reserved for connection to given domain */ |
547 | 50 | if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) { |
548 | 2 | used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED; |
549 | | /* notify client if requested - it will clear notification request */ |
550 | 2 | if (vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT] != VCHAN_PORT_UNUSED) |
551 | 1 | terminate_client(vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT]); |
552 | 2 | } |
553 | 50 | } |
554 | | |
555 | | static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr) |
556 | 0 | { |
557 | 0 | struct exec_params *params = NULL; |
558 | 0 | uint32_t len; |
559 | 0 | char *buf; |
560 | 0 | int use_default_user = 0; |
561 | 0 | int i; |
562 | |
|
563 | 0 | if (hdr->len <= sizeof(*params)) { |
564 | 0 | LOG(ERROR, "Too-short packet received from client %d: " |
565 | 0 | "type %" PRIu32 ", len %" PRIu32 "(min %zu)", |
566 | 0 | fd, hdr->type, hdr->len, sizeof(*params) + 1); |
567 | 0 | goto terminate; |
568 | 0 | } |
569 | 0 | if (hdr->len > MAX_QREXEC_CMD_LEN) { |
570 | 0 | LOG(ERROR, "Too-long packet received from client %d: " |
571 | 0 | "type %" PRIu32 ", len %" PRIu32 "(max %lu)", |
572 | 0 | fd, hdr->type, hdr->len, MAX_QREXEC_CMD_LEN); |
573 | 0 | goto terminate; |
574 | 0 | } |
575 | 0 | len = hdr->len - sizeof(*params); |
576 | |
|
577 | 0 | params = malloc(hdr->len); |
578 | 0 | if (params == NULL) { |
579 | 0 | PERROR("malloc"); |
580 | 0 | goto terminate; |
581 | 0 | } |
582 | | |
583 | 0 | if (!read_all(fd, params, hdr->len)) { |
584 | 0 | goto terminate; |
585 | 0 | } |
586 | 0 | buf = params->cmdline; |
587 | |
|
588 | 0 | if (buf[len - 1] != '\0') { |
589 | 0 | LOG(ERROR, "Client sent buffer of length %" PRIu32 " that is not " |
590 | 0 | "NUL-terminated", len); |
591 | 0 | goto terminate; |
592 | 0 | } |
593 | | |
594 | 0 | if (hdr->type == MSG_SERVICE_CONNECT) { |
595 | | /* if the service was accepted, do not send spurious |
596 | | * MSG_SERVICE_REFUSED when service process itself exit with non-zero |
597 | | * code. Avoid also sending MSG_SERVICE_CONNECT twice. */ |
598 | 0 | for (i = 0; i <= policy_pending_max; i++) { |
599 | 0 | if (policy_pending[i].pid && |
600 | 0 | policy_pending[i].response_sent == RESPONSE_PENDING && |
601 | 0 | strncmp(policy_pending[i].params.ident, buf, len) == 0) { |
602 | 0 | break; |
603 | 0 | } |
604 | 0 | } |
605 | 0 | if (i > policy_pending_max) { |
606 | 0 | LOG(ERROR, "Connection with ident %s not requested or already handled", |
607 | 0 | policy_pending[i].params.ident); |
608 | 0 | goto terminate; |
609 | 0 | } |
610 | 0 | policy_pending[i].response_sent = RESPONSE_ALLOW; |
611 | 0 | } else { |
612 | 0 | if (hdr->type != MSG_JUST_EXEC && hdr->type != MSG_EXEC_CMDLINE) { |
613 | | // Sending such a message would just cause the agent to terminate. |
614 | 0 | LOG(ERROR, "Invalid message type %" PRIu32 " from client", hdr->type); |
615 | 0 | goto terminate; |
616 | 0 | } |
617 | 0 | if (params->connect_port != 0) { |
618 | | // This is wrong, so log it, but allow it in case any code relies |
619 | | // on it. I did not find any such code. |
620 | 0 | LOG(ERROR, "Client provided port %" PRIu32 |
621 | 0 | " in non-MSG_SERVICE_CONNECT request (type %" PRIu32 ")", |
622 | 0 | params->connect_port, hdr->type); |
623 | 0 | } |
624 | 0 | } |
625 | | |
626 | 0 | if (!params->connect_port) { |
627 | 0 | struct exec_params client_params; |
628 | | /* allocate port and send it to the client */ |
629 | 0 | params->connect_port = allocate_vchan_port(params->connect_domain); |
630 | 0 | if (params->connect_port <= 0) { |
631 | 0 | LOG(ERROR, "Failed to allocate new vchan port, too many clients?"); |
632 | 0 | goto terminate; |
633 | 0 | } |
634 | | /* notify the client when this connection got terminated */ |
635 | 0 | vchan_port_notify_client[params->connect_port-VCHAN_BASE_DATA_PORT] = fd; |
636 | 0 | client_params.connect_port = params->connect_port; |
637 | 0 | client_params.connect_domain = remote_domain_id; |
638 | 0 | hdr->len = sizeof(client_params); |
639 | 0 | if (!write_all(fd, hdr, sizeof(*hdr)) || |
640 | 0 | !write_all(fd, &client_params, sizeof(client_params))) { |
641 | 0 | terminate_client(fd); |
642 | 0 | release_vchan_port(params->connect_port, params->connect_domain); |
643 | 0 | free(params); |
644 | 0 | return 0; |
645 | 0 | } |
646 | | /* restore original len value */ |
647 | 0 | hdr->len = len+sizeof(*params); |
648 | 0 | } else { |
649 | 0 | if (!((params->connect_port >= VCHAN_BASE_DATA_PORT) && |
650 | 0 | (params->connect_port < VCHAN_BASE_DATA_PORT+MAX_CLIENTS))) { |
651 | 0 | LOG(ERROR, "Invalid connect port %" PRIu32, params->connect_port); |
652 | 0 | goto terminate; |
653 | 0 | } |
654 | 0 | } |
655 | | |
656 | 0 | if ((hdr->type != MSG_SERVICE_CONNECT) && |
657 | 0 | (strncmp(buf, default_user_keyword, default_user_keyword_len_without_colon+1) == 0)) |
658 | 0 | { |
659 | 0 | use_default_user = 1; |
660 | 0 | hdr->len -= default_user_keyword_len_without_colon; |
661 | 0 | hdr->len += strlen(default_user); |
662 | 0 | } |
663 | 0 | if (libvchan_send(vchan, hdr, sizeof(*hdr)) != sizeof(*hdr)) |
664 | 0 | handle_vchan_error("send"); |
665 | 0 | if (use_default_user) { |
666 | 0 | int send_len = strlen(default_user); |
667 | 0 | if (libvchan_send(vchan, params, sizeof(*params)) != sizeof(*params)) |
668 | 0 | handle_vchan_error("send params"); |
669 | 0 | if (libvchan_send(vchan, default_user, send_len) != send_len) |
670 | 0 | handle_vchan_error("send default_user"); |
671 | 0 | send_len = len-default_user_keyword_len_without_colon; |
672 | 0 | if (libvchan_send(vchan, buf+default_user_keyword_len_without_colon, |
673 | 0 | send_len) != send_len) |
674 | 0 | handle_vchan_error("send buf"); |
675 | 0 | } else { |
676 | 0 | if (libvchan_send(vchan, params, hdr->len) != (int)hdr->len) |
677 | 0 | handle_vchan_error("send buf"); |
678 | 0 | } |
679 | 0 | free(params); |
680 | 0 | return 1; |
681 | 0 | terminate: |
682 | 0 | terminate_client(fd); |
683 | 0 | free(params); |
684 | 0 | return 0; |
685 | 0 | } |
686 | | |
687 | | static void handle_cmdline_message_from_client(int fd) |
688 | 0 | { |
689 | 0 | struct msg_header hdr; |
690 | 0 | if (!read_all(fd, &hdr, sizeof hdr)) { |
691 | 0 | terminate_client(fd); |
692 | 0 | return; |
693 | 0 | } |
694 | 0 | switch (hdr.type) { |
695 | 0 | case MSG_EXEC_CMDLINE: |
696 | 0 | case MSG_JUST_EXEC: |
697 | 0 | case MSG_SERVICE_CONNECT: |
698 | 0 | break; |
699 | 0 | default: |
700 | 0 | terminate_client(fd); |
701 | 0 | return; |
702 | 0 | } |
703 | | |
704 | 0 | if (!handle_cmdline_body_from_client(fd, &hdr)) { |
705 | | // client disconnected while sending cmdline, above call already |
706 | | // cleaned up client info |
707 | 0 | return; |
708 | 0 | } |
709 | 0 | clients[fd].state = CLIENT_RUNNING; |
710 | 0 | } |
711 | | |
712 | | static void handle_client_hello(int fd) |
713 | 0 | { |
714 | 0 | struct msg_header hdr; |
715 | 0 | struct peer_info info; |
716 | |
|
717 | 0 | if (!read_all(fd, &hdr, sizeof hdr)) { |
718 | 0 | terminate_client(fd); |
719 | 0 | return; |
720 | 0 | } |
721 | 0 | if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) { |
722 | 0 | LOG(ERROR, "Invalid HELLO packet received from client %d: " |
723 | 0 | "type %d, len %d", fd, hdr.type, hdr.len); |
724 | 0 | terminate_client(fd); |
725 | 0 | return; |
726 | 0 | } |
727 | 0 | if (!read_all(fd, &info, sizeof info)) { |
728 | 0 | terminate_client(fd); |
729 | 0 | return; |
730 | 0 | } |
731 | 0 | if (info.version != QREXEC_PROTOCOL_VERSION) { |
732 | 0 | LOG(ERROR, "Incompatible client protocol version (remote %d, local %d)", info.version, QREXEC_PROTOCOL_VERSION); |
733 | 0 | terminate_client(fd); |
734 | 0 | return; |
735 | 0 | } |
736 | 0 | clients[fd].state = CLIENT_CMDLINE; |
737 | 0 | } |
738 | | |
739 | | /* handle data received from one of qrexec_client processes */ |
740 | | static void handle_message_from_client(int fd) |
741 | 0 | { |
742 | 0 | char buf[1]; |
743 | |
|
744 | 0 | switch (clients[fd].state) { |
745 | 0 | case CLIENT_HELLO: |
746 | 0 | handle_client_hello(fd); |
747 | 0 | return; |
748 | 0 | case CLIENT_CMDLINE: |
749 | 0 | handle_cmdline_message_from_client(fd); |
750 | 0 | return; |
751 | 0 | case CLIENT_RUNNING: |
752 | | // expected EOF |
753 | 0 | if (read(fd, buf, sizeof(buf)) != 0) { |
754 | 0 | LOG(ERROR, "Unexpected data received from client %d", fd); |
755 | 0 | } |
756 | 0 | terminate_client(fd); |
757 | 0 | return; |
758 | 0 | case CLIENT_INVALID: |
759 | 0 | return; /* nothing to do */ |
760 | 0 | default: |
761 | 0 | LOG(ERROR, "Invalid client state %d", clients[fd].state); |
762 | 0 | exit(1); |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | |
767 | | /* |
768 | | * The signal handler executes asynchronously; therefore all it should do is |
769 | | * to set a flag "signal has arrived", and let the main even loop react to this |
770 | | * flag in appropriate moment. |
771 | | */ |
772 | | static void signal_handler(int sig) |
773 | 0 | { |
774 | 0 | switch (sig) { |
775 | 0 | case SIGCHLD: |
776 | 0 | child_exited = 1; |
777 | 0 | break; |
778 | 0 | case SIGTERM: |
779 | 0 | terminate_requested = 1; |
780 | 0 | break; |
781 | 0 | default: |
782 | | /* cannot happen */ |
783 | 0 | abort(); |
784 | 0 | } |
785 | 0 | } |
786 | | |
787 | 295 | static void send_service_refused(libvchan_t *vchan, const struct service_params *untrusted_params) { |
788 | 295 | struct msg_header hdr; |
789 | | |
790 | 295 | hdr.type = MSG_SERVICE_REFUSED; |
791 | 295 | hdr.len = sizeof(*untrusted_params); |
792 | | |
793 | 295 | if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
794 | 0 | LOG(ERROR, "Failed to send MSG_SERVICE_REFUSED hdr to agent"); |
795 | 0 | exit(1); |
796 | 0 | } |
797 | | |
798 | 295 | if (libvchan_send(vchan, untrusted_params, sizeof(*untrusted_params)) != sizeof(*untrusted_params)) { |
799 | 0 | LOG(ERROR, "Failed to send MSG_SERVICE_REFUSED to agent"); |
800 | 0 | exit(1); |
801 | 0 | } |
802 | 295 | } |
803 | | |
804 | | /* clean zombies, check for denied service calls */ |
805 | | static void reap_children(void) |
806 | 0 | { |
807 | 0 | int status; |
808 | 0 | int i; |
809 | |
|
810 | 0 | pid_t pid; |
811 | 0 | while ((pid=waitpid(-1, &status, WNOHANG)) > 0) { |
812 | 0 | for (i = 0; i <= policy_pending_max; i++) { |
813 | 0 | if (policy_pending[i].pid == pid) { |
814 | 0 | if (!WIFEXITED(status)) |
815 | 0 | continue; |
816 | 0 | status = WEXITSTATUS(status); |
817 | 0 | if (status != 0) { |
818 | 0 | if (policy_pending[i].response_sent != RESPONSE_PENDING) { |
819 | 0 | LOG(ERROR, "qrexec-policy-exec for connection %s exited with code %d, but the response (%s) was already sent", |
820 | 0 | policy_pending[i].params.ident, status, |
821 | 0 | policy_pending[i].response_sent == RESPONSE_ALLOW ? "allow" : "deny"); |
822 | 0 | } else { |
823 | 0 | policy_pending[i].response_sent = RESPONSE_DENY; |
824 | 0 | send_service_refused(vchan, &policy_pending[i].params); |
825 | 0 | } |
826 | 0 | } else { |
827 | 0 | policy_pending[i].response_sent = RESPONSE_ALLOW; |
828 | 0 | } |
829 | | /* in case of allowed calls, we will do the rest in |
830 | | * MSG_SERVICE_CONNECT from client handler */ |
831 | 0 | policy_pending[i].pid = 0; |
832 | 0 | while (policy_pending_max > 0 && |
833 | 0 | policy_pending[policy_pending_max].pid == 0) |
834 | 0 | policy_pending_max--; |
835 | 0 | break; |
836 | 0 | } |
837 | 0 | } |
838 | 0 | } |
839 | 0 | child_exited = 0; |
840 | 0 | } |
841 | | |
842 | 141 | static int find_policy_pending_slot(void) { |
843 | 10.0k | for (int i = 0; i < MAX_CLIENTS; i++) { |
844 | 10.0k | if (policy_pending[i].pid == 0) { |
845 | 141 | if (i > policy_pending_max) |
846 | 141 | policy_pending_max = i; |
847 | 141 | return i; |
848 | 141 | } |
849 | 10.0k | } |
850 | 0 | return -1; |
851 | 141 | } |
852 | | |
853 | | static void sanitize_name(char * untrusted_s_signed, char *extra_allowed_chars) |
854 | 648 | { |
855 | 648 | unsigned char * untrusted_s; |
856 | 12.4k | for (untrusted_s=(unsigned char*)untrusted_s_signed; *untrusted_s; untrusted_s++) { |
857 | 11.8k | if (*untrusted_s >= 'a' && *untrusted_s <= 'z') |
858 | 1.73k | continue; |
859 | 10.1k | if (*untrusted_s >= 'A' && *untrusted_s <= 'Z') |
860 | 1.67k | continue; |
861 | 8.43k | if (*untrusted_s >= '0' && *untrusted_s <= '9') |
862 | 1.40k | continue; |
863 | 7.02k | if (*untrusted_s == '_' || |
864 | 7.02k | *untrusted_s == '-' || |
865 | 7.02k | *untrusted_s == '.') |
866 | 2.75k | continue; |
867 | 4.27k | if (extra_allowed_chars && strchr(extra_allowed_chars, *untrusted_s)) |
868 | 635 | continue; |
869 | 3.64k | *untrusted_s = '_'; |
870 | 3.64k | } |
871 | 648 | } |
872 | | |
873 | | static int parse_policy_response( |
874 | | char *response, |
875 | | size_t result_bytes, |
876 | | bool daemon, |
877 | | char **user, |
878 | | char **target_uuid, |
879 | | char **target, |
880 | | char **requested_target, |
881 | | int *autostart |
882 | 0 | ) { |
883 | 0 | *user = *target_uuid = *target = *requested_target = NULL; |
884 | 0 | int result = *autostart = -1; |
885 | 0 | const char *const msg = daemon ? "qrexec-policy-daemon" : "qrexec-policy-exec"; |
886 | | // At least one byte must be returned |
887 | 0 | if (result_bytes < 1) { |
888 | 0 | LOG(ERROR, "%s didn't return any data", msg); |
889 | 0 | return RESPONSE_MALFORMED; |
890 | 0 | } |
891 | | // Forbid NUL bytes in response. qrexec_read_all_to_malloc() has added the |
892 | | // NUL terminator already. |
893 | 0 | if (strlen(response) != result_bytes) { |
894 | 0 | LOG(ERROR, "%s wrote a NUL byte", msg); |
895 | 0 | return RESPONSE_MALFORMED; |
896 | 0 | } |
897 | | // Strip any trailing newlines. |
898 | 0 | if (response[result_bytes - 1] == '\n') { |
899 | 0 | result_bytes--; |
900 | 0 | response[result_bytes] = '\0'; |
901 | 0 | } |
902 | 0 | char *resp = response, *current_response; |
903 | 0 | while ((current_response = strsep(&resp, "\n"))) { |
904 | 0 | if (!strncmp(current_response, "result=", sizeof("result=") - 1)) { |
905 | 0 | current_response += sizeof("result=") - 1; |
906 | 0 | if (result != -1) { |
907 | 0 | goto bad_response; |
908 | 0 | } |
909 | 0 | if (!strcmp(current_response, "allow")) |
910 | 0 | result = 0; |
911 | 0 | else if (!strcmp(current_response, "deny")) { |
912 | 0 | result = 1; |
913 | 0 | } else { |
914 | 0 | goto bad_response; |
915 | 0 | } |
916 | 0 | } else if (!strncmp(current_response, "user=", sizeof("user=") - 1)) { |
917 | 0 | if (*user) |
918 | 0 | goto bad_response; |
919 | 0 | *user = strdup(current_response + (sizeof("user=") - 1)); |
920 | 0 | if (*user == NULL) |
921 | 0 | abort(); |
922 | 0 | } else if (!strncmp(current_response, "target=", sizeof("target=") - 1)) { |
923 | 0 | if (*target != NULL) |
924 | 0 | goto bad_response; |
925 | 0 | *target = strdup(current_response + (sizeof("target=") - 1)); |
926 | 0 | if (*target == NULL) |
927 | 0 | abort(); |
928 | 0 | } else if (!strncmp(current_response, "target_uuid=", sizeof("target_uuid=") - 1)) { |
929 | 0 | if (*target_uuid != NULL) |
930 | 0 | goto bad_response; |
931 | 0 | *target_uuid = strdup(current_response + 12); |
932 | 0 | if (*target_uuid == NULL) |
933 | 0 | abort(); |
934 | 0 | } else if (!strncmp(current_response, "autostart=", sizeof("autostart=") - 1)) { |
935 | 0 | current_response += sizeof("autostart=") - 1; |
936 | 0 | if (*autostart != -1) |
937 | 0 | goto bad_response; |
938 | 0 | if (!strcmp(current_response, "True")) |
939 | 0 | *autostart = 1; |
940 | 0 | else if (!strcmp(current_response, "False")) |
941 | 0 | *autostart = 0; |
942 | 0 | else |
943 | 0 | goto bad_response; |
944 | 0 | } else if (!strncmp(current_response, "requested_target=", sizeof("requested_target=") - 1)) { |
945 | 0 | if (*requested_target != NULL) |
946 | 0 | goto bad_response; |
947 | 0 | *requested_target = strdup(current_response + (sizeof("requested_target=") - 1)); |
948 | 0 | if (*requested_target == NULL) |
949 | 0 | abort(); |
950 | 0 | } else { |
951 | 0 | char *p = strchr(current_response, '='); |
952 | 0 | if (p == NULL) |
953 | 0 | goto bad_response; |
954 | 0 | *p = '\0'; |
955 | 0 | LOG(ERROR, "Unknown response key %s, ignoring", current_response); |
956 | 0 | } |
957 | 0 | } |
958 | | |
959 | 0 | switch (result) { |
960 | 0 | case 0: |
961 | 0 | if (*user == NULL || *target == NULL || *requested_target == NULL || *autostart == -1) |
962 | 0 | break; |
963 | 0 | return RESPONSE_ALLOW; |
964 | 0 | case 1: |
965 | 0 | if (*user != NULL || *target != NULL || *requested_target != NULL || *autostart != -1) |
966 | 0 | break; |
967 | 0 | return RESPONSE_DENY; |
968 | 0 | default: |
969 | 0 | break; |
970 | 0 | } |
971 | | |
972 | 0 | bad_response: |
973 | 0 | LOG(ERROR, "%s sent invalid response", msg); |
974 | 0 | return RESPONSE_MALFORMED; |
975 | 0 | } |
976 | | |
977 | | struct QrexecPolicyRequest { |
978 | | }; |
979 | | |
980 | | static void send_request_to_daemon( |
981 | | const int daemon_socket, |
982 | | const char *remote_domain_name, |
983 | | const char *target_domain, |
984 | | const char *service_name) |
985 | 0 | { |
986 | 0 | char *command; |
987 | 0 | ssize_t bytes_sent = 0; |
988 | 0 | int command_size = asprintf(&command, |
989 | 0 | "source=%s\n" |
990 | 0 | "intended_target=%s\n" |
991 | 0 | "service_and_arg=%s\n\n", |
992 | 0 | remote_domain_name, |
993 | 0 | target_domain, |
994 | 0 | service_name); |
995 | 0 | if (command_size < 0) { |
996 | 0 | PERROR("failed to construct request"); |
997 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
998 | 0 | } |
999 | |
|
1000 | 0 | for (int i = 0; i < command_size; i += bytes_sent) { |
1001 | 0 | bytes_sent = send(daemon_socket, command + i, command_size - i, MSG_NOSIGNAL); |
1002 | 0 | if (bytes_sent > command_size - i) |
1003 | 0 | abort(); // kernel read beyond buffer bounds? |
1004 | 0 | if (bytes_sent < 0) { |
1005 | 0 | assert(bytes_sent == -1); |
1006 | 0 | PERROR("send to socket failed"); |
1007 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1008 | 0 | } |
1009 | 0 | } |
1010 | 0 | free(command); |
1011 | 0 | } |
1012 | | |
1013 | | static _Noreturn void null_exit(void) |
1014 | 0 | { |
1015 | | #ifdef COVERAGE |
1016 | | __gcov_dump(); |
1017 | | #endif |
1018 | 0 | _exit(QREXEC_EXIT_PROBLEM); |
1019 | 0 | } |
1020 | | |
1021 | | /* |
1022 | | * Called when agent sends a message asking to execute a predefined command. |
1023 | | */ |
1024 | | |
1025 | | static enum policy_response connect_daemon_socket( |
1026 | | const char *remote_domain_name, |
1027 | | const char *target_domain, |
1028 | | const char *service_name, |
1029 | | char **user, |
1030 | | char **target_uuid, |
1031 | | char **target, |
1032 | | char **requested_target, |
1033 | | int *autostart |
1034 | 0 | ) { |
1035 | 0 | int pid = -1; |
1036 | 0 | struct sockaddr_un daemon_socket_address = { |
1037 | 0 | .sun_family = AF_UNIX, |
1038 | 0 | .sun_path = QREXEC_SOCKET_PATH |
1039 | 0 | }; |
1040 | |
|
1041 | 0 | int daemon_socket = socket(AF_UNIX, SOCK_STREAM, 0); |
1042 | 0 | if (daemon_socket < 0) { |
1043 | 0 | PERROR("socket creation failed"); |
1044 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1045 | 0 | } |
1046 | |
|
1047 | 0 | int connect_result = connect(daemon_socket, |
1048 | 0 | (struct sockaddr *) &daemon_socket_address, |
1049 | 0 | sizeof(daemon_socket_address)); |
1050 | 0 | if (connect_result == 0) { |
1051 | 0 | send_request_to_daemon(daemon_socket, |
1052 | 0 | remote_domain_name, |
1053 | 0 | target_domain, |
1054 | 0 | service_name); |
1055 | 0 | size_t result_bytes; |
1056 | | // this closes the socket |
1057 | 0 | char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes); |
1058 | 0 | int policy_result = parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart); |
1059 | 0 | if (policy_result != RESPONSE_MALFORMED) { |
1060 | | // This leaks 'result', but as the code execs later anyway this isn't a problem. |
1061 | | // 'result' cannot be freed as 'user', 'target', and 'requested_target' point into |
1062 | | // the same buffer. |
1063 | 0 | return policy_result; |
1064 | 0 | } |
1065 | 0 | free(result); |
1066 | 0 | } else { |
1067 | 0 | PERROR("connection to socket failed"); |
1068 | 0 | assert(connect_result == -1); |
1069 | 0 | if (close(daemon_socket)) |
1070 | 0 | abort(); |
1071 | 0 | } |
1072 | 0 | int fds[2]; |
1073 | 0 | if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds)) { |
1074 | 0 | PERROR("socketpair()"); |
1075 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1076 | 0 | } |
1077 | 0 | daemon_socket = fds[0]; |
1078 | |
|
1079 | 0 | pid = fork(); |
1080 | 0 | switch (pid) { |
1081 | 0 | case -1: |
1082 | 0 | LOGE(ERROR, "Could not fork!"); |
1083 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1084 | 0 | case 0: |
1085 | 0 | if (close(fds[0])) |
1086 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1087 | 0 | if (dup2(fds[1], 0) != 0 || dup2(fds[1], 1) != 1) { |
1088 | 0 | PERROR("dup2()"); |
1089 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1090 | 0 | } |
1091 | 0 | if (close(fds[1])) |
1092 | 0 | abort(); |
1093 | 0 | char remote_domain_id_str[10]; |
1094 | 0 | int v = snprintf(remote_domain_id_str, sizeof(remote_domain_id_str), "%d", |
1095 | 0 | remote_domain_id); |
1096 | 0 | if (v >= 1 && v < (int)sizeof(remote_domain_id_str)) { |
1097 | 0 | execl(policy_program, |
1098 | 0 | "qrexec-policy-exec", |
1099 | 0 | "--", |
1100 | 0 | remote_domain_name, |
1101 | 0 | target_domain, |
1102 | 0 | service_name, |
1103 | 0 | NULL); |
1104 | 0 | PERROR("execl"); |
1105 | 0 | } else { |
1106 | 0 | PERROR("snprintf"); |
1107 | 0 | } |
1108 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1109 | 0 | default: |
1110 | 0 | if (close(fds[1])) |
1111 | 0 | abort(); |
1112 | 0 | size_t result_bytes; |
1113 | 0 | int status; |
1114 | | // this closes the socket |
1115 | 0 | char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes); |
1116 | 0 | do { |
1117 | 0 | if (waitpid(pid, &status, 0) != pid) { |
1118 | 0 | PERROR("waitpid"); |
1119 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1120 | 0 | } |
1121 | 0 | } while (!WIFEXITED(status)); |
1122 | 0 | if (WEXITSTATUS(status) != 0) { |
1123 | 0 | LOG(ERROR, "qrexec-policy-exec failed: %d", WEXITSTATUS(status)); |
1124 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1125 | 0 | } |
1126 | | // This leaks 'result', but as the code execs later anyway this isn't a problem. |
1127 | | // 'result' cannot be freed as 'user', 'target', and 'requested_target' point into |
1128 | | // the same buffer. |
1129 | 0 | return parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart); |
1130 | 0 | } |
1131 | 0 | } |
1132 | | |
1133 | | /* called from do_fork_exec */ |
1134 | | static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused))) |
1135 | 0 | { |
1136 | | /* avoid calling qubes-rpc-multiplexer through shell */ |
1137 | 0 | exec_qubes_rpc_if_requested(prog, environ); |
1138 | | |
1139 | | /* if above haven't executed qubes-rpc-multiplexer, pass it to shell */ |
1140 | 0 | execl("/bin/bash", "bash", "-c", prog, NULL); |
1141 | 0 | PERROR("exec bash"); |
1142 | 0 | _exit(QREXEC_EXIT_PROBLEM); |
1143 | 0 | } |
1144 | | |
1145 | | _Noreturn static void handle_execute_service_child( |
1146 | | const int remote_domain_id, |
1147 | | const char *remote_domain_name, |
1148 | | const char *target_domain, |
1149 | | const char *service_name, |
1150 | 0 | const struct service_params *request_id) { |
1151 | 0 | int i; |
1152 | |
|
1153 | | #ifdef SYS_close_range |
1154 | | int close_range_res = syscall(SYS_close_range, 3, ~0U, 0); |
1155 | | #else |
1156 | 0 | int close_range_res = -1; |
1157 | 0 | #endif |
1158 | 0 | if (close_range_res != 0) |
1159 | 0 | for (i = 3; i < MAX_FDS; i++) |
1160 | 0 | close(i); |
1161 | |
|
1162 | 0 | char *user = NULL, *target = NULL, *requested_target = NULL, *target_uuid = NULL; |
1163 | 0 | int autostart = -1; |
1164 | 0 | int policy_response = |
1165 | 0 | connect_daemon_socket(remote_domain_name, target_domain, service_name, |
1166 | 0 | &user, &target_uuid, &target, &requested_target, &autostart); |
1167 | |
|
1168 | 0 | if (policy_response != RESPONSE_ALLOW) |
1169 | 0 | daemon__exit(QREXEC_EXIT_REQUEST_REFUSED); |
1170 | | |
1171 | | /* Replace the target domain with the version normalized by the policy engine */ |
1172 | 0 | target_domain = requested_target; |
1173 | 0 | char *cmd = NULL; |
1174 | | |
1175 | | /* |
1176 | | * If there was no service argument, pretend that an empty argument was |
1177 | | * provided by appending "+" to the service name. |
1178 | | */ |
1179 | 0 | const char *const trailer = strchr(service_name, '+') ? "" : "+"; |
1180 | | |
1181 | | /* Check if the target is dom0, which requires special handling. */ |
1182 | 0 | bool target_is_dom0 = target_refers_to_dom0(target); |
1183 | 0 | if (target_is_dom0) { |
1184 | 0 | char *type; |
1185 | 0 | bool target_is_keyword = target_domain[0] == '@'; |
1186 | 0 | if (target_is_keyword) { |
1187 | 0 | target_domain++; |
1188 | 0 | type = "keyword"; |
1189 | 0 | } else { |
1190 | 0 | type = "name"; |
1191 | 0 | } |
1192 | 0 | if (asprintf(&cmd, "QUBESRPC %s%s %s %s %s", |
1193 | 0 | service_name, |
1194 | 0 | trailer, |
1195 | 0 | remote_domain_name, |
1196 | 0 | type, |
1197 | 0 | target_domain) <= 0) |
1198 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1199 | 0 | register_exec_func(&do_exec); |
1200 | 0 | daemon__exit(run_qrexec_to_dom0(request_id, |
1201 | 0 | remote_domain_id, |
1202 | 0 | remote_domain_name, |
1203 | 0 | cmd, |
1204 | 0 | 5 /* 5 second timeout */, |
1205 | 0 | false /* return 0 not remote status code */)); |
1206 | 0 | } else { |
1207 | 0 | bool const use_uuid = target_uuid != NULL; |
1208 | 0 | const char *const selected_target = use_uuid ? target_uuid : target; |
1209 | 0 | int service_length = asprintf(&cmd, "%s:QUBESRPC %s%s %s", |
1210 | 0 | user, |
1211 | 0 | service_name, |
1212 | 0 | trailer, |
1213 | 0 | remote_domain_name); |
1214 | 0 | if (service_length < 0) |
1215 | 0 | daemon__exit(QREXEC_EXIT_PROBLEM); |
1216 | 0 | daemon__exit(qrexec_execute_vm(selected_target, autostart, |
1217 | 0 | remote_domain_id, |
1218 | 0 | cmd, |
1219 | 0 | (size_t)service_length + 1, |
1220 | 0 | request_id->ident, false, false, |
1221 | 0 | use_uuid) |
1222 | 0 | ? 0 : QREXEC_EXIT_PROBLEM); |
1223 | 0 | } |
1224 | 0 | } |
1225 | | |
1226 | | static void handle_execute_service( |
1227 | | const int remote_domain_id, |
1228 | | const char *remote_domain_name, |
1229 | | const char *target_domain, |
1230 | | const char *service_name, |
1231 | | const struct service_params *request_id) |
1232 | 141 | { |
1233 | 141 | int policy_pending_slot; |
1234 | 141 | pid_t pid; |
1235 | 141 | struct sigaction sa = { .sa_handler = SIG_DFL }; |
1236 | | |
1237 | 141 | policy_pending_slot = find_policy_pending_slot(); |
1238 | 141 | if (policy_pending_slot < 0) { |
1239 | 0 | LOG(ERROR, "Service request denied, too many pending requests"); |
1240 | 0 | send_service_refused(vchan, request_id); |
1241 | 0 | return; |
1242 | 0 | } |
1243 | | |
1244 | 141 | switch (pid=fork()) { |
1245 | 0 | case -1: |
1246 | 0 | PERROR("fork"); |
1247 | 0 | exit(1); |
1248 | 0 | case 0: |
1249 | 0 | if (atexit(null_exit)) |
1250 | 0 | _exit(QREXEC_EXIT_PROBLEM); |
1251 | 0 | if (sigaction(SIGTERM, &sa, NULL)) |
1252 | 0 | LOG(WARNING, "Failed to restore SIGTERM handler: %d", errno); |
1253 | 0 | handle_execute_service_child(remote_domain_id, remote_domain_name, |
1254 | 0 | target_domain, service_name, request_id); |
1255 | 0 | abort(); |
1256 | 141 | default: |
1257 | 141 | policy_pending[policy_pending_slot].pid = pid; |
1258 | 141 | policy_pending[policy_pending_slot].params = *request_id; |
1259 | 141 | policy_pending[policy_pending_slot].response_sent = RESPONSE_PENDING; |
1260 | 141 | return; |
1261 | 141 | } |
1262 | 141 | } |
1263 | | |
1264 | | |
1265 | | static void handle_connection_terminated(void) |
1266 | 97 | { |
1267 | 97 | struct exec_params untrusted_params, params; |
1268 | | |
1269 | 97 | if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) |
1270 | 97 | != sizeof(untrusted_params)) |
1271 | 5 | handle_vchan_error("recv params"); |
1272 | | /* sanitize start */ |
1273 | 97 | if (untrusted_params.connect_port < VCHAN_BASE_DATA_PORT || |
1274 | 97 | untrusted_params.connect_port >= VCHAN_BASE_DATA_PORT+MAX_CLIENTS) { |
1275 | 42 | LOG(ERROR, "Invalid port in MSG_CONNECTION_TERMINATED (%d)", |
1276 | 42 | untrusted_params.connect_port); |
1277 | 42 | exit(1); |
1278 | 42 | } |
1279 | | /* untrusted_params.connect_domain even if invalid will not harm - in worst |
1280 | | * case the port will not be released */ |
1281 | 97 | params = untrusted_params; |
1282 | | /* sanitize end */ |
1283 | 97 | release_vchan_port(params.connect_port, params.connect_domain); |
1284 | 97 | } |
1285 | | |
1286 | | static void sanitize_message_from_agent(struct msg_header *untrusted_header) |
1287 | 794 | { |
1288 | 794 | switch (untrusted_header->type) { |
1289 | 127 | case MSG_TRIGGER_SERVICE: |
1290 | 127 | if (protocol_version >= QREXEC_PROTOCOL_V3) { |
1291 | 1 | LOG(ERROR, "agent sent (old) MSG_TRIGGER_SERVICE " |
1292 | 1 | "although it uses protocol %d", protocol_version); |
1293 | 1 | exit(1); |
1294 | 1 | } |
1295 | 127 | if (untrusted_header->len != sizeof(struct trigger_service_params)) { |
1296 | 40 | LOG(ERROR, "agent sent invalid MSG_TRIGGER_SERVICE packet"); |
1297 | 40 | exit(1); |
1298 | 40 | } |
1299 | 127 | break; |
1300 | 461 | case MSG_TRIGGER_SERVICE3: |
1301 | 461 | if (protocol_version < QREXEC_PROTOCOL_V3) { |
1302 | 1 | LOG(ERROR, "agent sent (new) MSG_TRIGGER_SERVICE3 " |
1303 | 1 | "although it uses protocol %d", protocol_version); |
1304 | 1 | exit(1); |
1305 | 1 | } |
1306 | 461 | if (untrusted_header->len <= sizeof(struct trigger_service_params3)) { |
1307 | 8 | LOG(ERROR, "agent sent invalid MSG_TRIGGER_SERVICE3 packet"); |
1308 | 8 | exit(1); |
1309 | 8 | } |
1310 | 461 | if (untrusted_header->len - sizeof(struct trigger_service_params3) |
1311 | 461 | > MAX_SERVICE_NAME_LEN) { |
1312 | 48 | LOG(ERROR, "agent sent too large MSG_TRIGGER_SERVICE3 packet"); |
1313 | 48 | exit(1); |
1314 | 48 | } |
1315 | 461 | break; |
1316 | 137 | case MSG_CONNECTION_TERMINATED: |
1317 | 137 | if (untrusted_header->len != sizeof(struct exec_params)) { |
1318 | 40 | LOG(ERROR, "agent sent invalid MSG_CONNECTION_TERMINATED packet"); |
1319 | 40 | exit(1); |
1320 | 40 | } |
1321 | 137 | break; |
1322 | 69 | default: |
1323 | 69 | LOG(ERROR, "unknown mesage type 0x%x from agent", |
1324 | 69 | untrusted_header->type); |
1325 | 69 | exit(1); |
1326 | 794 | } |
1327 | 794 | } |
1328 | | |
1329 | | static bool validate_request_id(struct service_params *untrusted_params, const char *msg) |
1330 | 436 | { |
1331 | 9.23k | for (size_t i = 0; i < sizeof(untrusted_params->ident); ++i) { |
1332 | 9.13k | switch (untrusted_params->ident[i]) { |
1333 | 1.46k | case '0' ... '9': |
1334 | 4.99k | case 'A' ... 'Z': |
1335 | 8.05k | case 'a' ... 'z': |
1336 | 8.17k | case '_': |
1337 | 8.39k | case '-': |
1338 | 8.70k | case '.': |
1339 | 8.79k | case ' ': |
1340 | 8.79k | continue; |
1341 | 224 | case '\0': { |
1342 | 224 | size_t terminator_offset = i; |
1343 | | /* Ensure that nothing non-NUL follows the terminator */ |
1344 | 2.72k | for (i++; i < sizeof(untrusted_params->ident); i++) { |
1345 | 2.54k | if (untrusted_params->ident[i]) { |
1346 | 47 | LOG(ERROR, "Non-NUL byte %u at offset %zu follows NUL terminator at offset %zu in message %s", |
1347 | 47 | untrusted_params->ident[i], i, terminator_offset, msg); |
1348 | 47 | return false; |
1349 | 47 | } |
1350 | 2.54k | } |
1351 | 177 | return true; |
1352 | 224 | } |
1353 | 113 | default: |
1354 | 113 | LOG(ERROR, "Bad byte %u at offset %zu for message %s", untrusted_params->ident[i], i, msg); |
1355 | 113 | return false; |
1356 | 9.13k | } |
1357 | 9.13k | } |
1358 | 99 | LOG(ERROR, "No NUL terminator in message %s", msg); |
1359 | 99 | return false; // no NUL terminator |
1360 | 436 | } |
1361 | | |
1362 | 576 | #define ENSURE_NULL_TERMINATED(x) x[sizeof(x)-1] = 0 |
1363 | | |
1364 | | static bool validate_service_name(char *untrusted_service_name) |
1365 | 148 | { |
1366 | 148 | switch (untrusted_service_name[0]) { |
1367 | 5 | case '\0': |
1368 | 5 | LOG(ERROR, "Empty service name not allowed"); |
1369 | 5 | return false; |
1370 | 2 | case '+': |
1371 | 2 | LOG(ERROR, "Service name must not start with '+'"); |
1372 | 2 | return false; |
1373 | 141 | default: |
1374 | 141 | sanitize_name(untrusted_service_name, "+"); |
1375 | 141 | return true; |
1376 | 148 | } |
1377 | 148 | } |
1378 | | |
1379 | | #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
1380 | | static |
1381 | | #endif |
1382 | | void handle_message_from_agent(void) |
1383 | 802 | { |
1384 | 802 | struct msg_header hdr, untrusted_hdr; |
1385 | | |
1386 | 802 | if (libvchan_recv(vchan, &untrusted_hdr, sizeof(untrusted_hdr)) |
1387 | 802 | != sizeof(untrusted_hdr)) |
1388 | 8 | handle_vchan_error("recv hdr"); |
1389 | | /* sanitize start */ |
1390 | 802 | sanitize_message_from_agent(&untrusted_hdr); |
1391 | 802 | hdr = untrusted_hdr; |
1392 | | /* sanitize end */ |
1393 | | |
1394 | | // fprintf(stderr, "got %x %x %x\n", hdr.type, hdr.client_id, |
1395 | | // hdr.len); |
1396 | | |
1397 | 802 | switch (hdr.type) { |
1398 | 86 | case MSG_TRIGGER_SERVICE: { |
1399 | 86 | struct trigger_service_params untrusted_params, params; |
1400 | 86 | if (libvchan_recv(vchan, &untrusted_params, sizeof(untrusted_params)) |
1401 | 86 | != sizeof(untrusted_params)) |
1402 | 15 | handle_vchan_error("recv params"); |
1403 | | |
1404 | | /* sanitize start */ |
1405 | 86 | ENSURE_NULL_TERMINATED(untrusted_params.service_name); |
1406 | 86 | ENSURE_NULL_TERMINATED(untrusted_params.target_domain); |
1407 | 86 | sanitize_name(untrusted_params.service_name, "+"); |
1408 | 86 | sanitize_name(untrusted_params.target_domain, "@:"); |
1409 | 86 | if (!validate_request_id(&untrusted_params.request_id, "MSG_TRIGGER_SERVICE")) { |
1410 | 31 | send_service_refused(vchan, &untrusted_params.request_id); |
1411 | 31 | return; |
1412 | 31 | } |
1413 | 55 | if (!validate_service_name(untrusted_params.service_name)) { |
1414 | 2 | send_service_refused(vchan, &untrusted_params.request_id); |
1415 | 2 | return; |
1416 | 2 | } |
1417 | 53 | params = untrusted_params; |
1418 | | /* sanitize end */ |
1419 | | |
1420 | 53 | handle_execute_service(remote_domain_id, remote_domain_name, |
1421 | 53 | params.target_domain, |
1422 | 53 | params.service_name, |
1423 | 53 | ¶ms.request_id); |
1424 | 53 | return; |
1425 | 55 | } |
1426 | 404 | case MSG_TRIGGER_SERVICE3: { |
1427 | 404 | struct trigger_service_params3 *untrusted_params3, *params3; |
1428 | | |
1429 | 404 | untrusted_params3 = malloc(hdr.len); |
1430 | 404 | if (!untrusted_params3) |
1431 | 0 | handle_vchan_error("malloc(service_name)"); |
1432 | | |
1433 | 404 | if (libvchan_recv(vchan, untrusted_params3, hdr.len) |
1434 | 404 | != (int)hdr.len) { |
1435 | 39 | free(untrusted_params3); |
1436 | 39 | handle_vchan_error("recv params3(service_name)"); |
1437 | 39 | } |
1438 | 404 | size_t const service_name_len = hdr.len - sizeof(*untrusted_params3) - 1; |
1439 | | |
1440 | | /* sanitize start */ |
1441 | 404 | ENSURE_NULL_TERMINATED(untrusted_params3->target_domain); |
1442 | 404 | sanitize_name(untrusted_params3->target_domain, "@:"); |
1443 | 404 | if (!validate_request_id(&untrusted_params3->request_id, "MSG_TRIGGER_SERVICE3")) |
1444 | 228 | goto fail3; |
1445 | 176 | if (untrusted_params3->service_name[service_name_len] != 0) { |
1446 | 11 | LOG(ERROR, "Service name not NUL-terminated"); |
1447 | 11 | goto fail3; |
1448 | 11 | } |
1449 | 165 | size_t const nul_offset = strlen(untrusted_params3->service_name); |
1450 | 165 | if (nul_offset != service_name_len) { |
1451 | 18 | LOG(ERROR, "Service name contains NUL byte at offset %zu", nul_offset); |
1452 | 18 | goto fail3; |
1453 | 18 | } |
1454 | 147 | if (!validate_service_name(untrusted_params3->service_name)) |
1455 | 5 | goto fail3; |
1456 | 142 | params3 = untrusted_params3; |
1457 | 142 | untrusted_params3 = NULL; |
1458 | | /* sanitize end */ |
1459 | | |
1460 | 142 | handle_execute_service(remote_domain_id, remote_domain_name, |
1461 | 142 | params3->target_domain, |
1462 | 142 | params3->service_name, |
1463 | 142 | ¶ms3->request_id); |
1464 | 142 | free(params3); |
1465 | 142 | return; |
1466 | 262 | fail3: |
1467 | 262 | send_service_refused(vchan, &untrusted_params3->request_id); |
1468 | 262 | free(untrusted_params3); |
1469 | 262 | return; |
1470 | 147 | } |
1471 | 97 | case MSG_CONNECTION_TERMINATED: |
1472 | 97 | handle_connection_terminated(); |
1473 | 97 | return; |
1474 | 802 | } |
1475 | 802 | } |
1476 | | |
1477 | | /* qrexec-agent has disconnected, cleanup local state and try to connect again. |
1478 | | * If remote domain dies, terminate qrexec-daemon. |
1479 | | */ |
1480 | 0 | static int handle_agent_restart(int xid) { |
1481 | 0 | size_t i; |
1482 | | |
1483 | | // Stop listening. |
1484 | 0 | unlink_qrexec_socket(); |
1485 | 0 | close(qrexec_daemon_unix_socket_fd); |
1486 | | |
1487 | | /* Close old (dead) vchan connection. */ |
1488 | 0 | libvchan_close(vchan); |
1489 | 0 | vchan = NULL; |
1490 | | |
1491 | | /* Disconnect all local clients. This will look like all the qrexec |
1492 | | * connections were terminated, which isn't necessary true (established |
1493 | | * qrexec connection may survive qrexec-agent and qrexec-daemon restart), |
1494 | | * but we won't be notified about its termination. This may kill DispVM |
1495 | | * prematurely (if anyone restarts qrexec-agent inside DispVM), but it's |
1496 | | * better than the alternative (leaking DispVMs). |
1497 | | * |
1498 | | * But, do not mark related vchan ports as unused. Since we won't get call |
1499 | | * end notification, we don't know when such ports will really be unused. |
1500 | | */ |
1501 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
1502 | 0 | if (clients[i].state != CLIENT_INVALID) |
1503 | 0 | terminate_client(i); |
1504 | 0 | } |
1505 | | |
1506 | | /* Abort pending qrexec requests */ |
1507 | 0 | for (i = 0; i < MAX_CLIENTS; i++) { |
1508 | 0 | if (policy_pending[i].pid != 0) |
1509 | 0 | policy_pending[i].pid = 0; |
1510 | 0 | } |
1511 | 0 | policy_pending_max = -1; |
1512 | | |
1513 | | /* Restore default SIGTERM handling: libvchan_client_init() might block |
1514 | | * indefinitely, so we want the program to be killable. |
1515 | | */ |
1516 | 0 | if (signal(SIGTERM, SIG_DFL) == SIG_ERR) |
1517 | 0 | err(1, "signal()"); |
1518 | 0 | if (terminate_requested) |
1519 | 0 | return -1; |
1520 | | |
1521 | | #ifdef COVERAGE |
1522 | | /* Dump coverage in case we are killed here. */ |
1523 | | __gcov_dump(); |
1524 | | __gcov_reset(); |
1525 | | #endif |
1526 | | |
1527 | 0 | vchan = libvchan_client_init(remote_domain_id, VCHAN_BASE_PORT); |
1528 | 0 | if (!vchan) { |
1529 | 0 | PERROR("cannot connect to qrexec agent"); |
1530 | 0 | return -1; |
1531 | 0 | } |
1532 | 0 | if (handle_agent_hello(vchan, remote_domain_name) < 0) { |
1533 | 0 | libvchan_close(vchan); |
1534 | 0 | vchan = NULL; |
1535 | 0 | return -1; |
1536 | 0 | } |
1537 | 0 | LOG(INFO, "qrexec-agent has reconnected"); |
1538 | |
|
1539 | 0 | struct sigaction action = { |
1540 | 0 | .sa_handler = signal_handler, |
1541 | 0 | .sa_flags = 0, |
1542 | 0 | }; |
1543 | 0 | if (sigaction(SIGTERM, &action, NULL)) |
1544 | 0 | err(1, "sigaction"); |
1545 | | |
1546 | 0 | qrexec_daemon_unix_socket_fd = |
1547 | 0 | create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid); |
1548 | 0 | return 0; |
1549 | 0 | } |
1550 | | |
1551 | | static struct option longopts[] = { |
1552 | | { "help", no_argument, 0, 'h' }, |
1553 | | { "quiet", no_argument, 0, 'q' }, |
1554 | | { "socket-dir", required_argument, 0, 'd' + 128 }, |
1555 | | { "policy-program", required_argument, 0, 'p' }, |
1556 | | { "direct", no_argument, 0, 'D' }, |
1557 | | { "uuid", required_argument, 0, 'u' }, |
1558 | | { NULL, 0, 0, 0 }, |
1559 | | }; |
1560 | | |
1561 | | static _Noreturn void usage(const char *argv0) |
1562 | 0 | { |
1563 | 0 | fprintf(stderr, "usage: %s [options] domainid domain-name [default user]\n", argv0); |
1564 | 0 | fprintf(stderr, "Options:\n"); |
1565 | 0 | fprintf(stderr, " -h, --help - display usage\n"); |
1566 | 0 | fprintf(stderr, " -q, --quiet - quiet mode\n"); |
1567 | 0 | fprintf(stderr, " --socket-dir=PATH - directory for qrexec socket, default: %s\n", |
1568 | 0 | QREXEC_DAEMON_SOCKET_DIR); |
1569 | 0 | fprintf(stderr, " -p, --policy-program=PATH - program to execute to check policy, default: %s\n", |
1570 | 0 | QREXEC_POLICY_PROGRAM); |
1571 | 0 | fprintf(stderr, " -D, --direct - run directly, don't daemonize, log to stderr\n"); |
1572 | 0 | fprintf(stderr, " -u, --uuid=UUID - domain UUID, mandatory\n"); |
1573 | 0 | exit(1); |
1574 | 0 | } |
1575 | | |
1576 | | int main(int argc, char **argv) |
1577 | 0 | { |
1578 | 0 | int i, opt; |
1579 | 0 | sigset_t selectmask; |
1580 | 0 | bool opt_direct = false; |
1581 | |
|
1582 | 0 | { |
1583 | 0 | int null_fd = open("/dev/null", O_RDONLY|O_NOCTTY); |
1584 | 0 | if (null_fd < 0) |
1585 | 0 | err(1, "open(%s)", "/dev/null"); |
1586 | 0 | if (dup2(null_fd, 0) != 0) |
1587 | 0 | err(1, "dup2(%d, 0)", null_fd); |
1588 | 0 | if (close(null_fd) != 0) |
1589 | 0 | err(1, "close(%d)", null_fd); |
1590 | 0 | } |
1591 | | |
1592 | 0 | setup_logging("qrexec-daemon"); |
1593 | |
|
1594 | 0 | while ((opt=getopt_long(argc, argv, "hqp:Du:", longopts, NULL)) != -1) { |
1595 | 0 | switch (opt) { |
1596 | 0 | case 'q': |
1597 | 0 | opt_quiet = 1; |
1598 | 0 | break; |
1599 | 0 | case 'd' + 128: |
1600 | 0 | if ((socket_dir = strdup(optarg)) == NULL) |
1601 | 0 | err(1, "strdup()"); |
1602 | 0 | break; |
1603 | 0 | case 'p': |
1604 | 0 | if ((policy_program = strdup(optarg)) == NULL) |
1605 | 0 | err(1, "strdup()"); |
1606 | 0 | break; |
1607 | 0 | case 'D': |
1608 | 0 | opt_direct = 1; |
1609 | 0 | break; |
1610 | 0 | case 'u': |
1611 | 0 | remote_domain_uuid = optarg; |
1612 | 0 | break; |
1613 | 0 | case 'h': |
1614 | 0 | default: /* '?' */ |
1615 | 0 | usage(argv[0]); |
1616 | 0 | } |
1617 | 0 | } |
1618 | 0 | if (argc - optind < 2 || argc - optind > 3) { |
1619 | 0 | usage(argv[0]); |
1620 | 0 | } |
1621 | 0 | if (!remote_domain_uuid) { |
1622 | 0 | fprintf(stderr, "Domain UUID option missing!\n"); |
1623 | 0 | usage(argv[0]); |
1624 | 0 | } |
1625 | 0 | remote_domain_id = atoi(argv[optind]); |
1626 | 0 | remote_domain_name = argv[optind+1]; |
1627 | 0 | if (argc - optind >= 3) |
1628 | 0 | default_user = argv[optind+2]; |
1629 | 0 | init(remote_domain_id, opt_direct); |
1630 | |
|
1631 | 0 | sigemptyset(&selectmask); |
1632 | 0 | sigaddset(&selectmask, SIGCHLD); |
1633 | 0 | sigprocmask(SIG_BLOCK, &selectmask, NULL); |
1634 | 0 | sigemptyset(&selectmask); |
1635 | | |
1636 | | /* |
1637 | | * The main event loop. Waits for one of the following events: |
1638 | | * - message from client |
1639 | | * - message from agent |
1640 | | * - new client |
1641 | | * - child exited |
1642 | | */ |
1643 | 0 | while (!terminate_requested) { |
1644 | 0 | struct timespec timeout = { 1, 0 }; |
1645 | 0 | int ret; |
1646 | |
|
1647 | 0 | if (child_exited) |
1648 | 0 | reap_children(); |
1649 | |
|
1650 | 0 | size_t nfds = 0; |
1651 | 0 | struct pollfd fds[MAX_CLIENTS + 2]; |
1652 | 0 | fds[nfds++] = (struct pollfd) { libvchan_fd_for_select(vchan), POLLIN | POLLHUP, 0 }; |
1653 | 0 | if (libvchan_buffer_space(vchan) > (int)sizeof(struct msg_header)) { |
1654 | 0 | assert(max_client_fd < MAX_CLIENTS); |
1655 | | // vchan not full, read from clients |
1656 | 0 | fds[nfds++] = (struct pollfd) { qrexec_daemon_unix_socket_fd, POLLIN | POLLHUP, 0 }; |
1657 | 0 | for (i = 0; i <= max_client_fd; i++) { |
1658 | 0 | if (clients[i].state != CLIENT_INVALID) |
1659 | 0 | fds[nfds++] = (struct pollfd) { i, POLLIN | POLLHUP, 0 }; |
1660 | 0 | } |
1661 | 0 | } |
1662 | | |
1663 | 0 | ret = ppoll_vchan(vchan, fds, nfds, &timeout, &selectmask); |
1664 | 0 | if (ret < 0) { |
1665 | 0 | if (errno == EINTR) |
1666 | 0 | continue; |
1667 | 0 | PERROR("ppoll"); |
1668 | 0 | return 1; |
1669 | 0 | } |
1670 | | |
1671 | 0 | if (!libvchan_is_open(vchan)) { |
1672 | 0 | LOG(WARNING, "qrexec-agent has disconnected"); |
1673 | 0 | if (handle_agent_restart(remote_domain_id) < 0) { |
1674 | 0 | LOG(ERROR, "Failed to reconnect to qrexec-agent, terminating"); |
1675 | 0 | return 1; |
1676 | 0 | } |
1677 | | /* rdset may be outdated at this point, calculate it again. */ |
1678 | 0 | continue; |
1679 | 0 | } |
1680 | | |
1681 | 0 | if (nfds > 1 && fds[1].revents) |
1682 | 0 | handle_new_client(); |
1683 | |
|
1684 | 0 | while (libvchan_data_ready(vchan)) |
1685 | 0 | handle_message_from_agent(); |
1686 | |
|
1687 | 0 | for (size_t i = 2; i < nfds; i++) { |
1688 | 0 | if (fds[i].revents) |
1689 | 0 | handle_message_from_client(fds[i].fd); |
1690 | 0 | } |
1691 | 0 | } |
1692 | | |
1693 | 0 | if (vchan) |
1694 | 0 | libvchan_close(vchan); |
1695 | |
|
1696 | 0 | return 0; |
1697 | 0 | } |
1698 | | |
1699 | | // vim:ts=4:sw=4:et: |