--- /dev/null
+/*
+ * 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
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <poll.h>
+
+#include <err.h>
+
+#include <xenctrl.h>
+#include <xencall.h>
+#include <xengnttab.h>
+#include <xenevtchn.h>
+
+/*
+ * 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;
+}