Message ID | 20180515082553.30310-1-richard_c_haines@btinternet.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote: > Add binder tests. See tests/binder/test_binder.c for details on > message flows to test security_binder*() functions. > > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> > --- > README.md | 8 + > defconfig | 8 + > policy/Makefile | 2 +- > policy/test_binder.te | 83 +++++++ > tests/Makefile | 2 +- > tests/binder/Makefile | 7 + > tests/binder/check_binder.c | 80 +++++++ > tests/binder/test | 131 +++++++++++ > tests/binder/test_binder.c | 543 ++++++++++++++++++++++++++++++++++++++++++++ > 9 files changed, 862 insertions(+), 2 deletions(-) > create mode 100644 policy/test_binder.te > create mode 100644 tests/binder/Makefile > create mode 100644 tests/binder/check_binder.c > create mode 100644 tests/binder/test > create mode 100644 tests/binder/test_binder.c > > diff --git a/README.md b/README.md > index c9f3b2b..60a249e 100644 > --- a/README.md > +++ b/README.md > @@ -141,6 +141,14 @@ directory or you can follow these broken-out steps: > The broken-out steps allow you to run the tests multiple times without > loading policy each time. > > +Note that if leaving the test policy in-place for further testing, the > +policy build process changes a boolean: > + On policy load: setsebool allow_domain_fd_use=0 > + On policy unload: setsebool allow_domain_fd_use=1 > +The consequence of this is that after a system reboot, the boolean > +defaults to true. Therefore if running the fdreceive or binder tests, > +reset the boolean to false, otherwise some tests will fail. This isn't accurate - we aren't doing setsebool -P so the boolean change is not persistent across reboots. It will persist across policy reloads however because the kernel preserves booleans across policy reloads. > + > 4) Review the test results. > > As each test script is run, the name of the script will be displayed followed > diff --git a/defconfig b/defconfig > index 7dce8bc..dc6ef30 100644 > --- a/defconfig > +++ b/defconfig > @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m > # This is enabled to test overlayfs SELinux integration. > # It is not required for SELinux operation itself. > CONFIG_OVERLAY_FS=m > + > +# Android binder implementations. > +# These are enabled to test the binder controls in > +# tests/binder; they are not required for SELinux operation itself. > +CONFIG_ANDROID=y > +CONFIG_ANDROID_BINDER_IPC=y > +CONFIG_ANDROID_BINDER_DEVICES="binder" > +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set I don't think we need the last line. > diff --git a/policy/Makefile b/policy/Makefile > index 8ed5e46..5a9d411 100644 > --- a/policy/Makefile > +++ b/policy/Makefile > @@ -25,7 +25,7 @@ TARGETS = \ > test_task_getsid.te test_task_setpgid.te test_task_setsched.te \ > test_transition.te test_inet_socket.te test_unix_socket.te \ > test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \ > - test_ibpkey.te test_atsecure.te > + test_ibpkey.te test_atsecure.te test_binder.te Likely need to make this conditional on the binder class being defined in the policy; see similar logic for e.g. cap_userns, icmp_socket, etc. Otherwise policy won't build on earlier Fedora/RHEL before definition of the binder class. > > ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true) > TARGETS += test_bounds.te > diff --git a/policy/test_binder.te b/policy/test_binder.te > new file mode 100644 > index 0000000..c4ad2ae > --- /dev/null > +++ b/policy/test_binder.te > @@ -0,0 +1,83 @@ > + > +attribute binderdomain; > + > +# > +################################## Manager ################################### > +# > +type test_binder_mgr_t; > +domain_type(test_binder_mgr_t) > +unconfined_runs_test(test_binder_mgr_t) > +typeattribute test_binder_mgr_t testdomain; > +typeattribute test_binder_mgr_t binderdomain; > +allow test_binder_mgr_t self:binder { set_context_mgr call }; > +allow test_binder_mgr_t device_t:chr_file { ioctl open read write map }; Wondering if we should define a .fc file with /dev/binder and a proper binder_device_t type and restorecon it before the test. But not clear it is worth it. Never mind. > +allow test_binder_mgr_t self:capability { sys_nice }; Needed or just to suppress noise in the audit? > +allow test_binder_client_t test_binder_mgr_t:fd use; > + > +# > +################################# Client #################################### > +# > +type test_binder_client_t; > +domain_type(test_binder_client_t) > +unconfined_runs_test(test_binder_client_t) > +typeattribute test_binder_client_t testdomain; > +typeattribute test_binder_client_t binderdomain; > +allow test_binder_client_t self:binder { call }; > +allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate }; Are you actually exercising impersonate? I largely didn't expect it to ever be used in Android itself, just included the check because I saw that it was technically possible as far as the kernel interface is concerned. > +allow test_binder_client_t device_t:chr_file { ioctl open read write map }; > +# For fstat: > +allow test_binder_client_t device_t:chr_file getattr; > + > +# > +############################## Client no call ################################ > +# > +type test_binder_client_no_call_t; > +domain_type(test_binder_client_no_call_t) > +unconfined_runs_test(test_binder_client_no_call_t) > +typeattribute test_binder_client_no_call_t testdomain; > +typeattribute test_binder_client_no_call_t binderdomain; > +allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map }; > + > +# > +############################ Client no transfer ############################# > +# > +type test_binder_client_no_transfer_t; > +domain_type(test_binder_client_no_transfer_t) > +unconfined_runs_test(test_binder_client_no_transfer_t) > +typeattribute test_binder_client_no_transfer_t testdomain; > +typeattribute test_binder_client_no_transfer_t binderdomain; > +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call }; > +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map }; > + > +# > +########################## Manager no fd {use} ############################### > +# > +type test_binder_mgr_no_fd_t; > +domain_type(test_binder_mgr_no_fd_t) > +unconfined_runs_test(test_binder_mgr_no_fd_t) > +typeattribute test_binder_mgr_no_fd_t testdomain; > +typeattribute test_binder_mgr_no_fd_t binderdomain; > +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call }; > +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map }; > +allow test_binder_mgr_no_fd_t self:capability { sys_nice }; > +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer }; > + > +# > +########################### Client no impersonate ############################ > +# > +type test_binder_client_no_im_t; > +domain_type(test_binder_client_no_im_t) > +unconfined_runs_test(test_binder_client_no_im_t) > +typeattribute test_binder_client_no_im_t testdomain; > +typeattribute test_binder_client_no_im_t binderdomain; > +allow test_binder_client_no_im_t self:binder { call }; > +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer }; > +allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map }; > +allow test_binder_client_no_im_t test_binder_mgr_t:fd use; > +allow test_binder_client_no_im_t device_t:chr_file getattr; > + > +# > +############ Allow these domains to be entered from sysadm domain ############ > +# > +miscfiles_domain_entry_test_files(binderdomain) > +userdom_sysadm_entry_spec_domtrans_to(binderdomain) > diff --git a/tests/Makefile b/tests/Makefile > index 27ed6eb..7607c22 100644 > --- a/tests/Makefile > +++ b/tests/Makefile > @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \ > task_setnice task_setscheduler task_getscheduler task_getsid \ > task_getpgid task_setpgid file ioctl capable_file capable_net \ > capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \ > - inet_socket overlay checkreqprot mqueue mac_admin atsecure > + inet_socket overlay checkreqprot mqueue mac_admin atsecure binder Likely needs to be conditional on binder class being defined in policy as with cap_userns, and also depends on e.g. linux/android/binder.h existing? Otherwise will break build on earlier Fedora/RHEL releases. > > ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true) > ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1) > diff --git a/tests/binder/Makefile b/tests/binder/Makefile > new file mode 100644 > index 0000000..a60eeb3 > --- /dev/null > +++ b/tests/binder/Makefile > @@ -0,0 +1,7 @@ > +TARGETS = check_binder test_binder > + > +LDLIBS += -lselinux > + > +all: $(TARGETS) > +clean: > + rm -f $(TARGETS) > diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c > new file mode 100644 > index 0000000..3d553a0 > --- /dev/null > +++ b/tests/binder/check_binder.c > @@ -0,0 +1,80 @@ > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > +#include <fcntl.h> > +#include <errno.h> > +#include <stdbool.h> > +#include <sys/mman.h> > +#include <sys/ioctl.h> > +#include <linux/android/binder.h> > + > +static void usage(char *progname) > +{ > + fprintf(stderr, > + "usage: %s [-v]\n" > + "Where:\n\t" > + "-v Print binder version.\n", progname); > + exit(-1); > +} > + > +int main(int argc, char **argv) > +{ > + int opt, result, fd; > + char *driver = "/dev/binder"; > + bool verbose; > + void *mapped; > + size_t mapsize = 1024; > + struct binder_version vers; > + > + while ((opt = getopt(argc, argv, "v")) != -1) { > + switch (opt) { > + case 'v': > + verbose = true; > + break; > + default: > + usage(argv[0]); > + } > + } > + > + fd = open(driver, O_RDWR | O_CLOEXEC); > + if (fd < 0) { > + fprintf(stderr, "Cannot open: %s error: %s\n", > + driver, strerror(errno)); > + exit(-1); > + } > + > + /* Need this or no VMA error from kernel */ > + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); > + if (mapped == MAP_FAILED) { > + fprintf(stderr, "mmap error: %s\n", strerror(errno)); > + close(fd); > + exit(-1); > + } > + > + result = ioctl(fd, BINDER_VERSION, &vers); > + if (result < 0) { > + fprintf(stderr, "ioctl BINDER_VERSION: %s\n", > + strerror(errno)); > + goto brexit; > + } > + > + if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) { > + fprintf(stderr, > + "Binder kernel version: %d differs from user space version: %d\n", > + vers.protocol_version, > + BINDER_CURRENT_PROTOCOL_VERSION); > + result = -1; > + goto brexit; > + } > + > + if (verbose) > + fprintf(stderr, "Binder kernel version: %d\n", > + vers.protocol_version); > + > +brexit: > + munmap(mapped, mapsize); > + close(fd); > + > + return result; > +} > diff --git a/tests/binder/test b/tests/binder/test > new file mode 100644 > index 0000000..434ae32 > --- /dev/null > +++ b/tests/binder/test > @@ -0,0 +1,131 @@ > +#!/usr/bin/perl > +use Test::More; > + > +BEGIN { > + $basedir = $0; > + $basedir =~ s|(.*)/[^/]*|$1|; > + > + # allow binder info to be shown > + $v = $ARGV[0]; > + if ($v) { > + if ( $v ne "-v" ) { > + plan skip_all => "Invalid option (use -v)"; > + } > + } > + > + # check if binder driver available and the kernel/userspace versions. > + if ( system("$basedir/check_binder 2> /dev/null") != 0 ) { > + plan skip_all => > + "Binder not supported or kernel/userspace versions differ"; > + } > + else { > + plan tests => 6; > + } > +} > + > +if ($v) { Duplicating the test cases for $v and !$v seems prone to inconsistency in the future; can't we just embed $v into the command string being executed? > + if ( ( $pid = fork() ) == 0 ) { > + exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager"; > + } > + > + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. > + > + # Verify that authorized client can transact with the manager. > + $result = > + system "runcon -t test_binder_client_t $basedir/test_binder -v client"; > + ok( $result eq 0 ); This test is failing for me (with or without -v): # ./test -v 1..6 Manager PID: 5608 Process context: unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023 Client PID: 5609 Process context: unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023 Client read_consumed: 28 Manager read_consumed: 72 Client command: BR_NOOP Manager command: BR_NOOP Client command: BR_INCREFS Manager command: BR_TRANSACTION Client command: BR_TRANSACTION_COMPLETE BR_TRANSACTION data: handle: 0 cookie: 0 code: 0 flag: TF_ACCEPT_FDS sender pid: 5609 sender euid: 0 data_size: 24 offsets_size: 8 Sending BC_REPLY Manager read_consumed: 8 Manager command: BR_NOOP Manager command: BR_TRANSACTION_COMPLETE Client read_consumed: 72 Client command: BR_NOOP Client command: BR_REPLY BR_REPLY data: handle: 0 cookie: 0 code: 0 flag: TF_ACCEPT_FDS sender pid: 0 sender euid: 0 data_size: 24 offsets_size: 8 Retrieved Managers fd: 4 st_dev: 6 Client read_consumed: 8 Client using Managers FD command: BR_NOOP Client using Managers FD command: BR_FAILED_REPLY Client using Managers received FD failed response Manager read_consumed: 4 Manager command: BR_NOOP not ok 1 # Failed test at ./test line 36. > + > + # Verify that client cannot call manager (no call perm). > + $result = system > +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Verify that client cannot communicate with manager (no impersonate perm). > + $result = system > +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1"; > + ok( $result >> 8 eq 102 ); > + > + # Verify that client cannot communicate with manager (no transfer perm). > + $result = system > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Kill the manager. > + kill TERM, $pid; > + > + # Verify that client cannot become a manager (no set_context_mgr perm). > + $result = > + system > + "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1"; > + ok( $result >> 8 eq 4 ); > + > +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. > + if ( ( $pid = fork() ) == 0 ) { > + exec > + "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager"; > + } > + > + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. > + > +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager > +# domain and binder will return BR_FAILED_REPLY. > + $result = > + system > + "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Kill the manager > + kill TERM, $pid; > +} > +else { > + if ( ( $pid = fork() ) == 0 ) { > + exec "runcon -t test_binder_mgr_t $basedir/test_binder manager"; > + } > + > + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. > + > + # Verify that authorized client can transact with the manager. > + $result = > + system "runcon -t test_binder_client_t $basedir/test_binder client"; > + ok( $result eq 0 ); > + > + # Verify that client cannot call manager (no call perm). > + $result = system > + "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Verify that client cannot communicate with manager (no impersonate perm). > + $result = system > + "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1"; > + ok( $result >> 8 eq 102 ); > + > + # Verify that client cannot communicate with manager (no transfer perm). > + $result = system > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Kill the manager. > + kill TERM, $pid; > + > + # Verify that client cannot become a manager (no set_context_mgr perm). > + $result = > + system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1"; > + ok( $result >> 8 eq 4 ); > + > +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. > + if ( ( $pid = fork() ) == 0 ) { > + exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager"; > + } > + > + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. > + > +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager > +# domain and binder will return BR_FAILED_REPLY. > + $result = > + system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1"; > + ok( $result >> 8 eq 9 ); > + > + # Kill the manager > + kill TERM, $pid; > +} > +exit; > diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c > new file mode 100644 > index 0000000..8881cce > --- /dev/null > +++ b/tests/binder/test_binder.c > @@ -0,0 +1,543 @@ > +/* > + * This is a simple binder client/server(manager) that only uses the > + * raw ioctl commands. It does not rely on a 'service manager' as in > + * the Android world as it only uses one 'target' = 0. > + * > + * The transaction/reply flow is basically: > + * Client Manager > + * ======== ========= > + * > + * Becomes context manager > + * Send transaction > + * requesting file > + * descriptor from > + * the Manager > + * ---------------------------------------> > + * Manager replies with its > + * binder file descriptor > + * <-------------------------------------- > + * Check fd valid, if so send > + * TF_ONE_WAY transaction to Manager > + * using the received fd > + * ---------------------------------------> > + * End of tests so Manager > + * waits to be killed > + * Client exits > + * > + * Using binder test policy the following will be validated: > + * security_binder_set_context_mgr() binder { set_context_mgr } > + * security_binder_transaction() binder { call impersonate } > + * security_binder_transfer_binder() binder { transfer } > + * security_binder_transfer_file() fd { use } > + * > + * TODO security_binder_transfer_file() uses BPF if configured in kernel. > + */ > + > +#include <errno.h> > +#include <fcntl.h> > +#include <inttypes.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > +#include <sys/stat.h> > +#include <stdbool.h> > +#include <sys/mman.h> > +#include <sys/ioctl.h> > +#include <selinux/selinux.h> > +#include <linux/android/binder.h> > + > +static uint32_t target; /* This will be set to '0' as only need one target */ > +static bool verbose; > + > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text); > + > +static void usage(char *progname) > +{ > + fprintf(stderr, > + "usage: %s [-v] manager | client\n" > + "Where:\n\t" > + "-v Print context and command information.\n\t" > + "manager Act as binder context manager.\n\t" > + "client Act as binder client.\n" > + "\nNote: Ensure this boolean command is run when " > + "testing after a reboot:\n\t" > + "setsebool allow_domain_fd_use=0\n", progname); > + exit(1); > +} > + > +static const char *cmd_name(uint32_t cmd) > +{ > + switch (cmd) { > + case BR_NOOP: > + return "BR_NOOP"; > + case BR_TRANSACTION_COMPLETE: > + return "BR_TRANSACTION_COMPLETE"; > + case BR_INCREFS: > + return "BR_INCREFS"; > + case BR_ACQUIRE: > + return "BR_ACQUIRE"; > + case BR_RELEASE: > + return "BR_RELEASE"; > + case BR_DECREFS: > + return "BR_DECREFS"; > + case BR_TRANSACTION: > + return "BR_TRANSACTION"; > + case BR_REPLY: > + return "BR_REPLY"; > + case BR_FAILED_REPLY: > + return "BR_FAILED_REPLY"; > + case BR_DEAD_REPLY: > + return "BR_DEAD_REPLY"; > + case BR_DEAD_BINDER: > + return "BR_DEAD_BINDER"; > + case BR_ERROR: > + return "BR_ERROR"; > + /* fallthrough */ > + default: > + return "Unknown command"; > + } > +} > + > +void print_trans_data(struct binder_transaction_data *txn) > +{ > + printf("\thandle: %ld\n", (unsigned long)txn->target.handle); > + printf("\tcookie: %lld\n", txn->cookie); > + printf("\tcode: %u\n", txn->code); > + switch (txn->flags) { > + case TF_ONE_WAY: > + printf("\tflag: TF_ONE_WAY\n"); > + break; > + case TF_ROOT_OBJECT: > + printf("\tflag: TF_ROOT_OBJECT\n"); > + break; > + case TF_STATUS_CODE: > + printf("\tflag: TF_STATUS_CODE\n"); > + break; > + case TF_ACCEPT_FDS: > + printf("\tflag: TF_ACCEPT_FDS\n"); > + break; > + default: > + printf("Unknown flag: %u\n", txn->flags); > + } > + printf("\tsender pid: %u\n", txn->sender_pid); > + printf("\tsender euid: %u\n", txn->sender_euid); > + printf("\tdata_size: %llu\n", txn->data_size); > + printf("\toffsets_size: %llu\n", txn->offsets_size); > +} > + > + > +static int send_reply(int fd, struct binder_transaction_data *txn_in) > +{ > + int result; > + unsigned int writebuf[1024]; > + struct flat_binder_object obj; > + struct binder_write_read bwr; > + struct binder_transaction_data *txn; > + > + if (txn_in->flags == TF_ONE_WAY) { > + if (verbose) > + printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n"); > + return 0; > + } > + > + if (verbose) > + printf("Sending BC_REPLY\n"); > + > + writebuf[0] = BC_REPLY; > + txn = (struct binder_transaction_data *)(&writebuf[1]); > + > + memset(txn, 0, sizeof(*txn)); > + txn->target.handle = txn_in->target.handle; > + txn->cookie = txn_in->cookie; > + txn->code = txn_in->code; > + txn->flags = TF_ACCEPT_FDS; > + > + memset(&obj, 0, sizeof(struct flat_binder_object)); > + obj.hdr.type = BINDER_TYPE_FD; > + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; > + obj.binder = txn->target.handle; > + obj.cookie = txn->cookie; > + /* The binder fd is used for testing as it allows policy to set > + * whether the Client/Manager can be allowed access (fd use) or > + * not. For example test_binder_mgr_t has: > + * allow test_binder_client_t test_binder_mgr_t:fd use; > + * whereas test_binder_mgr_no_fd_t does not allow this fd use. > + * > + * This also allows a check for the impersonate permission later as > + * the Client will use this (the Managers fd) to send a transaction. > + */ > + obj.handle = fd; > + > + txn->data_size = sizeof(struct flat_binder_object); > + txn->data.ptr.buffer = (uintptr_t)&obj; > + txn->data.ptr.offsets = (uintptr_t)&obj + > + sizeof(struct flat_binder_object); > + txn->offsets_size = sizeof(binder_size_t); > + > + memset(&bwr, 0, sizeof(bwr)); > + bwr.write_buffer = (unsigned long)writebuf; > + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); > + > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > + if (result < 0) { > + fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n", > + __func__, strerror(errno)); > + return -1; > + } > + > + return result; > +} > + > +/* This retrieves the requested Managers file descriptor, then using this > + * sends a simple transaction to trigger the impersonate permission. > + */ > +static void extract_fd_and_respond(struct binder_transaction_data *txn_in) > +{ > + int result; > + uint32_t cmd; > + struct stat sb; > + struct binder_write_read bwr; > + struct flat_binder_object *obj; > + struct binder_transaction_data *txn; > + unsigned int readbuf[32]; > + unsigned int writebuf[1024]; > + binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets; > + > + /* Get the fbo that contains the Managers binder file descriptor. */ > + obj = (struct flat_binder_object *) > + (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs); > + > + /* fstat this just to see if a valid fd */ > + result = fstat(obj->handle, &sb); > + if (result < 0) { > + fprintf(stderr, "Not a valid fd: %s\n", strerror(errno)); > + exit(100); > + } > + > + if (verbose) > + printf("Retrieved Managers fd: %d st_dev: %ld\n", > + obj->handle, sb.st_dev); > + > + /* Send response using Managers fd to trigger impersonate check. */ > + writebuf[0] = BC_TRANSACTION; > + txn = (struct binder_transaction_data *)(&writebuf[1]); > + memset(txn, 0, sizeof(*txn)); > + txn->target.handle = target; > + txn->cookie = 0; > + txn->code = 0; > + txn->flags = TF_ONE_WAY; > + > + txn->data_size = 0; > + txn->data.ptr.buffer = (uintptr_t)NULL; > + txn->data.ptr.offsets = (uintptr_t)NULL; > + txn->offsets_size = 0; > + > + memset(&bwr, 0, sizeof(bwr)); > + bwr.write_buffer = (unsigned long)writebuf; > + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); > + > + bwr.read_size = sizeof(readbuf); > + bwr.read_consumed = 0; > + bwr.read_buffer = (uintptr_t)readbuf; > + > + result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr); > + if (result < 0) { > + fprintf(stderr, > + "CLIENT ioctl BINDER_WRITE_READ: %s\n", > + strerror(errno)); > + exit(101); > + } > + > + if (verbose) > + printf("Client read_consumed: %lld\n", bwr.read_consumed); > + > + cmd = binder_parse(obj->handle, (uintptr_t)readbuf, > + bwr.read_consumed, > + "Client using Managers FD"); > + > + if (cmd == BR_FAILED_REPLY || > + cmd == BR_DEAD_REPLY || > + cmd == BR_DEAD_BINDER) { > + fprintf(stderr, > + "Client using Managers received FD failed response\n"); > + exit(102); > + } > +} > + > +/* Parse response, reply as required and then return last CMD */ > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text) > +{ > + uintptr_t end = ptr + (uintptr_t)size; > + uint32_t cmd; > + > + while (ptr < end) { > + cmd = *(uint32_t *)ptr; > + ptr += sizeof(uint32_t); > + > + if (verbose) > + printf("%s command: %s\n", text, cmd_name(cmd)); > + > + switch (cmd) { > + case BR_NOOP: > + break; > + case BR_TRANSACTION_COMPLETE: > + break; > + case BR_INCREFS: > + case BR_ACQUIRE: > + case BR_RELEASE: > + case BR_DECREFS: > + ptr += sizeof(struct binder_ptr_cookie); > + break; > + case BR_TRANSACTION: { > + struct binder_transaction_data *txn = > + (struct binder_transaction_data *)ptr; > + > + if (verbose) { > + printf("BR_TRANSACTION data:\n"); > + print_trans_data(txn); > + } > + > + /* The manager sends reply that will contain its fd */ > + if (send_reply(fd, txn) < 0) { > + fprintf(stderr, "send_reply() failed.\n"); > + return -1; > + } > + ptr += sizeof(*txn); > + break; > + } > + case BR_REPLY: { > + struct binder_transaction_data *txn = > + (struct binder_transaction_data *)ptr; > + > + if (verbose) { > + printf("BR_REPLY data:\n"); > + print_trans_data(txn); > + } > + > + /* Client extracts the Manager fd, and responds */ > + extract_fd_and_respond(txn); > + ptr += sizeof(*txn); > + break; > + } > + case BR_DEAD_BINDER: > + break; > + case BR_FAILED_REPLY: > + break; > + case BR_DEAD_REPLY: > + break; > + case BR_ERROR: > + ptr += sizeof(uint32_t); > + break; > + default: > + if (verbose) > + printf("%s Parsed unknown command: %d\n", > + text, cmd); > + return -1; > + } > + } > + > + return cmd; > +} > + > +int main(int argc, char **argv) > +{ > + int opt, option, result, fd, count; > + uint32_t cmd; > + pid_t pid; > + char *driver = "/dev/binder"; > + char *context; > + void *mapped; > + size_t mapsize = 2048; > + struct binder_write_read bwr; > + struct flat_binder_object obj; > + struct binder_transaction_data *txn; > + unsigned int readbuf[32]; > + unsigned int writebuf[1024]; > + > + target = 0; /* Only need one target - the Manager */ > + verbose = false; > + > + while ((opt = getopt(argc, argv, "v")) != -1) { > + switch (opt) { > + case 'v': > + verbose = true; > + break; > + default: > + usage(argv[0]); > + } > + } > + > + if ((argc - optind) != 1) > + usage(argv[0]); > + > + if (!strcmp(argv[optind], "manager")) > + option = 1; > + else if (!strcmp(argv[optind], "client")) > + option = 2; > + else > + usage(argv[0]); > + > + fd = open(driver, O_RDWR | O_CLOEXEC); > + if (fd < 0) { > + fprintf(stderr, "Cannot open %s error: %s\n", driver, > + strerror(errno)); > + exit(1); > + } > + > + /* Need this or "no VMA" error from kernel */ > + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); > + if (mapped == MAP_FAILED) { > + fprintf(stderr, "mmap error: %s\n", strerror(errno)); > + close(fd); > + exit(2); > + } > + > + /* Get our context and pid */ > + result = getcon(&context); > + if (result < 0) { > + fprintf(stderr, "Failed to obtain SELinux context\n"); > + result = 3; > + goto brexit; > + } > + pid = getpid(); > + > + switch (option) { > + case 1: /* manager */ > + if (verbose) { > + printf("Manager PID: %d Process context:\n\t%s\n", > + pid, context); > + } > + > + result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0); > + if (result < 0) { > + fprintf(stderr, > + "Failed to become context manager: %s\n", > + strerror(errno)); > + result = 4; > + goto brexit; > + } > + > + readbuf[0] = BC_ENTER_LOOPER; > + bwr.write_size = sizeof(readbuf[0]); > + bwr.write_consumed = 0; > + bwr.write_buffer = (uintptr_t)readbuf; > + > + bwr.read_size = 0; > + bwr.read_consumed = 0; > + bwr.read_buffer = 0; > + > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > + if (result < 0) { > + fprintf(stderr, > + "Manager ioctl BINDER_WRITE_READ: %s\n", > + strerror(errno)); > + result = 5; > + goto brexit; > + } > + > + while (true) { > + bwr.read_size = sizeof(readbuf); > + bwr.read_consumed = 0; > + bwr.read_buffer = (uintptr_t)readbuf; > + > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > + if (result < 0) { > + fprintf(stderr, > + "Manager ioctl BINDER_WRITE_READ: %s\n", > + strerror(errno)); > + result = 6; > + goto brexit; > + } > + > + if (bwr.read_consumed == 0) > + continue; > + > + if (verbose) > + printf("Manager read_consumed: %lld\n", > + bwr.read_consumed); > + > + cmd = binder_parse(fd, (uintptr_t)readbuf, > + bwr.read_consumed, "Manager"); > + } > + break; > + > + case 2: /* client */ > + if (verbose) { > + printf("Client PID: %d Process context:\n\t%s\n", > + pid, context); > + } > + > + writebuf[0] = BC_TRANSACTION; > + txn = (struct binder_transaction_data *)(&writebuf[1]); > + memset(txn, 0, sizeof(*txn)); > + txn->target.handle = target; > + txn->cookie = 0; > + txn->code = 0; > + txn->flags = TF_ACCEPT_FDS; > + > + memset(&obj, 0, sizeof(struct flat_binder_object)); > + obj.hdr.type = BINDER_TYPE_WEAK_BINDER; > + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; > + obj.binder = target; > + obj.handle = 0; > + obj.cookie = 0; > + > + txn->data_size = sizeof(struct flat_binder_object); > + txn->data.ptr.buffer = (uintptr_t)&obj; > + txn->data.ptr.offsets = (uintptr_t)&obj + > + sizeof(struct flat_binder_object); > + txn->offsets_size = sizeof(binder_size_t); > + > + memset(&bwr, 0, sizeof(bwr)); > + bwr.write_buffer = (unsigned long)writebuf; > + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); > + > + /* Expect client to get max two responses: > + * 1) From the above BC_TRANSACTION > + * 2) The responding BC_REPLY from send_reply() > + * unless an error. > + */ > + count = 0; > + while (count != 2) { > + bwr.read_size = sizeof(readbuf); > + bwr.read_consumed = 0; > + bwr.read_buffer = (uintptr_t)readbuf; > + > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > + if (result < 0) { > + fprintf(stderr, > + "Client ioctl BINDER_WRITE_READ: %s\n", > + strerror(errno)); > + result = 8; > + goto brexit; > + } > + > + if (verbose) > + printf("Client read_consumed: %lld\n", > + bwr.read_consumed); > + > + cmd = binder_parse(fd, (uintptr_t)readbuf, > + bwr.read_consumed, "Client"); > + > + if (cmd == BR_FAILED_REPLY || > + cmd == BR_DEAD_REPLY || > + cmd == BR_DEAD_BINDER) { > + result = 9; > + goto brexit; > + } > + count++; > + } > + break; > + > + default: > + result = -1; > + } > + > +brexit: > + free(context); > + munmap(mapped, mapsize); > + close(fd); > + > + return result; > +} >
On 05/15/2018 09:36 AM, Stephen Smalley wrote: > On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote: >> Add binder tests. See tests/binder/test_binder.c for details on >> message flows to test security_binder*() functions. >> >> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> >> --- >> README.md | 8 + >> defconfig | 8 + >> policy/Makefile | 2 +- >> policy/test_binder.te | 83 +++++++ >> tests/Makefile | 2 +- >> tests/binder/Makefile | 7 + >> tests/binder/check_binder.c | 80 +++++++ >> tests/binder/test | 131 +++++++++++ >> tests/binder/test_binder.c | 543 ++++++++++++++++++++++++++++++++++++++++++++ >> 9 files changed, 862 insertions(+), 2 deletions(-) >> create mode 100644 policy/test_binder.te >> create mode 100644 tests/binder/Makefile >> create mode 100644 tests/binder/check_binder.c >> create mode 100644 tests/binder/test >> create mode 100644 tests/binder/test_binder.c >> >> diff --git a/README.md b/README.md >> index c9f3b2b..60a249e 100644 >> --- a/README.md >> +++ b/README.md >> @@ -141,6 +141,14 @@ directory or you can follow these broken-out steps: >> The broken-out steps allow you to run the tests multiple times without >> loading policy each time. >> >> +Note that if leaving the test policy in-place for further testing, the >> +policy build process changes a boolean: >> + On policy load: setsebool allow_domain_fd_use=0 >> + On policy unload: setsebool allow_domain_fd_use=1 >> +The consequence of this is that after a system reboot, the boolean >> +defaults to true. Therefore if running the fdreceive or binder tests, >> +reset the boolean to false, otherwise some tests will fail. > > This isn't accurate - we aren't doing setsebool -P so the boolean change is not persistent across > reboots. It will persist across policy reloads however because the kernel preserves booleans across > policy reloads. Sorry, never mind - I misread the text above. You are correct. > >> + >> 4) Review the test results. >> >> As each test script is run, the name of the script will be displayed followed >> diff --git a/defconfig b/defconfig >> index 7dce8bc..dc6ef30 100644 >> --- a/defconfig >> +++ b/defconfig >> @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m >> # This is enabled to test overlayfs SELinux integration. >> # It is not required for SELinux operation itself. >> CONFIG_OVERLAY_FS=m >> + >> +# Android binder implementations. >> +# These are enabled to test the binder controls in >> +# tests/binder; they are not required for SELinux operation itself. >> +CONFIG_ANDROID=y >> +CONFIG_ANDROID_BINDER_IPC=y >> +CONFIG_ANDROID_BINDER_DEVICES="binder" >> +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set > > I don't think we need the last line. > >> diff --git a/policy/Makefile b/policy/Makefile >> index 8ed5e46..5a9d411 100644 >> --- a/policy/Makefile >> +++ b/policy/Makefile >> @@ -25,7 +25,7 @@ TARGETS = \ >> test_task_getsid.te test_task_setpgid.te test_task_setsched.te \ >> test_transition.te test_inet_socket.te test_unix_socket.te \ >> test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \ >> - test_ibpkey.te test_atsecure.te >> + test_ibpkey.te test_atsecure.te test_binder.te > > Likely need to make this conditional on the binder class being defined in the policy; > see similar logic for e.g. cap_userns, icmp_socket, etc. Otherwise policy won't build > on earlier Fedora/RHEL before definition of the binder class. > >> >> ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true) >> TARGETS += test_bounds.te >> diff --git a/policy/test_binder.te b/policy/test_binder.te >> new file mode 100644 >> index 0000000..c4ad2ae >> --- /dev/null >> +++ b/policy/test_binder.te >> @@ -0,0 +1,83 @@ >> + >> +attribute binderdomain; >> + >> +# >> +################################## Manager ################################### >> +# >> +type test_binder_mgr_t; >> +domain_type(test_binder_mgr_t) >> +unconfined_runs_test(test_binder_mgr_t) >> +typeattribute test_binder_mgr_t testdomain; >> +typeattribute test_binder_mgr_t binderdomain; >> +allow test_binder_mgr_t self:binder { set_context_mgr call }; >> +allow test_binder_mgr_t device_t:chr_file { ioctl open read write map }; > > Wondering if we should define a .fc file with /dev/binder and a proper binder_device_t type > and restorecon it before the test. But not clear it is worth it. Never mind. > >> +allow test_binder_mgr_t self:capability { sys_nice }; > > Needed or just to suppress noise in the audit? > >> +allow test_binder_client_t test_binder_mgr_t:fd use; >> + >> +# >> +################################# Client #################################### >> +# >> +type test_binder_client_t; >> +domain_type(test_binder_client_t) >> +unconfined_runs_test(test_binder_client_t) >> +typeattribute test_binder_client_t testdomain; >> +typeattribute test_binder_client_t binderdomain; >> +allow test_binder_client_t self:binder { call }; >> +allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate }; > > Are you actually exercising impersonate? I largely didn't expect it to ever be used in Android itself, > just included the check because I saw that it was technically possible as far as the kernel interface > is concerned. > >> +allow test_binder_client_t device_t:chr_file { ioctl open read write map }; >> +# For fstat: >> +allow test_binder_client_t device_t:chr_file getattr; >> + >> +# >> +############################## Client no call ################################ >> +# >> +type test_binder_client_no_call_t; >> +domain_type(test_binder_client_no_call_t) >> +unconfined_runs_test(test_binder_client_no_call_t) >> +typeattribute test_binder_client_no_call_t testdomain; >> +typeattribute test_binder_client_no_call_t binderdomain; >> +allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map }; >> + >> +# >> +############################ Client no transfer ############################# >> +# >> +type test_binder_client_no_transfer_t; >> +domain_type(test_binder_client_no_transfer_t) >> +unconfined_runs_test(test_binder_client_no_transfer_t) >> +typeattribute test_binder_client_no_transfer_t testdomain; >> +typeattribute test_binder_client_no_transfer_t binderdomain; >> +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call }; >> +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map }; >> + >> +# >> +########################## Manager no fd {use} ############################### >> +# >> +type test_binder_mgr_no_fd_t; >> +domain_type(test_binder_mgr_no_fd_t) >> +unconfined_runs_test(test_binder_mgr_no_fd_t) >> +typeattribute test_binder_mgr_no_fd_t testdomain; >> +typeattribute test_binder_mgr_no_fd_t binderdomain; >> +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call }; >> +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map }; >> +allow test_binder_mgr_no_fd_t self:capability { sys_nice }; >> +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer }; >> + >> +# >> +########################### Client no impersonate ############################ >> +# >> +type test_binder_client_no_im_t; >> +domain_type(test_binder_client_no_im_t) >> +unconfined_runs_test(test_binder_client_no_im_t) >> +typeattribute test_binder_client_no_im_t testdomain; >> +typeattribute test_binder_client_no_im_t binderdomain; >> +allow test_binder_client_no_im_t self:binder { call }; >> +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer }; >> +allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map }; >> +allow test_binder_client_no_im_t test_binder_mgr_t:fd use; >> +allow test_binder_client_no_im_t device_t:chr_file getattr; >> + >> +# >> +############ Allow these domains to be entered from sysadm domain ############ >> +# >> +miscfiles_domain_entry_test_files(binderdomain) >> +userdom_sysadm_entry_spec_domtrans_to(binderdomain) >> diff --git a/tests/Makefile b/tests/Makefile >> index 27ed6eb..7607c22 100644 >> --- a/tests/Makefile >> +++ b/tests/Makefile >> @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \ >> task_setnice task_setscheduler task_getscheduler task_getsid \ >> task_getpgid task_setpgid file ioctl capable_file capable_net \ >> capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \ >> - inet_socket overlay checkreqprot mqueue mac_admin atsecure >> + inet_socket overlay checkreqprot mqueue mac_admin atsecure binder > > Likely needs to be conditional on binder class being defined in policy as with cap_userns, > and also depends on e.g. linux/android/binder.h existing? Otherwise will break build on earlier > Fedora/RHEL releases. > >> >> ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true) >> ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1) >> diff --git a/tests/binder/Makefile b/tests/binder/Makefile >> new file mode 100644 >> index 0000000..a60eeb3 >> --- /dev/null >> +++ b/tests/binder/Makefile >> @@ -0,0 +1,7 @@ >> +TARGETS = check_binder test_binder >> + >> +LDLIBS += -lselinux >> + >> +all: $(TARGETS) >> +clean: >> + rm -f $(TARGETS) >> diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c >> new file mode 100644 >> index 0000000..3d553a0 >> --- /dev/null >> +++ b/tests/binder/check_binder.c >> @@ -0,0 +1,80 @@ >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <unistd.h> >> +#include <fcntl.h> >> +#include <errno.h> >> +#include <stdbool.h> >> +#include <sys/mman.h> >> +#include <sys/ioctl.h> >> +#include <linux/android/binder.h> >> + >> +static void usage(char *progname) >> +{ >> + fprintf(stderr, >> + "usage: %s [-v]\n" >> + "Where:\n\t" >> + "-v Print binder version.\n", progname); >> + exit(-1); >> +} >> + >> +int main(int argc, char **argv) >> +{ >> + int opt, result, fd; >> + char *driver = "/dev/binder"; >> + bool verbose; >> + void *mapped; >> + size_t mapsize = 1024; >> + struct binder_version vers; >> + >> + while ((opt = getopt(argc, argv, "v")) != -1) { >> + switch (opt) { >> + case 'v': >> + verbose = true; >> + break; >> + default: >> + usage(argv[0]); >> + } >> + } >> + >> + fd = open(driver, O_RDWR | O_CLOEXEC); >> + if (fd < 0) { >> + fprintf(stderr, "Cannot open: %s error: %s\n", >> + driver, strerror(errno)); >> + exit(-1); >> + } >> + >> + /* Need this or no VMA error from kernel */ >> + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); >> + if (mapped == MAP_FAILED) { >> + fprintf(stderr, "mmap error: %s\n", strerror(errno)); >> + close(fd); >> + exit(-1); >> + } >> + >> + result = ioctl(fd, BINDER_VERSION, &vers); >> + if (result < 0) { >> + fprintf(stderr, "ioctl BINDER_VERSION: %s\n", >> + strerror(errno)); >> + goto brexit; >> + } >> + >> + if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) { >> + fprintf(stderr, >> + "Binder kernel version: %d differs from user space version: %d\n", >> + vers.protocol_version, >> + BINDER_CURRENT_PROTOCOL_VERSION); >> + result = -1; >> + goto brexit; >> + } >> + >> + if (verbose) >> + fprintf(stderr, "Binder kernel version: %d\n", >> + vers.protocol_version); >> + >> +brexit: >> + munmap(mapped, mapsize); >> + close(fd); >> + >> + return result; >> +} >> diff --git a/tests/binder/test b/tests/binder/test >> new file mode 100644 >> index 0000000..434ae32 >> --- /dev/null >> +++ b/tests/binder/test >> @@ -0,0 +1,131 @@ >> +#!/usr/bin/perl >> +use Test::More; >> + >> +BEGIN { >> + $basedir = $0; >> + $basedir =~ s|(.*)/[^/]*|$1|; >> + >> + # allow binder info to be shown >> + $v = $ARGV[0]; >> + if ($v) { >> + if ( $v ne "-v" ) { >> + plan skip_all => "Invalid option (use -v)"; >> + } >> + } >> + >> + # check if binder driver available and the kernel/userspace versions. >> + if ( system("$basedir/check_binder 2> /dev/null") != 0 ) { >> + plan skip_all => >> + "Binder not supported or kernel/userspace versions differ"; >> + } >> + else { >> + plan tests => 6; >> + } >> +} >> + >> +if ($v) { > > Duplicating the test cases for $v and !$v seems prone to inconsistency in the future; > can't we just embed $v into the command string being executed? > >> + if ( ( $pid = fork() ) == 0 ) { >> + exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager"; >> + } >> + >> + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. >> + >> + # Verify that authorized client can transact with the manager. >> + $result = >> + system "runcon -t test_binder_client_t $basedir/test_binder -v client"; >> + ok( $result eq 0 ); > > This test is failing for me (with or without -v): > # ./test -v > 1..6 > Manager PID: 5608 Process context: > unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023 > Client PID: 5609 Process context: > unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023 > Client read_consumed: 28 > Manager read_consumed: 72 > Client command: BR_NOOP > Manager command: BR_NOOP > Client command: BR_INCREFS > Manager command: BR_TRANSACTION > Client command: BR_TRANSACTION_COMPLETE > BR_TRANSACTION data: > handle: 0 > cookie: 0 > code: 0 > flag: TF_ACCEPT_FDS > sender pid: 5609 > sender euid: 0 > data_size: 24 > offsets_size: 8 > Sending BC_REPLY > Manager read_consumed: 8 > Manager command: BR_NOOP > Manager command: BR_TRANSACTION_COMPLETE > Client read_consumed: 72 > Client command: BR_NOOP > Client command: BR_REPLY > BR_REPLY data: > handle: 0 > cookie: 0 > code: 0 > flag: TF_ACCEPT_FDS > sender pid: 0 > sender euid: 0 > data_size: 24 > offsets_size: 8 > Retrieved Managers fd: 4 st_dev: 6 > Client read_consumed: 8 > Client using Managers FD command: BR_NOOP > Client using Managers FD command: BR_FAILED_REPLY > Client using Managers received FD failed response > Manager read_consumed: 4 > Manager command: BR_NOOP > not ok 1 > # Failed test at ./test line 36. Just realized that I was testing with a kernel that still had Casey's stacking support enabled. Will re-try without that. > > >> + >> + # Verify that client cannot call manager (no call perm). >> + $result = system >> +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Verify that client cannot communicate with manager (no impersonate perm). >> + $result = system >> +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1"; >> + ok( $result >> 8 eq 102 ); >> + >> + # Verify that client cannot communicate with manager (no transfer perm). >> + $result = system >> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Kill the manager. >> + kill TERM, $pid; >> + >> + # Verify that client cannot become a manager (no set_context_mgr perm). >> + $result = >> + system >> + "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1"; >> + ok( $result >> 8 eq 4 ); >> + >> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. >> + if ( ( $pid = fork() ) == 0 ) { >> + exec >> + "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager"; >> + } >> + >> + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. >> + >> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager >> +# domain and binder will return BR_FAILED_REPLY. >> + $result = >> + system >> + "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Kill the manager >> + kill TERM, $pid; >> +} >> +else { >> + if ( ( $pid = fork() ) == 0 ) { >> + exec "runcon -t test_binder_mgr_t $basedir/test_binder manager"; >> + } >> + >> + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. >> + >> + # Verify that authorized client can transact with the manager. >> + $result = >> + system "runcon -t test_binder_client_t $basedir/test_binder client"; >> + ok( $result eq 0 ); >> + >> + # Verify that client cannot call manager (no call perm). >> + $result = system >> + "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Verify that client cannot communicate with manager (no impersonate perm). >> + $result = system >> + "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1"; >> + ok( $result >> 8 eq 102 ); >> + >> + # Verify that client cannot communicate with manager (no transfer perm). >> + $result = system >> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Kill the manager. >> + kill TERM, $pid; >> + >> + # Verify that client cannot become a manager (no set_context_mgr perm). >> + $result = >> + system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1"; >> + ok( $result >> 8 eq 4 ); >> + >> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. >> + if ( ( $pid = fork() ) == 0 ) { >> + exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager"; >> + } >> + >> + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. >> + >> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager >> +# domain and binder will return BR_FAILED_REPLY. >> + $result = >> + system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1"; >> + ok( $result >> 8 eq 9 ); >> + >> + # Kill the manager >> + kill TERM, $pid; >> +} >> +exit; >> diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c >> new file mode 100644 >> index 0000000..8881cce >> --- /dev/null >> +++ b/tests/binder/test_binder.c >> @@ -0,0 +1,543 @@ >> +/* >> + * This is a simple binder client/server(manager) that only uses the >> + * raw ioctl commands. It does not rely on a 'service manager' as in >> + * the Android world as it only uses one 'target' = 0. >> + * >> + * The transaction/reply flow is basically: >> + * Client Manager >> + * ======== ========= >> + * >> + * Becomes context manager >> + * Send transaction >> + * requesting file >> + * descriptor from >> + * the Manager >> + * ---------------------------------------> >> + * Manager replies with its >> + * binder file descriptor >> + * <-------------------------------------- >> + * Check fd valid, if so send >> + * TF_ONE_WAY transaction to Manager >> + * using the received fd >> + * ---------------------------------------> >> + * End of tests so Manager >> + * waits to be killed >> + * Client exits >> + * >> + * Using binder test policy the following will be validated: >> + * security_binder_set_context_mgr() binder { set_context_mgr } >> + * security_binder_transaction() binder { call impersonate } >> + * security_binder_transfer_binder() binder { transfer } >> + * security_binder_transfer_file() fd { use } >> + * >> + * TODO security_binder_transfer_file() uses BPF if configured in kernel. >> + */ >> + >> +#include <errno.h> >> +#include <fcntl.h> >> +#include <inttypes.h> >> +#include <stdio.h> >> +#include <stdlib.h> >> +#include <string.h> >> +#include <unistd.h> >> +#include <sys/stat.h> >> +#include <stdbool.h> >> +#include <sys/mman.h> >> +#include <sys/ioctl.h> >> +#include <selinux/selinux.h> >> +#include <linux/android/binder.h> >> + >> +static uint32_t target; /* This will be set to '0' as only need one target */ >> +static bool verbose; >> + >> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text); >> + >> +static void usage(char *progname) >> +{ >> + fprintf(stderr, >> + "usage: %s [-v] manager | client\n" >> + "Where:\n\t" >> + "-v Print context and command information.\n\t" >> + "manager Act as binder context manager.\n\t" >> + "client Act as binder client.\n" >> + "\nNote: Ensure this boolean command is run when " >> + "testing after a reboot:\n\t" >> + "setsebool allow_domain_fd_use=0\n", progname); >> + exit(1); >> +} >> + >> +static const char *cmd_name(uint32_t cmd) >> +{ >> + switch (cmd) { >> + case BR_NOOP: >> + return "BR_NOOP"; >> + case BR_TRANSACTION_COMPLETE: >> + return "BR_TRANSACTION_COMPLETE"; >> + case BR_INCREFS: >> + return "BR_INCREFS"; >> + case BR_ACQUIRE: >> + return "BR_ACQUIRE"; >> + case BR_RELEASE: >> + return "BR_RELEASE"; >> + case BR_DECREFS: >> + return "BR_DECREFS"; >> + case BR_TRANSACTION: >> + return "BR_TRANSACTION"; >> + case BR_REPLY: >> + return "BR_REPLY"; >> + case BR_FAILED_REPLY: >> + return "BR_FAILED_REPLY"; >> + case BR_DEAD_REPLY: >> + return "BR_DEAD_REPLY"; >> + case BR_DEAD_BINDER: >> + return "BR_DEAD_BINDER"; >> + case BR_ERROR: >> + return "BR_ERROR"; >> + /* fallthrough */ >> + default: >> + return "Unknown command"; >> + } >> +} >> + >> +void print_trans_data(struct binder_transaction_data *txn) >> +{ >> + printf("\thandle: %ld\n", (unsigned long)txn->target.handle); >> + printf("\tcookie: %lld\n", txn->cookie); >> + printf("\tcode: %u\n", txn->code); >> + switch (txn->flags) { >> + case TF_ONE_WAY: >> + printf("\tflag: TF_ONE_WAY\n"); >> + break; >> + case TF_ROOT_OBJECT: >> + printf("\tflag: TF_ROOT_OBJECT\n"); >> + break; >> + case TF_STATUS_CODE: >> + printf("\tflag: TF_STATUS_CODE\n"); >> + break; >> + case TF_ACCEPT_FDS: >> + printf("\tflag: TF_ACCEPT_FDS\n"); >> + break; >> + default: >> + printf("Unknown flag: %u\n", txn->flags); >> + } >> + printf("\tsender pid: %u\n", txn->sender_pid); >> + printf("\tsender euid: %u\n", txn->sender_euid); >> + printf("\tdata_size: %llu\n", txn->data_size); >> + printf("\toffsets_size: %llu\n", txn->offsets_size); >> +} >> + >> + >> +static int send_reply(int fd, struct binder_transaction_data *txn_in) >> +{ >> + int result; >> + unsigned int writebuf[1024]; >> + struct flat_binder_object obj; >> + struct binder_write_read bwr; >> + struct binder_transaction_data *txn; >> + >> + if (txn_in->flags == TF_ONE_WAY) { >> + if (verbose) >> + printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n"); >> + return 0; >> + } >> + >> + if (verbose) >> + printf("Sending BC_REPLY\n"); >> + >> + writebuf[0] = BC_REPLY; >> + txn = (struct binder_transaction_data *)(&writebuf[1]); >> + >> + memset(txn, 0, sizeof(*txn)); >> + txn->target.handle = txn_in->target.handle; >> + txn->cookie = txn_in->cookie; >> + txn->code = txn_in->code; >> + txn->flags = TF_ACCEPT_FDS; >> + >> + memset(&obj, 0, sizeof(struct flat_binder_object)); >> + obj.hdr.type = BINDER_TYPE_FD; >> + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; >> + obj.binder = txn->target.handle; >> + obj.cookie = txn->cookie; >> + /* The binder fd is used for testing as it allows policy to set >> + * whether the Client/Manager can be allowed access (fd use) or >> + * not. For example test_binder_mgr_t has: >> + * allow test_binder_client_t test_binder_mgr_t:fd use; >> + * whereas test_binder_mgr_no_fd_t does not allow this fd use. >> + * >> + * This also allows a check for the impersonate permission later as >> + * the Client will use this (the Managers fd) to send a transaction. >> + */ >> + obj.handle = fd; >> + >> + txn->data_size = sizeof(struct flat_binder_object); >> + txn->data.ptr.buffer = (uintptr_t)&obj; >> + txn->data.ptr.offsets = (uintptr_t)&obj + >> + sizeof(struct flat_binder_object); >> + txn->offsets_size = sizeof(binder_size_t); >> + >> + memset(&bwr, 0, sizeof(bwr)); >> + bwr.write_buffer = (unsigned long)writebuf; >> + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); >> + >> + result = ioctl(fd, BINDER_WRITE_READ, &bwr); >> + if (result < 0) { >> + fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n", >> + __func__, strerror(errno)); >> + return -1; >> + } >> + >> + return result; >> +} >> + >> +/* This retrieves the requested Managers file descriptor, then using this >> + * sends a simple transaction to trigger the impersonate permission. >> + */ >> +static void extract_fd_and_respond(struct binder_transaction_data *txn_in) >> +{ >> + int result; >> + uint32_t cmd; >> + struct stat sb; >> + struct binder_write_read bwr; >> + struct flat_binder_object *obj; >> + struct binder_transaction_data *txn; >> + unsigned int readbuf[32]; >> + unsigned int writebuf[1024]; >> + binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets; >> + >> + /* Get the fbo that contains the Managers binder file descriptor. */ >> + obj = (struct flat_binder_object *) >> + (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs); >> + >> + /* fstat this just to see if a valid fd */ >> + result = fstat(obj->handle, &sb); >> + if (result < 0) { >> + fprintf(stderr, "Not a valid fd: %s\n", strerror(errno)); >> + exit(100); >> + } >> + >> + if (verbose) >> + printf("Retrieved Managers fd: %d st_dev: %ld\n", >> + obj->handle, sb.st_dev); >> + >> + /* Send response using Managers fd to trigger impersonate check. */ >> + writebuf[0] = BC_TRANSACTION; >> + txn = (struct binder_transaction_data *)(&writebuf[1]); >> + memset(txn, 0, sizeof(*txn)); >> + txn->target.handle = target; >> + txn->cookie = 0; >> + txn->code = 0; >> + txn->flags = TF_ONE_WAY; >> + >> + txn->data_size = 0; >> + txn->data.ptr.buffer = (uintptr_t)NULL; >> + txn->data.ptr.offsets = (uintptr_t)NULL; >> + txn->offsets_size = 0; >> + >> + memset(&bwr, 0, sizeof(bwr)); >> + bwr.write_buffer = (unsigned long)writebuf; >> + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); >> + >> + bwr.read_size = sizeof(readbuf); >> + bwr.read_consumed = 0; >> + bwr.read_buffer = (uintptr_t)readbuf; >> + >> + result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr); >> + if (result < 0) { >> + fprintf(stderr, >> + "CLIENT ioctl BINDER_WRITE_READ: %s\n", >> + strerror(errno)); >> + exit(101); >> + } >> + >> + if (verbose) >> + printf("Client read_consumed: %lld\n", bwr.read_consumed); >> + >> + cmd = binder_parse(obj->handle, (uintptr_t)readbuf, >> + bwr.read_consumed, >> + "Client using Managers FD"); >> + >> + if (cmd == BR_FAILED_REPLY || >> + cmd == BR_DEAD_REPLY || >> + cmd == BR_DEAD_BINDER) { >> + fprintf(stderr, >> + "Client using Managers received FD failed response\n"); >> + exit(102); >> + } >> +} >> + >> +/* Parse response, reply as required and then return last CMD */ >> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text) >> +{ >> + uintptr_t end = ptr + (uintptr_t)size; >> + uint32_t cmd; >> + >> + while (ptr < end) { >> + cmd = *(uint32_t *)ptr; >> + ptr += sizeof(uint32_t); >> + >> + if (verbose) >> + printf("%s command: %s\n", text, cmd_name(cmd)); >> + >> + switch (cmd) { >> + case BR_NOOP: >> + break; >> + case BR_TRANSACTION_COMPLETE: >> + break; >> + case BR_INCREFS: >> + case BR_ACQUIRE: >> + case BR_RELEASE: >> + case BR_DECREFS: >> + ptr += sizeof(struct binder_ptr_cookie); >> + break; >> + case BR_TRANSACTION: { >> + struct binder_transaction_data *txn = >> + (struct binder_transaction_data *)ptr; >> + >> + if (verbose) { >> + printf("BR_TRANSACTION data:\n"); >> + print_trans_data(txn); >> + } >> + >> + /* The manager sends reply that will contain its fd */ >> + if (send_reply(fd, txn) < 0) { >> + fprintf(stderr, "send_reply() failed.\n"); >> + return -1; >> + } >> + ptr += sizeof(*txn); >> + break; >> + } >> + case BR_REPLY: { >> + struct binder_transaction_data *txn = >> + (struct binder_transaction_data *)ptr; >> + >> + if (verbose) { >> + printf("BR_REPLY data:\n"); >> + print_trans_data(txn); >> + } >> + >> + /* Client extracts the Manager fd, and responds */ >> + extract_fd_and_respond(txn); >> + ptr += sizeof(*txn); >> + break; >> + } >> + case BR_DEAD_BINDER: >> + break; >> + case BR_FAILED_REPLY: >> + break; >> + case BR_DEAD_REPLY: >> + break; >> + case BR_ERROR: >> + ptr += sizeof(uint32_t); >> + break; >> + default: >> + if (verbose) >> + printf("%s Parsed unknown command: %d\n", >> + text, cmd); >> + return -1; >> + } >> + } >> + >> + return cmd; >> +} >> + >> +int main(int argc, char **argv) >> +{ >> + int opt, option, result, fd, count; >> + uint32_t cmd; >> + pid_t pid; >> + char *driver = "/dev/binder"; >> + char *context; >> + void *mapped; >> + size_t mapsize = 2048; >> + struct binder_write_read bwr; >> + struct flat_binder_object obj; >> + struct binder_transaction_data *txn; >> + unsigned int readbuf[32]; >> + unsigned int writebuf[1024]; >> + >> + target = 0; /* Only need one target - the Manager */ >> + verbose = false; >> + >> + while ((opt = getopt(argc, argv, "v")) != -1) { >> + switch (opt) { >> + case 'v': >> + verbose = true; >> + break; >> + default: >> + usage(argv[0]); >> + } >> + } >> + >> + if ((argc - optind) != 1) >> + usage(argv[0]); >> + >> + if (!strcmp(argv[optind], "manager")) >> + option = 1; >> + else if (!strcmp(argv[optind], "client")) >> + option = 2; >> + else >> + usage(argv[0]); >> + >> + fd = open(driver, O_RDWR | O_CLOEXEC); >> + if (fd < 0) { >> + fprintf(stderr, "Cannot open %s error: %s\n", driver, >> + strerror(errno)); >> + exit(1); >> + } >> + >> + /* Need this or "no VMA" error from kernel */ >> + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); >> + if (mapped == MAP_FAILED) { >> + fprintf(stderr, "mmap error: %s\n", strerror(errno)); >> + close(fd); >> + exit(2); >> + } >> + >> + /* Get our context and pid */ >> + result = getcon(&context); >> + if (result < 0) { >> + fprintf(stderr, "Failed to obtain SELinux context\n"); >> + result = 3; >> + goto brexit; >> + } >> + pid = getpid(); >> + >> + switch (option) { >> + case 1: /* manager */ >> + if (verbose) { >> + printf("Manager PID: %d Process context:\n\t%s\n", >> + pid, context); >> + } >> + >> + result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0); >> + if (result < 0) { >> + fprintf(stderr, >> + "Failed to become context manager: %s\n", >> + strerror(errno)); >> + result = 4; >> + goto brexit; >> + } >> + >> + readbuf[0] = BC_ENTER_LOOPER; >> + bwr.write_size = sizeof(readbuf[0]); >> + bwr.write_consumed = 0; >> + bwr.write_buffer = (uintptr_t)readbuf; >> + >> + bwr.read_size = 0; >> + bwr.read_consumed = 0; >> + bwr.read_buffer = 0; >> + >> + result = ioctl(fd, BINDER_WRITE_READ, &bwr); >> + if (result < 0) { >> + fprintf(stderr, >> + "Manager ioctl BINDER_WRITE_READ: %s\n", >> + strerror(errno)); >> + result = 5; >> + goto brexit; >> + } >> + >> + while (true) { >> + bwr.read_size = sizeof(readbuf); >> + bwr.read_consumed = 0; >> + bwr.read_buffer = (uintptr_t)readbuf; >> + >> + result = ioctl(fd, BINDER_WRITE_READ, &bwr); >> + if (result < 0) { >> + fprintf(stderr, >> + "Manager ioctl BINDER_WRITE_READ: %s\n", >> + strerror(errno)); >> + result = 6; >> + goto brexit; >> + } >> + >> + if (bwr.read_consumed == 0) >> + continue; >> + >> + if (verbose) >> + printf("Manager read_consumed: %lld\n", >> + bwr.read_consumed); >> + >> + cmd = binder_parse(fd, (uintptr_t)readbuf, >> + bwr.read_consumed, "Manager"); >> + } >> + break; >> + >> + case 2: /* client */ >> + if (verbose) { >> + printf("Client PID: %d Process context:\n\t%s\n", >> + pid, context); >> + } >> + >> + writebuf[0] = BC_TRANSACTION; >> + txn = (struct binder_transaction_data *)(&writebuf[1]); >> + memset(txn, 0, sizeof(*txn)); >> + txn->target.handle = target; >> + txn->cookie = 0; >> + txn->code = 0; >> + txn->flags = TF_ACCEPT_FDS; >> + >> + memset(&obj, 0, sizeof(struct flat_binder_object)); >> + obj.hdr.type = BINDER_TYPE_WEAK_BINDER; >> + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; >> + obj.binder = target; >> + obj.handle = 0; >> + obj.cookie = 0; >> + >> + txn->data_size = sizeof(struct flat_binder_object); >> + txn->data.ptr.buffer = (uintptr_t)&obj; >> + txn->data.ptr.offsets = (uintptr_t)&obj + >> + sizeof(struct flat_binder_object); >> + txn->offsets_size = sizeof(binder_size_t); >> + >> + memset(&bwr, 0, sizeof(bwr)); >> + bwr.write_buffer = (unsigned long)writebuf; >> + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); >> + >> + /* Expect client to get max two responses: >> + * 1) From the above BC_TRANSACTION >> + * 2) The responding BC_REPLY from send_reply() >> + * unless an error. >> + */ >> + count = 0; >> + while (count != 2) { >> + bwr.read_size = sizeof(readbuf); >> + bwr.read_consumed = 0; >> + bwr.read_buffer = (uintptr_t)readbuf; >> + >> + result = ioctl(fd, BINDER_WRITE_READ, &bwr); >> + if (result < 0) { >> + fprintf(stderr, >> + "Client ioctl BINDER_WRITE_READ: %s\n", >> + strerror(errno)); >> + result = 8; >> + goto brexit; >> + } >> + >> + if (verbose) >> + printf("Client read_consumed: %lld\n", >> + bwr.read_consumed); >> + >> + cmd = binder_parse(fd, (uintptr_t)readbuf, >> + bwr.read_consumed, "Client"); >> + >> + if (cmd == BR_FAILED_REPLY || >> + cmd == BR_DEAD_REPLY || >> + cmd == BR_DEAD_BINDER) { >> + result = 9; >> + goto brexit; >> + } >> + count++; >> + } >> + break; >> + >> + default: >> + result = -1; >> + } >> + >> +brexit: >> + free(context); >> + munmap(mapped, mapsize); >> + close(fd); >> + >> + return result; >> +} >> >
On Tue, 2018-05-15 at 09:43 -0400, Stephen Smalley wrote: > On 05/15/2018 09:36 AM, Stephen Smalley wrote: > > On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote: > > > Add binder tests. See tests/binder/test_binder.c for details on > > > message flows to test security_binder*() functions. > > > > > > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> > > > --- > > > README.md | 8 + > > > defconfig | 8 + > > > policy/Makefile | 2 +- > > > policy/test_binder.te | 83 +++++++ > > > tests/Makefile | 2 +- > > > tests/binder/Makefile | 7 + > > > tests/binder/check_binder.c | 80 +++++++ > > > tests/binder/test | 131 +++++++++++ > > > tests/binder/test_binder.c | 543 > > > ++++++++++++++++++++++++++++++++++++++++++++ > > > 9 files changed, 862 insertions(+), 2 deletions(-) > > > create mode 100644 policy/test_binder.te > > > create mode 100644 tests/binder/Makefile > > > create mode 100644 tests/binder/check_binder.c > > > create mode 100644 tests/binder/test > > > create mode 100644 tests/binder/test_binder.c > > > > > > diff --git a/README.md b/README.md > > > index c9f3b2b..60a249e 100644 > > > --- a/README.md > > > +++ b/README.md > > > @@ -141,6 +141,14 @@ directory or you can follow these broken-out > > > steps: > > > The broken-out steps allow you to run the tests multiple times > > > without > > > loading policy each time. > > > > > > +Note that if leaving the test policy in-place for further > > > testing, the > > > +policy build process changes a boolean: > > > + On policy load: setsebool allow_domain_fd_use=0 > > > + On policy unload: setsebool allow_domain_fd_use=1 > > > +The consequence of this is that after a system reboot, the > > > boolean > > > +defaults to true. Therefore if running the fdreceive or binder > > > tests, > > > +reset the boolean to false, otherwise some tests will fail. > > > > This isn't accurate - we aren't doing setsebool -P so the boolean > > change is not persistent across > > reboots. It will persist across policy reloads however because the > > kernel preserves booleans across > > policy reloads. > > Sorry, never mind - I misread the text above. You are correct. > > > > > > + > > > 4) Review the test results. > > > > > > As each test script is run, the name of the script will be > > > displayed followed > > > diff --git a/defconfig b/defconfig > > > index 7dce8bc..dc6ef30 100644 > > > --- a/defconfig > > > +++ b/defconfig > > > @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m > > > # This is enabled to test overlayfs SELinux integration. > > > # It is not required for SELinux operation itself. > > > CONFIG_OVERLAY_FS=m > > > + > > > +# Android binder implementations. > > > +# These are enabled to test the binder controls in > > > +# tests/binder; they are not required for SELinux operation > > > itself. > > > +CONFIG_ANDROID=y > > > +CONFIG_ANDROID_BINDER_IPC=y > > > +CONFIG_ANDROID_BINDER_DEVICES="binder" > > > +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set > > > > I don't think we need the last line. It appears it is requred as if not there are complaints when building, in fact I missed some out and should be: # Android binder implementations. # These are enabled to test the binder controls in # tests/binder; they are not required for SELinux operation itself. # The 'is not set' items MUST be included unless they are required by your configuration. # CONFIG_ASHMEM is not set # CONFIG_ION is not set CONFIG_ANDROID_BINDER_DEVICES="binder" CONFIG_ANDROID_BINDER_IPC=y # CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set > > > > > diff --git a/policy/Makefile b/policy/Makefile > > > index 8ed5e46..5a9d411 100644 > > > --- a/policy/Makefile > > > +++ b/policy/Makefile > > > @@ -25,7 +25,7 @@ TARGETS = \ > > > test_task_getsid.te test_task_setpgid.te > > > test_task_setsched.te \ > > > test_transition.te test_inet_socket.te > > > test_unix_socket.te \ > > > test_mmap.te test_overlayfs.te test_mqueue.te > > > test_mac_admin.te \ > > > - test_ibpkey.te test_atsecure.te > > > + test_ibpkey.te test_atsecure.te test_binder.te > > > > Likely need to make this conditional on the binder class being > > defined in the policy; > > see similar logic for e.g. cap_userns, icmp_socket, etc. Otherwise > > policy won't build > > on earlier Fedora/RHEL before definition of the binder class. > > I'll fix > > > > > > ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true) > > > TARGETS += test_bounds.te > > > diff --git a/policy/test_binder.te b/policy/test_binder.te > > > new file mode 100644 > > > index 0000000..c4ad2ae > > > --- /dev/null > > > +++ b/policy/test_binder.te > > > @@ -0,0 +1,83 @@ > > > + > > > +attribute binderdomain; > > > + > > > +# > > > +################################## Manager > > > ################################### > > > +# > > > +type test_binder_mgr_t; > > > +domain_type(test_binder_mgr_t) > > > +unconfined_runs_test(test_binder_mgr_t) > > > +typeattribute test_binder_mgr_t testdomain; > > > +typeattribute test_binder_mgr_t binderdomain; > > > +allow test_binder_mgr_t self:binder { set_context_mgr call }; > > > +allow test_binder_mgr_t device_t:chr_file { ioctl open read > > > write map }; > > > > Wondering if we should define a .fc file with /dev/binder and a > > proper binder_device_t type > > and restorecon it before the test. But not clear it is worth > > it. Never mind. > > > > > +allow test_binder_mgr_t self:capability { sys_nice }; > > > > Needed or just to suppress noise in the audit? The binder driver does something with nice, but tests work okay without. > > > > > +allow test_binder_client_t test_binder_mgr_t:fd use; > > > + > > > +# > > > +################################# Client > > > #################################### > > > +# > > > +type test_binder_client_t; > > > +domain_type(test_binder_client_t) > > > +unconfined_runs_test(test_binder_client_t) > > > +typeattribute test_binder_client_t testdomain; > > > +typeattribute test_binder_client_t binderdomain; > > > +allow test_binder_client_t self:binder { call }; > > > +allow test_binder_client_t test_binder_mgr_t:binder { call > > > transfer impersonate }; > > > > Are you actually exercising impersonate? I largely didn't expect > > it to ever be used in Android itself, > > just included the check because I saw that it was technically > > possible as far as the kernel interface > > is concerned. I think I am. The client requests the Managers fd, then uses it to send message. I patched kernel to check this. Also I read your helpful note: https://marc.info/?l=seandroid-list&m=141901419332280&w=2 > > > > > +allow test_binder_client_t device_t:chr_file { ioctl open read > > > write map }; > > > +# For fstat: > > > +allow test_binder_client_t device_t:chr_file getattr; > > > + > > > +# > > > +############################## Client no call > > > ################################ > > > +# > > > +type test_binder_client_no_call_t; > > > +domain_type(test_binder_client_no_call_t) > > > +unconfined_runs_test(test_binder_client_no_call_t) > > > +typeattribute test_binder_client_no_call_t testdomain; > > > +typeattribute test_binder_client_no_call_t binderdomain; > > > +allow test_binder_client_no_call_t device_t:chr_file { ioctl > > > open read write map }; > > > + > > > +# > > > +############################ Client no transfer > > > ############################# > > > +# > > > +type test_binder_client_no_transfer_t; > > > +domain_type(test_binder_client_no_transfer_t) > > > +unconfined_runs_test(test_binder_client_no_transfer_t) > > > +typeattribute test_binder_client_no_transfer_t testdomain; > > > +typeattribute test_binder_client_no_transfer_t binderdomain; > > > +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder > > > { call }; > > > +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl > > > open read write map }; > > > + > > > +# > > > +########################## Manager no fd {use} > > > ############################### > > > +# > > > +type test_binder_mgr_no_fd_t; > > > +domain_type(test_binder_mgr_no_fd_t) > > > +unconfined_runs_test(test_binder_mgr_no_fd_t) > > > +typeattribute test_binder_mgr_no_fd_t testdomain; > > > +typeattribute test_binder_mgr_no_fd_t binderdomain; > > > +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call > > > }; > > > +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open > > > read write map }; > > > +allow test_binder_mgr_no_fd_t self:capability { sys_nice }; > > > +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call > > > transfer }; > > > + > > > +# > > > +########################### Client no impersonate > > > ############################ > > > +# > > > +type test_binder_client_no_im_t; > > > +domain_type(test_binder_client_no_im_t) > > > +unconfined_runs_test(test_binder_client_no_im_t) > > > +typeattribute test_binder_client_no_im_t testdomain; > > > +typeattribute test_binder_client_no_im_t binderdomain; > > > +allow test_binder_client_no_im_t self:binder { call }; > > > +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call > > > transfer }; > > > +allow test_binder_client_no_im_t device_t:chr_file { ioctl open > > > read write map }; > > > +allow test_binder_client_no_im_t test_binder_mgr_t:fd use; > > > +allow test_binder_client_no_im_t device_t:chr_file getattr; > > > + > > > +# > > > +############ Allow these domains to be entered from sysadm > > > domain ############ > > > +# > > > +miscfiles_domain_entry_test_files(binderdomain) > > > +userdom_sysadm_entry_spec_domtrans_to(binderdomain) > > > diff --git a/tests/Makefile b/tests/Makefile > > > index 27ed6eb..7607c22 100644 > > > --- a/tests/Makefile > > > +++ b/tests/Makefile > > > @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare > > > exectrace execute_no_trans \ > > > task_setnice task_setscheduler task_getscheduler > > > task_getsid \ > > > task_getpgid task_setpgid file ioctl capable_file > > > capable_net \ > > > capable_sys dyntrans dyntrace bounds nnp_nosuid mmap > > > unix_socket \ > > > - inet_socket overlay checkreqprot mqueue mac_admin > > > atsecure > > > + inet_socket overlay checkreqprot mqueue mac_admin > > > atsecure binder > > > > Likely needs to be conditional on binder class being defined in > > policy as with cap_userns, > > and also depends on e.g. linux/android/binder.h > > existing? Otherwise will break build on earlier > > Fedora/RHEL releases. > > I'll fix > > > > > > ifeq ($(shell grep -q cap_userns > > > $(POLDEV)/include/support/all_perms.spt && echo true),true) > > > ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1) > > > diff --git a/tests/binder/Makefile b/tests/binder/Makefile > > > new file mode 100644 > > > index 0000000..a60eeb3 > > > --- /dev/null > > > +++ b/tests/binder/Makefile > > > @@ -0,0 +1,7 @@ > > > +TARGETS = check_binder test_binder > > > + > > > +LDLIBS += -lselinux > > > + > > > +all: $(TARGETS) > > > +clean: > > > + rm -f $(TARGETS) > > > diff --git a/tests/binder/check_binder.c > > > b/tests/binder/check_binder.c > > > new file mode 100644 > > > index 0000000..3d553a0 > > > --- /dev/null > > > +++ b/tests/binder/check_binder.c > > > @@ -0,0 +1,80 @@ > > > +#include <stdio.h> > > > +#include <stdlib.h> > > > +#include <string.h> > > > +#include <unistd.h> > > > +#include <fcntl.h> > > > +#include <errno.h> > > > +#include <stdbool.h> > > > +#include <sys/mman.h> > > > +#include <sys/ioctl.h> > > > +#include <linux/android/binder.h> > > > + > > > +static void usage(char *progname) > > > +{ > > > + fprintf(stderr, > > > + "usage: %s [-v]\n" > > > + "Where:\n\t" > > > + "-v Print binder version.\n", progname); > > > + exit(-1); > > > +} > > > + > > > +int main(int argc, char **argv) > > > +{ > > > + int opt, result, fd; > > > + char *driver = "/dev/binder"; > > > + bool verbose; > > > + void *mapped; > > > + size_t mapsize = 1024; > > > + struct binder_version vers; > > > + > > > + while ((opt = getopt(argc, argv, "v")) != -1) { > > > + switch (opt) { > > > + case 'v': > > > + verbose = true; > > > + break; > > > + default: > > > + usage(argv[0]); > > > + } > > > + } > > > + > > > + fd = open(driver, O_RDWR | O_CLOEXEC); > > > + if (fd < 0) { > > > + fprintf(stderr, "Cannot open: %s error: %s\n", > > > + driver, strerror(errno)); > > > + exit(-1); > > > + } > > > + > > > + /* Need this or no VMA error from kernel */ > > > + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, > > > 0); > > > + if (mapped == MAP_FAILED) { > > > + fprintf(stderr, "mmap error: %s\n", > > > strerror(errno)); > > > + close(fd); > > > + exit(-1); > > > + } > > > + > > > + result = ioctl(fd, BINDER_VERSION, &vers); > > > + if (result < 0) { > > > + fprintf(stderr, "ioctl BINDER_VERSION: %s\n", > > > + strerror(errno)); > > > + goto brexit; > > > + } > > > + > > > + if (vers.protocol_version != > > > BINDER_CURRENT_PROTOCOL_VERSION) { > > > + fprintf(stderr, > > > + "Binder kernel version: %d differs from > > > user space version: %d\n", > > > + vers.protocol_version, > > > + BINDER_CURRENT_PROTOCOL_VERSION); > > > + result = -1; > > > + goto brexit; > > > + } > > > + > > > + if (verbose) > > > + fprintf(stderr, "Binder kernel version: %d\n", > > > + vers.protocol_version); > > > + > > > +brexit: > > > + munmap(mapped, mapsize); > > > + close(fd); > > > + > > > + return result; > > > +} > > > diff --git a/tests/binder/test b/tests/binder/test > > > new file mode 100644 > > > index 0000000..434ae32 > > > --- /dev/null > > > +++ b/tests/binder/test > > > @@ -0,0 +1,131 @@ > > > +#!/usr/bin/perl > > > +use Test::More; > > > + > > > +BEGIN { > > > + $basedir = $0; > > > + $basedir =~ s|(.*)/[^/]*|$1|; > > > + > > > + # allow binder info to be shown > > > + $v = $ARGV[0]; > > > + if ($v) { > > > + if ( $v ne "-v" ) { > > > + plan skip_all => "Invalid option (use -v)"; > > > + } > > > + } > > > + > > > + # check if binder driver available and the kernel/userspace > > > versions. > > > + if ( system("$basedir/check_binder 2> /dev/null") != 0 ) { > > > + plan skip_all => > > > + "Binder not supported or kernel/userspace versions > > > differ"; > > > + } > > > + else { > > > + plan tests => 6; > > > + } > > > +} > > > + > > > +if ($v) { > > > > Duplicating the test cases for $v and !$v seems prone to > > inconsistency in the future; > > can't we just embed $v into the command string being executed? I'll find another way as I tried that but perl complained about uninitialised variables (or something like that) > > > > > + if ( ( $pid = fork() ) == 0 ) { > > > + exec "runcon -t test_binder_mgr_t $basedir/test_binder > > > -v manager"; > > > + } > > > + > > > + select( undef, undef, undef, 0.25 ); # Give it a moment > > > to initialize. > > > + > > > + # Verify that authorized client can transact with the > > > manager. > > > + $result = > > > + system "runcon -t test_binder_client_t > > > $basedir/test_binder -v client"; > > > + ok( $result eq 0 ); > > > > This test is failing for me (with or without -v): > > # ./test -v > > 1..6 > > Manager PID: 5608 Process context: > > unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023 > > Client PID: 5609 Process context: > > unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023 > > Client read_consumed: 28 > > Manager read_consumed: 72 > > Client command: BR_NOOP > > Manager command: BR_NOOP > > Client command: BR_INCREFS > > Manager command: BR_TRANSACTION > > Client command: BR_TRANSACTION_COMPLETE > > BR_TRANSACTION data: > > handle: 0 > > cookie: 0 > > code: 0 > > flag: TF_ACCEPT_FDS > > sender pid: 5609 > > sender euid: 0 > > data_size: 24 > > offsets_size: 8 > > Sending BC_REPLY > > Manager read_consumed: 8 > > Manager command: BR_NOOP > > Manager command: BR_TRANSACTION_COMPLETE > > Client read_consumed: 72 > > Client command: BR_NOOP > > Client command: BR_REPLY > > BR_REPLY data: > > handle: 0 > > cookie: 0 > > code: 0 > > flag: TF_ACCEPT_FDS > > sender pid: 0 > > sender euid: 0 > > data_size: 24 > > offsets_size: 8 > > Retrieved Managers fd: 4 st_dev: 6 > > Client read_consumed: 8 > > Client using Managers FD command: BR_NOOP > > Client using Managers FD command: BR_FAILED_REPLY > > Client using Managers received FD failed response > > Manager read_consumed: 4 > > Manager command: BR_NOOP > > not ok 1 > > # Failed test at ./test line 36. > > Just realized that I was testing with a kernel that still had Casey's > stacking support enabled. > Will re-try without that. I'm rebuilding on Fedora 28 so I'll test that as well. > > > > > > > > + > > > + # Verify that client cannot call manager (no call perm). > > > + $result = system > > > +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v > > > client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Verify that client cannot communicate with manager (no > > > impersonate perm). > > > + $result = system > > > +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v > > > client 2>&1"; > > > + ok( $result >> 8 eq 102 ); > > > + > > > + # Verify that client cannot communicate with manager (no > > > transfer perm). > > > + $result = system > > > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder > > > -v client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Kill the manager. > > > + kill TERM, $pid; > > > + > > > + # Verify that client cannot become a manager (no > > > set_context_mgr perm). > > > + $result = > > > + system > > > + "runcon -t test_binder_client_t $basedir/test_binder -v > > > manager 2>&1"; > > > + ok( $result >> 8 eq 4 ); > > > + > > > +# Start manager to test that selinux_binder_transfer_file() > > > fails when fd { use } is denied by policy. > > > + if ( ( $pid = fork() ) == 0 ) { > > > + exec > > > + "runcon -t test_binder_mgr_no_fd_t > > > $basedir/test_binder -v manager"; > > > + } > > > + > > > + select( undef, undef, undef, 0.25 ); # Give it a moment > > > to initialize. > > > + > > > +# Verify that authorized client can communicate with the server, > > > however the fd passed will not be valid for manager > > > +# domain and binder will return BR_FAILED_REPLY. > > > + $result = > > > + system > > > + "runcon -t test_binder_client_t $basedir/test_binder -v > > > client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Kill the manager > > > + kill TERM, $pid; > > > +} > > > +else { > > > + if ( ( $pid = fork() ) == 0 ) { > > > + exec "runcon -t test_binder_mgr_t $basedir/test_binder > > > manager"; > > > + } > > > + > > > + select( undef, undef, undef, 0.25 ); # Give it a moment > > > to initialize. > > > + > > > + # Verify that authorized client can transact with the > > > manager. > > > + $result = > > > + system "runcon -t test_binder_client_t > > > $basedir/test_binder client"; > > > + ok( $result eq 0 ); > > > + > > > + # Verify that client cannot call manager (no call perm). > > > + $result = system > > > + "runcon -t test_binder_client_no_call_t > > > $basedir/test_binder client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Verify that client cannot communicate with manager (no > > > impersonate perm). > > > + $result = system > > > + "runcon -t test_binder_client_no_im_t $basedir/test_binder > > > client 2>&1"; > > > + ok( $result >> 8 eq 102 ); > > > + > > > + # Verify that client cannot communicate with manager (no > > > transfer perm). > > > + $result = system > > > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder > > > client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Kill the manager. > > > + kill TERM, $pid; > > > + > > > + # Verify that client cannot become a manager (no > > > set_context_mgr perm). > > > + $result = > > > + system "runcon -t test_binder_client_t > > > $basedir/test_binder manager 2>&1"; > > > + ok( $result >> 8 eq 4 ); > > > + > > > +# Start manager to test that selinux_binder_transfer_file() > > > fails when fd { use } is denied by policy. > > > + if ( ( $pid = fork() ) == 0 ) { > > > + exec "runcon -t test_binder_mgr_no_fd_t > > > $basedir/test_binder manager"; > > > + } > > > + > > > + select( undef, undef, undef, 0.25 ); # Give it a moment > > > to initialize. > > > + > > > +# Verify that authorized client can communicate with the server, > > > however the fd passed will not be valid for manager > > > +# domain and binder will return BR_FAILED_REPLY. > > > + $result = > > > + system "runcon -t test_binder_client_t > > > $basedir/test_binder client 2>&1"; > > > + ok( $result >> 8 eq 9 ); > > > + > > > + # Kill the manager > > > + kill TERM, $pid; > > > +} > > > +exit; > > > diff --git a/tests/binder/test_binder.c > > > b/tests/binder/test_binder.c > > > new file mode 100644 > > > index 0000000..8881cce > > > --- /dev/null > > > +++ b/tests/binder/test_binder.c > > > @@ -0,0 +1,543 @@ > > > +/* > > > + * This is a simple binder client/server(manager) that only uses > > > the > > > + * raw ioctl commands. It does not rely on a 'service manager' > > > as in > > > + * the Android world as it only uses one 'target' = 0. > > > + * > > > + * The transaction/reply flow is basically: > > > + * Client Manager > > > + * ======== ========= > > > + * > > > + * Becomes context > > > manager > > > + * Send transaction > > > + * requesting file > > > + * descriptor from > > > + * the Manager > > > + * ---------------------------------------> > > > + * Manager replies with > > > its > > > + * binder file descriptor > > > + * <-------------------------------------- > > > + * Check fd valid, if so send > > > + * TF_ONE_WAY transaction to Manager > > > + * using the received fd > > > + * ---------------------------------------> > > > + * End of tests so > > > Manager > > > + * waits to be killed > > > + * Client exits > > > + * > > > + * Using binder test policy the following will be validated: > > > + * security_binder_set_context_mgr() binder { set_context_mgr > > > } > > > + * security_binder_transaction() binder { call > > > impersonate } > > > + * security_binder_transfer_binder() binder { transfer } > > > + * security_binder_transfer_file() fd { use } > > > + * > > > + * TODO security_binder_transfer_file() uses BPF if configured > > > in kernel. > > > + */ > > > + > > > +#include <errno.h> > > > +#include <fcntl.h> > > > +#include <inttypes.h> > > > +#include <stdio.h> > > > +#include <stdlib.h> > > > +#include <string.h> > > > +#include <unistd.h> > > > +#include <sys/stat.h> > > > +#include <stdbool.h> > > > +#include <sys/mman.h> > > > +#include <sys/ioctl.h> > > > +#include <selinux/selinux.h> > > > +#include <linux/android/binder.h> > > > + > > > +static uint32_t target; /* This will be set to '0' as only need > > > one target */ > > > +static bool verbose; > > > + > > > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char > > > *text); > > > + > > > +static void usage(char *progname) > > > +{ > > > + fprintf(stderr, > > > + "usage: %s [-v] manager | client\n" > > > + "Where:\n\t" > > > + "-v Print context and command > > > information.\n\t" > > > + "manager Act as binder context manager.\n\t" > > > + "client Act as binder client.\n" > > > + "\nNote: Ensure this boolean command is run when > > > " > > > + "testing after a reboot:\n\t" > > > + "setsebool allow_domain_fd_use=0\n", progname); > > > + exit(1); > > > +} > > > + > > > +static const char *cmd_name(uint32_t cmd) > > > +{ > > > + switch (cmd) { > > > + case BR_NOOP: > > > + return "BR_NOOP"; > > > + case BR_TRANSACTION_COMPLETE: > > > + return "BR_TRANSACTION_COMPLETE"; > > > + case BR_INCREFS: > > > + return "BR_INCREFS"; > > > + case BR_ACQUIRE: > > > + return "BR_ACQUIRE"; > > > + case BR_RELEASE: > > > + return "BR_RELEASE"; > > > + case BR_DECREFS: > > > + return "BR_DECREFS"; > > > + case BR_TRANSACTION: > > > + return "BR_TRANSACTION"; > > > + case BR_REPLY: > > > + return "BR_REPLY"; > > > + case BR_FAILED_REPLY: > > > + return "BR_FAILED_REPLY"; > > > + case BR_DEAD_REPLY: > > > + return "BR_DEAD_REPLY"; > > > + case BR_DEAD_BINDER: > > > + return "BR_DEAD_BINDER"; > > > + case BR_ERROR: > > > + return "BR_ERROR"; > > > + /* fallthrough */ > > > + default: > > > + return "Unknown command"; > > > + } > > > +} > > > + > > > +void print_trans_data(struct binder_transaction_data *txn) > > > +{ > > > + printf("\thandle: %ld\n", (unsigned long)txn- > > > >target.handle); > > > + printf("\tcookie: %lld\n", txn->cookie); > > > + printf("\tcode: %u\n", txn->code); > > > + switch (txn->flags) { > > > + case TF_ONE_WAY: > > > + printf("\tflag: TF_ONE_WAY\n"); > > > + break; > > > + case TF_ROOT_OBJECT: > > > + printf("\tflag: TF_ROOT_OBJECT\n"); > > > + break; > > > + case TF_STATUS_CODE: > > > + printf("\tflag: TF_STATUS_CODE\n"); > > > + break; > > > + case TF_ACCEPT_FDS: > > > + printf("\tflag: TF_ACCEPT_FDS\n"); > > > + break; > > > + default: > > > + printf("Unknown flag: %u\n", txn->flags); > > > + } > > > + printf("\tsender pid: %u\n", txn->sender_pid); > > > + printf("\tsender euid: %u\n", txn->sender_euid); > > > + printf("\tdata_size: %llu\n", txn->data_size); > > > + printf("\toffsets_size: %llu\n", txn->offsets_size); > > > +} > > > + > > > + > > > +static int send_reply(int fd, struct binder_transaction_data > > > *txn_in) > > > +{ > > > + int result; > > > + unsigned int writebuf[1024]; > > > + struct flat_binder_object obj; > > > + struct binder_write_read bwr; > > > + struct binder_transaction_data *txn; > > > + > > > + if (txn_in->flags == TF_ONE_WAY) { > > > + if (verbose) > > > + printf("No reply to BC_TRANSACTION as > > > flags = TF_ONE_WAY\n"); > > > + return 0; > > > + } > > > + > > > + if (verbose) > > > + printf("Sending BC_REPLY\n"); > > > + > > > + writebuf[0] = BC_REPLY; > > > + txn = (struct binder_transaction_data *)(&writebuf[1]); > > > + > > > + memset(txn, 0, sizeof(*txn)); > > > + txn->target.handle = txn_in->target.handle; > > > + txn->cookie = txn_in->cookie; > > > + txn->code = txn_in->code; > > > + txn->flags = TF_ACCEPT_FDS; > > > + > > > + memset(&obj, 0, sizeof(struct flat_binder_object)); > > > + obj.hdr.type = BINDER_TYPE_FD; > > > + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; > > > + obj.binder = txn->target.handle; > > > + obj.cookie = txn->cookie; > > > + /* The binder fd is used for testing as it allows policy > > > to set > > > + * whether the Client/Manager can be allowed access (fd > > > use) or > > > + * not. For example test_binder_mgr_t has: > > > + * allow test_binder_client_t > > > test_binder_mgr_t:fd use; > > > + * whereas test_binder_mgr_no_fd_t does not allow this > > > fd use. > > > + * > > > + * This also allows a check for the impersonate > > > permission later as > > > + * the Client will use this (the Managers fd) to send a > > > transaction. > > > + */ > > > + obj.handle = fd; > > > + > > > + txn->data_size = sizeof(struct flat_binder_object); > > > + txn->data.ptr.buffer = (uintptr_t)&obj; > > > + txn->data.ptr.offsets = (uintptr_t)&obj + > > > + sizeof(struct > > > flat_binder_object); > > > + txn->offsets_size = sizeof(binder_size_t); > > > + > > > + memset(&bwr, 0, sizeof(bwr)); > > > + bwr.write_buffer = (unsigned long)writebuf; > > > + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); > > > + > > > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > > > + if (result < 0) { > > > + fprintf(stderr, "%s ioctl BINDER_WRITE_READ: > > > %s\n", > > > + __func__, strerror(errno)); > > > + return -1; > > > + } > > > + > > > + return result; > > > +} > > > + > > > +/* This retrieves the requested Managers file descriptor, then > > > using this > > > + * sends a simple transaction to trigger the impersonate > > > permission. > > > + */ > > > +static void extract_fd_and_respond(struct > > > binder_transaction_data *txn_in) > > > +{ > > > + int result; > > > + uint32_t cmd; > > > + struct stat sb; > > > + struct binder_write_read bwr; > > > + struct flat_binder_object *obj; > > > + struct binder_transaction_data *txn; > > > + unsigned int readbuf[32]; > > > + unsigned int writebuf[1024]; > > > + binder_size_t *offs = (binder_size_t > > > *)(uintptr_t)txn_in->data.ptr.offsets; > > > + > > > + /* Get the fbo that contains the Managers binder file > > > descriptor. */ > > > + obj = (struct flat_binder_object *) > > > + (((char *)(uintptr_t)txn_in->data.ptr.buffer) + > > > *offs); > > > + > > > + /* fstat this just to see if a valid fd */ > > > + result = fstat(obj->handle, &sb); > > > + if (result < 0) { > > > + fprintf(stderr, "Not a valid fd: %s\n", > > > strerror(errno)); > > > + exit(100); > > > + } > > > + > > > + if (verbose) > > > + printf("Retrieved Managers fd: %d st_dev: > > > %ld\n", > > > + obj->handle, sb.st_dev); > > > + > > > + /* Send response using Managers fd to trigger > > > impersonate check. */ > > > + writebuf[0] = BC_TRANSACTION; > > > + txn = (struct binder_transaction_data *)(&writebuf[1]); > > > + memset(txn, 0, sizeof(*txn)); > > > + txn->target.handle = target; > > > + txn->cookie = 0; > > > + txn->code = 0; > > > + txn->flags = TF_ONE_WAY; > > > + > > > + txn->data_size = 0; > > > + txn->data.ptr.buffer = (uintptr_t)NULL; > > > + txn->data.ptr.offsets = (uintptr_t)NULL; > > > + txn->offsets_size = 0; > > > + > > > + memset(&bwr, 0, sizeof(bwr)); > > > + bwr.write_buffer = (unsigned long)writebuf; > > > + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); > > > + > > > + bwr.read_size = sizeof(readbuf); > > > + bwr.read_consumed = 0; > > > + bwr.read_buffer = (uintptr_t)readbuf; > > > + > > > + result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr); > > > + if (result < 0) { > > > + fprintf(stderr, > > > + "CLIENT ioctl BINDER_WRITE_READ: %s\n", > > > + strerror(errno)); > > > + exit(101); > > > + } > > > + > > > + if (verbose) > > > + printf("Client read_consumed: %lld\n", > > > bwr.read_consumed); > > > + > > > + cmd = binder_parse(obj->handle, (uintptr_t)readbuf, > > > + bwr.read_consumed, > > > + "Client using Managers FD"); > > > + > > > + if (cmd == BR_FAILED_REPLY || > > > + cmd == BR_DEAD_REPLY || > > > + cmd == BR_DEAD_BINDER) { > > > + fprintf(stderr, > > > + "Client using Managers received FD > > > failed response\n"); > > > + exit(102); > > > + } > > > +} > > > + > > > +/* Parse response, reply as required and then return last CMD */ > > > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char > > > *text) > > > +{ > > > + uintptr_t end = ptr + (uintptr_t)size; > > > + uint32_t cmd; > > > + > > > + while (ptr < end) { > > > + cmd = *(uint32_t *)ptr; > > > + ptr += sizeof(uint32_t); > > > + > > > + if (verbose) > > > + printf("%s command: %s\n", text, > > > cmd_name(cmd)); > > > + > > > + switch (cmd) { > > > + case BR_NOOP: > > > + break; > > > + case BR_TRANSACTION_COMPLETE: > > > + break; > > > + case BR_INCREFS: > > > + case BR_ACQUIRE: > > > + case BR_RELEASE: > > > + case BR_DECREFS: > > > + ptr += sizeof(struct binder_ptr_cookie); > > > + break; > > > + case BR_TRANSACTION: { > > > + struct binder_transaction_data *txn = > > > + (struct binder_transaction_data > > > *)ptr; > > > + > > > + if (verbose) { > > > + printf("BR_TRANSACTION > > > data:\n"); > > > + print_trans_data(txn); > > > + } > > > + > > > + /* The manager sends reply that will > > > contain its fd */ > > > + if (send_reply(fd, txn) < 0) { > > > + fprintf(stderr, "send_reply() > > > failed.\n"); > > > + return -1; > > > + } > > > + ptr += sizeof(*txn); > > > + break; > > > + } > > > + case BR_REPLY: { > > > + struct binder_transaction_data *txn = > > > + (struct binder_transaction_data > > > *)ptr; > > > + > > > + if (verbose) { > > > + printf("BR_REPLY data:\n"); > > > + print_trans_data(txn); > > > + } > > > + > > > + /* Client extracts the Manager fd, and > > > responds */ > > > + extract_fd_and_respond(txn); > > > + ptr += sizeof(*txn); > > > + break; > > > + } > > > + case BR_DEAD_BINDER: > > > + break; > > > + case BR_FAILED_REPLY: > > > + break; > > > + case BR_DEAD_REPLY: > > > + break; > > > + case BR_ERROR: > > > + ptr += sizeof(uint32_t); > > > + break; > > > + default: > > > + if (verbose) > > > + printf("%s Parsed unknown > > > command: %d\n", > > > + text, cmd); > > > + return -1; > > > + } > > > + } > > > + > > > + return cmd; > > > +} > > > + > > > +int main(int argc, char **argv) > > > +{ > > > + int opt, option, result, fd, count; > > > + uint32_t cmd; > > > + pid_t pid; > > > + char *driver = "/dev/binder"; > > > + char *context; > > > + void *mapped; > > > + size_t mapsize = 2048; > > > + struct binder_write_read bwr; > > > + struct flat_binder_object obj; > > > + struct binder_transaction_data *txn; > > > + unsigned int readbuf[32]; > > > + unsigned int writebuf[1024]; > > > + > > > + target = 0; /* Only need one target - the Manager */ > > > + verbose = false; > > > + > > > + while ((opt = getopt(argc, argv, "v")) != -1) { > > > + switch (opt) { > > > + case 'v': > > > + verbose = true; > > > + break; > > > + default: > > > + usage(argv[0]); > > > + } > > > + } > > > + > > > + if ((argc - optind) != 1) > > > + usage(argv[0]); > > > + > > > + if (!strcmp(argv[optind], "manager")) > > > + option = 1; > > > + else if (!strcmp(argv[optind], "client")) > > > + option = 2; > > > + else > > > + usage(argv[0]); > > > + > > > + fd = open(driver, O_RDWR | O_CLOEXEC); > > > + if (fd < 0) { > > > + fprintf(stderr, "Cannot open %s error: %s\n", > > > driver, > > > + strerror(errno)); > > > + exit(1); > > > + } > > > + > > > + /* Need this or "no VMA" error from kernel */ > > > + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, > > > 0); > > > + if (mapped == MAP_FAILED) { > > > + fprintf(stderr, "mmap error: %s\n", > > > strerror(errno)); > > > + close(fd); > > > + exit(2); > > > + } > > > + > > > + /* Get our context and pid */ > > > + result = getcon(&context); > > > + if (result < 0) { > > > + fprintf(stderr, "Failed to obtain SELinux > > > context\n"); > > > + result = 3; > > > + goto brexit; > > > + } > > > + pid = getpid(); > > > + > > > + switch (option) { > > > + case 1: /* manager */ > > > + if (verbose) { > > > + printf("Manager PID: %d Process > > > context:\n\t%s\n", > > > + pid, context); > > > + } > > > + > > > + result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0); > > > + if (result < 0) { > > > + fprintf(stderr, > > > + "Failed to become context > > > manager: %s\n", > > > + strerror(errno)); > > > + result = 4; > > > + goto brexit; > > > + } > > > + > > > + readbuf[0] = BC_ENTER_LOOPER; > > > + bwr.write_size = sizeof(readbuf[0]); > > > + bwr.write_consumed = 0; > > > + bwr.write_buffer = (uintptr_t)readbuf; > > > + > > > + bwr.read_size = 0; > > > + bwr.read_consumed = 0; > > > + bwr.read_buffer = 0; > > > + > > > + result = ioctl(fd, BINDER_WRITE_READ, &bwr); > > > + if (result < 0) { > > > + fprintf(stderr, > > > + "Manager ioctl > > > BINDER_WRITE_READ: %s\n", > > > + strerror(errno)); > > > + result = 5; > > > + goto brexit; > > > + } > > > + > > > + while (true) { > > > + bwr.read_size = sizeof(readbuf); > > > + bwr.read_consumed = 0; > > > + bwr.read_buffer = (uintptr_t)readbuf; > > > + > > > + result = ioctl(fd, BINDER_WRITE_READ, > > > &bwr); > > > + if (result < 0) { > > > + fprintf(stderr, > > > + "Manager ioctl > > > BINDER_WRITE_READ: %s\n", > > > + strerror(errno)); > > > + result = 6; > > > + goto brexit; > > > + } > > > + > > > + if (bwr.read_consumed == 0) > > > + continue; > > > + > > > + if (verbose) > > > + printf("Manager read_consumed: > > > %lld\n", > > > + bwr.read_consumed); > > > + > > > + cmd = binder_parse(fd, > > > (uintptr_t)readbuf, > > > + bwr.read_consumed, > > > "Manager"); > > > + } > > > + break; > > > + > > > + case 2: /* client */ > > > + if (verbose) { > > > + printf("Client PID: %d Process > > > context:\n\t%s\n", > > > + pid, context); > > > + } > > > + > > > + writebuf[0] = BC_TRANSACTION; > > > + txn = (struct binder_transaction_data > > > *)(&writebuf[1]); > > > + memset(txn, 0, sizeof(*txn)); > > > + txn->target.handle = target; > > > + txn->cookie = 0; > > > + txn->code = 0; > > > + txn->flags = TF_ACCEPT_FDS; > > > + > > > + memset(&obj, 0, sizeof(struct > > > flat_binder_object)); > > > + obj.hdr.type = BINDER_TYPE_WEAK_BINDER; > > > + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; > > > + obj.binder = target; > > > + obj.handle = 0; > > > + obj.cookie = 0; > > > + > > > + txn->data_size = sizeof(struct > > > flat_binder_object); > > > + txn->data.ptr.buffer = (uintptr_t)&obj; > > > + txn->data.ptr.offsets = (uintptr_t)&obj + > > > + sizeof(struct > > > flat_binder_object); > > > + txn->offsets_size = sizeof(binder_size_t); > > > + > > > + memset(&bwr, 0, sizeof(bwr)); > > > + bwr.write_buffer = (unsigned long)writebuf; > > > + bwr.write_size = sizeof(writebuf[0]) + > > > sizeof(*txn); > > > + > > > + /* Expect client to get max two responses: > > > + * 1) From the above BC_TRANSACTION > > > + * 2) The responding BC_REPLY from > > > send_reply() > > > + * unless an error. > > > + */ > > > + count = 0; > > > + while (count != 2) { > > > + bwr.read_size = sizeof(readbuf); > > > + bwr.read_consumed = 0; > > > + bwr.read_buffer = (uintptr_t)readbuf; > > > + > > > + result = ioctl(fd, BINDER_WRITE_READ, > > > &bwr); > > > + if (result < 0) { > > > + fprintf(stderr, > > > + "Client ioctl > > > BINDER_WRITE_READ: %s\n", > > > + strerror(errno)); > > > + result = 8; > > > + goto brexit; > > > + } > > > + > > > + if (verbose) > > > + printf("Client read_consumed: > > > %lld\n", > > > + bwr.read_consumed); > > > + > > > + cmd = binder_parse(fd, > > > (uintptr_t)readbuf, > > > + bwr.read_consumed, > > > "Client"); > > > + > > > + if (cmd == BR_FAILED_REPLY || > > > + cmd == BR_DEAD_REPLY || > > > + cmd == BR_DEAD_BINDER) { > > > + result = 9; > > > + goto brexit; > > > + } > > > + count++; > > > + } > > > + break; > > > + > > > + default: > > > + result = -1; > > > + } > > > + > > > +brexit: > > > + free(context); > > > + munmap(mapped, mapsize); > > > + close(fd); > > > + > > > + return result; > > > +} > > > > >
On 05/15/2018 09:43 AM, Stephen Smalley wrote: > On 05/15/2018 09:36 AM, Stephen Smalley wrote: >> This test is failing for me (with or without -v): >> # ./test -v >> 1..6 >> Manager PID: 5608 Process context: >> unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023 >> Client PID: 5609 Process context: >> unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023 >> Client read_consumed: 28 >> Manager read_consumed: 72 >> Client command: BR_NOOP >> Manager command: BR_NOOP >> Client command: BR_INCREFS >> Manager command: BR_TRANSACTION >> Client command: BR_TRANSACTION_COMPLETE >> BR_TRANSACTION data: >> handle: 0 >> cookie: 0 >> code: 0 >> flag: TF_ACCEPT_FDS >> sender pid: 5609 >> sender euid: 0 >> data_size: 24 >> offsets_size: 8 >> Sending BC_REPLY >> Manager read_consumed: 8 >> Manager command: BR_NOOP >> Manager command: BR_TRANSACTION_COMPLETE >> Client read_consumed: 72 >> Client command: BR_NOOP >> Client command: BR_REPLY >> BR_REPLY data: >> handle: 0 >> cookie: 0 >> code: 0 >> flag: TF_ACCEPT_FDS >> sender pid: 0 >> sender euid: 0 >> data_size: 24 >> offsets_size: 8 >> Retrieved Managers fd: 4 st_dev: 6 >> Client read_consumed: 8 >> Client using Managers FD command: BR_NOOP >> Client using Managers FD command: BR_FAILED_REPLY >> Client using Managers received FD failed response >> Manager read_consumed: 4 >> Manager command: BR_NOOP >> not ok 1 >> # Failed test at ./test line 36. > > Just realized that I was testing with a kernel that still had Casey's stacking support enabled. > Will re-try without that. Still fails for me on F28 with stock/linus 4.17.0-rc5. No AVC messages from the failing test itself, just the other ones.
diff --git a/README.md b/README.md index c9f3b2b..60a249e 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,14 @@ directory or you can follow these broken-out steps: The broken-out steps allow you to run the tests multiple times without loading policy each time. +Note that if leaving the test policy in-place for further testing, the +policy build process changes a boolean: + On policy load: setsebool allow_domain_fd_use=0 + On policy unload: setsebool allow_domain_fd_use=1 +The consequence of this is that after a system reboot, the boolean +defaults to true. Therefore if running the fdreceive or binder tests, +reset the boolean to false, otherwise some tests will fail. + 4) Review the test results. As each test script is run, the name of the script will be displayed followed diff --git a/defconfig b/defconfig index 7dce8bc..dc6ef30 100644 --- a/defconfig +++ b/defconfig @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m # This is enabled to test overlayfs SELinux integration. # It is not required for SELinux operation itself. CONFIG_OVERLAY_FS=m + +# Android binder implementations. +# These are enabled to test the binder controls in +# tests/binder; they are not required for SELinux operation itself. +CONFIG_ANDROID=y +CONFIG_ANDROID_BINDER_IPC=y +CONFIG_ANDROID_BINDER_DEVICES="binder" +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set diff --git a/policy/Makefile b/policy/Makefile index 8ed5e46..5a9d411 100644 --- a/policy/Makefile +++ b/policy/Makefile @@ -25,7 +25,7 @@ TARGETS = \ test_task_getsid.te test_task_setpgid.te test_task_setsched.te \ test_transition.te test_inet_socket.te test_unix_socket.te \ test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \ - test_ibpkey.te test_atsecure.te + test_ibpkey.te test_atsecure.te test_binder.te ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true) TARGETS += test_bounds.te diff --git a/policy/test_binder.te b/policy/test_binder.te new file mode 100644 index 0000000..c4ad2ae --- /dev/null +++ b/policy/test_binder.te @@ -0,0 +1,83 @@ + +attribute binderdomain; + +# +################################## Manager ################################### +# +type test_binder_mgr_t; +domain_type(test_binder_mgr_t) +unconfined_runs_test(test_binder_mgr_t) +typeattribute test_binder_mgr_t testdomain; +typeattribute test_binder_mgr_t binderdomain; +allow test_binder_mgr_t self:binder { set_context_mgr call }; +allow test_binder_mgr_t device_t:chr_file { ioctl open read write map }; +allow test_binder_mgr_t self:capability { sys_nice }; +allow test_binder_client_t test_binder_mgr_t:fd use; + +# +################################# Client #################################### +# +type test_binder_client_t; +domain_type(test_binder_client_t) +unconfined_runs_test(test_binder_client_t) +typeattribute test_binder_client_t testdomain; +typeattribute test_binder_client_t binderdomain; +allow test_binder_client_t self:binder { call }; +allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate }; +allow test_binder_client_t device_t:chr_file { ioctl open read write map }; +# For fstat: +allow test_binder_client_t device_t:chr_file getattr; + +# +############################## Client no call ################################ +# +type test_binder_client_no_call_t; +domain_type(test_binder_client_no_call_t) +unconfined_runs_test(test_binder_client_no_call_t) +typeattribute test_binder_client_no_call_t testdomain; +typeattribute test_binder_client_no_call_t binderdomain; +allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map }; + +# +############################ Client no transfer ############################# +# +type test_binder_client_no_transfer_t; +domain_type(test_binder_client_no_transfer_t) +unconfined_runs_test(test_binder_client_no_transfer_t) +typeattribute test_binder_client_no_transfer_t testdomain; +typeattribute test_binder_client_no_transfer_t binderdomain; +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call }; +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map }; + +# +########################## Manager no fd {use} ############################### +# +type test_binder_mgr_no_fd_t; +domain_type(test_binder_mgr_no_fd_t) +unconfined_runs_test(test_binder_mgr_no_fd_t) +typeattribute test_binder_mgr_no_fd_t testdomain; +typeattribute test_binder_mgr_no_fd_t binderdomain; +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call }; +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map }; +allow test_binder_mgr_no_fd_t self:capability { sys_nice }; +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer }; + +# +########################### Client no impersonate ############################ +# +type test_binder_client_no_im_t; +domain_type(test_binder_client_no_im_t) +unconfined_runs_test(test_binder_client_no_im_t) +typeattribute test_binder_client_no_im_t testdomain; +typeattribute test_binder_client_no_im_t binderdomain; +allow test_binder_client_no_im_t self:binder { call }; +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer }; +allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map }; +allow test_binder_client_no_im_t test_binder_mgr_t:fd use; +allow test_binder_client_no_im_t device_t:chr_file getattr; + +# +############ Allow these domains to be entered from sysadm domain ############ +# +miscfiles_domain_entry_test_files(binderdomain) +userdom_sysadm_entry_spec_domtrans_to(binderdomain) diff --git a/tests/Makefile b/tests/Makefile index 27ed6eb..7607c22 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \ task_setnice task_setscheduler task_getscheduler task_getsid \ task_getpgid task_setpgid file ioctl capable_file capable_net \ capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \ - inet_socket overlay checkreqprot mqueue mac_admin atsecure + inet_socket overlay checkreqprot mqueue mac_admin atsecure binder ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true) ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1) diff --git a/tests/binder/Makefile b/tests/binder/Makefile new file mode 100644 index 0000000..a60eeb3 --- /dev/null +++ b/tests/binder/Makefile @@ -0,0 +1,7 @@ +TARGETS = check_binder test_binder + +LDLIBS += -lselinux + +all: $(TARGETS) +clean: + rm -f $(TARGETS) diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c new file mode 100644 index 0000000..3d553a0 --- /dev/null +++ b/tests/binder/check_binder.c @@ -0,0 +1,80 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <stdbool.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <linux/android/binder.h> + +static void usage(char *progname) +{ + fprintf(stderr, + "usage: %s [-v]\n" + "Where:\n\t" + "-v Print binder version.\n", progname); + exit(-1); +} + +int main(int argc, char **argv) +{ + int opt, result, fd; + char *driver = "/dev/binder"; + bool verbose; + void *mapped; + size_t mapsize = 1024; + struct binder_version vers; + + while ((opt = getopt(argc, argv, "v")) != -1) { + switch (opt) { + case 'v': + verbose = true; + break; + default: + usage(argv[0]); + } + } + + fd = open(driver, O_RDWR | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "Cannot open: %s error: %s\n", + driver, strerror(errno)); + exit(-1); + } + + /* Need this or no VMA error from kernel */ + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped == MAP_FAILED) { + fprintf(stderr, "mmap error: %s\n", strerror(errno)); + close(fd); + exit(-1); + } + + result = ioctl(fd, BINDER_VERSION, &vers); + if (result < 0) { + fprintf(stderr, "ioctl BINDER_VERSION: %s\n", + strerror(errno)); + goto brexit; + } + + if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) { + fprintf(stderr, + "Binder kernel version: %d differs from user space version: %d\n", + vers.protocol_version, + BINDER_CURRENT_PROTOCOL_VERSION); + result = -1; + goto brexit; + } + + if (verbose) + fprintf(stderr, "Binder kernel version: %d\n", + vers.protocol_version); + +brexit: + munmap(mapped, mapsize); + close(fd); + + return result; +} diff --git a/tests/binder/test b/tests/binder/test new file mode 100644 index 0000000..434ae32 --- /dev/null +++ b/tests/binder/test @@ -0,0 +1,131 @@ +#!/usr/bin/perl +use Test::More; + +BEGIN { + $basedir = $0; + $basedir =~ s|(.*)/[^/]*|$1|; + + # allow binder info to be shown + $v = $ARGV[0]; + if ($v) { + if ( $v ne "-v" ) { + plan skip_all => "Invalid option (use -v)"; + } + } + + # check if binder driver available and the kernel/userspace versions. + if ( system("$basedir/check_binder 2> /dev/null") != 0 ) { + plan skip_all => + "Binder not supported or kernel/userspace versions differ"; + } + else { + plan tests => 6; + } +} + +if ($v) { + if ( ( $pid = fork() ) == 0 ) { + exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager"; + } + + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. + + # Verify that authorized client can transact with the manager. + $result = + system "runcon -t test_binder_client_t $basedir/test_binder -v client"; + ok( $result eq 0 ); + + # Verify that client cannot call manager (no call perm). + $result = system +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Verify that client cannot communicate with manager (no impersonate perm). + $result = system +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1"; + ok( $result >> 8 eq 102 ); + + # Verify that client cannot communicate with manager (no transfer perm). + $result = system +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Kill the manager. + kill TERM, $pid; + + # Verify that client cannot become a manager (no set_context_mgr perm). + $result = + system + "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1"; + ok( $result >> 8 eq 4 ); + +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. + if ( ( $pid = fork() ) == 0 ) { + exec + "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager"; + } + + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. + +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager +# domain and binder will return BR_FAILED_REPLY. + $result = + system + "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Kill the manager + kill TERM, $pid; +} +else { + if ( ( $pid = fork() ) == 0 ) { + exec "runcon -t test_binder_mgr_t $basedir/test_binder manager"; + } + + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. + + # Verify that authorized client can transact with the manager. + $result = + system "runcon -t test_binder_client_t $basedir/test_binder client"; + ok( $result eq 0 ); + + # Verify that client cannot call manager (no call perm). + $result = system + "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Verify that client cannot communicate with manager (no impersonate perm). + $result = system + "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1"; + ok( $result >> 8 eq 102 ); + + # Verify that client cannot communicate with manager (no transfer perm). + $result = system +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Kill the manager. + kill TERM, $pid; + + # Verify that client cannot become a manager (no set_context_mgr perm). + $result = + system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1"; + ok( $result >> 8 eq 4 ); + +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy. + if ( ( $pid = fork() ) == 0 ) { + exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager"; + } + + select( undef, undef, undef, 0.25 ); # Give it a moment to initialize. + +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager +# domain and binder will return BR_FAILED_REPLY. + $result = + system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1"; + ok( $result >> 8 eq 9 ); + + # Kill the manager + kill TERM, $pid; +} +exit; diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c new file mode 100644 index 0000000..8881cce --- /dev/null +++ b/tests/binder/test_binder.c @@ -0,0 +1,543 @@ +/* + * This is a simple binder client/server(manager) that only uses the + * raw ioctl commands. It does not rely on a 'service manager' as in + * the Android world as it only uses one 'target' = 0. + * + * The transaction/reply flow is basically: + * Client Manager + * ======== ========= + * + * Becomes context manager + * Send transaction + * requesting file + * descriptor from + * the Manager + * ---------------------------------------> + * Manager replies with its + * binder file descriptor + * <-------------------------------------- + * Check fd valid, if so send + * TF_ONE_WAY transaction to Manager + * using the received fd + * ---------------------------------------> + * End of tests so Manager + * waits to be killed + * Client exits + * + * Using binder test policy the following will be validated: + * security_binder_set_context_mgr() binder { set_context_mgr } + * security_binder_transaction() binder { call impersonate } + * security_binder_transfer_binder() binder { transfer } + * security_binder_transfer_file() fd { use } + * + * TODO security_binder_transfer_file() uses BPF if configured in kernel. + */ + +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <sys/mman.h> +#include <sys/ioctl.h> +#include <selinux/selinux.h> +#include <linux/android/binder.h> + +static uint32_t target; /* This will be set to '0' as only need one target */ +static bool verbose; + +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text); + +static void usage(char *progname) +{ + fprintf(stderr, + "usage: %s [-v] manager | client\n" + "Where:\n\t" + "-v Print context and command information.\n\t" + "manager Act as binder context manager.\n\t" + "client Act as binder client.\n" + "\nNote: Ensure this boolean command is run when " + "testing after a reboot:\n\t" + "setsebool allow_domain_fd_use=0\n", progname); + exit(1); +} + +static const char *cmd_name(uint32_t cmd) +{ + switch (cmd) { + case BR_NOOP: + return "BR_NOOP"; + case BR_TRANSACTION_COMPLETE: + return "BR_TRANSACTION_COMPLETE"; + case BR_INCREFS: + return "BR_INCREFS"; + case BR_ACQUIRE: + return "BR_ACQUIRE"; + case BR_RELEASE: + return "BR_RELEASE"; + case BR_DECREFS: + return "BR_DECREFS"; + case BR_TRANSACTION: + return "BR_TRANSACTION"; + case BR_REPLY: + return "BR_REPLY"; + case BR_FAILED_REPLY: + return "BR_FAILED_REPLY"; + case BR_DEAD_REPLY: + return "BR_DEAD_REPLY"; + case BR_DEAD_BINDER: + return "BR_DEAD_BINDER"; + case BR_ERROR: + return "BR_ERROR"; + /* fallthrough */ + default: + return "Unknown command"; + } +} + +void print_trans_data(struct binder_transaction_data *txn) +{ + printf("\thandle: %ld\n", (unsigned long)txn->target.handle); + printf("\tcookie: %lld\n", txn->cookie); + printf("\tcode: %u\n", txn->code); + switch (txn->flags) { + case TF_ONE_WAY: + printf("\tflag: TF_ONE_WAY\n"); + break; + case TF_ROOT_OBJECT: + printf("\tflag: TF_ROOT_OBJECT\n"); + break; + case TF_STATUS_CODE: + printf("\tflag: TF_STATUS_CODE\n"); + break; + case TF_ACCEPT_FDS: + printf("\tflag: TF_ACCEPT_FDS\n"); + break; + default: + printf("Unknown flag: %u\n", txn->flags); + } + printf("\tsender pid: %u\n", txn->sender_pid); + printf("\tsender euid: %u\n", txn->sender_euid); + printf("\tdata_size: %llu\n", txn->data_size); + printf("\toffsets_size: %llu\n", txn->offsets_size); +} + + +static int send_reply(int fd, struct binder_transaction_data *txn_in) +{ + int result; + unsigned int writebuf[1024]; + struct flat_binder_object obj; + struct binder_write_read bwr; + struct binder_transaction_data *txn; + + if (txn_in->flags == TF_ONE_WAY) { + if (verbose) + printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n"); + return 0; + } + + if (verbose) + printf("Sending BC_REPLY\n"); + + writebuf[0] = BC_REPLY; + txn = (struct binder_transaction_data *)(&writebuf[1]); + + memset(txn, 0, sizeof(*txn)); + txn->target.handle = txn_in->target.handle; + txn->cookie = txn_in->cookie; + txn->code = txn_in->code; + txn->flags = TF_ACCEPT_FDS; + + memset(&obj, 0, sizeof(struct flat_binder_object)); + obj.hdr.type = BINDER_TYPE_FD; + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj.binder = txn->target.handle; + obj.cookie = txn->cookie; + /* The binder fd is used for testing as it allows policy to set + * whether the Client/Manager can be allowed access (fd use) or + * not. For example test_binder_mgr_t has: + * allow test_binder_client_t test_binder_mgr_t:fd use; + * whereas test_binder_mgr_no_fd_t does not allow this fd use. + * + * This also allows a check for the impersonate permission later as + * the Client will use this (the Managers fd) to send a transaction. + */ + obj.handle = fd; + + txn->data_size = sizeof(struct flat_binder_object); + txn->data.ptr.buffer = (uintptr_t)&obj; + txn->data.ptr.offsets = (uintptr_t)&obj + + sizeof(struct flat_binder_object); + txn->offsets_size = sizeof(binder_size_t); + + memset(&bwr, 0, sizeof(bwr)); + bwr.write_buffer = (unsigned long)writebuf; + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); + + result = ioctl(fd, BINDER_WRITE_READ, &bwr); + if (result < 0) { + fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n", + __func__, strerror(errno)); + return -1; + } + + return result; +} + +/* This retrieves the requested Managers file descriptor, then using this + * sends a simple transaction to trigger the impersonate permission. + */ +static void extract_fd_and_respond(struct binder_transaction_data *txn_in) +{ + int result; + uint32_t cmd; + struct stat sb; + struct binder_write_read bwr; + struct flat_binder_object *obj; + struct binder_transaction_data *txn; + unsigned int readbuf[32]; + unsigned int writebuf[1024]; + binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets; + + /* Get the fbo that contains the Managers binder file descriptor. */ + obj = (struct flat_binder_object *) + (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs); + + /* fstat this just to see if a valid fd */ + result = fstat(obj->handle, &sb); + if (result < 0) { + fprintf(stderr, "Not a valid fd: %s\n", strerror(errno)); + exit(100); + } + + if (verbose) + printf("Retrieved Managers fd: %d st_dev: %ld\n", + obj->handle, sb.st_dev); + + /* Send response using Managers fd to trigger impersonate check. */ + writebuf[0] = BC_TRANSACTION; + txn = (struct binder_transaction_data *)(&writebuf[1]); + memset(txn, 0, sizeof(*txn)); + txn->target.handle = target; + txn->cookie = 0; + txn->code = 0; + txn->flags = TF_ONE_WAY; + + txn->data_size = 0; + txn->data.ptr.buffer = (uintptr_t)NULL; + txn->data.ptr.offsets = (uintptr_t)NULL; + txn->offsets_size = 0; + + memset(&bwr, 0, sizeof(bwr)); + bwr.write_buffer = (unsigned long)writebuf; + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); + + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (uintptr_t)readbuf; + + result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr); + if (result < 0) { + fprintf(stderr, + "CLIENT ioctl BINDER_WRITE_READ: %s\n", + strerror(errno)); + exit(101); + } + + if (verbose) + printf("Client read_consumed: %lld\n", bwr.read_consumed); + + cmd = binder_parse(obj->handle, (uintptr_t)readbuf, + bwr.read_consumed, + "Client using Managers FD"); + + if (cmd == BR_FAILED_REPLY || + cmd == BR_DEAD_REPLY || + cmd == BR_DEAD_BINDER) { + fprintf(stderr, + "Client using Managers received FD failed response\n"); + exit(102); + } +} + +/* Parse response, reply as required and then return last CMD */ +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text) +{ + uintptr_t end = ptr + (uintptr_t)size; + uint32_t cmd; + + while (ptr < end) { + cmd = *(uint32_t *)ptr; + ptr += sizeof(uint32_t); + + if (verbose) + printf("%s command: %s\n", text, cmd_name(cmd)); + + switch (cmd) { + case BR_NOOP: + break; + case BR_TRANSACTION_COMPLETE: + break; + case BR_INCREFS: + case BR_ACQUIRE: + case BR_RELEASE: + case BR_DECREFS: + ptr += sizeof(struct binder_ptr_cookie); + break; + case BR_TRANSACTION: { + struct binder_transaction_data *txn = + (struct binder_transaction_data *)ptr; + + if (verbose) { + printf("BR_TRANSACTION data:\n"); + print_trans_data(txn); + } + + /* The manager sends reply that will contain its fd */ + if (send_reply(fd, txn) < 0) { + fprintf(stderr, "send_reply() failed.\n"); + return -1; + } + ptr += sizeof(*txn); + break; + } + case BR_REPLY: { + struct binder_transaction_data *txn = + (struct binder_transaction_data *)ptr; + + if (verbose) { + printf("BR_REPLY data:\n"); + print_trans_data(txn); + } + + /* Client extracts the Manager fd, and responds */ + extract_fd_and_respond(txn); + ptr += sizeof(*txn); + break; + } + case BR_DEAD_BINDER: + break; + case BR_FAILED_REPLY: + break; + case BR_DEAD_REPLY: + break; + case BR_ERROR: + ptr += sizeof(uint32_t); + break; + default: + if (verbose) + printf("%s Parsed unknown command: %d\n", + text, cmd); + return -1; + } + } + + return cmd; +} + +int main(int argc, char **argv) +{ + int opt, option, result, fd, count; + uint32_t cmd; + pid_t pid; + char *driver = "/dev/binder"; + char *context; + void *mapped; + size_t mapsize = 2048; + struct binder_write_read bwr; + struct flat_binder_object obj; + struct binder_transaction_data *txn; + unsigned int readbuf[32]; + unsigned int writebuf[1024]; + + target = 0; /* Only need one target - the Manager */ + verbose = false; + + while ((opt = getopt(argc, argv, "v")) != -1) { + switch (opt) { + case 'v': + verbose = true; + break; + default: + usage(argv[0]); + } + } + + if ((argc - optind) != 1) + usage(argv[0]); + + if (!strcmp(argv[optind], "manager")) + option = 1; + else if (!strcmp(argv[optind], "client")) + option = 2; + else + usage(argv[0]); + + fd = open(driver, O_RDWR | O_CLOEXEC); + if (fd < 0) { + fprintf(stderr, "Cannot open %s error: %s\n", driver, + strerror(errno)); + exit(1); + } + + /* Need this or "no VMA" error from kernel */ + mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapped == MAP_FAILED) { + fprintf(stderr, "mmap error: %s\n", strerror(errno)); + close(fd); + exit(2); + } + + /* Get our context and pid */ + result = getcon(&context); + if (result < 0) { + fprintf(stderr, "Failed to obtain SELinux context\n"); + result = 3; + goto brexit; + } + pid = getpid(); + + switch (option) { + case 1: /* manager */ + if (verbose) { + printf("Manager PID: %d Process context:\n\t%s\n", + pid, context); + } + + result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0); + if (result < 0) { + fprintf(stderr, + "Failed to become context manager: %s\n", + strerror(errno)); + result = 4; + goto brexit; + } + + readbuf[0] = BC_ENTER_LOOPER; + bwr.write_size = sizeof(readbuf[0]); + bwr.write_consumed = 0; + bwr.write_buffer = (uintptr_t)readbuf; + + bwr.read_size = 0; + bwr.read_consumed = 0; + bwr.read_buffer = 0; + + result = ioctl(fd, BINDER_WRITE_READ, &bwr); + if (result < 0) { + fprintf(stderr, + "Manager ioctl BINDER_WRITE_READ: %s\n", + strerror(errno)); + result = 5; + goto brexit; + } + + while (true) { + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (uintptr_t)readbuf; + + result = ioctl(fd, BINDER_WRITE_READ, &bwr); + if (result < 0) { + fprintf(stderr, + "Manager ioctl BINDER_WRITE_READ: %s\n", + strerror(errno)); + result = 6; + goto brexit; + } + + if (bwr.read_consumed == 0) + continue; + + if (verbose) + printf("Manager read_consumed: %lld\n", + bwr.read_consumed); + + cmd = binder_parse(fd, (uintptr_t)readbuf, + bwr.read_consumed, "Manager"); + } + break; + + case 2: /* client */ + if (verbose) { + printf("Client PID: %d Process context:\n\t%s\n", + pid, context); + } + + writebuf[0] = BC_TRANSACTION; + txn = (struct binder_transaction_data *)(&writebuf[1]); + memset(txn, 0, sizeof(*txn)); + txn->target.handle = target; + txn->cookie = 0; + txn->code = 0; + txn->flags = TF_ACCEPT_FDS; + + memset(&obj, 0, sizeof(struct flat_binder_object)); + obj.hdr.type = BINDER_TYPE_WEAK_BINDER; + obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; + obj.binder = target; + obj.handle = 0; + obj.cookie = 0; + + txn->data_size = sizeof(struct flat_binder_object); + txn->data.ptr.buffer = (uintptr_t)&obj; + txn->data.ptr.offsets = (uintptr_t)&obj + + sizeof(struct flat_binder_object); + txn->offsets_size = sizeof(binder_size_t); + + memset(&bwr, 0, sizeof(bwr)); + bwr.write_buffer = (unsigned long)writebuf; + bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn); + + /* Expect client to get max two responses: + * 1) From the above BC_TRANSACTION + * 2) The responding BC_REPLY from send_reply() + * unless an error. + */ + count = 0; + while (count != 2) { + bwr.read_size = sizeof(readbuf); + bwr.read_consumed = 0; + bwr.read_buffer = (uintptr_t)readbuf; + + result = ioctl(fd, BINDER_WRITE_READ, &bwr); + if (result < 0) { + fprintf(stderr, + "Client ioctl BINDER_WRITE_READ: %s\n", + strerror(errno)); + result = 8; + goto brexit; + } + + if (verbose) + printf("Client read_consumed: %lld\n", + bwr.read_consumed); + + cmd = binder_parse(fd, (uintptr_t)readbuf, + bwr.read_consumed, "Client"); + + if (cmd == BR_FAILED_REPLY || + cmd == BR_DEAD_REPLY || + cmd == BR_DEAD_BINDER) { + result = 9; + goto brexit; + } + count++; + } + break; + + default: + result = -1; + } + +brexit: + free(context); + munmap(mapped, mapsize); + close(fd); + + return result; +}
Add binder tests. See tests/binder/test_binder.c for details on message flows to test security_binder*() functions. Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> --- README.md | 8 + defconfig | 8 + policy/Makefile | 2 +- policy/test_binder.te | 83 +++++++ tests/Makefile | 2 +- tests/binder/Makefile | 7 + tests/binder/check_binder.c | 80 +++++++ tests/binder/test | 131 +++++++++++ tests/binder/test_binder.c | 543 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 862 insertions(+), 2 deletions(-) create mode 100644 policy/test_binder.te create mode 100644 tests/binder/Makefile create mode 100644 tests/binder/check_binder.c create mode 100644 tests/binder/test create mode 100644 tests/binder/test_binder.c