/*
* 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
# tun FD is expected to be an open tun device
*
* 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, exactly one of:
* CLASS pass|fail FD INFORMATION... X-INFO
* tun maybe FD IFNAME X-INFO
*
* "pass" means that the descriptor was restricted as expected.
* "fail" means that the descriptor was unrestricted.
* "maybe" means that further information is printed, as detailed above,
* and the caller should check that it is as expected
*/
/*
* 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);
}
#if defined(__linux__)
#include
#include
#include
#include
#include
#ifndef TUNGETIFF
#define TUNGETIFF _IOR('T', 210, unsigned int)
#endif
/* linux tun */
static void setup_tun(void) { }
static void check_tun(void) {
struct ifreq ifr;
int r;
memset(&ifr,0,sizeof(ifr));
r = ioctl(object_fd, TUNGETIFF, (void*)&ifr);
if (r<0) trouble("TUNGETIFF");
printf("tun maybe %d %.*s %s\n", object_fd,
(int)IFNAMSIZ, ifr.ifr_ifrn.ifrn_name,
fd_desc);
}
#define PLATFORM_CLASSES \
DEFCHECK(tun),
#else /* !defined(__linux__) */
#define PLATFORM_CLASSES /* empty */
#endif
/* 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),
PLATFORM_CLASSES
{ 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;
}