1 /*
2 * depriv-fd-checker
3 *
4 * utility to check whether file descriptor(s) are deprivileged
5 *
6 * usage:
7 * .../depriv-fd-checker CLASS FD X-INFO [CLASS FD X-INFO...]
8 *
9 * CLASS is one of:
10 * privcmd gntdev evtchn FD should be appropriate Xen control fd
11 * readonly FD is expected to be readonly
12 * appendonly FD is expected to be append write only
13 # tun FD is expected to be an open tun device
14 *
15 * In each case FD is probably a reference to an open-file stolen
16 * from another process, eg by the use of fishdescriptor.
17 *
18 * X-INFO is simply appended to the discursive reportage.
19 *
20 * It is an error if depriv-fd-checker cannot open the control
21 * facilities itself, or something goes wrong with checking, or an FD
22 * is entirely the wrong kind for the specified CLASS. Otherwise:
23 *
24 * depriv-fd-checker will perhaps print, for each triplet:
25 * CLASS checking FD INFORMATION... X-INFO
26 * and in any case print, for each triplet, exactly one of:
27 * CLASS pass|fail FD INFORMATION... X-INFO
28 * tun maybe FD IFNAME X-INFO
29 *
30 * "pass" means that the descriptor was restricted as expected.
31 * "fail" means that the descriptor was unrestricted.
32 * "maybe" means that further information is printed, as detailed above,
33 * and the caller should check that it is as expected
34 */
35 /*
36 * Copyright (C)2018 Citrix Systems R&D
37 *
38 * This program is free software; you can redistribute it and/or
39 * modify it under the terms of the GNU Lesser General Public License
40 * as published by the Free Software Foundation; version 2.1 of the
41 * License.
42 *
43 * This library is distributed in the hope that it will be useful,
44 * but WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * Lesser General Public License for more details.
47 *
48 * You should have received a copy of the GNU Lesser General Public
49 * License along with this library; If not, see
50 * <http://www.gnu.org/licenses/>.
51 */
52
53 #include <stdlib.h>
54 #include <errno.h>
55 #include <string.h>
56 #include <stdio.h>
57 #include <assert.h>
58 #include <string.h>
59 #include <unistd.h>
60 #include <fcntl.h>
61 #include <poll.h>
62
63 #include <err.h>
64
65 #include <xenctrl.h>
66 #include <xencall.h>
67 #include <xengnttab.h>
68 #include <xenevtchn.h>
69
70 /*
71 * Every class needs setup. setup is called once per class at program
72 * startup.
73 *
74 * Then it can have
75 * open test getfd close
76 * In which case the core code will for every fd
77 * open test getfd dup2 test close
78 * And test should call blocked or succeeded and then immediately
79 * return, or error out
80 *
81 * Or it can have
82 * check
83 * which should call report, or error out
84 *
85 * Errors: use trouble for simple syscall errors. Or use err or errx
86 * and maybe print fd_desc and test_which, according to the comments
87 * in struct classinfo.
88 */
89
90 static xentoollog_logger *logger;
91
92 static int object_fd;
93 static const char *classname;
94 static const char *fd_desc;
95 static const char *test_which;
96
97 static const char *test_wh_unrest = "test (unrestricted)";
98 static const char *test_wh_rest = "test (restricted)";
99
100
101 static void trouble(const char *what) __attribute__((noreturn));
trouble(const char * what)102 static void trouble(const char *what) {
103 fprintf(stderr,
104 "trouble: %s %s %d (%s) %s: %s\n",
105 classname, test_which, object_fd, fd_desc, what, strerror(errno));
106 exit(-1);
107 }
108
report(const char * pass_or_fail,const char * what,const char * notes)109 static void report(const char *pass_or_fail, const char *what,
110 const char *notes) {
111 printf("%s %s %d %s (%s) %s\n",
112 classname, pass_or_fail,
113 object_fd, what, notes, fd_desc);
114 if (ferror(stdout) || fflush(stdout)) err(16,"stdout");
115 }
116
succeeded(const char * what)117 static void succeeded(const char *what) {
118 if (test_which == test_wh_unrest) {
119 /* ok */
120 test_which = 0;
121 } else if (test_which == test_wh_rest) {
122 report("fail",what,"unexpectedly succeeded");
123 test_which = 0;
124 } else {
125 abort();
126 }
127 }
128
blocked(const char * what)129 static void blocked(const char *what) {
130 if (test_which == test_wh_rest) {
131 /* yay */
132 report("pass", what,"blocked");
133 test_which = 0;
134 } else if (test_which == test_wh_unrest) {
135 err(4,"test blocked on unrestricted fd: %s {%s}",what,test_which);
136 } else {
137 abort();
138 }
139 }
140
141 /* privcmd */
142
143 static xc_interface *xch;
setup_privcmd(void)144 static void setup_privcmd(void) { }
open_privcmd(void)145 static void open_privcmd(void) {
146 xch = xc_interface_open(logger,0,0);
147 if (!xch) trouble("xc_interface_open");
148 }
test_privcmd(void)149 static void test_privcmd(void) {
150 int r = xc_get_online_cpus(xch);
151 if (r>0)
152 succeeded("xc_get_online_cpus");
153 else if (r==0)
154 errx(-1,"xc_get_online_cpus{%s, %s}=0", test_which, fd_desc);
155 else if (errno==EPERM || errno==EACCES)
156 blocked("xc_get_online_cpus");
157 else
158 trouble("xc_get_online_cpus");
159 }
getfd_privcmd(void)160 static int getfd_privcmd(void) {
161 return xencall_fd(xc_interface_xcall_handle(xch));
162 }
close_privcmd(void)163 static void close_privcmd(void) {
164 xc_interface_close(xch);
165 }
166
167 /* gntdev */
168
169 static xengntshr_handle *xgs;
170 static uint32_t gntshr_gref;
171 static xengnttab_handle *xgt;
setup_gntdev(void)172 static void setup_gntdev(void) {
173 void *r;
174 xgs = xengntshr_open(logger,0);
175 if (!xgs) trouble("xengntshr_open");
176 r = xengntshr_share_pages(xgs, 0, 1, &gntshr_gref, 1);
177 if (!r || r==(void*)-1) trouble("xengntshr_share_pages");
178 memset(r, 0x55, XC_PAGE_SIZE);
179 }
open_gntdev(void)180 static void open_gntdev(void) {
181 xgt = xengnttab_open(logger,0);
182 if (!xgt) trouble("xengnttab_open");
183 }
test_gntdev(void)184 static void test_gntdev(void) {
185 char mybuf[XC_PAGE_SIZE];
186 memset(mybuf, 0xaa, XC_PAGE_SIZE);
187 xengnttab_grant_copy_segment_t seg;
188 seg.source.foreign.ref = gntshr_gref;
189 seg.source.foreign.offset = 0;
190 seg.source.foreign.domid = 0;
191 seg.dest.virt = mybuf;
192 seg.len = 1;
193 seg.flags = GNTCOPY_source_gref;
194 for (;;) {
195 seg.status = 0;
196 int r = xengnttab_grant_copy(xgt,1,&seg);
197 if (r<0) {
198 if (errno==EPERM || errno==EACCES || errno==ENOTTY)
199 blocked("xengnttab_grant_copy");
200 else
201 trouble("xengnttab_grant_copy");
202 } else if (r==0) {
203 if (seg.status==GNTST_okay)
204 succeeded("xengnttab_grant_copy okay");
205 else if (seg.status==GNTST_eagain)
206 continue;
207 else errx(-1,"xengnttab_grant_copy=%d {%s, %s} but .status=%d",
208 r, test_which, fd_desc,(int)seg.status);
209 } else {
210 errx(-1,"xengnttab_grant_copy=%d {%s, %s}",
211 r, test_which, fd_desc);
212 }
213 break;
214 }
215 }
getfd_gntdev(void)216 static int getfd_gntdev(void) {
217 return xengnttab_fd(xgt);
218 }
close_gntdev(void)219 static void close_gntdev(void) {
220 xengnttab_close(xgt);
221 }
222
223 /* evtchn */
224
225 static xenevtchn_handle *xce_recip, *xce;
setup_evtchn(void)226 static void setup_evtchn(void) {
227 xce_recip = xenevtchn_open(logger, 0);
228 if (!xce_recip) err(-1,"xenevtchn_open (donor)");
229 }
open_evtchn(void)230 static void open_evtchn(void) {
231 xce = xenevtchn_open(logger, 0);
232 if (!xce) err(-1,"xenevtchn_open");
233 }
test_evtchn(void)234 static void test_evtchn(void) {
235 xenevtchn_port_or_error_t
236 recip_port=-1, test_unbound_port=-1, test_send_port=-1;
237
238 recip_port = xenevtchn_bind_unbound_port(xce_recip, 0);
239 if (recip_port < 0) trouble("xenevtchn_bind_unbound_port");
240
241 test_unbound_port = xenevtchn_bind_unbound_port(xce, 0);
242 if (test_unbound_port >= 0) {
243 succeeded("xenevtchn_bind_unbound_port");
244 goto out;
245 }
246
247 test_send_port = xenevtchn_bind_interdomain(xce, 0, recip_port);
248 /* bind_interdomain marks the channel pending */
249 struct pollfd pfd;
250 for (;;) {
251 pfd.fd = xenevtchn_fd(xce_recip);
252 pfd.events = POLLIN;
253 pfd.revents = 0;
254 int r = poll(&pfd,1,0);
255 if (r>=0) break;
256 if (errno!=EINTR) err(-1,"poll(xce_recip)");
257 }
258 if (pfd.revents & POLLIN) {
259 xenevtchn_port_or_error_t p3 = xenevtchn_pending(xce_recip);
260 if (p3 < 0) err(-1,"xenevtchn_pending(check)");
261 if (p3 != recip_port)
262 errx(-1,"xenevtchn_pending=%d expected %d",p3,recip_port);
263 xenevtchn_unmask(xce_recip, recip_port);
264 }
265
266 if (test_send_port>=0 && (pfd.revents & POLLIN)) {
267 succeeded("xenevtchn_bind_interdomain/poll");
268 /* we make no attempt to undo what we did to this stolen fd;
269 * the rightful owner will see a spurious event on test_send_port */
270 } else if (test_send_port==-1 && !(pfd.revents & POLLIN) &&
271 (errno==EPERM || errno==EACCES || errno==ENOTTY)) {
272 blocked("xenevtchn_notify");
273 } else {
274 err(-1,"%s %s xenevtchn_bind_interdomain=%d .revents=0x%x",
275 test_which, fd_desc, test_send_port, pfd.revents);
276 }
277
278 out:
279 if (recip_port > 0) xenevtchn_unbind(xce, recip_port);
280 if (test_unbound_port > 0) xenevtchn_unbind(xce, test_unbound_port);
281 if (test_send_port > 0) xenevtchn_unbind(xce, test_send_port);
282 }
getfd_evtchn(void)283 static int getfd_evtchn(void) {
284 return xenevtchn_fd(xce);
285 }
close_evtchn(void)286 static void close_evtchn(void) {
287 xenevtchn_close(xce);
288 }
289
290 /* fcntl */
291
292 #define CHECK_FCNTL(openmode) \
293 int r = fcntl(object_fd, F_GETFL); \
294 if (r < 0) trouble("fcntl F_GETFL"); \
295 int m = r & (O_RDONLY | O_WRONLY | O_RDWR); \
296 \
297 char mbuf[100 + 30*3]; \
298 snprintf(mbuf,sizeof(mbuf), \
299 "F_GETFL=%#o m=%#o " #openmode "=%#o", \
300 r,m,(int)openmode); \
301 \
302 if (m != openmode) { \
303 report("fail", #openmode, mbuf); \
304 return; \
305 }
306
307 /* readonly */
308
setup_readonly(void)309 static void setup_readonly(void) { }
check_readonly(void)310 static void check_readonly(void) {
311 CHECK_FCNTL(O_RDONLY);
312 report("pass", "fcntl", mbuf);
313 }
314
315 /* appendonly */
316
setup_appendonly(void)317 static void setup_appendonly(void) { }
check_appendonly(void)318 static void check_appendonly(void) {
319 CHECK_FCNTL(O_WRONLY);
320 if (!(r & O_APPEND)) {
321 report("fail", "O_APPEND", mbuf);
322 return;
323 }
324 report("pass", "fcntl", mbuf);
325 }
326
327 #if defined(__linux__)
328 #include <sys/ioctl.h>
329 #include <sys/types.h>
330 #include <sys/socket.h>
331 #include <linux/if.h>
332 #include <linux/if_tun.h>
333 #ifndef TUNGETIFF
334 #define TUNGETIFF _IOR('T', 210, unsigned int)
335 #endif
336
337 /* linux tun */
338
setup_tun(void)339 static void setup_tun(void) { }
check_tun(void)340 static void check_tun(void) {
341 struct ifreq ifr;
342 int r;
343
344 memset(&ifr,0,sizeof(ifr));
345 r = ioctl(object_fd, TUNGETIFF, (void*)&ifr);
346 if (r<0) trouble("TUNGETIFF");
347 printf("tun maybe %d %.*s %s\n", object_fd,
348 (int)IFNAMSIZ, ifr.ifr_ifrn.ifrn_name,
349 fd_desc);
350 }
351
352 #define PLATFORM_CLASSES \
353 DEFCHECK(tun),
354
355 #else /* !defined(__linux__) */
356 #define PLATFORM_CLASSES /* empty */
357 #endif
358
359 /* class table and main program */
360
361 #define DEFCLASS(cl) \
362 { #cl, setup_##cl, 0, open_##cl, test_##cl, getfd_##cl, close_##cl }
363 #define DEFCHECK(meth) \
364 { #meth, setup_##meth, check_##meth }
365
366 static const struct classinfo {
367 const char *name; /* errors: print fd_desc test_which */
368 void (*setup)(void); /* best not best not */
369 void (*check)(void); /* must may */
370 void (*open)(void); /* must may */
371 void (*test)(void); /* must must */
372 int (*getfd)(void); /* must may */
373 void (*close)(void); /* must may */
374 } classinfos[] = {
375 DEFCLASS(privcmd),
376 DEFCLASS(gntdev),
377 DEFCLASS(evtchn),
378 DEFCHECK(readonly),
379 DEFCHECK(appendonly),
380 PLATFORM_CLASSES
381 { 0 }
382 };
383
main(int argc,char ** argv)384 int main(int argc, char **argv) {
385 const struct classinfo *cli;
386 int r;
387
388 argv++;
389
390 logger = (xentoollog_logger*)xtl_createlogger_stdiostream
391 (stderr, XTL_NOTICE, XTL_STDIOSTREAM_HIDE_PROGRESS);
392
393 fd_desc = "setup";
394 test_which = "setup";
395 for (cli = classinfos; cli->name; cli++)
396 cli->setup();
397
398 while ((classname = *argv++)) {
399 if (!*argv) errx(8,"need fd after class");
400 object_fd = atoi(*argv++);
401
402 fd_desc = *argv++;
403 if (!fd_desc) errx(8,"need info after fd");
404
405 for (cli = classinfos; cli->name; cli++)
406 if (!strcmp(cli->name, classname))
407 goto found;
408 report("fail","unknown class","");
409 continue;
410
411 found:
412 if (cli->check) {
413 report("checking","check","in progress");
414 test_which = "check";
415 cli->check();
416 } else {
417 test_which = "open";
418 report("checking","dup-hack","in progress");
419 cli->open();
420
421 test_which = test_wh_unrest; cli->test();
422 assert(!test_which);
423
424 test_which = "getfd"; int intern_fd = cli->getfd();
425 r = dup2(object_fd, intern_fd);
426 if (r != intern_fd) err(-1, "dup2");
427
428 test_which = test_wh_rest; cli->test();
429 assert(!test_which);
430
431 test_which = "close"; cli->close();
432 }
433 }
434
435 return 0;
436 }
437