/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 | } |