Coverage Report

Created: 2024-02-25 06:27

/src/qubes-os/qubes-core-qrexec/libqrexec/exec.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 <stdlib.h>
23
#include <stdio.h>
24
#include <string.h>
25
#include <assert.h>
26
#include <errno.h>
27
#include <stddef.h>
28
#include <limits.h>
29
30
#include <sys/socket.h>
31
#include <sys/types.h>
32
#include <sys/stat.h>
33
#include <sys/un.h>
34
#include <sys/wait.h>
35
#include <unistd.h>
36
#include <fcntl.h>
37
#include "qrexec.h"
38
#include "libqrexec-utils.h"
39
40
static do_exec_t *exec_func = NULL;
41
0
void register_exec_func(do_exec_t *func) {
42
0
    if (exec_func != NULL)
43
0
        abort();
44
0
    exec_func = func;
45
0
}
46
47
0
void exec_qubes_rpc_if_requested(const char *prog, char *const envp[]) {
48
    /* avoid calling qubes-rpc-multiplexer through shell */
49
0
    if (strncmp(prog, RPC_REQUEST_COMMAND, RPC_REQUEST_COMMAND_LEN) == 0) {
50
0
        char *prog_copy;
51
0
        char *tok, *savetok;
52
0
        char *argv[16]; // right now 6 are used, but allow future extensions
53
0
        size_t i = 0;
54
55
0
        prog_copy = strdup(prog);
56
0
        if (!prog_copy) {
57
0
            PERROR("strdup");
58
0
            _exit(1);
59
0
        }
60
61
0
        tok=strtok_r(prog_copy, " ", &savetok);
62
0
        do {
63
0
            if (i >= sizeof(argv)/sizeof(argv[0])-1) {
64
0
                LOG(ERROR, "To many arguments to %s", RPC_REQUEST_COMMAND);
65
0
                exit(1);
66
0
            }
67
0
            argv[i++] = tok;
68
0
        } while ((tok=strtok_r(NULL, " ", &savetok)));
69
0
        argv[i] = NULL;
70
71
0
        argv[0] = getenv("QREXEC_MULTIPLEXER_PATH");
72
0
        if (!argv[0])
73
0
            argv[0] = QUBES_RPC_MULTIPLEXER_PATH;
74
0
        execve(argv[0], argv, envp);
75
0
        PERROR("exec qubes-rpc-multiplexer");
76
0
        _exit(126);
77
0
    }
78
0
}
79
80
void fix_fds(int fdin, int fdout, int fderr)
81
0
{
82
0
    int i;
83
0
    for (i = 3; i < 256; i++)
84
0
        if (i != fdin && i != fdout && i != fderr)
85
0
            close(i);
86
0
    if (dup2(fdin, 0) < 0 || dup2(fdout, 1) < 0 || dup2(fderr, 2) < 0) {
87
0
        PERROR("dup2");
88
0
        abort();
89
0
    }
90
91
0
    if (close(fdin) || (fdin != fdout && close(fdout)) ||
92
0
        (fdin != fderr && fdout != fderr && fderr != 2 && close(fderr))) {
93
0
        PERROR("close");
94
0
        abort();
95
0
    }
96
0
}
97
98
static int do_fork_exec(const char *user,
99
        const char *cmdline,
100
        int *pid,
101
        int *stdin_fd,
102
        int *stdout_fd,
103
        int *stderr_fd)
104
0
{
105
0
    int inpipe[2], outpipe[2], errpipe[2], statuspipe[2], retval;
106
#ifndef SOCK_CLOEXEC
107
#define SOCK_CLOEXEC 0
108
#endif
109
0
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, inpipe) ||
110
0
            socketpair(AF_UNIX, SOCK_STREAM, 0, outpipe) ||
111
0
            (stderr_fd && socketpair(AF_UNIX, SOCK_STREAM, 0, errpipe)) ||
112
0
            socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, statuspipe)) {
113
0
        PERROR("socketpair");
114
0
        exit(1);
115
0
    }
