[xen staging] tools/tests/depriv: New test utility for deprivilege auditing

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[xen staging] tools/tests/depriv: New test utility for deprivilege auditing

patchbot
commit 3f561daff1697ed747aaae708fb1ab20c2d6ee74
Author:     Ian Jackson <[hidden email]>
AuthorDate: Fri May 25 15:40:27 2018 +0100
Commit:     Ian Jackson <[hidden email]>
CommitDate: Fri Jul 6 16:27:52 2018 +0100

    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 <[hidden email]>
    Acked-by: Wei Liu <[hidden email]>
---
 .gitignore                             |   1 +
 tools/tests/depriv/Makefile            |  42 ++++
 tools/tests/depriv/depriv-fd-checker.c | 399 +++++++++++++++++++++++++++++++++
 3 files changed, 442 insertions(+)

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
+ * <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;
+}
--
generated by git-patchbot for /home/xen/git/xen.git#staging

_______________________________________________
Xen-changelog mailing list
[hidden email]
https://lists.xenproject.org/xen-changelog