]>
Commit | Line | Data |
---|---|---|
17bff52b MK |
1 | /* |
2 | * Helper for QEMU Proxy FS Driver | |
3 | * Copyright IBM, Corp. 2011 | |
4 | * | |
5 | * Authors: | |
6 | * M. Mohan Kumar <[email protected]> | |
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> | |
daf0b9ac | 12 | #include <sys/socket.h> |
17bff52b MK |
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" | |
daf0b9ac | 31 | #include "fsdev/virtio-9p-marshal.h" |
17bff52b MK |
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 | ||
daf0b9ac MK |
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 | ||
17bff52b MK |
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 | ||
daf0b9ac MK |
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 | ||
17bff52b MK |
351 | static int process_requests(int sock) |
352 | { | |
daf0b9ac | 353 | int retval = 0; |
17bff52b MK |
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) { | |
daf0b9ac MK |
360 | /* |
361 | * initialize the header type, so that we send | |
362 | * response to proper request type. | |
363 | */ | |
364 | header.type = 0; | |
17bff52b MK |
365 | retval = read_request(sock, &in_iovec, &header); |
366 | if (retval < 0) { | |
daf0b9ac MK |
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; | |
17bff52b MK |
384 | } |
385 | } | |
386 | (void)socket_write; | |
daf0b9ac | 387 | err_out: |
17bff52b MK |
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 | } |