116
0
    switch (*pid = fork()) {
117
0
        case -1:
118
0
            PERROR("fork");
119
0
            exit(-1);
120
0
        case 0: {
121
0
            int status;
122
0
            if (signal(SIGPIPE, SIG_DFL) == SIG_ERR)
123
0
                abort();
124
0
            if (stderr_fd) {
125
0
                fix_fds(inpipe[0], outpipe[1], errpipe[1]);
126
0
            } else
127
0
                fix_fds(inpipe[0], outpipe[1], 2);
128
129
0
            close(statuspipe[0]);
130
0
            if (SOCK_CLOEXEC == (0)) {
131
0
                status = fcntl(statuspipe[1], F_GETFD);
132
0
                fcntl(statuspipe[1], F_SETFD, status | FD_CLOEXEC);
133
0
            }
134
0
            if (exec_func != NULL)
135
0
                exec_func(cmdline, user);
136
0
            else
137
0
                abort();
138
0
            status = errno;
139
0
            while (write(statuspipe[1], &status, sizeof status) <= 0) {}
140
0
            exit(-1);
141
0
        }
142
0
        default: {
143
0
            close(statuspipe[1]);
144
0
            if (read(statuspipe[0], &retval, sizeof retval) == sizeof retval) {
145
0
                siginfo_t siginfo;
146
0
                memset(&siginfo, 0, sizeof siginfo);
147
0
                waitid(P_PID, *pid, &siginfo, WEXITED); // discard result
148
0
            } else {
149
0
                retval = 0;
150
0
            }
151
0
        }
152
0
    }
153
0
    close(inpipe[0]);
154
0
    close(outpipe[1]);
155
0
    *stdin_fd = inpipe[1];
156
0
    *stdout_fd = outpipe[0];
157
0
    if (stderr_fd) {
158
0
        close(errpipe[1]);
159
0
        *stderr_fd = errpipe[0];
160
0
    }
161
0
    return retval;
162
0
}
163
164
#define QUBES_SOCKADDR_UN_MAX_PATH_LEN 1024
165
166
0
static int qubes_connect(int s, const char *connect_path, const size_t total_path_length) {
167
    // Avoiding an extra copy is NOT worth it!
168
0
#define QUBES_TMP_DIRECTORY "/tmp/qrexec-XXXXXX"
169
0
    char buf[] = QUBES_TMP_DIRECTORY "\0qrexec-socket";
170
0
    struct sockaddr_un remote = { .sun_family = AF_UNIX, .sun_path = { '\0' } };
171
0
    static_assert(sizeof buf <= sizeof remote.sun_path,
172
0
                  "maximum path length of AF_UNIX sockets too small");
173
0
    static const size_t path_separator_offset = sizeof QUBES_TMP_DIRECTORY - 1;
174
0
    int result = -1, dummy_errno = -1;
175
0
    socklen_t socket_len;
176
0
    if (sizeof remote.sun_path <= total_path_length) {
177
        // sockaddr_un too small :(
178
0
        if (NULL == mkdtemp(buf))
179
0
            return -1;
180
0
        buf[path_separator_offset] = '/';
181
0
        if (symlink(connect_path, buf)) {
182
0
           dummy_errno = errno;
183
0
           goto out;
184
0
        }
185
0
        memcpy(remote.sun_path, buf, sizeof buf);
186
0
        socket_len = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + sizeof buf);
187
0
    } else {
188
0
        memcpy(remote.sun_path, connect_path, total_path_length);
189
0
        remote.sun_path[total_path_length] = '\0';
190
0
        socket_len = (socklen_t)(offsetof(struct sockaddr_un, sun_path) + total_path_length + 1);
191
0
    }
192
193
0
    do
194
0
       result = connect(s, (struct sockaddr *) &remote, socket_len);
195
0
    while (result < 0 && (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN));
196
0
    dummy_errno = errno;
197
0
out:
198
0
    if (buf[path_separator_offset] == '/') {
199
0
        unlink(buf);
200
0
        buf[path_separator_offset] = '\0';
201
0
        rmdir(buf);
202
0
    }
203
0
    errno = dummy_errno;
204
0
    return result;
