]> git.proxmox.com Git - mirror_qemu.git/blob - fsdev/virtfs-proxy-helper.c
hw/9pfs: Open and create files
[mirror_qemu.git] / fsdev / virtfs-proxy-helper.c
1 /*
2 * Helper for QEMU Proxy FS Driver
3 * Copyright IBM, Corp. 2011
4 *
5 * Authors:
6 * M. Mohan Kumar <mohan@in.ibm.com>
7 *
8 * This work is licensed under the terms of the GNU GPL, version 2. See
9 * the COPYING file in the top-level directory.
10 */
11 #include <stdio.h>
12 #include <sys/socket.h>
13 #include <string.h>
14 #include <sys/un.h>
15 #include <limits.h>
16 #include <signal.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <sys/resource.h>
20 #include <sys/stat.h>
21 #include <getopt.h>
22 #include <unistd.h>
23 #include <syslog.h>
24 #include <sys/capability.h>
25 #include <sys/fsuid.h>
26 #include <stdarg.h>
27 #include <stdbool.h>
28 #include "qemu-common.h"
29 #include "virtio-9p-marshal.h"
30 #include "hw/9pfs/virtio-9p-proxy.h"
31 #include "fsdev/virtio-9p-marshal.h"
32
33 #define PROGNAME "virtfs-proxy-helper"
34
35 static struct option helper_opts[] = {
36 {"fd", required_argument, NULL, 'f'},
37 {"path", required_argument, NULL, 'p'},
38 {"nodaemon", no_argument, NULL, 'n'},
39 };
40
41 static bool is_daemon;
42
43 static void do_log(int loglevel, const char *format, ...)
44 {
45 va_list ap;
46
47 va_start(ap, format);
48 if (is_daemon) {
49 vsyslog(LOG_CRIT, format, ap);
50 } else {
51 vfprintf(stderr, format, ap);
52 }
53 va_end(ap);
54 }
55
56 static void do_perror(const char *string)
57 {
58 if (is_daemon) {
59 syslog(LOG_CRIT, "%s:%s", string, strerror(errno));
60 } else {
61 fprintf(stderr, "%s:%s\n", string, strerror(errno));
62 }
63 }
64
65 static int do_cap_set(cap_value_t *cap_value, int size, int reset)
66 {
67 cap_t caps;
68 if (reset) {
69 /*
70 * Start with an empty set and set permitted and effective
71 */
72 caps = cap_init();
73 if (caps == NULL) {
74 do_perror("cap_init");
75 return -1;
76 }
77 if (cap_set_flag(caps, CAP_PERMITTED, size, cap_value, CAP_SET) < 0) {
78 do_perror("cap_set_flag");
79 goto error;
80 }
81 } else {
82 caps = cap_get_proc();
83 if (!caps) {
84 do_perror("cap_get_proc");
85 return -1;
86 }
87 }
88 if (cap_set_flag(caps, CAP_EFFECTIVE, size, cap_value, CAP_SET) < 0) {
89 do_perror("cap_set_flag");
90 goto error;
91 }
92 if (cap_set_proc(caps) < 0) {
93 do_perror("cap_set_proc");
94 goto error;
95 }
96 cap_free(caps);
97 return 0;
98
99 error:
100 cap_free(caps);
101 return -1;
102 }
103
104 static int init_capabilities(void)
105 {
106 /* helper needs following capbabilities only */
107 cap_value_t cap_list[] = {
108 CAP_CHOWN,
109 CAP_DAC_OVERRIDE,
110 CAP_FOWNER,
111 CAP_FSETID,
112 CAP_SETGID,
113 CAP_MKNOD,
114 CAP_SETUID,
115 };
116 return do_cap_set(cap_list, ARRAY_SIZE(cap_list), 1);
117 }
118
119 static int socket_read(int sockfd, void *buff, ssize_t size)
120 {
121 ssize_t retval, total = 0;
122
123 while (size) {
124 retval = read(sockfd, buff, size);
125 if (retval == 0) {
126 return -EIO;
127 }
128 if (retval < 0) {
129 if (errno == EINTR) {
130 continue;
131 }
132 return -errno;
133 }
134 size -= retval;
135 buff += retval;
136 total += retval;
137 }
138 return total;
139 }
140
141 static int socket_write(int sockfd, void *buff, ssize_t size)
142 {
143 ssize_t retval, total = 0;
144
145 while (size) {
146 retval = write(sockfd, buff, size);
147 if (retval < 0) {
148 if (errno == EINTR) {
149 continue;
150 }
151 return -errno;
152 }
153 size -= retval;
154 buff += retval;
155 total += retval;
156 }
157 return total;
158 }
159
160 static int read_request(int sockfd, struct iovec *iovec, ProxyHeader *header)
161 {
162 int retval;
163
164 /*
165 * read the request header.
166 */
167 iovec->iov_len = 0;
168 retval = socket_read(sockfd, iovec->iov_base, PROXY_HDR_SZ);
169 if (retval < 0) {
170 return retval;
171 }
172 iovec->iov_len = PROXY_HDR_SZ;
173 retval = proxy_unmarshal(iovec, 0, "dd", &header->type, &header->size);
174 if (retval < 0) {
175 return retval;
176 }
177 /*
178 * We can't process message.size > PROXY_MAX_IO_SZ.
179 * Treat it as fatal error
180 */
181 if (header->size > PROXY_MAX_IO_SZ) {
182 return -ENOBUFS;
183 }
184 retval = socket_read(sockfd, iovec->iov_base + PROXY_HDR_SZ, header->size);
185 if (retval < 0) {
186 return retval;
187 }
188 iovec->iov_len += header->size;
189 return 0;
190 }
191
192 static int send_fd(int sockfd, int fd)
193 {
194 struct msghdr msg;
195 struct iovec iov;
196 int retval, data;
197 struct cmsghdr *cmsg;
198 union MsgControl msg_control;
199
200 iov.iov_base = &data;
201 iov.iov_len = sizeof(data);
202
203 memset(&msg, 0, sizeof(msg));
204 msg.msg_iov = &iov;
205 msg.msg_iovlen = 1;
206 /* No ancillary data on error */
207 if (fd < 0) {
208 /* fd is really negative errno if the request failed */
209 data = fd;
210 } else {
211 data = V9FS_FD_VALID;
212 msg.msg_control = &msg_control;
213 msg.msg_controllen = sizeof(msg_control);
214
215 cmsg = &msg_control.cmsg;
216 cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
217 cmsg->cmsg_level = SOL_SOCKET;
218 cmsg->cmsg_type = SCM_RIGHTS;
219 memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
220 }
221
222 do {
223 retval = sendmsg(sockfd, &msg, 0);
224 } while (retval < 0 && errno == EINTR);
225 if (fd >= 0) {
226 close(fd);
227 }
228 if (retval < 0) {
229 return retval;
230 }
231 return 0;
232 }
233
234 /*
235 * from man 7 capabilities, section
236 * Effect of User ID Changes on Capabilities:
237 * 4. If the file system user ID is changed from 0 to nonzero (see setfsuid(2))
238 * then the following capabilities are cleared from the effective set:
239 * CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID,
240 * CAP_LINUX_IMMUTABLE (since Linux 2.2.30), CAP_MAC_OVERRIDE, and CAP_MKNOD
241 * (since Linux 2.2.30). If the file system UID is changed from nonzero to 0,
242 * then any of these capabilities that are enabled in the permitted set
243 * are enabled in the effective set.
244 */
245 static int setfsugid(int uid, int gid)
246 {
247 /*
248 * We still need DAC_OVERRIDE because we don't change
249 * supplementary group ids, and hence may be subjected DAC rules
250 */
251 cap_value_t cap_list[] = {
252 CAP_DAC_OVERRIDE,
253 };
254
255 setfsgid(gid);
256 setfsuid(uid);
257
258 if (uid != 0 || gid != 0) {
259 return do_cap_set(cap_list, ARRAY_SIZE(cap_list), 0);
260 }
261 return 0;
262 }
263
264 /*
265 * create a file and send fd on success
266 * return -errno on error
267 */
268 static int do_create(struct iovec *iovec)
269 {
270 int ret;
271 V9fsString path;
272 int flags, mode, uid, gid, cur_uid, cur_gid;
273
274 v9fs_string_init(&path);
275 ret = proxy_unmarshal(iovec, PROXY_HDR_SZ, "sdddd",
276 &path, &flags, &mode, &uid, &gid);
277 if (ret < 0) {
278 goto unmarshal_err_out;
279 }
280 cur_uid = geteuid();
281 cur_gid = getegid();
282 ret = setfsugid(uid, gid);
283 if (ret < 0) {
284 /*
285 * On failure reset back to the
286 * old uid/gid
287 */
288 ret = -errno;
289 goto err_out;
290 }
291 ret = open(path.data, flags, mode);
292 if (ret < 0) {
293 ret = -errno;
294 }
295
296 err_out:
297 setfsugid(cur_uid, cur_gid);
298 unmarshal_err_out:
299 v9fs_string_free(&path);
300 return ret;
301 }
302
303 /*
304 * open a file and send fd on success
305 * return -errno on error
306 */
307 static int do_open(struct iovec *iovec)
308 {
309 int flags, ret;
310 V9fsString path;
311
312 v9fs_string_init(&path);
313 ret = proxy_unmarshal(iovec, PROXY_HDR_SZ, "sd", &path, &flags);
314 if (ret < 0) {
315 goto err_out;
316 }
317 ret = open(path.data, flags);
318 if (ret < 0) {
319 ret = -errno;
320 }
321 err_out:
322 v9fs_string_free(&path);
323 return ret;
324 }
325
326 static void usage(char *prog)
327 {
328 fprintf(stderr, "usage: %s\n"
329 " -p|--path <path> 9p path to export\n"
330 " {-f|--fd <socket-descriptor>} socket file descriptor to be used\n"
331 " [-n|--nodaemon] Run as a normal program\n",
332 basename(prog));
333 }
334
335 static int process_reply(int sock, int type, int retval)
336 {
337 switch (type) {
338 case T_OPEN:
339 case T_CREATE:
340 if (send_fd(sock, retval) < 0) {
341 return -1;
342 }
343 break;
344 default:
345 return -1;
346 break;
347 }
348 return 0;
349 }
350
351 static int process_requests(int sock)
352 {
353 int retval = 0;
354 ProxyHeader header;
355 struct iovec in_iovec;
356
357 in_iovec.iov_base = g_malloc(PROXY_MAX_IO_SZ + PROXY_HDR_SZ);
358 in_iovec.iov_len = PROXY_MAX_IO_SZ + PROXY_HDR_SZ;
359 while (1) {
360 /*
361 * initialize the header type, so that we send
362 * response to proper request type.
363 */
364 header.type = 0;
365 retval = read_request(sock, &in_iovec, &header);
366 if (retval < 0) {
367 goto err_out;
368 }
369
370 switch (header.type) {
371 case T_OPEN:
372 retval = do_open(&in_iovec);
373 break;
374 case T_CREATE:
375 retval = do_create(&in_iovec);
376 break;
377 default:
378 goto err_out;
379 break;
380 }
381
382 if (process_reply(sock, header.type, retval) < 0) {
383 goto err_out;
384 }
385 }
386 (void)socket_write;
387 err_out:
388 g_free(in_iovec.iov_base);
389 return -1;
390 }
391
392 int main(int argc, char **argv)
393 {
394 int sock;
395 char *rpath = NULL;
396 struct stat stbuf;
397 int c, option_index;
398
399 is_daemon = true;
400 sock = -1;
401 while (1) {
402 option_index = 0;
403 c = getopt_long(argc, argv, "p:nh?f:", helper_opts,
404 &option_index);
405 if (c == -1) {
406 break;
407 }
408 switch (c) {
409 case 'p':
410 rpath = strdup(optarg);
411 break;
412 case 'n':
413 is_daemon = false;
414 break;
415 case 'f':
416 sock = atoi(optarg);
417 break;
418 case '?':
419 case 'h':
420 default:
421 usage(argv[0]);
422 exit(EXIT_FAILURE);
423 }
424 }
425
426 /* Parameter validation */
427 if (sock == -1 || rpath == NULL) {
428 fprintf(stderr, "socket descriptor or path not specified\n");
429 usage(argv[0]);
430 exit(EXIT_FAILURE);
431 }
432
433 if (lstat(rpath, &stbuf) < 0) {
434 fprintf(stderr, "invalid path \"%s\" specified, %s\n",
435 rpath, strerror(errno));
436 exit(EXIT_FAILURE);
437 }
438
439 if (!S_ISDIR(stbuf.st_mode)) {
440 fprintf(stderr, "specified path \"%s\" is not directory\n", rpath);
441 exit(EXIT_FAILURE);
442 }
443
444 if (is_daemon) {
445 if (daemon(0, 0) < 0) {
446 fprintf(stderr, "daemon call failed\n");
447 exit(EXIT_FAILURE);
448 }
449 openlog(PROGNAME, LOG_PID, LOG_DAEMON);
450 }
451
452 do_log(LOG_INFO, "Started\n");
453
454 if (chdir("/") < 0) {
455 do_perror("chdir");
456 goto error;
457 }
458 if (chroot(rpath) < 0) {
459 do_perror("chroot");
460 goto error;
461 }
462 umask(0);
463
464 if (init_capabilities() < 0) {
465 goto error;
466 }
467
468 process_requests(sock);
469 error:
470 do_log(LOG_INFO, "Done\n");
471 closelog();
472 return 0;
473 }