Richard Haines via Selinux
2018-05-15 08:25:53 UTC
Add binder tests. See tests/binder/test_binder.c for details on
message flows to test security_binder*() functions.
Signed-off-by: Richard 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.
+
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;
+}
message flows to test security_binder*() functions.
Signed-off-by: Richard 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.
+
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
2.14.3