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