/src/qubes-os/qubes-core-qrexec/libqrexec/remote.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * The Qubes OS Project, http://www.qubes-os.org |
3 | | * |
4 | | * Copyright (C) 2020 Paweł Marczewski <pawel@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 <string.h> |
23 | | #include <stdlib.h> |
24 | | #include <stdio.h> |
25 | | #include <inttypes.h> |
26 | | #include <unistd.h> |
27 | | #include <errno.h> |
28 | | #include <sys/socket.h> |
29 | | #include <limits.h> |
30 | | #include <assert.h> |
31 | | |
32 | | #include "libqrexec-utils.h" |
33 | | |
34 | | int handle_remote_data( |
35 | | libvchan_t *data_vchan, int stdin_fd, int *status, |
36 | | struct buffer *stdin_buf, int data_protocol_version, |
37 | | bool replace_chars_stdout, bool replace_chars_stderr, bool is_service) |
38 | 421 | { |
39 | 421 | struct msg_header hdr; |
40 | 421 | const size_t max_len = max_data_chunk_size(data_protocol_version); |
41 | 421 | char *buf; |
42 | 421 | int rc = REMOTE_ERROR; |
43 | 421 | bool msg_data_warned = false; |
44 | | |
45 | | /* do not receive any data if we have something already buffered */ |
46 | 421 | switch (flush_client_data(stdin_fd, stdin_buf)) { |
47 | 421 | case WRITE_STDIN_OK: |
48 | 421 | break; |
49 | 0 | case WRITE_STDIN_BUFFERED: |
50 | 0 | return REMOTE_OK; |
51 | 0 | case WRITE_STDIN_ERROR: |
52 | 0 | PERROR("write"); |
53 | 0 | return REMOTE_EOF; |
54 | 421 | } |
55 | | |
56 | 421 | buf = malloc(max_len); |
57 | 421 | if (!buf) { |
58 | 0 | PERROR("malloc"); |
59 | 0 | return REMOTE_ERROR; |
60 | 0 | } |
61 | | |
62 | 3.27k | while (libvchan_data_ready(data_vchan) > 0) { |
63 | 3.12k | if (libvchan_recv(data_vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) |
64 | 28 | goto out; |
65 | 3.09k | if (hdr.len > max_len) { |
66 | 73 | LOG(ERROR, "Too big data chunk received: %" PRIu32 " > %zu", |
67 | 73 | hdr.len, max_len); |
68 | 73 | goto out; |
69 | 73 | } |
70 | 3.02k | if (!read_vchan_all(data_vchan, buf, hdr.len)) |
71 | 37 | goto out; |
72 | | |
73 | 2.98k | switch (hdr.type) { |
74 | | /* handle both directions because this can be either server or client |
75 | | * of VM-VM connection */ |
76 | 481 | case MSG_DATA_STDIN: |
77 | 1.52k | case MSG_DATA_STDOUT: |
78 | 1.52k | if (hdr.type != (is_service ? MSG_DATA_STDIN : MSG_DATA_STDOUT) && |
79 | 1.52k | !msg_data_warned) { |
80 | 25 | LOG(ERROR, is_service ? "client sent MSG_DATA_STDOUT" : "service sent MSG_DATA_STDIN"); |
81 | 25 | msg_data_warned = true; |
82 | 25 | } |
83 | | |
84 | 1.52k | if (stdin_fd < 0) |
85 | | /* discard the data */ |
86 | 0 | continue; |
87 | 1.52k | if (hdr.len == 0) { |
88 | 16 | rc = REMOTE_EOF; |
89 | 16 | goto out; |
90 | 1.50k | } else { |
91 | 1.50k | if (replace_chars_stdout) |
92 | 0 | do_replace_chars(buf, hdr.len); |
93 | 1.50k | switch (write_stdin(stdin_fd, buf, hdr.len, stdin_buf)) { |
94 | 1.50k | case WRITE_STDIN_OK: |
95 | 1.50k | break; |
96 | 0 | case WRITE_STDIN_BUFFERED: |
97 | 0 | rc = REMOTE_OK; |
98 | 0 | goto out; |
99 | 0 | case WRITE_STDIN_ERROR: |
100 | 0 | if (!(errno == EPIPE || errno == ECONNRESET)) { |
101 | 0 | PERROR("write"); |
102 | 0 | } |
103 | 0 | rc = REMOTE_EOF; |
104 | 0 | goto out; |
105 | 1.50k | } |
106 | 1.50k | } |
107 | 1.50k | break; |
108 | 1.50k | case MSG_DATA_STDERR: |
109 | 1.34k | if (is_service) { |
110 | 0 | LOG(ERROR, "client sent MSG_DATA_STDERR"); |
111 | 0 | continue; |
112 | 0 | } |
113 | | |
114 | 1.34k | if (replace_chars_stderr) |
115 | 1.34k | do_replace_chars(buf, hdr.len); |
116 | | /* stderr of remote service, log locally */ |
117 | 1.34k | if (!write_all(2, buf, hdr.len)) { |
118 | 0 | PERROR("write"); |
119 | | /* only log the error */ |
120 | 0 | } |
121 | 1.34k | break; |
122 | 15 | case MSG_DATA_EXIT_CODE: |
123 | 15 | if (is_service) { |
124 | 0 | LOG(ERROR, "client sent MSG_DATA_EXIT_CODE"); |
125 | 0 | continue; |
126 | 0 | } |
127 | | |
128 | | /* remote process exited, so there is no sense to send any data |
129 | | * to it */ |
130 | 15 | if (hdr.len < sizeof(*status)) { |
131 | 3 | LOG(ERROR, "MSG_DATA_EXIT_CODE too short: " PRIu32 " < %zu", |
132 | 3 | hdr.len, sizeof(*status)); |
133 | 3 | *status = 255; |
134 | 3 | } else |
135 | 12 | memcpy(status, buf, sizeof(*status)); |
136 | 15 | rc = REMOTE_EXITED; |
137 | 15 | goto out; |
138 | 100 | default: |
139 | 100 | LOG(ERROR, "unknown msg %d", hdr.type); |
140 | 100 | rc = REMOTE_ERROR; |
141 | 100 | goto out; |
142 | 2.98k | } |
143 | 2.98k | } |
144 | 152 | rc = REMOTE_OK; |
145 | 421 | out: |
146 | 421 | free(buf); |
147 | 421 | return rc; |
148 | 152 | } |
149 | | |
150 | | int handle_input( |
151 | | libvchan_t *vchan, int fd, int msg_type, |
152 | | int data_protocol_version, struct prefix_data *prefix_data) |
153 | 0 | { |
154 | 0 | const size_t max_len = max_data_chunk_size(data_protocol_version); |
155 | 0 | char *buf; |
156 | 0 | ssize_t len; |
157 | 0 | struct msg_header hdr; |
158 | 0 | int rc = REMOTE_ERROR, buf_space; |
159 | |
|
160 | 0 | buf = malloc(max_len); |
161 | 0 | if (!buf) { |
162 | 0 | PERROR("malloc"); |
163 | 0 | return REMOTE_ERROR; |
164 | 0 | } |
165 | | |
166 | 0 | static_assert(SSIZE_MAX >= INT_MAX, "can't happen on Linux"); |
167 | 0 | hdr.type = msg_type; |
168 | 0 | while ((buf_space = libvchan_buffer_space(vchan)) > (int)sizeof(struct msg_header)) { |
169 | 0 | len = buf_space - sizeof(struct msg_header); |
170 | 0 | if ((size_t)len > max_len) |
171 | 0 | len = max_len; |
172 | 0 | if (prefix_data->len) { |
173 | 0 | if ((size_t)len > prefix_data->len) |
174 | 0 | len = prefix_data->len; |
175 | 0 | memcpy(buf, prefix_data->data, len); |
176 | 0 | prefix_data->data += len; |
177 | 0 | prefix_data->len -= len; |
178 | 0 | } else { |
179 | 0 | len = read(fd, buf, len); |
180 | | /* If the other side of the socket is a process that is already dead, |
181 | | * read from such socket could fail with ECONNRESET instead of |
182 | | * just 0. */ |
183 | 0 | if (len < 0 && errno == ECONNRESET) |
184 | 0 | len = 0; |
185 | 0 | if (len < 0) { |
186 | 0 | if (errno == EAGAIN || errno == EWOULDBLOCK) |
187 | 0 | rc = REMOTE_OK; |
188 | | /* otherwise keep rc = REMOTE_ERROR */ |
189 | 0 | goto out; |
190 | 0 | } |
191 | 0 | } |
192 | 0 | hdr.len = (uint32_t)len; |
193 | | /* do not fail on sending EOF (think: close()), it will be handled just below */ |
194 | 0 | if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr) && hdr.len != 0) |
195 | 0 | goto out; |
196 | | |
197 | 0 | if (len && !write_vchan_all(vchan, buf, len)) |
198 | 0 | goto out; |
199 | | |
200 | 0 | if (len == 0) { |
201 | 0 | rc = REMOTE_EOF; |
202 | 0 | goto out; |
203 | 0 | } |
204 | 0 | } |
205 | 0 | rc = REMOTE_OK; |
206 | 0 | out: |
207 | 0 | free(buf); |
208 | 0 | return rc; |
209 | 0 | } |
210 | | |
211 | | int send_exit_code(libvchan_t *vchan, int status) |
212 | 0 | { |
213 | 0 | struct msg_header hdr; |
214 | |
|
215 | 0 | hdr.type = MSG_DATA_EXIT_CODE; |
216 | 0 | hdr.len = sizeof(int); |
217 | 0 | if (libvchan_send(vchan, &hdr, sizeof(hdr)) != sizeof(hdr)) { |
218 | 0 | PERROR("send_exit_code hdr"); |
219 | 0 | return -1; |
220 | 0 | } |
221 | 0 | if (libvchan_send(vchan, &status, sizeof(status)) != sizeof(status)) { |
222 | 0 | PERROR("send_exit_code status"); |
223 | 0 | return -1; |
224 | 0 | } |
225 | 0 | return 0; |
226 | 0 | } |