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@xxxxxxxxxxxxxx> > --- > 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; > +} >