Coverage Report

Created: 2023-09-25 06:33

/src/qubes-os/qubes-core-qrexec/libqrexec/ioall.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 <limits.h>
23
#include <stdio.h>
24
#include <stdlib.h>
25
#include <fcntl.h>
26
#include <errno.h>
27
#include <assert.h>
28
#include <string.h>
29
30
#include <unistd.h>
31
#include <sys/socket.h>
32
#include <sys/un.h>
33
#include "libqrexec-utils.h"
34
#include "ioall.h"
35
36
0
#define QUBESD_SOCK "/run/qubesd.sock"
37
38
void set_nonblock(int fd)
39
0
{
40
0
    int fl = fcntl(fd, F_GETFL, 0);
41
0
    if (fl < 0 && errno == EBADF)
42
0
        abort();
43
0
    if (fl & O_NONBLOCK)
44
0
        return;
45
0
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
46
0
}
47
48
void set_block(int fd)
49
0
{
50
0
    int fl = fcntl(fd, F_GETFL, 0);
51
0
    if (!(fl & O_NONBLOCK))
52
0
        return;
53
0
    fcntl(fd, F_SETFL, fl & ~O_NONBLOCK);
54
0
}
55
56
int write_all(int fd, const void *buf, int size)
57
1.34k
{
58
1.34k
    int written = 0;
59
1.34k
    int ret;
60
1.71k
    while (written < size) {
61
366
        ret = write(fd, (char *) buf + written, size - written);
62
366
        if (ret == -1 && errno == EINTR)
63
0
            continue;
64
366
        if (ret <= 0) {
65
0
            return 0;
66
0
        }
67
366
        written += ret;
68
366
    }
69
    //      fprintf(stderr, "sent %d bytes\n", size);
70
1.34k
    return 1;
71
1.34k
}
72
73
int read_all(int fd, void *buf, int size)
74
0
{
75
0
    int got_read = 0;
76
0
    int ret;
77
0
    while (got_read < size) {
78
0
        ret = read(fd, (char *) buf + got_read, size - got_read);
79
0
        if (ret == -1 && errno == EINTR)
80
0
            continue;
81
0
        if (ret == 0) {
82
0
            errno = 0;
83
0
            LOG(INFO, "EOF");
84
0
            return 0;
85
0
        }
86
0
        if (ret < 0) {
87
0
            if (errno != EAGAIN)
88
0
                PERROR("read");
89
0
            return 0;
90
0
        }
91
0
        if (got_read == 0) {
92
            // force blocking operation on further reads
93
0
            set_block(fd);
94
0
        }
95
0
        got_read += ret;
96
0
    }
97
    //      fprintf(stderr, "read %d bytes\n", size);
98
0
    return 1;
99
0
}
100
101
// FIXME: this code is seemingly-correct but needs careful review
102
void *qubes_read_all_to_malloc(int fd, size_t initial_buffer_size, size_t max_bytes, size_t *len)
103
0
{
104
0
    size_t buf_size = initial_buffer_size;
105
0
    size_t offset = 0;
106
#if PTRDIFF_MAX < INT_MAX
107
#error unsupported platform
108
#endif
109
0
    if (max_bytes > (size_t)INT_MAX) {
110
0
        LOG(ERROR,
111
0
            "Maximum buffer size %zu exceeds INT_MAX (%d)",
112
0
            max_bytes,
113
0
            INT_MAX);
114
0
        abort();
115
0
    }
116
0
    if (buf_size < 2 || buf_size > max_bytes) {
117
0
        LOG(ERROR,
118
0
            "Minimum buffer size must between 2 and maximum buffer size (%zu) inclusive, got %zu",
119
0
            max_bytes,
120
0
            buf_size);
121
0
        abort();
122
0
    }
123
0
    char *buf = malloc(buf_size);
124
0
    if (buf == NULL) {
125
0
        LOG(ERROR, "malloc() for %zu bytes failed!", buf_size);
126
0
        abort();
127
0
    }
128
0
    *len = 0;
129
0
    for (;;) {
130
0
        size_t to_read = buf_size - offset;
131
0
        ssize_t res = read(fd, buf + offset, to_read);
132
0
        if (res < 0) {
133
            // save errno as PERROR() and free() might clobber it
134
0
            int e = errno;
135
0
            if (res != -1)
136
0
                abort(); // kernel bug or some sort of corruption
137
0
            if (e == EINTR || e == EAGAIN || e == EWOULDBLOCK)
138
0
                continue;
139
0
            PERROR("recv");
140
0
            free(buf);
141
0
            errno = e;
142
0
            buf = NULL;
143
0
            break;
144
0
        }
145
0
        size_t const bytes_read_this_time = (size_t)res;
146
0
        if (bytes_read_this_time == 0) {
147
            /* EOF */
148
0
            buf[offset] = 0;
149
0
            *len = offset;
150
0
            break;
151
0
        }
152
0
        if (bytes_read_this_time > to_read)
153
0
            abort(); // kernel bug or some sort of corruption
154
0
        if (bytes_read_this_time == to_read) {
155
            /* Buffer full.  See if a new buffer can be allocated. */
156
0
            if (buf_size >= max_bytes) {
157
                /* Nope, limit reached. */
158
0
                LOG(ERROR, "Too many bytes read (limit %zu)", max_bytes - 1);
159
0
                free(buf);
160
0
                errno = ENOBUFS;
161
0
                buf = NULL;
162
0
                break;
163
0
            }
164
            /* Grow by a factor of 1.5 if possible, but do not exceed the buffer limit. */
165
0
            if (max_bytes - buf_size > buf_size / 2)
166
0
                buf_size += buf_size / 2;
167
0
            else
168
0
                buf_size = max_bytes;
169
0
            char *new_buf = realloc(buf, buf_size);
170
0
            if (new_buf == NULL) {
171
                /*
172
                 * Out of memory!  While calling abort() here would be acceptable,
173
                 * callers need to handle a NULL return _anyway_, so propagating
174
                 * the error will not make callers more complex.  Therefore, free
175
                 * the buffer, set errno to ENOMEM, and return NULL.
176
                 */
177
0
                PERROR("realloc()");
178
0
                free(buf);
179
0
                errno = ENOMEM;
180
0
                buf = NULL;
181
0
                break;
182
0
            } else {
183
                /*
184
                 * Old buffer has been freed, so using it is undefined behavior.
185
                 * Overwrite the pointer to it with the pointer to the new buffer.
186
                 */
187
0
                buf = new_buf;
188
0
            }
189
0
        }
190
191
        /* Advance the offset past the already-read bytes */
192
0
        offset += bytes_read_this_time;
193
0
    }
194
0
    close(fd);
195
0
    return buf;
196
0
}
197
198
int copy_fd_all(int fdout, int fdin)
199
0
{
200
0
    int ret;
201
0
    char buf[4096];
202
0
    for (;;) {
203
0
        ret = read(fdin, buf, sizeof(buf));
204
0
        if (ret == -1 && errno == EINTR)
205
0
            continue;
206
0
        if (!ret)
207
0
            break;
208
0
        if (ret < 0) {
209
0
            PERROR("read");
210
0
            return 0;
211
0
        }
212
0
        if (!write_all(fdout, buf, ret)) {
213
0
            PERROR("write");
214
0
            return 0;
215
0
        }
216
0
    }
217
0
    return 1;
218
0
}
219
220
bool qubes_sendmsg_all(struct msghdr *const msg, int const sock)
221
0
{
222
0
    while (msg->msg_iovlen) {
223
0
        ssize_t const res = sendmsg(sock, msg, MSG_NOSIGNAL);
224
0
        if (res < 0) {
225
0
            int const i = errno;
226
0
            assert(res == -1);
227
0
            if (i == EAGAIN || i == EWOULDBLOCK || i == EINTR)
228
0
                continue;
229
0
            LOG(ERROR, "sendmsg(): %m");
230
0
            errno = i;
231
0
            return false;
232
0
        }
233
234
0
        size_t unsigned_res = (size_t)res;
235
0
        while (unsigned_res) {
236
0
            assert(msg->msg_iovlen > 0);
237
0
            struct iovec *const v = msg->msg_iov;
238
0
            if (unsigned_res < v->iov_len) {
239
0
                v->iov_base += unsigned_res;
240
0
                v->iov_len -= unsigned_res;
241
0
                break;
242
0
            }
243
0
            unsigned_res -= msg->msg_iov[0].iov_len;
244
0
            msg->msg_iovlen--;
245
0
            msg->msg_iov++;
246
0
        }
247
0
    }
248
0
    return true;
249
0
}
250
251
char *qubesd_call(const char *dest, char *method, char *arg, size_t *len)
252
0
{
253
0
    char *buf = NULL;
254
0
    char *word;
255
0
    int sock = -1;
256
0
    size_t wordlen;
257
0
    if (dest[0] == '@') {
258
0
        word = " dom0 keyword ";
259
0
        wordlen = sizeof(" dom0 keyword ") - 1;
260
0
        dest++;
261
        // assert(valid_keyword(dest + 1));
262
0
    } else {
263
0
        word = " dom0 name ";
264
0
        wordlen = sizeof(" dom0 name ") - 1;
265
        // assert(valid_qube_name(dest));
266
0
    }
267
268
0
    char plus[1] = {'+'};
269
0
    struct iovec v[] = {
270
0
        { .iov_base = method, .iov_len = strlen(method) },
271
0
        { .iov_base = plus, .iov_len = sizeof plus },
272
0
        { .iov_base = arg, .iov_len = arg ? strlen(arg) : 0 },
273
0
        { .iov_base = word, .iov_len = wordlen },
274
0
        { .iov_base = (void *)dest, .iov_len = strlen(dest) + 1 },
275
0
    };
276
277
0
    struct sockaddr_un qubesd_sock = {
278
0
        .sun_family = AF_UNIX,
279
0
        .sun_path = QUBESD_SOCK,
280
0
    };
281
282
0
    sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
283
0
    if (sock < 0) {
284
0
        int i = errno;
285
0
        PERROR("socket");
286
0
        errno = i;
287
0
        goto out;
288
0
    }
289
290
0
    if (connect(sock,
291
0
                (struct sockaddr *)&qubesd_sock,
292
0
                offsetof(struct sockaddr_un, sun_path) + sizeof(QUBESD_SOCK))) {
293
0
        LOG(ERROR, "connect(): %m");
294
0
        goto out;
295
0
    }
296
297
0
    struct msghdr msg = {
298
0
        .msg_name = NULL,
299
0
        .msg_namelen = 0,
300
0
        .msg_iov = v,
301
0
        .msg_iovlen = sizeof(v)/sizeof(v[0]),
302
0
        .msg_control = NULL,
303
0
        .msg_controllen = 0,
304
0
        .msg_flags = 0,
305
0
    };
306
307
0
    if (!qubes_sendmsg_all(&msg, sock))
308
0
        goto out;
309
310
0
    if (shutdown(sock, SHUT_WR)) {
311
0
        PERROR("shutdown()");
312
0
        goto out;
313
0
    }
314
315
0
#define BUF_SIZE 35
316
0
#define BUF_MAX 65535
317
0
    buf = qubes_read_all_to_malloc(sock, BUF_SIZE, BUF_MAX, len);
318
0
    if (buf && (*len < 2 || strlen(buf) >= *len)) {
319
0
        LOG(ERROR,
320
0
            "Truncated response to %s: got %zu bytes",
321
0
            method,
322
0
            *len);
323
0
        *len = 0;
324
0
        free(buf);
325
0
        buf = NULL;
326
0
    }
327
0
out:
328
0
    if (sock != -1)
329
0
        close(sock);
330
0
    return buf;
331
0
}