205
0
}
206
207
static int execute_qrexec_service(
208
    const struct qrexec_parsed_command *cmd,
209
    int *pid, int *stdin_fd, int *stdout_fd, int *stderr_fd,
210
    struct buffer *stdin_buffer);
211
212
/*
213
  Find a file in the ':'-delimited list of paths given in path_list.
214
  Returns 0 on success, -1 on failure.
215
  On success, fills buffer and statbuf (unless statbuf is NULL).
216
 */
217
static int find_file(
218
    const char *path_list,
219
    const char *name,
220
    char *buffer,
221
    size_t buffer_size,
222
0
    struct stat *statbuf) {
223
224
0
    struct stat dummy_buf;
225
0
    const char *path_start = path_list;
226
0
    size_t name_length = strlen(name);
227
228
0
    if (!statbuf)
229
0
        statbuf = &dummy_buf;
230
231
0
    while (*path_start) {
232
        /* Find next path (up to ':') */
233
0
        const char *path_end = strchrnul(path_start, ':');
234
0
        size_t path_length = (size_t)(path_end - path_start);
235
236
0
        if (path_length + name_length + 1 >= buffer_size) {
237
0
            LOG(ERROR, "find_qrexec_service_file: buffer too small for file path");
238
0
            return -1;
239
0
        }
240
241
0
        memcpy(buffer, path_start, path_length);
242
0
        buffer[path_length] = '/';
243
0
        strcpy(buffer + path_length + 1, name);
244
        //LOG(INFO, "stat(%s)", buffer);
245
0
        if (stat(buffer, statbuf) == 0)
246
0
            return 0;
247
248
0
        path_start = path_end;
249
0
        while (*path_start == ':')
250
0
            path_start++;
251
0
    }
252
0
    return -1;
253
0
}
254
int load_service_config(const struct qrexec_parsed_command *cmd,
255
0
                        int *wait_for_session, char **user) {
256
0
    assert(cmd->service_descriptor);
257
0
    assert(*user == NULL);
258
259
0
    const char *config_path = getenv("QUBES_RPC_CONFIG_PATH");
260
0
    if (!config_path)
261
0
        config_path = QUBES_RPC_CONFIG_PATH;
262
263
0
    char config_full_path[256];
264
265
0
    int ret = find_file(config_path, cmd->service_descriptor,
266
0
                        config_full_path, sizeof(config_full_path), NULL);
267
0
    if (ret < 0 && strcmp(cmd->service_descriptor, cmd->service_name) != 0)
268
0
        ret = find_file(config_path, cmd->service_name,
269
0
                        config_full_path, sizeof(config_full_path), NULL);
270
0
    if (ret < 0)
271
0
        return 0;
272
273
0
    return qubes_toml_config_parse(config_full_path, wait_for_session, user);
274
0
}
275
276
struct qrexec_parsed_command *parse_qubes_rpc_command(
277
207
    const char *cmdline, bool strip_username) {
278
279
207
    struct qrexec_parsed_command *cmd;
280
281
207
    if (!(cmd = malloc(sizeof(*cmd)))) {
282
0
        PERROR("malloc");
283
0
        return NULL;
284
0
    }
285
286
207
    memset(cmd, 0, sizeof(*cmd));
287
207
    cmd->cmdline = cmdline;
288
289
207
    if (strip_username) {
290
207
        const char *colon = strchr(cmdline, ':');
291
207
        if (!colon) {
292
1
            LOG(ERROR, "Bad command from dom0 (%s): no colon", cmdline);
293
1
            goto err;
294
1
        }
295
206
        cmd->username = strndup(cmdline, (size_t)(colon - cmdline));
296
206
        if (!cmd->username) {
297
0
            PERROR("strndup");
298
0
            goto err;
299
0
        }
300
206
        cmd->command = colon + 1;
301
206
    } else
302
0
        cmd->command = cmdline;
303
304
206
    if (strncmp(cmd->command, NOGUI_CMD_PREFIX, NOGUI_CMD_PREFIX_LEN) == 0) {
305
1
        cmd->nogui = true;
306
1
        cmd->command += NOGUI_CMD_PREFIX_LEN;
307
1
    } else
308
205
        cmd->nogui = false;
309
310
    /* If the command starts with "QUBESRPC ", parse service descriptor */
311
206
    if (strncmp(cmd->command, RPC_REQUEST_COMMAND " ",
312
206
                RPC_REQUEST_COMMAND_LEN + 1) == 0) {
313
60
        const char *start, *end;
314
315
        /* Parse service descriptor ("qubes.Service+arg") */
316
317
60
        start = cmd->command + RPC_REQUEST_COMMAND_LEN + 1;
318
60
        end = strchr(start, ' ');
319
60
        if (!end) {
320
1
            LOG(ERROR, "No space found after service descriptor");
321
1
            goto err;
322
1
        }
323
324
59
        if (end - start > MAX_SERVICE_NAME_LEN) {
325
0
            LOG(ERROR, "Command too long (length %zu)",
326
0
                end - start);
327
0
            goto err;
328
0
        }
329
330
59
        cmd->service_descriptor = strndup(start, end - start);
331
59
        if (!cmd->service_descriptor) {
332
0
            PERROR("strndup");
333
0
            goto err;
334
0
        }
335
336
        /* Parse service name ("qubes.Service") */
337
338
59
        const char *plus = memchr(start, '+', end - start);
339
59
        if (plus) {
340
43
            if (plus - start == 0) {
341
1
                LOG(ERROR, "Service name empty");
342
1
                goto err;
343
1
            }
344
42
            if (plus - start > NAME_MAX) {
345
11
                LOG(ERROR, "Service name too long to execute (length %zu)",
346
11
                    plus - start);
347
11
                goto err;
348
11
            }
349
31
            cmd->service_name = strndup(start, plus - start);
350
31
            if (!cmd->service_name) {
351
0
                PERROR("strndup");
352
0
                goto err;
353
0
            }
354
31
        } else {
355
16
            cmd->service_name = strndup(start, end - start);
356
16
            if (!cmd->service_name) {
357
0
                PERROR("strdup");
358
0
                goto err;
359
0
            }
360
16
        }
361
362
        /* Parse source domain */
363
364
47
        start = end + 1; /* after the space */
365
47
        end = strchrnul(start, ' ');
366
47
        cmd->source_domain = strndup(start, end - start);
367
47
        if (!cmd->source_domain) {
368
0
            PERROR("strndup");
369
0
            goto err;
370
0
        }
371
47
    }
372
373
193
    return cmd;
374
375
14
err:
376
14
    destroy_qrexec_parsed_command(cmd);
377
14
    return NULL;
378
206
}
379
380
207
void destroy_qrexec_parsed_command(struct qrexec_parsed_command *cmd) {
381
207
    if (cmd == NULL)
382
0
        return;
383
207
    if (cmd->username)
384
206
        free(cmd->username);
385
207
    if (cmd->service_descriptor)
386
59
        free(cmd->service_descriptor);
387
207
    if (cmd->service_name)
388
47
        free(cmd->service_name);
389
207
    if (cmd->source_domain)
390
47
        free(cmd->source_domain);
391
207
    free(cmd);
392
207
}
393
394
int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
395
0
        int *stdout_fd, int *stderr_fd, bool strip_username, struct buffer *stdin_buffer) {
396
397
0
    struct qrexec_parsed_command *cmd;
398
0
    int ret;
399
400
0
    if (!(cmd = parse_qubes_rpc_command(cmdline, strip_username))) {
401
0
        LOG(ERROR, "Could not parse command line: %s", cmdline);
402
0
        return -1;
403
0
    }
404
405
0
    ret = execute_parsed_qubes_rpc_command(cmd, pid, stdin_fd,
406
0
                                           stdout_fd, stderr_fd, stdin_buffer);
407
408
0
    destroy_qrexec_parsed_command(cmd);
409
0
    return ret;
410
0
}
411
412
int execute_parsed_qubes_rpc_command(
413
        const struct qrexec_parsed_command *cmd, int *pid, int *stdin_fd,
414
0
        int *stdout_fd, int *stderr_fd, struct buffer *stdin_buffer) {
415
0
    if (cmd->service_descriptor) {
416
        // Proper Qubes RPC call
417
0
        return execute_qrexec_service(
418
0
            cmd, pid, stdin_fd, stdout_fd, stderr_fd, stdin_buffer);
419
0
    } else {
420
        // Legacy qrexec behavior: spawn shell directly
421
0
        return do_fork_exec(cmd->username, cmd->command,
422
0
                           pid, stdin_fd, stdout_fd, stderr_fd);
423
0
    }
424
0
}
425
426
static int execute_qrexec_service(
427
        const struct qrexec_parsed_command *cmd,
428
        int *pid, int *stdin_fd, int *stdout_fd, int *stderr_fd,
429
0
        struct buffer *stdin_buffer) {
430
431
0
    assert(cmd->service_descriptor);
432
433
0
    const char *qrexec_service_path = getenv("QREXEC_SERVICE_PATH");
434
0
    if (!qrexec_service_path)
435
0
        qrexec_service_path = QREXEC_SERVICE_PATH;
436
437
0
    char service_full_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
438
0
    struct stat statbuf;
439
440
0
    int ret = find_file(qrexec_service_path, cmd->service_descriptor,
441
0
                        service_full_path, sizeof(service_full_path),
442
0
                        &statbuf);
443
0
    if (ret < 0 && strcmp(cmd->service_descriptor, cmd->service_name) != 0)
444
0
        ret = find_file(qrexec_service_path, cmd->service_name,
445
0
                        service_full_path, sizeof(service_full_path),
446
0
                        &statbuf);
447
0
    if (ret < 0) {
448
0
        LOG(ERROR, "Service not found: %s",
449
0
            cmd->service_descriptor);
450
0
        return -1;
451
0
    }
452
453
0
    if (S_ISSOCK(statbuf.st_mode)) {
454
        /* Socket-based service. */
455
0
        int s;
456
0
        if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
457
0
            PERROR("socket");
458
0
            return -1;
459
0
        }
460
0
        if (qubes_connect(s, service_full_path, strlen(service_full_path))) {
461
0
            PERROR("qubes_connect");
462
0
            close(s);
463
0
            return -1;
464
0
        }
465
466
0
        *stdout_fd = *stdin_fd = s;
467
0
        if (stderr_fd)
468
0
            *stderr_fd = -1;
469
0
        *pid = 0;
470
0
        set_nonblock(s);
471
472
        /* send part after "QUBESRPC ", including trailing NUL */
473
0
        const char *desc = cmd->command + RPC_REQUEST_COMMAND_LEN + 1;
474
0
        buffer_append(stdin_buffer, desc, strlen(desc) + 1);
475
0
        return 0;
476
0
    }
