All of lore.kernel.org
 help / color / mirror / Atom feed
From: Richard Haines <richard_c_haines@btinternet.com>
To: selinux@tycho.nsa.gov
Subject: [RFC PATCH 1/1] selinux-testsuite: Add binder tests
Date: Tue, 15 May 2018 09:25:53 +0100	[thread overview]
Message-ID: <20180515082553.30310-1-richard_c_haines@btinternet.com> (raw)

Add binder tests. See tests/binder/test_binder.c for details on
message flows to test security_binder*() functions.

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
---
 README.md                   |   8 +
 defconfig                   |   8 +
 policy/Makefile             |   2 +-
 policy/test_binder.te       |  83 +++++++
 tests/Makefile              |   2 +-
 tests/binder/Makefile       |   7 +
 tests/binder/check_binder.c |  80 +++++++
 tests/binder/test           | 131 +++++++++++
 tests/binder/test_binder.c  | 543 ++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 862 insertions(+), 2 deletions(-)
 create mode 100644 policy/test_binder.te
 create mode 100644 tests/binder/Makefile
 create mode 100644 tests/binder/check_binder.c
 create mode 100644 tests/binder/test
 create mode 100644 tests/binder/test_binder.c

diff --git a/README.md b/README.md
index c9f3b2b..60a249e 100644
--- a/README.md
+++ b/README.md
@@ -141,6 +141,14 @@ directory or you can follow these broken-out steps:
 The broken-out steps allow you to run the tests multiple times without
 loading policy each time.
 
+Note that if leaving the test policy in-place for further testing, the
+policy build process changes a boolean:
+   On policy load:   setsebool allow_domain_fd_use=0
+   On policy unload: setsebool allow_domain_fd_use=1
+The consequence of this is that after a system reboot, the boolean
+defaults to true. Therefore if running the fdreceive or binder tests,
+reset the boolean to false, otherwise some tests will fail.
+
 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

             reply	other threads:[~2018-05-15  8:26 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-05-15  8:25 Richard Haines [this message]
2018-05-15 13:36 ` [RFC PATCH 1/1] selinux-testsuite: Add binder tests Stephen Smalley
2018-05-15 13:43   ` Stephen Smalley
2018-05-15 15:35     ` Richard Haines
2018-05-15 16:38     ` Stephen Smalley
2018-05-15 16:56       ` Stephen Smalley
2018-05-15 17:34         ` Richard Haines
2018-05-15 17:45           ` Stephen Smalley
2018-05-15 17:55             ` Richard Haines

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180515082553.30310-1-richard_c_haines@btinternet.com \
    --to=richard_c_haines@btinternet.com \
    --cc=selinux@tycho.nsa.gov \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.