diff mbox

[RFC,V2,1/1] selinux-testsuite: Add binder tests

Message ID 20180520182506.19665-1-richard_c_haines@btinternet.com (mailing list archive)
State Superseded
Headers show

Commit Message

Jann Horn via Selinux May 20, 2018, 6:25 p.m. 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 <richard_c_haines@btinternet.com>
---
 README.md                   |   8 +
 defconfig                   |   7 +
 policy/Makefile             |   4 +
 policy/test_binder.te       |  96 +++++
 tests/Makefile              |   4 +
 tests/binder/Makefile       |   7 +
 tests/binder/check_binder.c |  80 +++++
 tests/binder/test           |  89 +++++
 tests/binder/test_binder.c  | 685 ++++++++++++++++++++++++++++++++++++
 9 files changed, 980 insertions(+)
 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

Comments

Stephen Smalley May 21, 2018, 2:37 p.m. UTC | #1
On 05/20/2018 02:25 PM, Richard Haines wrote:
> Add binder tests. See tests/binder/test_binder.c for details on
> message flows to test security_binder*() functions.

Every test fails for me with:
create_shm shm_open: Permission denied
create_shm shm_open: No such file or directory

and denials of the form:
type=AVC msg=audit(1526902487.392:712): avc:  denied  { write } for  pid=3693 comm="test_binder" name="/" dev="tmpfs" ino=14124 scontext=unconfined_u:unconfined_r:test_binder_provider_t:s0-s0:c0.c1023 tcontext=system_u:object_r:tmpfs_t:s0 tclass=dir permissive=0

> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>  README.md                   |   8 +
>  defconfig                   |   7 +
>  policy/Makefile             |   4 +
>  policy/test_binder.te       |  96 +++++
>  tests/Makefile              |   4 +
>  tests/binder/Makefile       |   7 +
>  tests/binder/check_binder.c |  80 +++++
>  tests/binder/test           |  89 +++++
>  tests/binder/test_binder.c  | 685 ++++++++++++++++++++++++++++++++++++
>  9 files changed, 980 insertions(+)
>  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..c48d3cc 100644
> --- a/defconfig
> +++ b/defconfig
> @@ -51,3 +51,10 @@ 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_DEVICES="binder"
> +CONFIG_ANDROID_BINDER_IPC=y
> diff --git a/policy/Makefile b/policy/Makefile
> index 5e07ee2..15e3a0c 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -63,6 +63,10 @@ ifeq ($(shell grep -q nnp_transition $(POLDEV)/include/support/all_perms.spt &&
>  export M4PARAM += -Dnnp_nosuid_transition_permission_defined
>  endif
>  
> +ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
> +TARGETS += test_binder.te
> +endif
> +
>  ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
>  TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
>  endif
> diff --git a/policy/test_binder.te b/policy/test_binder.te
> new file mode 100644
> index 0000000..3fd4dd5
> --- /dev/null
> +++ b/policy/test_binder.te
> @@ -0,0 +1,96 @@
> +
> +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 test_binder_provider_t:binder 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_provider_t test_binder_mgr_t:fd use;
> +fs_getattr_tmpfs(test_binder_mgr_t)
> +allow test_binder_mgr_t tmpfs_t:file { read write open 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 test_binder_provider_t:binder { call };
> +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_provider_t test_binder_mgr_no_fd_t:binder { call transfer impersonate };
> +fs_getattr_tmpfs(test_binder_mgr_no_fd_t)
> +allow test_binder_mgr_no_fd_t tmpfs_t:file { read write open map };
> +
> +#
> +########################## Service Provider ################################
> +#
> +type test_binder_provider_t;
> +domain_type(test_binder_provider_t)
> +unconfined_runs_test(test_binder_provider_t)
> +typeattribute test_binder_provider_t testdomain;
> +typeattribute test_binder_provider_t binderdomain;
> +allow test_binder_provider_t self:binder { call };
> +allow test_binder_provider_t test_binder_mgr_t:binder { call transfer impersonate };
> +allow test_binder_provider_t device_t:chr_file { ioctl open read write map };
> +# For fstat:
> +allow test_binder_provider_t device_t:chr_file getattr;
> +fs_getattr_tmpfs(test_binder_provider_t)
> +allow test_binder_provider_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no call ################################
> +#
> +type test_binder_provider_no_call_t;
> +domain_type(test_binder_provider_no_call_t)
> +unconfined_runs_test(test_binder_provider_no_call_t)
> +typeattribute test_binder_provider_no_call_t testdomain;
> +typeattribute test_binder_provider_no_call_t binderdomain;
> +allow test_binder_provider_no_call_t device_t:chr_file { ioctl open read write map };
> +fs_getattr_tmpfs(test_binder_provider_no_call_t)
> +allow test_binder_provider_no_call_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no transfer #############################
> +#
> +type test_binder_provider_no_transfer_t;
> +domain_type(test_binder_provider_no_transfer_t)
> +unconfined_runs_test(test_binder_provider_no_transfer_t)
> +typeattribute test_binder_provider_no_transfer_t testdomain;
> +typeattribute test_binder_provider_no_transfer_t binderdomain;
> +allow test_binder_provider_no_transfer_t test_binder_mgr_t:binder { call };
> +allow test_binder_provider_no_transfer_t device_t:chr_file { ioctl open read write map };
> +fs_getattr_tmpfs(test_binder_provider_no_transfer_t)
> +allow test_binder_provider_no_transfer_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no impersonate ##########################
> +#
> +type test_binder_provider_no_im_t;
> +domain_type(test_binder_provider_no_im_t)
> +unconfined_runs_test(test_binder_provider_no_im_t)
> +typeattribute test_binder_provider_no_im_t testdomain;
> +typeattribute test_binder_provider_no_im_t binderdomain;
> +allow test_binder_provider_no_im_t self:binder { call };
> +allow test_binder_provider_no_im_t test_binder_mgr_t:binder { call transfer };
> +allow test_binder_provider_no_im_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_provider_no_im_t test_binder_mgr_t:fd use;
> +allow test_binder_provider_no_im_t device_t:chr_file getattr;
> +fs_getattr_tmpfs(test_binder_provider_no_im_t)
> +allow test_binder_provider_no_im_t tmpfs_t:file { read write open map };
> +
> +#
> +############ 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..494b761 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -30,6 +30,10 @@ ifeq ($(shell grep -q getrlimit $(POLDEV)/include/support/all_perms.spt && echo
>  SUBDIRS += prlimit
>  endif
>  
> +ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
> +SUBDIRS += binder
> +endif
> +
>  ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
>  SUBDIRS += infiniband_endport
>  endif
> diff --git a/tests/binder/Makefile b/tests/binder/Makefile
> new file mode 100644
> index 0000000..0d76723
> --- /dev/null
> +++ b/tests/binder/Makefile
> @@ -0,0 +1,7 @@
> +TARGETS = check_binder test_binder
> +
> +LDLIBS += -lselinux -lrt
> +
> +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..7b81a3d
> --- /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 = 2;
> +		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..86fab52
> --- /dev/null
> +++ b/tests/binder/test
> @@ -0,0 +1,89 @@
> +#!/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)";
> +        }
> +    }
> +    else {
> +        $v = " ";
> +    }
> +
> +    # check if binder driver available and kernel/userspace versions.
> +    $result = system("$basedir/check_binder 2> /dev/null");
> +
> +    if ( $result >> 8 eq 0 ) {
> +        plan tests => 6;
> +    }
> +    elsif ( $result >> 8 eq 1 ) {
> +        plan skip_all => "Binder not supported by kernel";
> +    }
> +    elsif ( $result >> 8 eq 2 ) {
> +        plan skip_all => "Binder kernel/userspace versions differ";
> +    }
> +    else {
> +        plan skip_all => "Error checking Binder driver";
> +    }
> +}
> +
> +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.
> +
> +# 1 Verify that authorized provider can transact with the manager.
> +$result =
> +  system
> +  "runcon -t test_binder_provider_t $basedir/test_binder $v -r 1 provider";
> +ok( $result eq 0 );
> +
> +# 2 Verify that provider cannot call manager (no call perm).
> +$result = system
> +"runcon -t test_binder_provider_no_call_t $basedir/test_binder $v -r 2 provider 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# 3 Verify that provider cannot communicate with manager (no impersonate perm).
> +$result = system
> +"runcon -t test_binder_provider_no_im_t $basedir/test_binder $v -r 1 provider 2>&1";
> +ok( $result >> 8 eq 103 );
> +
> +# 4 Verify that provider cannot communicate with manager (no transfer perm).
> +$result = system
> +"runcon -t test_binder_provider_no_transfer_t $basedir/test_binder $v -r 1 provider 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# Kill the manager.
> +kill TERM, $pid;
> +
> +# 5 Verify that provider cannot become a manager (no set_context_mgr perm).
> +$result =
> +  system
> +  "runcon -t test_binder_provider_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.
> +
> +# 6 Verify that authorized provider 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_provider_t $basedir/test_binder $v provider -r 1 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# 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..919e568
> --- /dev/null
> +++ b/tests/binder/test_binder.c
> @@ -0,0 +1,685 @@
> +/*
> + * This is a simple binder Service Manager/Service Provider that only uses
> + * the raw ioctl commands to test the SELinux binder permissions:
> + *     set_context_mgr, call, transfer, impersonate.
> + *
> + * 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>
> +
> +/* These are the Binder txn->code values used by the Service Provider and
> + * Manager to request/retrieve a binder handle.
> + */
> +#define ADD_TEST_SERVICE 100 /* Sent by Service Provider */
> +#define GET_TEST_SERVICE 101 /* Sent by Client */
> +
> +#define TEST_SERVICE_MANAGER_HANDLE 0
> +
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager);
> +
> +static bool verbose;
> +uint32_t sp_handle;
> +static unsigned char *shm_base;
> +static int shm_fd;
> +static int shm_size = 32;
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-r replies] [-v] manager | provider\n"
> +		"Where:\n\t"
> +		"-r       Number of replies to expect from test - default 0.\n\t"
> +		"-v       Print context and command information.\n\t"
> +		"manager  Act as Service Manager.\n\t"
> +		"service  Act as Service Provider.\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);
> +}
> +
> +/* Create a small piece of shared memory between the Manager and Service
> + * Provider to share a handle as explained in the do_service_manager()
> + * function.
> + */
> +static void create_shm(bool manager)
> +{
> +	char *name =  "sp_handle";
> +
> +	if (manager)
> +		shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
> +	else
> +		shm_fd = shm_open(name, O_RDONLY, 0666);
> +	if (shm_fd < 0) {
> +		fprintf(stderr, "%s shm_open: %s\n", __func__, strerror(errno));
> +		exit(-1);
> +	}
> +
> +	ftruncate(shm_fd, shm_size);
> +
> +	if (manager)
> +		shm_base = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED,
> +				shm_fd, 0);
> +	else
> +		shm_base = mmap(0, shm_size, PROT_READ, MAP_SHARED, shm_fd, 0);
> +	if (shm_base == MAP_FAILED) {
> +		fprintf(stderr, "%s mmap: %s\n", __func__, strerror(errno));
> +		close(shm_fd);
> +		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";
> +	}
> +}
> +
> +static void print_trans_data(struct binder_transaction_data *txn_in)
> +{
> +	struct flat_binder_object *obj;
> +	binder_size_t *offs = (binder_size_t *)
> +			      (uintptr_t)txn_in->data.ptr.offsets;
> +	size_t count = txn_in->offsets_size / sizeof(binder_size_t);
> +
> +	printf("\thandle: %ld\n", (unsigned long)txn_in->target.handle);
> +	printf("\tcookie: %lld\n", txn_in->cookie);
> +	printf("\tcode: %d\n", txn_in->code);
> +	switch (txn_in->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: %x\n", txn_in->flags);
> +		return;
> +	}
> +	printf("\tsender pid: %u\n", txn_in->sender_pid);
> +	printf("\tsender euid: %u\n", txn_in->sender_euid);
> +	printf("\tdata_size: %llu\n", txn_in->data_size);
> +	printf("\toffsets_size: %llu\n", txn_in->offsets_size);
> +
> +	while (count--) {
> +		obj = (struct flat_binder_object *)
> +		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs++);
> +
> +		switch (obj->hdr.type) {
> +		case BINDER_TYPE_BINDER:
> +			printf("\thdr: BINDER_TYPE_BINDER\n");
> +			printf("\tbinder: %llx\n", obj->binder);
> +			break;
> +		case BINDER_TYPE_HANDLE:
> +			printf("\thdr: BINDER_TYPE_HANDLE\n");
> +			printf("\thandle: %x\n", obj->handle);
> +			break;
> +		case BINDER_TYPE_FD:
> +			printf("\thdr: BINDER_TYPE_FD\n");
> +			printf("\tfd: %x\n", obj->handle);
> +			break;
> +		default:
> +			printf("Unknown header: %u\n", obj->hdr.type);
> +			return;
> +		}
> +		printf("\tflags: priority: 0x%x accept FDS: %s\n",
> +		       obj->flags & FLAT_BINDER_FLAG_PRIORITY_MASK,
> +		       obj->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS ? "YES" : "NO");
> +		printf("\tcookie: %llx\n", obj->cookie);
> +	}
> +}
> +
> +/* If add a service provider, then obtain a handle for it and store in
> + * shared memory. The handle will then be used by the service provider
> + * process to contact the Manager for its file descriptor, thus triggering
> + * the 'impersonate' permission (as current_sid() != task_sid(from))
> + * It is done this way as being a cheapskate it saved adding code to the
> + * GET_TEST_SERVICE process plus running a Client as well. This achieves
> + * the same objective.
> + */
> +static void do_service_manager(int fd, struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	struct flat_binder_object *obj;
> +	struct binder_write_read bwr;
> +	uint32_t acmd[2];
> +	binder_size_t *offs;
> +
> +	switch (txn_in->code) {
> +	case ADD_TEST_SERVICE:
> +		offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
> +
> +		/* Get fbo that contains the Managers binder file descriptor. */
> +		obj = (struct flat_binder_object *)
> +		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
> +
> +		if (obj->hdr.type == BINDER_TYPE_HANDLE) {
> +			sp_handle = obj->handle;
> +			memcpy(shm_base, &sp_handle, sizeof(sp_handle));
> +			if (verbose)
> +				printf("Manager has BINDER_TYPE_HANDLE obj->handle: %d\n",
> +				       sp_handle);
> +		} else {
> +			fprintf(stderr, "Failed to obtain a handle\n");
> +			exit(-1);
> +		}
> +
> +		acmd[0] = BC_ACQUIRE;
> +		acmd[1] = obj->handle;
> +
> +		memset(&bwr, 0, sizeof(bwr));
> +		bwr.write_buffer = (uintptr_t)&acmd;
> +		bwr.write_size = sizeof(acmd);
> +
> +		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +		if (result < 0) {
> +			fprintf(stderr,
> +				"ServiceProvider ioctl BINDER_WRITE_READ: %s\n",
> +				strerror(errno));
> +			exit(-1);
> +		}
> +
> +		if (verbose)
> +			printf("Manager acquired handle: %d for Service Provider\n",
> +			       sp_handle);
> +		break;
> +
> +	case GET_TEST_SERVICE:
> +		if (verbose)
> +			printf("GET_TEST_SERVICE not supported\n");
> +		break;
> +	default:
> +		fprintf(stderr, "Unknown txn->code: %d\n", txn_in->code);
> +		exit(-1);
> +	}
> +}
> +
> +static void request_manager_fd(int fd, struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	unsigned int writebuf[1024];
> +	struct binder_fd_object obj;
> +	struct binder_write_read bwr;
> +	struct binder_transaction_data *txn;
> +
> +	if (txn_in->flags == TF_ONE_WAY) {
> +		if (verbose)
> +			printf("Manager no reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
> +		return;
> +	}
> +
> +	if (verbose)
> +		printf("Manager sending BC_REPLY to obtain its FD\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 binder_fd_object));
> +	obj.hdr.type = BINDER_TYPE_FD;
> +	obj.pad_flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +	obj.cookie = txn->cookie;
> +	/* The binder fd is used for testing as it allows policy to set
> +	 * whether the Service and Manager can be allowed access (fd use)
> +	 * or not. For example test_binder_mgr_t has:
> +	 *        allow test_binder_service_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 Service Provider will use this (the Managers fd) to
> +	 * send a transaction.
> +	 */
> +	obj.fd = fd;
> +
> +	if (verbose)
> +		printf("Manager handle: %d and its FD: %d\n",
> +		       txn->target.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 = (uintptr_t)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));
> +		exit(-1);
> +	}
> +}
> +
> +/* 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,
> +				   bool manager)
> +{
> +	int result;
> +	uint32_t cmd;
> +	struct stat sb;
> +	struct binder_write_read bwr;
> +	struct binder_fd_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 bfdo that contains the Managers binder file descriptor. */
> +	obj = (struct binder_fd_object *)
> +	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
> +
> +	if (obj->hdr.type != BINDER_TYPE_FD) {
> +		fprintf(stderr, "Header not BINDER_TYPE_FD: %d\n",
> +			obj->hdr.type);
> +		exit(100);
> +	}
> +
> +	/* fstat this just to see if a valid fd */
> +	result = fstat(obj->fd, &sb);
> +	if (result < 0) {
> +		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
> +		exit(101);
> +	}
> +
> +	if (verbose)
> +		printf("Service Provider retrieved Managers fd: %d st_dev: %ld\n",
> +		       obj->fd, 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));
> +	/* Copy handle from the Manager */
> +	memcpy(&txn->target.handle, shm_base, sizeof(uint32_t));
> +
> +	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 = (uintptr_t)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->fd, BINDER_WRITE_READ, &bwr);
> +	if (result < 0) {
> +		fprintf(stderr,
> +			"Service Provider ioctl BINDER_WRITE_READ: %s\n",
> +			strerror(errno));
> +		exit(102);
> +	}
> +
> +	if (verbose)
> +		printf("Service Provider read_consumed: %lld\n",
> +		       bwr.read_consumed);
> +
> +	cmd = binder_parse(obj->fd, (uintptr_t)readbuf,
> +			   bwr.read_consumed, manager);
> +
> +	if (verbose)
> +		printf("Service Provider using Managers FD\n");
> +
> +	if (cmd == BR_FAILED_REPLY ||
> +	    cmd == BR_DEAD_REPLY ||
> +	    cmd == BR_DEAD_BINDER) {
> +		fprintf(stderr,
> +			"Failed response from Service Provider using Managers FD\n");
> +		exit(103);
> +	}
> +}
> +
> +/* Parse response, reply as required and then return last CMD */
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager)
> +{
> +	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",
> +			       manager ? "Manager" : "Service Provider",
> +			       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("%s BR_TRANSACTION data:\n",
> +				       manager ? "Manager" : "Service Provider");
> +				print_trans_data(txn);
> +			}
> +
> +			if (manager) {
> +				do_service_manager(fd, txn);
> +				request_manager_fd(fd, txn);
> +			}
> +
> +			ptr += sizeof(*txn);
> +			break;
> +		}
> +		case BR_REPLY: {
> +			struct binder_transaction_data *txn =
> +				(struct binder_transaction_data *)ptr;
> +
> +			if (verbose) {
> +				printf("%s BR_REPLY data:\n",
> +				       manager ? "Manager" : "Service Provider");
> +				print_trans_data(txn);
> +			}
> +
> +			/* Service Provider extracts the Manager fd, and responds */
> +			if (!manager)
> +				extract_fd_and_respond(txn, manager);
> +
> +			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",
> +				       manager ? "Manager" : "Service Provider",
> +				       cmd);
> +			exit(-1);
> +		}
> +	}
> +
> +	return cmd;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, result, binder_fd, provider_replies = 0;
> +	uint32_t cmd;
> +	bool manager;
> +	pid_t pid;
> +	char *driver = "/dev/binder";
> +	char *context;
> +	void *map_base;
> +	size_t map_size = 2048;
> +	struct binder_write_read bwr;
> +	struct flat_binder_object obj;
> +	struct binder_transaction_data *txn;
> +	unsigned int readbuf[32];
> +	unsigned int writebuf[1024];
> +
> +
> +	verbose = false;
> +
> +	while ((opt = getopt(argc, argv, "vr:")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		case 'r':
> +			provider_replies = atoi(optarg);
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if ((argc - optind) != 1)
> +		usage(argv[0]);
> +
> +	if (!strcmp(argv[optind], "manager"))
> +		manager = true;
> +	else if (!strcmp(argv[optind], "provider"))
> +		manager = false;
> +	else
> +		usage(argv[0]);
> +
> +	binder_fd = open(driver, O_RDWR | O_CLOEXEC);
> +	if (binder_fd < 0) {
> +		fprintf(stderr, "Cannot open %s error: %s\n", driver,
> +			strerror(errno));
> +		exit(1);
> +	}
> +
> +	map_base = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, binder_fd, 0);
> +	if (map_base == MAP_FAILED) {
> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
> +		close(binder_fd);
> +		exit(2);
> +	}
> +
> +	/* Create the appropriate shared memory for passing the Service
> +	 * Providers handle from the Manager to the Service Provider for
> +	 * use in the impersonate tests. This saves adding a Client to
> +	 * do this job.
> +	 */
> +	create_shm(manager);
> +
> +	/* 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();
> +
> +	if (manager) { /* Service Manager */
> +		if (verbose) {
> +			printf("Manager PID: %d Process context:\n\t%s\n",
> +			       pid, context);
> +		}
> +
> +		result = ioctl(binder_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(binder_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(binder_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(binder_fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, manager);
> +		}
> +	} else { /* Service Provider */
> +		if (verbose) {
> +			printf("Service Provider 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 = TEST_SERVICE_MANAGER_HANDLE;
> +		txn->cookie = 0;
> +		txn->code = ADD_TEST_SERVICE;
> +		txn->flags = TF_ACCEPT_FDS;
> +
> +		memset(&obj, 0, sizeof(struct flat_binder_object));
> +		obj.hdr.type = BINDER_TYPE_BINDER;
> +		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +		obj.binder = (uintptr_t)NULL;
> +		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 = (uintptr_t)writebuf;
> +		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> +
> +		if (verbose)
> +			printf("Service Provider sending transaction to Manager - ADD_TEST_SERVICE\n");
> +
> +		/* Each test will expect a different number of replies */
> +		while (provider_replies) {
> +			bwr.read_size = sizeof(readbuf);
> +			bwr.read_consumed = 0;
> +			bwr.read_buffer = (uintptr_t)readbuf;
> +
> +			result = ioctl(binder_fd, BINDER_WRITE_READ, &bwr);
> +			if (result < 0) {
> +				fprintf(stderr,
> +					"Service Provider ioctl BINDER_WRITE_READ: %s\n",
> +					strerror(errno));
> +				result = 7;
> +				goto brexit;
> +			}
> +
> +			if (verbose)
> +				printf("Service Provider read_consumed: %lld\n",
> +				       bwr.read_consumed);
> +
> +			cmd = binder_parse(binder_fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, manager);
> +
> +			if (cmd == BR_FAILED_REPLY ||
> +			    cmd == BR_DEAD_REPLY ||
> +			    cmd == BR_DEAD_BINDER) {
> +				result = 8;
> +				goto brexit;
> +			}
> +			provider_replies--;
> +		}
> +	}
> +
> +brexit:
> +	free(context);
> +	munmap(shm_base, shm_size);
> +	close(shm_fd);
> +	munmap(map_base, map_size);
> +	close(binder_fd);
> +
> +	return result;
> +}
>
Stephen Smalley May 21, 2018, 2:41 p.m. UTC | #2
On 05/20/2018 02:25 PM, Richard Haines wrote:
> Add binder tests. See tests/binder/test_binder.c for details on
> message flows to test security_binder*() functions.

Also, it breaks the policy build on RHEL/CentOS 7, due to map permission not being defined.
You need to use the allow_map() macro as we already do in many of the test policy files,
allow_map(domain, type, class).

> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>  README.md                   |   8 +
>  defconfig                   |   7 +
>  policy/Makefile             |   4 +
>  policy/test_binder.te       |  96 +++++
>  tests/Makefile              |   4 +
>  tests/binder/Makefile       |   7 +
>  tests/binder/check_binder.c |  80 +++++
>  tests/binder/test           |  89 +++++
>  tests/binder/test_binder.c  | 685 ++++++++++++++++++++++++++++++++++++
>  9 files changed, 980 insertions(+)
>  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..c48d3cc 100644
> --- a/defconfig
> +++ b/defconfig
> @@ -51,3 +51,10 @@ 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_DEVICES="binder"
> +CONFIG_ANDROID_BINDER_IPC=y
> diff --git a/policy/Makefile b/policy/Makefile
> index 5e07ee2..15e3a0c 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -63,6 +63,10 @@ ifeq ($(shell grep -q nnp_transition $(POLDEV)/include/support/all_perms.spt &&
>  export M4PARAM += -Dnnp_nosuid_transition_permission_defined
>  endif
>  
> +ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
> +TARGETS += test_binder.te
> +endif
> +
>  ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
>  TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
>  endif
> diff --git a/policy/test_binder.te b/policy/test_binder.te
> new file mode 100644
> index 0000000..3fd4dd5
> --- /dev/null
> +++ b/policy/test_binder.te
> @@ -0,0 +1,96 @@
> +
> +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 test_binder_provider_t:binder 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_provider_t test_binder_mgr_t:fd use;
> +fs_getattr_tmpfs(test_binder_mgr_t)
> +allow test_binder_mgr_t tmpfs_t:file { read write open 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 test_binder_provider_t:binder { call };
> +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_provider_t test_binder_mgr_no_fd_t:binder { call transfer impersonate };
> +fs_getattr_tmpfs(test_binder_mgr_no_fd_t)
> +allow test_binder_mgr_no_fd_t tmpfs_t:file { read write open map };
> +
> +#
> +########################## Service Provider ################################
> +#
> +type test_binder_provider_t;
> +domain_type(test_binder_provider_t)
> +unconfined_runs_test(test_binder_provider_t)
> +typeattribute test_binder_provider_t testdomain;
> +typeattribute test_binder_provider_t binderdomain;
> +allow test_binder_provider_t self:binder { call };
> +allow test_binder_provider_t test_binder_mgr_t:binder { call transfer impersonate };
> +allow test_binder_provider_t device_t:chr_file { ioctl open read write map };
> +# For fstat:
> +allow test_binder_provider_t device_t:chr_file getattr;
> +fs_getattr_tmpfs(test_binder_provider_t)
> +allow test_binder_provider_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no call ################################
> +#
> +type test_binder_provider_no_call_t;
> +domain_type(test_binder_provider_no_call_t)
> +unconfined_runs_test(test_binder_provider_no_call_t)
> +typeattribute test_binder_provider_no_call_t testdomain;
> +typeattribute test_binder_provider_no_call_t binderdomain;
> +allow test_binder_provider_no_call_t device_t:chr_file { ioctl open read write map };
> +fs_getattr_tmpfs(test_binder_provider_no_call_t)
> +allow test_binder_provider_no_call_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no transfer #############################
> +#
> +type test_binder_provider_no_transfer_t;
> +domain_type(test_binder_provider_no_transfer_t)
> +unconfined_runs_test(test_binder_provider_no_transfer_t)
> +typeattribute test_binder_provider_no_transfer_t testdomain;
> +typeattribute test_binder_provider_no_transfer_t binderdomain;
> +allow test_binder_provider_no_transfer_t test_binder_mgr_t:binder { call };
> +allow test_binder_provider_no_transfer_t device_t:chr_file { ioctl open read write map };
> +fs_getattr_tmpfs(test_binder_provider_no_transfer_t)
> +allow test_binder_provider_no_transfer_t tmpfs_t:file { read write open map };
> +
> +#
> +#################### Service Provider no impersonate ##########################
> +#
> +type test_binder_provider_no_im_t;
> +domain_type(test_binder_provider_no_im_t)
> +unconfined_runs_test(test_binder_provider_no_im_t)
> +typeattribute test_binder_provider_no_im_t testdomain;
> +typeattribute test_binder_provider_no_im_t binderdomain;
> +allow test_binder_provider_no_im_t self:binder { call };
> +allow test_binder_provider_no_im_t test_binder_mgr_t:binder { call transfer };
> +allow test_binder_provider_no_im_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_provider_no_im_t test_binder_mgr_t:fd use;
> +allow test_binder_provider_no_im_t device_t:chr_file getattr;
> +fs_getattr_tmpfs(test_binder_provider_no_im_t)
> +allow test_binder_provider_no_im_t tmpfs_t:file { read write open map };
> +
> +#
> +############ 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..494b761 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -30,6 +30,10 @@ ifeq ($(shell grep -q getrlimit $(POLDEV)/include/support/all_perms.spt && echo
>  SUBDIRS += prlimit
>  endif
>  
> +ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
> +SUBDIRS += binder
> +endif
> +
>  ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
>  SUBDIRS += infiniband_endport
>  endif
> diff --git a/tests/binder/Makefile b/tests/binder/Makefile
> new file mode 100644
> index 0000000..0d76723
> --- /dev/null
> +++ b/tests/binder/Makefile
> @@ -0,0 +1,7 @@
> +TARGETS = check_binder test_binder
> +
> +LDLIBS += -lselinux -lrt
> +
> +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..7b81a3d
> --- /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 = 2;
> +		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..86fab52
> --- /dev/null
> +++ b/tests/binder/test
> @@ -0,0 +1,89 @@
> +#!/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)";
> +        }
> +    }
> +    else {
> +        $v = " ";
> +    }
> +
> +    # check if binder driver available and kernel/userspace versions.
> +    $result = system("$basedir/check_binder 2> /dev/null");
> +
> +    if ( $result >> 8 eq 0 ) {
> +        plan tests => 6;
> +    }
> +    elsif ( $result >> 8 eq 1 ) {
> +        plan skip_all => "Binder not supported by kernel";
> +    }
> +    elsif ( $result >> 8 eq 2 ) {
> +        plan skip_all => "Binder kernel/userspace versions differ";
> +    }
> +    else {
> +        plan skip_all => "Error checking Binder driver";
> +    }
> +}
> +
> +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.
> +
> +# 1 Verify that authorized provider can transact with the manager.
> +$result =
> +  system
> +  "runcon -t test_binder_provider_t $basedir/test_binder $v -r 1 provider";
> +ok( $result eq 0 );
> +
> +# 2 Verify that provider cannot call manager (no call perm).
> +$result = system
> +"runcon -t test_binder_provider_no_call_t $basedir/test_binder $v -r 2 provider 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# 3 Verify that provider cannot communicate with manager (no impersonate perm).
> +$result = system
> +"runcon -t test_binder_provider_no_im_t $basedir/test_binder $v -r 1 provider 2>&1";
> +ok( $result >> 8 eq 103 );
> +
> +# 4 Verify that provider cannot communicate with manager (no transfer perm).
> +$result = system
> +"runcon -t test_binder_provider_no_transfer_t $basedir/test_binder $v -r 1 provider 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# Kill the manager.
> +kill TERM, $pid;
> +
> +# 5 Verify that provider cannot become a manager (no set_context_mgr perm).
> +$result =
> +  system
> +  "runcon -t test_binder_provider_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.
> +
> +# 6 Verify that authorized provider 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_provider_t $basedir/test_binder $v provider -r 1 2>&1";
> +ok( $result >> 8 eq 8 );
> +
> +# 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..919e568
> --- /dev/null
> +++ b/tests/binder/test_binder.c
> @@ -0,0 +1,685 @@
> +/*
> + * This is a simple binder Service Manager/Service Provider that only uses
> + * the raw ioctl commands to test the SELinux binder permissions:
> + *     set_context_mgr, call, transfer, impersonate.
> + *
> + * 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>
> +
> +/* These are the Binder txn->code values used by the Service Provider and
> + * Manager to request/retrieve a binder handle.
> + */
> +#define ADD_TEST_SERVICE 100 /* Sent by Service Provider */
> +#define GET_TEST_SERVICE 101 /* Sent by Client */
> +
> +#define TEST_SERVICE_MANAGER_HANDLE 0
> +
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager);
> +
> +static bool verbose;
> +uint32_t sp_handle;
> +static unsigned char *shm_base;
> +static int shm_fd;
> +static int shm_size = 32;
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-r replies] [-v] manager | provider\n"
> +		"Where:\n\t"
> +		"-r       Number of replies to expect from test - default 0.\n\t"
> +		"-v       Print context and command information.\n\t"
> +		"manager  Act as Service Manager.\n\t"
> +		"service  Act as Service Provider.\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);
> +}
> +
> +/* Create a small piece of shared memory between the Manager and Service
> + * Provider to share a handle as explained in the do_service_manager()
> + * function.
> + */
> +static void create_shm(bool manager)
> +{
> +	char *name =  "sp_handle";
> +
> +	if (manager)
> +		shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
> +	else
> +		shm_fd = shm_open(name, O_RDONLY, 0666);
> +	if (shm_fd < 0) {
> +		fprintf(stderr, "%s shm_open: %s\n", __func__, strerror(errno));
> +		exit(-1);
> +	}
> +
> +	ftruncate(shm_fd, shm_size);
> +
> +	if (manager)
> +		shm_base = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED,
> +				shm_fd, 0);
> +	else
> +		shm_base = mmap(0, shm_size, PROT_READ, MAP_SHARED, shm_fd, 0);
> +	if (shm_base == MAP_FAILED) {
> +		fprintf(stderr, "%s mmap: %s\n", __func__, strerror(errno));
> +		close(shm_fd);
> +		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";
> +	}
> +}
> +
> +static void print_trans_data(struct binder_transaction_data *txn_in)
> +{
> +	struct flat_binder_object *obj;
> +	binder_size_t *offs = (binder_size_t *)
> +			      (uintptr_t)txn_in->data.ptr.offsets;
> +	size_t count = txn_in->offsets_size / sizeof(binder_size_t);
> +
> +	printf("\thandle: %ld\n", (unsigned long)txn_in->target.handle);
> +	printf("\tcookie: %lld\n", txn_in->cookie);
> +	printf("\tcode: %d\n", txn_in->code);
> +	switch (txn_in->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: %x\n", txn_in->flags);
> +		return;
> +	}
> +	printf("\tsender pid: %u\n", txn_in->sender_pid);
> +	printf("\tsender euid: %u\n", txn_in->sender_euid);
> +	printf("\tdata_size: %llu\n", txn_in->data_size);
> +	printf("\toffsets_size: %llu\n", txn_in->offsets_size);
> +
> +	while (count--) {
> +		obj = (struct flat_binder_object *)
> +		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs++);
> +
> +		switch (obj->hdr.type) {
> +		case BINDER_TYPE_BINDER:
> +			printf("\thdr: BINDER_TYPE_BINDER\n");
> +			printf("\tbinder: %llx\n", obj->binder);
> +			break;
> +		case BINDER_TYPE_HANDLE:
> +			printf("\thdr: BINDER_TYPE_HANDLE\n");
> +			printf("\thandle: %x\n", obj->handle);
> +			break;
> +		case BINDER_TYPE_FD:
> +			printf("\thdr: BINDER_TYPE_FD\n");
> +			printf("\tfd: %x\n", obj->handle);
> +			break;
> +		default:
> +			printf("Unknown header: %u\n", obj->hdr.type);
> +			return;
> +		}
> +		printf("\tflags: priority: 0x%x accept FDS: %s\n",
> +		       obj->flags & FLAT_BINDER_FLAG_PRIORITY_MASK,
> +		       obj->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS ? "YES" : "NO");
> +		printf("\tcookie: %llx\n", obj->cookie);
> +	}
> +}
> +
> +/* If add a service provider, then obtain a handle for it and store in
> + * shared memory. The handle will then be used by the service provider
> + * process to contact the Manager for its file descriptor, thus triggering
> + * the 'impersonate' permission (as current_sid() != task_sid(from))
> + * It is done this way as being a cheapskate it saved adding code to the
> + * GET_TEST_SERVICE process plus running a Client as well. This achieves
> + * the same objective.
> + */
> +static void do_service_manager(int fd, struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	struct flat_binder_object *obj;
> +	struct binder_write_read bwr;
> +	uint32_t acmd[2];
> +	binder_size_t *offs;
> +
> +	switch (txn_in->code) {
> +	case ADD_TEST_SERVICE:
> +		offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
> +
> +		/* Get fbo that contains the Managers binder file descriptor. */
> +		obj = (struct flat_binder_object *)
> +		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
> +
> +		if (obj->hdr.type == BINDER_TYPE_HANDLE) {
> +			sp_handle = obj->handle;
> +			memcpy(shm_base, &sp_handle, sizeof(sp_handle));
> +			if (verbose)
> +				printf("Manager has BINDER_TYPE_HANDLE obj->handle: %d\n",
> +				       sp_handle);
> +		} else {
> +			fprintf(stderr, "Failed to obtain a handle\n");
> +			exit(-1);
> +		}
> +
> +		acmd[0] = BC_ACQUIRE;
> +		acmd[1] = obj->handle;
> +
> +		memset(&bwr, 0, sizeof(bwr));
> +		bwr.write_buffer = (uintptr_t)&acmd;
> +		bwr.write_size = sizeof(acmd);
> +
> +		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +		if (result < 0) {
> +			fprintf(stderr,
> +				"ServiceProvider ioctl BINDER_WRITE_READ: %s\n",
> +				strerror(errno));
> +			exit(-1);
> +		}
> +
> +		if (verbose)
> +			printf("Manager acquired handle: %d for Service Provider\n",
> +			       sp_handle);
> +		break;
> +
> +	case GET_TEST_SERVICE:
> +		if (verbose)
> +			printf("GET_TEST_SERVICE not supported\n");
> +		break;
> +	default:
> +		fprintf(stderr, "Unknown txn->code: %d\n", txn_in->code);
> +		exit(-1);
> +	}
> +}
> +
> +static void request_manager_fd(int fd, struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	unsigned int writebuf[1024];
> +	struct binder_fd_object obj;
> +	struct binder_write_read bwr;
> +	struct binder_transaction_data *txn;
> +
> +	if (txn_in->flags == TF_ONE_WAY) {
> +		if (verbose)
> +			printf("Manager no reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
> +		return;
> +	}
> +
> +	if (verbose)
> +		printf("Manager sending BC_REPLY to obtain its FD\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 binder_fd_object));
> +	obj.hdr.type = BINDER_TYPE_FD;
> +	obj.pad_flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +	obj.cookie = txn->cookie;
> +	/* The binder fd is used for testing as it allows policy to set
> +	 * whether the Service and Manager can be allowed access (fd use)
> +	 * or not. For example test_binder_mgr_t has:
> +	 *        allow test_binder_service_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 Service Provider will use this (the Managers fd) to
> +	 * send a transaction.
> +	 */
> +	obj.fd = fd;
> +
> +	if (verbose)
> +		printf("Manager handle: %d and its FD: %d\n",
> +		       txn->target.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 = (uintptr_t)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));
> +		exit(-1);
> +	}
> +}
> +
> +/* 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,
> +				   bool manager)
> +{
> +	int result;
> +	uint32_t cmd;
> +	struct stat sb;
> +	struct binder_write_read bwr;
> +	struct binder_fd_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 bfdo that contains the Managers binder file descriptor. */
> +	obj = (struct binder_fd_object *)
> +	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
> +
> +	if (obj->hdr.type != BINDER_TYPE_FD) {
> +		fprintf(stderr, "Header not BINDER_TYPE_FD: %d\n",
> +			obj->hdr.type);
> +		exit(100);
> +	}
> +
> +	/* fstat this just to see if a valid fd */
> +	result = fstat(obj->fd, &sb);
> +	if (result < 0) {
> +		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
> +		exit(101);
> +	}
> +
> +	if (verbose)
> +		printf("Service Provider retrieved Managers fd: %d st_dev: %ld\n",
> +		       obj->fd, 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));
> +	/* Copy handle from the Manager */
> +	memcpy(&txn->target.handle, shm_base, sizeof(uint32_t));
> +
> +	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 = (uintptr_t)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->fd, BINDER_WRITE_READ, &bwr);
> +	if (result < 0) {
> +		fprintf(stderr,
> +			"Service Provider ioctl BINDER_WRITE_READ: %s\n",
> +			strerror(errno));
> +		exit(102);
> +	}
> +
> +	if (verbose)
> +		printf("Service Provider read_consumed: %lld\n",
> +		       bwr.read_consumed);
> +
> +	cmd = binder_parse(obj->fd, (uintptr_t)readbuf,
> +			   bwr.read_consumed, manager);
> +
> +	if (verbose)
> +		printf("Service Provider using Managers FD\n");
> +
> +	if (cmd == BR_FAILED_REPLY ||
> +	    cmd == BR_DEAD_REPLY ||
> +	    cmd == BR_DEAD_BINDER) {
> +		fprintf(stderr,
> +			"Failed response from Service Provider using Managers FD\n");
> +		exit(103);
> +	}
> +}
> +
> +/* Parse response, reply as required and then return last CMD */
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager)
> +{
> +	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",
> +			       manager ? "Manager" : "Service Provider",
> +			       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("%s BR_TRANSACTION data:\n",
> +				       manager ? "Manager" : "Service Provider");
> +				print_trans_data(txn);
> +			}
> +
> +			if (manager) {
> +				do_service_manager(fd, txn);
> +				request_manager_fd(fd, txn);
> +			}
> +
> +			ptr += sizeof(*txn);
> +			break;
> +		}
> +		case BR_REPLY: {
> +			struct binder_transaction_data *txn =
> +				(struct binder_transaction_data *)ptr;
> +
> +			if (verbose) {
> +				printf("%s BR_REPLY data:\n",
> +				       manager ? "Manager" : "Service Provider");
> +				print_trans_data(txn);
> +			}
> +
> +			/* Service Provider extracts the Manager fd, and responds */
> +			if (!manager)
> +				extract_fd_and_respond(txn, manager);
> +
> +			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",
> +				       manager ? "Manager" : "Service Provider",
> +				       cmd);
> +			exit(-1);
> +		}
> +	}
> +
> +	return cmd;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, result, binder_fd, provider_replies = 0;
> +	uint32_t cmd;
> +	bool manager;
> +	pid_t pid;
> +	char *driver = "/dev/binder";
> +	char *context;
> +	void *map_base;
> +	size_t map_size = 2048;
> +	struct binder_write_read bwr;
> +	struct flat_binder_object obj;
> +	struct binder_transaction_data *txn;
> +	unsigned int readbuf[32];
> +	unsigned int writebuf[1024];
> +
> +
> +	verbose = false;
> +
> +	while ((opt = getopt(argc, argv, "vr:")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		case 'r':
> +			provider_replies = atoi(optarg);
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if ((argc - optind) != 1)
> +		usage(argv[0]);
> +
> +	if (!strcmp(argv[optind], "manager"))
> +		manager = true;
> +	else if (!strcmp(argv[optind], "provider"))
> +		manager = false;
> +	else
> +		usage(argv[0]);
> +
> +	binder_fd = open(driver, O_RDWR | O_CLOEXEC);
> +	if (binder_fd < 0) {
> +		fprintf(stderr, "Cannot open %s error: %s\n", driver,
> +			strerror(errno));
> +		exit(1);
> +	}
> +
> +	map_base = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, binder_fd, 0);
> +	if (map_base == MAP_FAILED) {
> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
> +		close(binder_fd);
> +		exit(2);
> +	}
> +
> +	/* Create the appropriate shared memory for passing the Service
> +	 * Providers handle from the Manager to the Service Provider for
> +	 * use in the impersonate tests. This saves adding a Client to
> +	 * do this job.
> +	 */
> +	create_shm(manager);
> +
> +	/* 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();
> +
> +	if (manager) { /* Service Manager */
> +		if (verbose) {
> +			printf("Manager PID: %d Process context:\n\t%s\n",
> +			       pid, context);
> +		}
> +
> +		result = ioctl(binder_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(binder_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(binder_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(binder_fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, manager);
> +		}
> +	} else { /* Service Provider */
> +		if (verbose) {
> +			printf("Service Provider 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 = TEST_SERVICE_MANAGER_HANDLE;
> +		txn->cookie = 0;
> +		txn->code = ADD_TEST_SERVICE;
> +		txn->flags = TF_ACCEPT_FDS;
> +
> +		memset(&obj, 0, sizeof(struct flat_binder_object));
> +		obj.hdr.type = BINDER_TYPE_BINDER;
> +		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +		obj.binder = (uintptr_t)NULL;
> +		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 = (uintptr_t)writebuf;
> +		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> +
> +		if (verbose)
> +			printf("Service Provider sending transaction to Manager - ADD_TEST_SERVICE\n");
> +
> +		/* Each test will expect a different number of replies */
> +		while (provider_replies) {
> +			bwr.read_size = sizeof(readbuf);
> +			bwr.read_consumed = 0;
> +			bwr.read_buffer = (uintptr_t)readbuf;
> +
> +			result = ioctl(binder_fd, BINDER_WRITE_READ, &bwr);
> +			if (result < 0) {
> +				fprintf(stderr,
> +					"Service Provider ioctl BINDER_WRITE_READ: %s\n",
> +					strerror(errno));
> +				result = 7;
> +				goto brexit;
> +			}
> +
> +			if (verbose)
> +				printf("Service Provider read_consumed: %lld\n",
> +				       bwr.read_consumed);
> +
> +			cmd = binder_parse(binder_fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, manager);
> +
> +			if (cmd == BR_FAILED_REPLY ||
> +			    cmd == BR_DEAD_REPLY ||
> +			    cmd == BR_DEAD_BINDER) {
> +				result = 8;
> +				goto brexit;
> +			}
> +			provider_replies--;
> +		}
> +	}
> +
> +brexit:
> +	free(context);
> +	munmap(shm_base, shm_size);
> +	close(shm_fd);
> +	munmap(map_base, map_size);
> +	close(binder_fd);
> +
> +	return result;
> +}
>
diff mbox

Patch

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..c48d3cc 100644
--- a/defconfig
+++ b/defconfig
@@ -51,3 +51,10 @@  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_DEVICES="binder"
+CONFIG_ANDROID_BINDER_IPC=y
diff --git a/policy/Makefile b/policy/Makefile
index 5e07ee2..15e3a0c 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -63,6 +63,10 @@  ifeq ($(shell grep -q nnp_transition $(POLDEV)/include/support/all_perms.spt &&
 export M4PARAM += -Dnnp_nosuid_transition_permission_defined
 endif
 
+ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
+TARGETS += test_binder.te
+endif
+
 ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
 TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te, $(TARGETS))
 endif
diff --git a/policy/test_binder.te b/policy/test_binder.te
new file mode 100644
index 0000000..3fd4dd5
--- /dev/null
+++ b/policy/test_binder.te
@@ -0,0 +1,96 @@ 
+
+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 test_binder_provider_t:binder 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_provider_t test_binder_mgr_t:fd use;
+fs_getattr_tmpfs(test_binder_mgr_t)
+allow test_binder_mgr_t tmpfs_t:file { read write open 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 test_binder_provider_t:binder { call };
+allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
+allow test_binder_provider_t test_binder_mgr_no_fd_t:binder { call transfer impersonate };
+fs_getattr_tmpfs(test_binder_mgr_no_fd_t)
+allow test_binder_mgr_no_fd_t tmpfs_t:file { read write open map };
+
+#
+########################## Service Provider ################################
+#
+type test_binder_provider_t;
+domain_type(test_binder_provider_t)
+unconfined_runs_test(test_binder_provider_t)
+typeattribute test_binder_provider_t testdomain;
+typeattribute test_binder_provider_t binderdomain;
+allow test_binder_provider_t self:binder { call };
+allow test_binder_provider_t test_binder_mgr_t:binder { call transfer impersonate };
+allow test_binder_provider_t device_t:chr_file { ioctl open read write map };
+# For fstat:
+allow test_binder_provider_t device_t:chr_file getattr;
+fs_getattr_tmpfs(test_binder_provider_t)
+allow test_binder_provider_t tmpfs_t:file { read write open map };
+
+#
+#################### Service Provider no call ################################
+#
+type test_binder_provider_no_call_t;
+domain_type(test_binder_provider_no_call_t)
+unconfined_runs_test(test_binder_provider_no_call_t)
+typeattribute test_binder_provider_no_call_t testdomain;
+typeattribute test_binder_provider_no_call_t binderdomain;
+allow test_binder_provider_no_call_t device_t:chr_file { ioctl open read write map };
+fs_getattr_tmpfs(test_binder_provider_no_call_t)
+allow test_binder_provider_no_call_t tmpfs_t:file { read write open map };
+
+#
+#################### Service Provider no transfer #############################
+#
+type test_binder_provider_no_transfer_t;
+domain_type(test_binder_provider_no_transfer_t)
+unconfined_runs_test(test_binder_provider_no_transfer_t)
+typeattribute test_binder_provider_no_transfer_t testdomain;
+typeattribute test_binder_provider_no_transfer_t binderdomain;
+allow test_binder_provider_no_transfer_t test_binder_mgr_t:binder { call };
+allow test_binder_provider_no_transfer_t device_t:chr_file { ioctl open read write map };
+fs_getattr_tmpfs(test_binder_provider_no_transfer_t)
+allow test_binder_provider_no_transfer_t tmpfs_t:file { read write open map };
+
+#
+#################### Service Provider no impersonate ##########################
+#
+type test_binder_provider_no_im_t;
+domain_type(test_binder_provider_no_im_t)
+unconfined_runs_test(test_binder_provider_no_im_t)
+typeattribute test_binder_provider_no_im_t testdomain;
+typeattribute test_binder_provider_no_im_t binderdomain;
+allow test_binder_provider_no_im_t self:binder { call };
+allow test_binder_provider_no_im_t test_binder_mgr_t:binder { call transfer };
+allow test_binder_provider_no_im_t device_t:chr_file { ioctl open read write map };
+allow test_binder_provider_no_im_t test_binder_mgr_t:fd use;
+allow test_binder_provider_no_im_t device_t:chr_file getattr;
+fs_getattr_tmpfs(test_binder_provider_no_im_t)
+allow test_binder_provider_no_im_t tmpfs_t:file { read write open map };
+
+#
+############ 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..494b761 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -30,6 +30,10 @@  ifeq ($(shell grep -q getrlimit $(POLDEV)/include/support/all_perms.spt && echo
 SUBDIRS += prlimit
 endif
 
+ifeq ($(shell grep -q binder $(POLDEV)/include/support/all_perms.spt && echo true),true)
+SUBDIRS += binder
+endif
+
 ifeq ($(shell grep "^SELINUX_INFINIBAND_ENDPORT_TEST=" infiniband_endport/ibendport_test.conf | cut -d'=' -f 2),1)
 SUBDIRS += infiniband_endport
 endif
diff --git a/tests/binder/Makefile b/tests/binder/Makefile
new file mode 100644
index 0000000..0d76723
--- /dev/null
+++ b/tests/binder/Makefile
@@ -0,0 +1,7 @@ 
+TARGETS = check_binder test_binder
+
+LDLIBS += -lselinux -lrt
+
+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..7b81a3d
--- /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 = 2;
+		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..86fab52
--- /dev/null
+++ b/tests/binder/test
@@ -0,0 +1,89 @@ 
+#!/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)";
+        }
+    }
+    else {
+        $v = " ";
+    }
+
+    # check if binder driver available and kernel/userspace versions.
+    $result = system("$basedir/check_binder 2> /dev/null");
+
+    if ( $result >> 8 eq 0 ) {
+        plan tests => 6;
+    }
+    elsif ( $result >> 8 eq 1 ) {
+        plan skip_all => "Binder not supported by kernel";
+    }
+    elsif ( $result >> 8 eq 2 ) {
+        plan skip_all => "Binder kernel/userspace versions differ";
+    }
+    else {
+        plan skip_all => "Error checking Binder driver";
+    }
+}
+
+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.
+
+# 1 Verify that authorized provider can transact with the manager.
+$result =
+  system
+  "runcon -t test_binder_provider_t $basedir/test_binder $v -r 1 provider";
+ok( $result eq 0 );
+
+# 2 Verify that provider cannot call manager (no call perm).
+$result = system
+"runcon -t test_binder_provider_no_call_t $basedir/test_binder $v -r 2 provider 2>&1";
+ok( $result >> 8 eq 8 );
+
+# 3 Verify that provider cannot communicate with manager (no impersonate perm).
+$result = system
+"runcon -t test_binder_provider_no_im_t $basedir/test_binder $v -r 1 provider 2>&1";
+ok( $result >> 8 eq 103 );
+
+# 4 Verify that provider cannot communicate with manager (no transfer perm).
+$result = system
+"runcon -t test_binder_provider_no_transfer_t $basedir/test_binder $v -r 1 provider 2>&1";
+ok( $result >> 8 eq 8 );
+
+# Kill the manager.
+kill TERM, $pid;
+
+# 5 Verify that provider cannot become a manager (no set_context_mgr perm).
+$result =
+  system
+  "runcon -t test_binder_provider_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.
+
+# 6 Verify that authorized provider 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_provider_t $basedir/test_binder $v provider -r 1 2>&1";
+ok( $result >> 8 eq 8 );
+
+# 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..919e568
--- /dev/null
+++ b/tests/binder/test_binder.c
@@ -0,0 +1,685 @@ 
+/*
+ * This is a simple binder Service Manager/Service Provider that only uses
+ * the raw ioctl commands to test the SELinux binder permissions:
+ *     set_context_mgr, call, transfer, impersonate.
+ *
+ * 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>
+
+/* These are the Binder txn->code values used by the Service Provider and
+ * Manager to request/retrieve a binder handle.
+ */
+#define ADD_TEST_SERVICE 100 /* Sent by Service Provider */
+#define GET_TEST_SERVICE 101 /* Sent by Client */
+
+#define TEST_SERVICE_MANAGER_HANDLE 0
+
+static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager);
+
+static bool verbose;
+uint32_t sp_handle;
+static unsigned char *shm_base;
+static int shm_fd;
+static int shm_size = 32;
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-r replies] [-v] manager | provider\n"
+		"Where:\n\t"
+		"-r       Number of replies to expect from test - default 0.\n\t"
+		"-v       Print context and command information.\n\t"
+		"manager  Act as Service Manager.\n\t"
+		"service  Act as Service Provider.\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);
+}
+
+/* Create a small piece of shared memory between the Manager and Service
+ * Provider to share a handle as explained in the do_service_manager()
+ * function.
+ */
+static void create_shm(bool manager)
+{
+	char *name =  "sp_handle";
+
+	if (manager)
+		shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666);
+	else
+		shm_fd = shm_open(name, O_RDONLY, 0666);
+	if (shm_fd < 0) {
+		fprintf(stderr, "%s shm_open: %s\n", __func__, strerror(errno));
+		exit(-1);
+	}
+
+	ftruncate(shm_fd, shm_size);
+
+	if (manager)
+		shm_base = mmap(0, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED,
+				shm_fd, 0);
+	else
+		shm_base = mmap(0, shm_size, PROT_READ, MAP_SHARED, shm_fd, 0);
+	if (shm_base == MAP_FAILED) {
+		fprintf(stderr, "%s mmap: %s\n", __func__, strerror(errno));
+		close(shm_fd);
+		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";
+	}
+}
+
+static void print_trans_data(struct binder_transaction_data *txn_in)
+{
+	struct flat_binder_object *obj;
+	binder_size_t *offs = (binder_size_t *)
+			      (uintptr_t)txn_in->data.ptr.offsets;
+	size_t count = txn_in->offsets_size / sizeof(binder_size_t);
+
+	printf("\thandle: %ld\n", (unsigned long)txn_in->target.handle);
+	printf("\tcookie: %lld\n", txn_in->cookie);
+	printf("\tcode: %d\n", txn_in->code);
+	switch (txn_in->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: %x\n", txn_in->flags);
+		return;
+	}
+	printf("\tsender pid: %u\n", txn_in->sender_pid);
+	printf("\tsender euid: %u\n", txn_in->sender_euid);
+	printf("\tdata_size: %llu\n", txn_in->data_size);
+	printf("\toffsets_size: %llu\n", txn_in->offsets_size);
+
+	while (count--) {
+		obj = (struct flat_binder_object *)
+		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs++);
+
+		switch (obj->hdr.type) {
+		case BINDER_TYPE_BINDER:
+			printf("\thdr: BINDER_TYPE_BINDER\n");
+			printf("\tbinder: %llx\n", obj->binder);
+			break;
+		case BINDER_TYPE_HANDLE:
+			printf("\thdr: BINDER_TYPE_HANDLE\n");
+			printf("\thandle: %x\n", obj->handle);
+			break;
+		case BINDER_TYPE_FD:
+			printf("\thdr: BINDER_TYPE_FD\n");
+			printf("\tfd: %x\n", obj->handle);
+			break;
+		default:
+			printf("Unknown header: %u\n", obj->hdr.type);
+			return;
+		}
+		printf("\tflags: priority: 0x%x accept FDS: %s\n",
+		       obj->flags & FLAT_BINDER_FLAG_PRIORITY_MASK,
+		       obj->flags & FLAT_BINDER_FLAG_ACCEPTS_FDS ? "YES" : "NO");
+		printf("\tcookie: %llx\n", obj->cookie);
+	}
+}
+
+/* If add a service provider, then obtain a handle for it and store in
+ * shared memory. The handle will then be used by the service provider
+ * process to contact the Manager for its file descriptor, thus triggering
+ * the 'impersonate' permission (as current_sid() != task_sid(from))
+ * It is done this way as being a cheapskate it saved adding code to the
+ * GET_TEST_SERVICE process plus running a Client as well. This achieves
+ * the same objective.
+ */
+static void do_service_manager(int fd, struct binder_transaction_data *txn_in)
+{
+	int result;
+	struct flat_binder_object *obj;
+	struct binder_write_read bwr;
+	uint32_t acmd[2];
+	binder_size_t *offs;
+
+	switch (txn_in->code) {
+	case ADD_TEST_SERVICE:
+		offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
+
+		/* Get fbo that contains the Managers binder file descriptor. */
+		obj = (struct flat_binder_object *)
+		      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
+
+		if (obj->hdr.type == BINDER_TYPE_HANDLE) {
+			sp_handle = obj->handle;
+			memcpy(shm_base, &sp_handle, sizeof(sp_handle));
+			if (verbose)
+				printf("Manager has BINDER_TYPE_HANDLE obj->handle: %d\n",
+				       sp_handle);
+		} else {
+			fprintf(stderr, "Failed to obtain a handle\n");
+			exit(-1);
+		}
+
+		acmd[0] = BC_ACQUIRE;
+		acmd[1] = obj->handle;
+
+		memset(&bwr, 0, sizeof(bwr));
+		bwr.write_buffer = (uintptr_t)&acmd;
+		bwr.write_size = sizeof(acmd);
+
+		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
+		if (result < 0) {
+			fprintf(stderr,
+				"ServiceProvider ioctl BINDER_WRITE_READ: %s\n",
+				strerror(errno));
+			exit(-1);
+		}
+
+		if (verbose)
+			printf("Manager acquired handle: %d for Service Provider\n",
+			       sp_handle);
+		break;
+
+	case GET_TEST_SERVICE:
+		if (verbose)
+			printf("GET_TEST_SERVICE not supported\n");
+		break;
+	default:
+		fprintf(stderr, "Unknown txn->code: %d\n", txn_in->code);
+		exit(-1);
+	}
+}
+
+static void request_manager_fd(int fd, struct binder_transaction_data *txn_in)
+{
+	int result;
+	unsigned int writebuf[1024];
+	struct binder_fd_object obj;
+	struct binder_write_read bwr;
+	struct binder_transaction_data *txn;
+
+	if (txn_in->flags == TF_ONE_WAY) {
+		if (verbose)
+			printf("Manager no reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
+		return;
+	}
+
+	if (verbose)
+		printf("Manager sending BC_REPLY to obtain its FD\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 binder_fd_object));
+	obj.hdr.type = BINDER_TYPE_FD;
+	obj.pad_flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+	obj.cookie = txn->cookie;
+	/* The binder fd is used for testing as it allows policy to set
+	 * whether the Service and Manager can be allowed access (fd use)
+	 * or not. For example test_binder_mgr_t has:
+	 *        allow test_binder_service_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 Service Provider will use this (the Managers fd) to
+	 * send a transaction.
+	 */
+	obj.fd = fd;
+
+	if (verbose)
+		printf("Manager handle: %d and its FD: %d\n",
+		       txn->target.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 = (uintptr_t)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));
+		exit(-1);
+	}
+}
+
+/* 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,
+				   bool manager)
+{
+	int result;
+	uint32_t cmd;
+	struct stat sb;
+	struct binder_write_read bwr;
+	struct binder_fd_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 bfdo that contains the Managers binder file descriptor. */
+	obj = (struct binder_fd_object *)
+	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
+
+	if (obj->hdr.type != BINDER_TYPE_FD) {
+		fprintf(stderr, "Header not BINDER_TYPE_FD: %d\n",
+			obj->hdr.type);
+		exit(100);
+	}
+
+	/* fstat this just to see if a valid fd */
+	result = fstat(obj->fd, &sb);
+	if (result < 0) {
+		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
+		exit(101);
+	}
+
+	if (verbose)
+		printf("Service Provider retrieved Managers fd: %d st_dev: %ld\n",
+		       obj->fd, 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));
+	/* Copy handle from the Manager */
+	memcpy(&txn->target.handle, shm_base, sizeof(uint32_t));
+
+	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 = (uintptr_t)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->fd, BINDER_WRITE_READ, &bwr);
+	if (result < 0) {
+		fprintf(stderr,
+			"Service Provider ioctl BINDER_WRITE_READ: %s\n",
+			strerror(errno));
+		exit(102);
+	}
+
+	if (verbose)
+		printf("Service Provider read_consumed: %lld\n",
+		       bwr.read_consumed);
+
+	cmd = binder_parse(obj->fd, (uintptr_t)readbuf,
+			   bwr.read_consumed, manager);
+
+	if (verbose)
+		printf("Service Provider using Managers FD\n");
+
+	if (cmd == BR_FAILED_REPLY ||
+	    cmd == BR_DEAD_REPLY ||
+	    cmd == BR_DEAD_BINDER) {
+		fprintf(stderr,
+			"Failed response from Service Provider using Managers FD\n");
+		exit(103);
+	}
+}
+
+/* Parse response, reply as required and then return last CMD */
+static int binder_parse(int fd, uintptr_t ptr, size_t size, bool manager)
+{
+	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",
+			       manager ? "Manager" : "Service Provider",
+			       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("%s BR_TRANSACTION data:\n",
+				       manager ? "Manager" : "Service Provider");
+				print_trans_data(txn);
+			}
+
+			if (manager) {
+				do_service_manager(fd, txn);
+				request_manager_fd(fd, txn);
+			}
+
+			ptr += sizeof(*txn);
+			break;
+		}
+		case BR_REPLY: {
+			struct binder_transaction_data *txn =
+				(struct binder_transaction_data *)ptr;
+
+			if (verbose) {
+				printf("%s BR_REPLY data:\n",
+				       manager ? "Manager" : "Service Provider");
+				print_trans_data(txn);
+			}
+
+			/* Service Provider extracts the Manager fd, and responds */
+			if (!manager)
+				extract_fd_and_respond(txn, manager);
+
+			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",
+				       manager ? "Manager" : "Service Provider",
+				       cmd);
+			exit(-1);
+		}
+	}
+
+	return cmd;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, result, binder_fd, provider_replies = 0;
+	uint32_t cmd;
+	bool manager;
+	pid_t pid;
+	char *driver = "/dev/binder";
+	char *context;
+	void *map_base;
+	size_t map_size = 2048;
+	struct binder_write_read bwr;
+	struct flat_binder_object obj;
+	struct binder_transaction_data *txn;
+	unsigned int readbuf[32];
+	unsigned int writebuf[1024];
+
+
+	verbose = false;
+
+	while ((opt = getopt(argc, argv, "vr:")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		case 'r':
+			provider_replies = atoi(optarg);
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if ((argc - optind) != 1)
+		usage(argv[0]);
+
+	if (!strcmp(argv[optind], "manager"))
+		manager = true;
+	else if (!strcmp(argv[optind], "provider"))
+		manager = false;
+	else
+		usage(argv[0]);
+
+	binder_fd = open(driver, O_RDWR | O_CLOEXEC);
+	if (binder_fd < 0) {
+		fprintf(stderr, "Cannot open %s error: %s\n", driver,
+			strerror(errno));
+		exit(1);
+	}
+
+	map_base = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, binder_fd, 0);
+	if (map_base == MAP_FAILED) {
+		fprintf(stderr, "mmap error: %s\n", strerror(errno));
+		close(binder_fd);
+		exit(2);
+	}
+
+	/* Create the appropriate shared memory for passing the Service
+	 * Providers handle from the Manager to the Service Provider for
+	 * use in the impersonate tests. This saves adding a Client to
+	 * do this job.
+	 */
+	create_shm(manager);
+
+	/* 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();
+
+	if (manager) { /* Service Manager */
+		if (verbose) {
+			printf("Manager PID: %d Process context:\n\t%s\n",
+			       pid, context);
+		}
+
+		result = ioctl(binder_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(binder_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(binder_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(binder_fd, (uintptr_t)readbuf,
+					   bwr.read_consumed, manager);
+		}
+	} else { /* Service Provider */
+		if (verbose) {
+			printf("Service Provider 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 = TEST_SERVICE_MANAGER_HANDLE;
+		txn->cookie = 0;
+		txn->code = ADD_TEST_SERVICE;
+		txn->flags = TF_ACCEPT_FDS;
+
+		memset(&obj, 0, sizeof(struct flat_binder_object));
+		obj.hdr.type = BINDER_TYPE_BINDER;
+		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+		obj.binder = (uintptr_t)NULL;
+		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 = (uintptr_t)writebuf;
+		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
+
+		if (verbose)
+			printf("Service Provider sending transaction to Manager - ADD_TEST_SERVICE\n");
+
+		/* Each test will expect a different number of replies */
+		while (provider_replies) {
+			bwr.read_size = sizeof(readbuf);
+			bwr.read_consumed = 0;
+			bwr.read_buffer = (uintptr_t)readbuf;
+
+			result = ioctl(binder_fd, BINDER_WRITE_READ, &bwr);
+			if (result < 0) {
+				fprintf(stderr,
+					"Service Provider ioctl BINDER_WRITE_READ: %s\n",
+					strerror(errno));
+				result = 7;
+				goto brexit;
+			}
+
+			if (verbose)
+				printf("Service Provider read_consumed: %lld\n",
+				       bwr.read_consumed);
+
+			cmd = binder_parse(binder_fd, (uintptr_t)readbuf,
+					   bwr.read_consumed, manager);
+
+			if (cmd == BR_FAILED_REPLY ||
+			    cmd == BR_DEAD_REPLY ||
+			    cmd == BR_DEAD_BINDER) {
+				result = 8;
+				goto brexit;
+			}
+			provider_replies--;
+		}
+	}
+
+brexit:
+	free(context);
+	munmap(shm_base, shm_size);
+	close(shm_fd);
+	munmap(map_base, map_size);
+	close(binder_fd);
+
+	return result;
+}