diff --git a/.gitignore b/.gitignore index 7004349d5a..5b8448d8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -237,6 +237,7 @@ tools/python/build/* tools/security/secpol_tool tools/security/xen/* tools/security/xensec_tool +tools/tests/depriv/depriv-fd-checker tools/tests/x86_emulator/*.bin tools/tests/x86_emulator/*.tmp tools/tests/x86_emulator/3dnow*.[ch] diff --git a/tools/tests/depriv/Makefile b/tools/tests/depriv/Makefile new file mode 100644 index 0000000000..2af1e29b06 --- /dev/null +++ b/tools/tests/depriv/Makefile @@ -0,0 +1,42 @@ +XEN_ROOT=$(CURDIR)/../../.. +include $(XEN_ROOT)/tools/Rules.mk + +CFLAGS += -Werror -Wno-declaration-after-statement + +CFLAGS += $(CFLAGS_xeninclude) +CFLAGS += $(CFLAGS_libxenctrl) +CFLAGS += $(CFLAGS_libxencall) +CFLAGS += $(CFLAGS_libxenevtchn) +CFLAGS += $(CFLAGS_libxengnttab) +CFLAGS += $(CFLAGS_libxenforeignmemory) +CFLAGS += $(CFLAGS_libxendevicemodel) +CFLAGS += $(CFLAGS_libxentoolcore) +CFLAGS += $(CFLAGS_libxentoollog) + +LDLIBS += $(LDLIBS_xeninclude) +LDLIBS += $(LDLIBS_libxenctrl) +LDLIBS += $(LDLIBS_libxencall) +LDLIBS += $(LDLIBS_libxenevtchn) +LDLIBS += $(LDLIBS_libxengnttab) +LDLIBS += $(LDLIBS_libxenforeignmemory) +LDLIBS += $(LDLIBS_libxendevicemodel) +LDLIBS += $(LDLIBS_libxentoolcore) +LDLIBS += $(LDLIBS_libxentoollog) + +TARGETS-y := depriv-fd-checker +TARGETS := $(TARGETS-y) + +.PHONY: all +all: build + +.PHONY: build +build: $(TARGETS) + +.PHONY: clean +clean: + $(RM) *.o $(TARGETS) *~ $(DEPS_RM) + +.PHONY: distclean +distclean: clean + +-include $(DEPS_INCLUDE) diff --git a/tools/tests/depriv/depriv-fd-checker.c b/tools/tests/depriv/depriv-fd-checker.c new file mode 100644 index 0000000000..67a36745e5 --- /dev/null +++ b/tools/tests/depriv/depriv-fd-checker.c @@ -0,0 +1,399 @@ +/* + * depriv-fd-checker + * + * utility to check whether file descriptor(s) are deprivileged + * + * usage: + * .../depriv-fd-checker CLASS FD X-INFO [CLASS FD X-INFO...] + * + * CLASS is one of: + * privcmd gntdev evtchn FD should be appropriate Xen control fd + * readonly FD is expected to be readonly + * appendonly FD is expected to be append write only + * + * In each case FD is probably a reference to an open-file stolen + * from another process, eg by the use of fishdescriptor. + * + * X-INFO is simply appended to the discursive reportage. + * + * It is an error if depriv-fd-checker cannot open the control + * facilities itself, or something goes wrong with checking, or an FD + * is entirely the wrong kind for the specified CLASS. Otherwise: + * + * depriv-fd-checker will perhaps print, for each triplet: + * CLASS checking FD INFORMATION... X-INFO + * and in any case print, for each triplet: + * CLASS pass|fail FD INFORMATION... X-INFO + * + * "pass" means that the descriptor was restricted as expected. + * "fail" means that the descriptor was unrestricted. + */ +/* + * Copyright (C)2018 Citrix Systems R&D + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 of the + * License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; If not, see + * . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +/* + * Every class needs setup. setup is called once per class at program + * startup. + * + * Then it can have + * open test getfd close + * In which case the core code will for every fd + * open test getfd dup2 test close + * And test should call blocked or succeeded and then immediately + * return, or error out + * + * Or it can have + * check + * which should call report, or error out + * + * Errors: use trouble for simple syscall errors. Or use err or errx + * and maybe print fd_desc and test_which, according to the comments + * in struct classinfo. + */ + +static xentoollog_logger *logger; + +static int object_fd; +static const char *classname; +static const char *fd_desc; +static const char *test_which; + +static const char *test_wh_unrest = "test (unrestricted)"; +static const char *test_wh_rest = "test (restricted)"; + + +static void trouble(const char *what) __attribute__((noreturn)); +static void trouble(const char *what) { + fprintf(stderr, + "trouble: %s %s %d (%s) %s: %s\n", + classname, test_which, object_fd, fd_desc, what, strerror(errno)); + exit(-1); +} + +static void report(const char *pass_or_fail, const char *what, + const char *notes) { + printf("%s %s %d %s (%s) %s\n", + classname, pass_or_fail, + object_fd, what, notes, fd_desc); + if (ferror(stdout) || fflush(stdout)) err(16,"stdout"); +} + +static void succeeded(const char *what) { + if (test_which == test_wh_unrest) { + /* ok */ + test_which = 0; + } else if (test_which == test_wh_rest) { + report("fail",what,"unexpectedly succeeded"); + test_which = 0; + } else { + abort(); + } +} + +static void blocked(const char *what) { + if (test_which == test_wh_rest) { + /* yay */ + report("pass", what,"blocked"); + test_which = 0; + } else if (test_which == test_wh_unrest) { + err(4,"test blocked on unrestricted fd: %s {%s}",what,test_which); + } else { + abort(); + } +} + +/* privcmd */ + +static xc_interface *xch; +static void setup_privcmd(void) { } +static void open_privcmd(void) { + xch = xc_interface_open(logger,0,0); + if (!xch) trouble("xc_interface_open"); +} +static void test_privcmd(void) { + int r = xc_get_online_cpus(xch); + if (r>0) + succeeded("xc_get_online_cpus"); + else if (r==0) + errx(-1,"xc_get_online_cpus{%s, %s}=0", test_which, fd_desc); + else if (errno==EPERM || errno==EACCES) + blocked("xc_get_online_cpus"); + else + trouble("xc_get_online_cpus"); +} +static int getfd_privcmd(void) { + return xencall_fd(xc_interface_xcall_handle(xch)); +} +static void close_privcmd(void) { + xc_interface_close(xch); +} + +/* gntdev */ + +static xengntshr_handle *xgs; +static uint32_t gntshr_gref; +static xengnttab_handle *xgt; +static void setup_gntdev(void) { + void *r; + xgs = xengntshr_open(logger,0); + if (!xgs) trouble("xengntshr_open"); + r = xengntshr_share_pages(xgs, 0, 1, &gntshr_gref, 1); + if (!r || r==(void*)-1) trouble("xengntshr_share_pages"); + memset(r, 0x55, XC_PAGE_SIZE); +} +static void open_gntdev(void) { + xgt = xengnttab_open(logger,0); + if (!xgt) trouble("xengnttab_open"); +} +static void test_gntdev(void) { + char mybuf[XC_PAGE_SIZE]; + memset(mybuf, 0xaa, XC_PAGE_SIZE); + xengnttab_grant_copy_segment_t seg; + seg.source.foreign.ref = gntshr_gref; + seg.source.foreign.offset = 0; + seg.source.foreign.domid = 0; + seg.dest.virt = mybuf; + seg.len = 1; + seg.flags = GNTCOPY_source_gref; + for (;;) { + seg.status = 0; + int r = xengnttab_grant_copy(xgt,1,&seg); + if (r<0) { + if (errno==EPERM || errno==EACCES || errno==ENOTTY) + blocked("xengnttab_grant_copy"); + else + trouble("xengnttab_grant_copy"); + } else if (r==0) { + if (seg.status==GNTST_okay) + succeeded("xengnttab_grant_copy okay"); + else if (seg.status==GNTST_eagain) + continue; + else errx(-1,"xengnttab_grant_copy=%d {%s, %s} but .status=%d", + r, test_which, fd_desc,(int)seg.status); + } else { + errx(-1,"xengnttab_grant_copy=%d {%s, %s}", + r, test_which, fd_desc); + } + break; + } +} +static int getfd_gntdev(void) { + return xengnttab_fd(xgt); +} +static void close_gntdev(void) { + xengnttab_close(xgt); +} + +/* evtchn */ + +static xenevtchn_handle *xce_recip, *xce; +static void setup_evtchn(void) { + xce_recip = xenevtchn_open(logger, 0); + if (!xce_recip) err(-1,"xenevtchn_open (donor)"); +} +static void open_evtchn(void) { + xce = xenevtchn_open(logger, 0); + if (!xce) err(-1,"xenevtchn_open"); +} +static void test_evtchn(void) { + xenevtchn_port_or_error_t + recip_port=-1, test_unbound_port=-1, test_send_port=-1; + + recip_port = xenevtchn_bind_unbound_port(xce_recip, 0); + if (recip_port < 0) trouble("xenevtchn_bind_unbound_port"); + + test_unbound_port = xenevtchn_bind_unbound_port(xce, 0); + if (test_unbound_port >= 0) { + succeeded("xenevtchn_bind_unbound_port"); + goto out; + } + + test_send_port = xenevtchn_bind_interdomain(xce, 0, recip_port); + /* bind_interdomain marks the channel pending */ + struct pollfd pfd; + for (;;) { + pfd.fd = xenevtchn_fd(xce_recip); + pfd.events = POLLIN; + pfd.revents = 0; + int r = poll(&pfd,1,0); + if (r>=0) break; + if (errno!=EINTR) err(-1,"poll(xce_recip)"); + } + if (pfd.revents & POLLIN) { + xenevtchn_port_or_error_t p3 = xenevtchn_pending(xce_recip); + if (p3 < 0) err(-1,"xenevtchn_pending(check)"); + if (p3 != recip_port) + errx(-1,"xenevtchn_pending=%d expected %d",p3,recip_port); + xenevtchn_unmask(xce_recip, recip_port); + } + + if (test_send_port>=0 && (pfd.revents & POLLIN)) { + succeeded("xenevtchn_bind_interdomain/poll"); + /* we make no attempt to undo what we did to this stolen fd; + * the rightful owner will see a spurious event on test_send_port */ + } else if (test_send_port==-1 && !(pfd.revents & POLLIN) && + (errno==EPERM || errno==EACCES || errno==ENOTTY)) { + blocked("xenevtchn_notify"); + } else { + err(-1,"%s %s xenevtchn_bind_interdomain=%d .revents=0x%x", + test_which, fd_desc, test_send_port, pfd.revents); + } + + out: + if (recip_port > 0) xenevtchn_unbind(xce, recip_port); + if (test_unbound_port > 0) xenevtchn_unbind(xce, test_unbound_port); + if (test_send_port > 0) xenevtchn_unbind(xce, test_send_port); +} +static int getfd_evtchn(void) { + return xenevtchn_fd(xce); +} +static void close_evtchn(void) { + xenevtchn_close(xce); +} + +/* fcntl */ + +#define CHECK_FCNTL(openmode) \ + int r = fcntl(object_fd, F_GETFL); \ + if (r < 0) trouble("fcntl F_GETFL"); \ + int m = r & (O_RDONLY | O_WRONLY | O_RDWR); \ + \ + char mbuf[100 + 30*3]; \ + snprintf(mbuf,sizeof(mbuf), \ + "F_GETFL=%#o m=%#o " #openmode "=%#o", \ + r,m,(int)openmode); \ + \ + if (m != openmode) { \ + report("fail", #openmode, mbuf); \ + return; \ + } + +/* readonly */ + +static void setup_readonly(void) { } +static void check_readonly(void) { + CHECK_FCNTL(O_RDONLY); + report("pass", "fcntl", mbuf); +} + +/* appendonly */ + +static void setup_appendonly(void) { } +static void check_appendonly(void) { + CHECK_FCNTL(O_WRONLY); + if (!(r & O_APPEND)) { + report("fail", "O_APPEND", mbuf); + return; + } + report("pass", "fcntl", mbuf); +} + +/* class table and main program */ + +#define DEFCLASS(cl) \ + { #cl, setup_##cl, 0, open_##cl, test_##cl, getfd_##cl, close_##cl } +#define DEFCHECK(meth) \ + { #meth, setup_##meth, check_##meth } + +static const struct classinfo { + const char *name; /* errors: print fd_desc test_which */ + void (*setup)(void); /* best not best not */ + void (*check)(void); /* must may */ + void (*open)(void); /* must may */ + void (*test)(void); /* must must */ + int (*getfd)(void); /* must may */ + void (*close)(void); /* must may */ +} classinfos[] = { + DEFCLASS(privcmd), + DEFCLASS(gntdev), + DEFCLASS(evtchn), + DEFCHECK(readonly), + DEFCHECK(appendonly), + { 0 } +}; + +int main(int argc, char **argv) { + const struct classinfo *cli; + int r; + + argv++; + + logger = (xentoollog_logger*)xtl_createlogger_stdiostream + (stderr, XTL_NOTICE, XTL_STDIOSTREAM_HIDE_PROGRESS); + + fd_desc = "setup"; + test_which = "setup"; + for (cli = classinfos; cli->name; cli++) + cli->setup(); + + while ((classname = *argv++)) { + if (!*argv) errx(8,"need fd after class"); + object_fd = atoi(*argv++); + + fd_desc = *argv++; + if (!fd_desc) errx(8,"need info after fd"); + + for (cli = classinfos; cli->name; cli++) + if (!strcmp(cli->name, classname)) + goto found; + report("fail","unknown class",""); + continue; + + found: + if (cli->check) { + report("checking","check","in progress"); + test_which = "check"; + cli->check(); + } else { + test_which = "open"; + report("checking","dup-hack","in progress"); + cli->open(); + + test_which = test_wh_unrest; cli->test(); + assert(!test_which); + + test_which = "getfd"; int intern_fd = cli->getfd(); + r = dup2(object_fd, intern_fd); + if (r != intern_fd) err(-1, "dup2"); + + test_which = test_wh_rest; cli->test(); + assert(!test_which); + + test_which = "close"; cli->close(); + } + } + + return 0; +}