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. + 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; +} -- 2.14.3