[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Xen-devel] [PATCH v2 5/8] tools/tests/depriv: New test utility for deprivilege auditing



I have chosen to licence this utility as LGPL-v2.1-only, similar to
other LGPL elements of the Xen tools, because it may want to be moved
into or combined with osstest or some other project at some point in
the future, so it wants a licence compatible with osstest's AGPLv3+.

Signed-off-by: Ian Jackson <ian.jackson@xxxxxxxxxxxxx>
---
v2: New patch
---
 .gitignore                             |   1 +
 tools/tests/depriv/Makefile            |  42 ++++
 tools/tests/depriv/depriv-fd-checker.c | 399 +++++++++++++++++++++++++++++++++
 3 files changed, 442 insertions(+)
 create mode 100644 tools/tests/depriv/Makefile
 create mode 100644 tools/tests/depriv/depriv-fd-checker.c

diff --git a/.gitignore b/.gitignore
index 7004349..5b8448d 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 0000000..2af1e29
--- /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 0000000..67a3674
--- /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
+ * <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;
+}
-- 
2.1.4


_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxxxxxxxxx
https://lists.xenproject.org/mailman/listinfo/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.