477
478
0
    if (euidaccess(service_full_path, X_OK) == 0) {
479
        /*
480
          Executable-based service.
481
482
          Note that this delegates to qubes-rpc-multiplexer, which, for the
483
          moment, searches for the right file again.
484
        */
485
0
        return do_fork_exec(cmd->username, cmd->command,
486
0
                            pid, stdin_fd, stdout_fd, stderr_fd);
487
0
    }
488
489
0
    LOG(ERROR, "Unknown service type (not executable, not a socket): %s",
490
0
        service_full_path);
491
0
    return -1;
492
0
}
493
494
0
int exec_wait_for_session(const char *source_domain) {
495
0
    const char *qrexec_service_path = getenv("QREXEC_SERVICE_PATH");
496
0
    if (!qrexec_service_path)
497
0
        qrexec_service_path = QREXEC_SERVICE_PATH;
498
499
0
    const char *service_name = "qubes.WaitForSession";
500
501
0
    char service_full_path[256];
502
503
0
    int ret = find_file(qrexec_service_path, service_name,
504
0
                        service_full_path, sizeof(service_full_path), NULL);
505
0
    if (ret < 0) {
506
0
        LOG(ERROR, "Service not found: %s", service_name);
507
0
        return -1;
508
0
    }
509
510
0
    setenv("QREXEC_REMOTE_DOMAIN", source_domain, 1);
511
0
    return execl(service_full_path, service_name, NULL);
512
0
}
513
// vim: set sw=4 ts=4 sts=4 et: