diff mbox

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

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

Commit Message

Jann Horn via Selinux May 15, 2018, 8:25 a.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                   |   8 +
 policy/Makefile             |   2 +-
 policy/test_binder.te       |  83 +++++++
 tests/Makefile              |   2 +-
 tests/binder/Makefile       |   7 +
 tests/binder/check_binder.c |  80 +++++++
 tests/binder/test           | 131 +++++++++++
 tests/binder/test_binder.c  | 543 ++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 862 insertions(+), 2 deletions(-)
 create mode 100644 policy/test_binder.te
 create mode 100644 tests/binder/Makefile
 create mode 100644 tests/binder/check_binder.c
 create mode 100644 tests/binder/test
 create mode 100644 tests/binder/test_binder.c

Comments

Stephen Smalley May 15, 2018, 1:36 p.m. UTC | #1
On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote:
> Add binder tests. See tests/binder/test_binder.c for details on
> message flows to test security_binder*() functions.
> 
> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> ---
>  README.md                   |   8 +
>  defconfig                   |   8 +
>  policy/Makefile             |   2 +-
>  policy/test_binder.te       |  83 +++++++
>  tests/Makefile              |   2 +-
>  tests/binder/Makefile       |   7 +
>  tests/binder/check_binder.c |  80 +++++++
>  tests/binder/test           | 131 +++++++++++
>  tests/binder/test_binder.c  | 543 ++++++++++++++++++++++++++++++++++++++++++++
>  9 files changed, 862 insertions(+), 2 deletions(-)
>  create mode 100644 policy/test_binder.te
>  create mode 100644 tests/binder/Makefile
>  create mode 100644 tests/binder/check_binder.c
>  create mode 100644 tests/binder/test
>  create mode 100644 tests/binder/test_binder.c
> 
> diff --git a/README.md b/README.md
> index c9f3b2b..60a249e 100644
> --- a/README.md
> +++ b/README.md
> @@ -141,6 +141,14 @@ directory or you can follow these broken-out steps:
>  The broken-out steps allow you to run the tests multiple times without
>  loading policy each time.
>  
> +Note that if leaving the test policy in-place for further testing, the
> +policy build process changes a boolean:
> +   On policy load:   setsebool allow_domain_fd_use=0
> +   On policy unload: setsebool allow_domain_fd_use=1
> +The consequence of this is that after a system reboot, the boolean
> +defaults to true. Therefore if running the fdreceive or binder tests,
> +reset the boolean to false, otherwise some tests will fail.

This isn't accurate - we aren't doing setsebool -P so the boolean change is not persistent across
reboots.  It will persist across policy reloads however because the kernel preserves booleans across
policy reloads.

> +
>  4) Review the test results.
>  
>  As each test script is run, the name of the script will be displayed followed
> diff --git a/defconfig b/defconfig
> index 7dce8bc..dc6ef30 100644
> --- a/defconfig
> +++ b/defconfig
> @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m
>  # This is enabled to test overlayfs SELinux integration.
>  # It is not required for SELinux operation itself.
>  CONFIG_OVERLAY_FS=m
> +
> +# Android binder implementations.
> +# These are enabled to test the binder controls in
> +# tests/binder; they are not required for SELinux operation itself.
> +CONFIG_ANDROID=y
> +CONFIG_ANDROID_BINDER_IPC=y
> +CONFIG_ANDROID_BINDER_DEVICES="binder"
> +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set

I don't think we need the last line.

> diff --git a/policy/Makefile b/policy/Makefile
> index 8ed5e46..5a9d411 100644
> --- a/policy/Makefile
> +++ b/policy/Makefile
> @@ -25,7 +25,7 @@ TARGETS = \
>  	test_task_getsid.te test_task_setpgid.te test_task_setsched.te \
>  	test_transition.te test_inet_socket.te test_unix_socket.te \
>  	test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \
> -	test_ibpkey.te test_atsecure.te
> +	test_ibpkey.te test_atsecure.te test_binder.te

Likely need to make this conditional on the binder class being defined in the policy;
see similar logic for e.g. cap_userns, icmp_socket, etc.  Otherwise policy won't build
on earlier Fedora/RHEL before definition of the binder class.

>  
>  ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true)
>  TARGETS += test_bounds.te
> diff --git a/policy/test_binder.te b/policy/test_binder.te
> new file mode 100644
> index 0000000..c4ad2ae
> --- /dev/null
> +++ b/policy/test_binder.te
> @@ -0,0 +1,83 @@
> +
> +attribute binderdomain;
> +
> +#
> +################################## Manager ###################################
> +#
> +type test_binder_mgr_t;
> +domain_type(test_binder_mgr_t)
> +unconfined_runs_test(test_binder_mgr_t)
> +typeattribute test_binder_mgr_t testdomain;
> +typeattribute test_binder_mgr_t binderdomain;
> +allow test_binder_mgr_t self:binder { set_context_mgr call };
> +allow test_binder_mgr_t device_t:chr_file { ioctl open read write map };

Wondering if we should define a .fc file with /dev/binder and a proper binder_device_t type
and restorecon it before the test.  But not clear it is worth it.  Never mind.

> +allow test_binder_mgr_t self:capability { sys_nice };

Needed or just to suppress noise in the audit?

> +allow test_binder_client_t test_binder_mgr_t:fd use;
> +
> +#
> +################################# Client ####################################
> +#
> +type test_binder_client_t;
> +domain_type(test_binder_client_t)
> +unconfined_runs_test(test_binder_client_t)
> +typeattribute test_binder_client_t testdomain;
> +typeattribute test_binder_client_t binderdomain;
> +allow test_binder_client_t self:binder { call };
> +allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate };

Are you actually exercising impersonate?  I largely didn't expect it to ever be used in Android itself,
just included the check because I saw that it was technically possible as far as the kernel interface
is concerned.

> +allow test_binder_client_t device_t:chr_file { ioctl open read write map };
> +# For fstat:
> +allow test_binder_client_t device_t:chr_file getattr;
> +
> +#
> +############################## Client no call ################################
> +#
> +type test_binder_client_no_call_t;
> +domain_type(test_binder_client_no_call_t)
> +unconfined_runs_test(test_binder_client_no_call_t)
> +typeattribute test_binder_client_no_call_t testdomain;
> +typeattribute test_binder_client_no_call_t binderdomain;
> +allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map };
> +
> +#
> +############################ Client no transfer #############################
> +#
> +type test_binder_client_no_transfer_t;
> +domain_type(test_binder_client_no_transfer_t)
> +unconfined_runs_test(test_binder_client_no_transfer_t)
> +typeattribute test_binder_client_no_transfer_t testdomain;
> +typeattribute test_binder_client_no_transfer_t binderdomain;
> +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call };
> +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map };
> +
> +#
> +########################## Manager no fd {use} ###############################
> +#
> +type test_binder_mgr_no_fd_t;
> +domain_type(test_binder_mgr_no_fd_t)
> +unconfined_runs_test(test_binder_mgr_no_fd_t)
> +typeattribute test_binder_mgr_no_fd_t testdomain;
> +typeattribute test_binder_mgr_no_fd_t binderdomain;
> +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call };
> +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_mgr_no_fd_t self:capability { sys_nice };
> +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer };
> +
> +#
> +########################### Client no impersonate ############################
> +#
> +type test_binder_client_no_im_t;
> +domain_type(test_binder_client_no_im_t)
> +unconfined_runs_test(test_binder_client_no_im_t)
> +typeattribute test_binder_client_no_im_t testdomain;
> +typeattribute test_binder_client_no_im_t binderdomain;
> +allow test_binder_client_no_im_t self:binder { call };
> +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer };
> +allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map };
> +allow test_binder_client_no_im_t test_binder_mgr_t:fd use;
> +allow test_binder_client_no_im_t device_t:chr_file getattr;
> +
> +#
> +############ Allow these domains to be entered from sysadm domain ############
> +#
> +miscfiles_domain_entry_test_files(binderdomain)
> +userdom_sysadm_entry_spec_domtrans_to(binderdomain)
> diff --git a/tests/Makefile b/tests/Makefile
> index 27ed6eb..7607c22 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \
>  	task_setnice task_setscheduler task_getscheduler task_getsid \
>  	task_getpgid task_setpgid file ioctl capable_file capable_net \
>  	capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \
> -        inet_socket overlay checkreqprot mqueue mac_admin atsecure
> +        inet_socket overlay checkreqprot mqueue mac_admin atsecure binder

Likely needs to be conditional on binder class being defined in policy as with cap_userns,
and also depends on e.g. linux/android/binder.h existing?  Otherwise will break build on earlier
Fedora/RHEL releases.

>  
>  ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true)
>  ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1)
> diff --git a/tests/binder/Makefile b/tests/binder/Makefile
> new file mode 100644
> index 0000000..a60eeb3
> --- /dev/null
> +++ b/tests/binder/Makefile
> @@ -0,0 +1,7 @@
> +TARGETS = check_binder test_binder
> +
> +LDLIBS += -lselinux
> +
> +all: $(TARGETS)
> +clean:
> +	rm -f $(TARGETS)
> diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c
> new file mode 100644
> index 0000000..3d553a0
> --- /dev/null
> +++ b/tests/binder/check_binder.c
> @@ -0,0 +1,80 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <errno.h>
> +#include <stdbool.h>
> +#include <sys/mman.h>
> +#include <sys/ioctl.h>
> +#include <linux/android/binder.h>
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-v]\n"
> +		"Where:\n\t"
> +		"-v Print binder version.\n", progname);
> +	exit(-1);
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, result, fd;
> +	char *driver = "/dev/binder";
> +	bool verbose;
> +	void *mapped;
> +	size_t mapsize = 1024;
> +	struct binder_version vers;
> +
> +	while ((opt = getopt(argc, argv, "v")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	fd = open(driver, O_RDWR | O_CLOEXEC);
> +	if (fd < 0) {
> +		fprintf(stderr, "Cannot open: %s error: %s\n",
> +			driver, strerror(errno));
> +		exit(-1);
> +	}
> +
> +	/* Need this or no VMA error from kernel */
> +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
> +	if (mapped == MAP_FAILED) {
> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
> +		close(fd);
> +		exit(-1);
> +	}
> +
> +	result = ioctl(fd, BINDER_VERSION, &vers);
> +	if (result < 0) {
> +		fprintf(stderr, "ioctl BINDER_VERSION: %s\n",
> +			strerror(errno));
> +		goto brexit;
> +	}
> +
> +	if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
> +		fprintf(stderr,
> +			"Binder kernel version: %d differs from user space version: %d\n",
> +			vers.protocol_version,
> +			BINDER_CURRENT_PROTOCOL_VERSION);
> +		result = -1;
> +		goto brexit;
> +	}
> +
> +	if (verbose)
> +		fprintf(stderr, "Binder kernel version: %d\n",
> +			vers.protocol_version);
> +
> +brexit:
> +	munmap(mapped, mapsize);
> +	close(fd);
> +
> +	return result;
> +}
> diff --git a/tests/binder/test b/tests/binder/test
> new file mode 100644
> index 0000000..434ae32
> --- /dev/null
> +++ b/tests/binder/test
> @@ -0,0 +1,131 @@
> +#!/usr/bin/perl
> +use Test::More;
> +
> +BEGIN {
> +    $basedir = $0;
> +    $basedir =~ s|(.*)/[^/]*|$1|;
> +
> +    # allow binder info to be shown
> +    $v = $ARGV[0];
> +    if ($v) {
> +        if ( $v ne "-v" ) {
> +            plan skip_all => "Invalid option (use -v)";
> +        }
> +    }
> +
> +    # check if binder driver available and the kernel/userspace versions.
> +    if ( system("$basedir/check_binder 2> /dev/null") != 0 ) {
> +        plan skip_all =>
> +          "Binder not supported or kernel/userspace versions differ";
> +    }
> +    else {
> +        plan tests => 6;
> +    }
> +}
> +
> +if ($v) {

Duplicating the test cases for $v and !$v seems prone to inconsistency in the future;
can't we just embed $v into the command string being executed?

> +    if ( ( $pid = fork() ) == 0 ) {
> +        exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager";
> +    }
> +
> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
> +
> +    # Verify that authorized client can transact with the manager.
> +    $result =
> +      system "runcon -t test_binder_client_t $basedir/test_binder -v client";
> +    ok( $result eq 0 );

This test is failing for me (with or without -v):
# ./test -v
1..6
Manager PID: 5608 Process context:
	unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023
Client PID: 5609 Process context:
	unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023
Client read_consumed: 28
Manager read_consumed: 72
Client command: BR_NOOP
Manager command: BR_NOOP
Client command: BR_INCREFS
Manager command: BR_TRANSACTION
Client command: BR_TRANSACTION_COMPLETE
BR_TRANSACTION data:
	handle: 0
	cookie: 0
	code: 0
	flag: TF_ACCEPT_FDS
	sender pid: 5609
	sender euid: 0
	data_size: 24
	offsets_size: 8
Sending BC_REPLY
Manager read_consumed: 8
Manager command: BR_NOOP
Manager command: BR_TRANSACTION_COMPLETE
Client read_consumed: 72
Client command: BR_NOOP
Client command: BR_REPLY
BR_REPLY data:
	handle: 0
	cookie: 0
	code: 0
	flag: TF_ACCEPT_FDS
	sender pid: 0
	sender euid: 0
	data_size: 24
	offsets_size: 8
Retrieved Managers fd: 4 st_dev: 6
Client read_consumed: 8
Client using Managers FD command: BR_NOOP
Client using Managers FD command: BR_FAILED_REPLY
Client using Managers received FD failed response
Manager read_consumed: 4
Manager command: BR_NOOP
not ok 1
#   Failed test at ./test line 36.


> +
> +    # Verify that client cannot call manager (no call perm).
> +    $result = system
> +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Verify that client cannot communicate with manager (no impersonate perm).
> +    $result = system
> +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1";
> +    ok( $result >> 8 eq 102 );
> +
> +    # Verify that client cannot communicate with manager (no transfer perm).
> +    $result = system
> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Kill the manager.
> +    kill TERM, $pid;
> +
> +    # Verify that client cannot become a manager (no set_context_mgr perm).
> +    $result =
> +      system
> +      "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1";
> +    ok( $result >> 8 eq 4 );
> +
> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
> +    if ( ( $pid = fork() ) == 0 ) {
> +        exec
> +          "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager";
> +    }
> +
> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
> +
> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
> +# domain and binder will return BR_FAILED_REPLY.
> +    $result =
> +      system
> +      "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Kill the manager
> +    kill TERM, $pid;
> +}
> +else {
> +    if ( ( $pid = fork() ) == 0 ) {
> +        exec "runcon -t test_binder_mgr_t $basedir/test_binder manager";
> +    }
> +
> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
> +
> +    # Verify that authorized client can transact with the manager.
> +    $result =
> +      system "runcon -t test_binder_client_t $basedir/test_binder client";
> +    ok( $result eq 0 );
> +
> +    # Verify that client cannot call manager (no call perm).
> +    $result = system
> +      "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Verify that client cannot communicate with manager (no impersonate perm).
> +    $result = system
> +      "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1";
> +    ok( $result >> 8 eq 102 );
> +
> +    # Verify that client cannot communicate with manager (no transfer perm).
> +    $result = system
> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Kill the manager.
> +    kill TERM, $pid;
> +
> +    # Verify that client cannot become a manager (no set_context_mgr perm).
> +    $result =
> +      system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1";
> +    ok( $result >> 8 eq 4 );
> +
> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
> +    if ( ( $pid = fork() ) == 0 ) {
> +        exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager";
> +    }
> +
> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
> +
> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
> +# domain and binder will return BR_FAILED_REPLY.
> +    $result =
> +      system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1";
> +    ok( $result >> 8 eq 9 );
> +
> +    # Kill the manager
> +    kill TERM, $pid;
> +}
> +exit;
> diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c
> new file mode 100644
> index 0000000..8881cce
> --- /dev/null
> +++ b/tests/binder/test_binder.c
> @@ -0,0 +1,543 @@
> +/*
> + * This is a simple binder client/server(manager) that only uses the
> + * raw ioctl commands. It does not rely on a 'service manager' as in
> + * the Android world as it only uses one 'target' = 0.
> + *
> + * The transaction/reply flow is basically:
> + *      Client                                  Manager
> + *     ========                                =========
> + *
> + *                                        Becomes context manager
> + *   Send transaction
> + *   requesting file
> + *   descriptor from
> + *    the Manager
> + *         --------------------------------------->
> + *                                       Manager replies with its
> + *                                        binder file descriptor
> + *         <--------------------------------------
> + *     Check fd valid, if so send
> + *  TF_ONE_WAY transaction to Manager
> + *  using the received fd
> + *          --------------------------------------->
> + *                                        End of tests so Manager
> + *                                          waits to be killed
> + *   Client exits
> + *
> + * Using binder test policy the following will be validated:
> + *    security_binder_set_context_mgr() binder { set_context_mgr }
> + *    security_binder_transaction()     binder { call impersonate }
> + *    security_binder_transfer_binder() binder { transfer }
> + *    security_binder_transfer_file()   fd { use }
> + *
> + * TODO security_binder_transfer_file() uses BPF if configured in kernel.
> + */
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <inttypes.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/stat.h>
> +#include <stdbool.h>
> +#include <sys/mman.h>
> +#include <sys/ioctl.h>
> +#include <selinux/selinux.h>
> +#include <linux/android/binder.h>
> +
> +static uint32_t target; /* This will be set to '0' as only need one target */
> +static bool verbose;
> +
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text);
> +
> +static void usage(char *progname)
> +{
> +	fprintf(stderr,
> +		"usage:  %s [-v] manager | client\n"
> +		"Where:\n\t"
> +		"-v       Print context and command information.\n\t"
> +		"manager  Act as binder context manager.\n\t"
> +		"client   Act as binder client.\n"
> +		"\nNote: Ensure this boolean command is run when "
> +		"testing after a reboot:\n\t"
> +		"setsebool allow_domain_fd_use=0\n", progname);
> +	exit(1);
> +}
> +
> +static const char *cmd_name(uint32_t cmd)
> +{
> +	switch (cmd) {
> +	case BR_NOOP:
> +		return "BR_NOOP";
> +	case BR_TRANSACTION_COMPLETE:
> +		return "BR_TRANSACTION_COMPLETE";
> +	case BR_INCREFS:
> +		return "BR_INCREFS";
> +	case BR_ACQUIRE:
> +		return "BR_ACQUIRE";
> +	case BR_RELEASE:
> +		return "BR_RELEASE";
> +	case BR_DECREFS:
> +		return "BR_DECREFS";
> +	case BR_TRANSACTION:
> +		return "BR_TRANSACTION";
> +	case BR_REPLY:
> +		return "BR_REPLY";
> +	case BR_FAILED_REPLY:
> +		return "BR_FAILED_REPLY";
> +	case BR_DEAD_REPLY:
> +		return "BR_DEAD_REPLY";
> +	case BR_DEAD_BINDER:
> +		return "BR_DEAD_BINDER";
> +	case BR_ERROR:
> +		return "BR_ERROR";
> +	/* fallthrough */
> +	default:
> +		return "Unknown command";
> +	}
> +}
> +
> +void print_trans_data(struct binder_transaction_data *txn)
> +{
> +	printf("\thandle: %ld\n", (unsigned long)txn->target.handle);
> +	printf("\tcookie: %lld\n", txn->cookie);
> +	printf("\tcode: %u\n", txn->code);
> +	switch (txn->flags) {
> +	case TF_ONE_WAY:
> +		printf("\tflag: TF_ONE_WAY\n");
> +		break;
> +	case TF_ROOT_OBJECT:
> +		printf("\tflag: TF_ROOT_OBJECT\n");
> +		break;
> +	case TF_STATUS_CODE:
> +		printf("\tflag: TF_STATUS_CODE\n");
> +		break;
> +	case TF_ACCEPT_FDS:
> +		printf("\tflag: TF_ACCEPT_FDS\n");
> +		break;
> +	default:
> +		printf("Unknown flag: %u\n", txn->flags);
> +	}
> +	printf("\tsender pid: %u\n", txn->sender_pid);
> +	printf("\tsender euid: %u\n", txn->sender_euid);
> +	printf("\tdata_size: %llu\n", txn->data_size);
> +	printf("\toffsets_size: %llu\n", txn->offsets_size);
> +}
> +
> +
> +static int send_reply(int fd, struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	unsigned int writebuf[1024];
> +	struct flat_binder_object obj;
> +	struct binder_write_read bwr;
> +	struct binder_transaction_data *txn;
> +
> +	if (txn_in->flags == TF_ONE_WAY) {
> +		if (verbose)
> +			printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
> +		return 0;
> +	}
> +
> +	if (verbose)
> +		printf("Sending BC_REPLY\n");
> +
> +	writebuf[0] = BC_REPLY;
> +	txn = (struct binder_transaction_data *)(&writebuf[1]);
> +
> +	memset(txn, 0, sizeof(*txn));
> +	txn->target.handle = txn_in->target.handle;
> +	txn->cookie = txn_in->cookie;
> +	txn->code = txn_in->code;
> +	txn->flags = TF_ACCEPT_FDS;
> +
> +	memset(&obj, 0, sizeof(struct flat_binder_object));
> +	obj.hdr.type = BINDER_TYPE_FD;
> +	obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +	obj.binder = txn->target.handle;
> +	obj.cookie = txn->cookie;
> +	/* The binder fd is used for testing as it allows policy to set
> +	 * whether the Client/Manager can be allowed access (fd use) or
> +	 * not. For example test_binder_mgr_t has:
> +	 *        allow test_binder_client_t test_binder_mgr_t:fd use;
> +	 * whereas test_binder_mgr_no_fd_t does not allow this fd use.
> +	 *
> +	 * This also allows a check for the impersonate permission later as
> +	 * the Client will use this (the Managers fd) to send a transaction.
> +	 */
> +	obj.handle = fd;
> +
> +	txn->data_size = sizeof(struct flat_binder_object);
> +	txn->data.ptr.buffer = (uintptr_t)&obj;
> +	txn->data.ptr.offsets = (uintptr_t)&obj +
> +				sizeof(struct flat_binder_object);
> +	txn->offsets_size = sizeof(binder_size_t);
> +
> +	memset(&bwr, 0, sizeof(bwr));
> +	bwr.write_buffer = (unsigned long)writebuf;
> +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> +
> +	result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +	if (result < 0) {
> +		fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n",
> +			__func__, strerror(errno));
> +		return -1;
> +	}
> +
> +	return result;
> +}
> +
> +/* This retrieves the requested Managers file descriptor, then using this
> + * sends a simple transaction to trigger the impersonate permission.
> + */
> +static void extract_fd_and_respond(struct binder_transaction_data *txn_in)
> +{
> +	int result;
> +	uint32_t cmd;
> +	struct stat sb;
> +	struct binder_write_read bwr;
> +	struct flat_binder_object *obj;
> +	struct binder_transaction_data *txn;
> +	unsigned int readbuf[32];
> +	unsigned int writebuf[1024];
> +	binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
> +
> +	/* Get the fbo that contains the Managers binder file descriptor. */
> +	obj = (struct flat_binder_object *)
> +	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
> +
> +	/* fstat this just to see if a valid fd */
> +	result = fstat(obj->handle, &sb);
> +	if (result < 0) {
> +		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
> +		exit(100);
> +	}
> +
> +	if (verbose)
> +		printf("Retrieved Managers fd: %d st_dev: %ld\n",
> +		       obj->handle, sb.st_dev);
> +
> +	/* Send response using Managers fd to trigger impersonate check. */
> +	writebuf[0] = BC_TRANSACTION;
> +	txn = (struct binder_transaction_data *)(&writebuf[1]);
> +	memset(txn, 0, sizeof(*txn));
> +	txn->target.handle = target;
> +	txn->cookie = 0;
> +	txn->code = 0;
> +	txn->flags = TF_ONE_WAY;
> +
> +	txn->data_size = 0;
> +	txn->data.ptr.buffer = (uintptr_t)NULL;
> +	txn->data.ptr.offsets = (uintptr_t)NULL;
> +	txn->offsets_size = 0;
> +
> +	memset(&bwr, 0, sizeof(bwr));
> +	bwr.write_buffer = (unsigned long)writebuf;
> +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> +
> +	bwr.read_size = sizeof(readbuf);
> +	bwr.read_consumed = 0;
> +	bwr.read_buffer = (uintptr_t)readbuf;
> +
> +	result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr);
> +	if (result < 0) {
> +		fprintf(stderr,
> +			"CLIENT ioctl BINDER_WRITE_READ: %s\n",
> +			strerror(errno));
> +		exit(101);
> +	}
> +
> +	if (verbose)
> +		printf("Client read_consumed: %lld\n", bwr.read_consumed);
> +
> +	cmd = binder_parse(obj->handle, (uintptr_t)readbuf,
> +			   bwr.read_consumed,
> +			   "Client using Managers FD");
> +
> +	if (cmd == BR_FAILED_REPLY ||
> +	    cmd == BR_DEAD_REPLY ||
> +	    cmd == BR_DEAD_BINDER) {
> +		fprintf(stderr,
> +			"Client using Managers received FD failed response\n");
> +		exit(102);
> +	}
> +}
> +
> +/* Parse response, reply as required and then return last CMD */
> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text)
> +{
> +	uintptr_t end = ptr + (uintptr_t)size;
> +	uint32_t cmd;
> +
> +	while (ptr < end) {
> +		cmd = *(uint32_t *)ptr;
> +		ptr += sizeof(uint32_t);
> +
> +		if (verbose)
> +			printf("%s command: %s\n", text, cmd_name(cmd));
> +
> +		switch (cmd) {
> +		case BR_NOOP:
> +			break;
> +		case BR_TRANSACTION_COMPLETE:
> +			break;
> +		case BR_INCREFS:
> +		case BR_ACQUIRE:
> +		case BR_RELEASE:
> +		case BR_DECREFS:
> +			ptr += sizeof(struct binder_ptr_cookie);
> +			break;
> +		case BR_TRANSACTION: {
> +			struct binder_transaction_data *txn =
> +				(struct binder_transaction_data *)ptr;
> +
> +			if (verbose) {
> +				printf("BR_TRANSACTION data:\n");
> +				print_trans_data(txn);
> +			}
> +
> +			/* The manager sends reply that will contain its fd */
> +			if (send_reply(fd, txn) < 0) {
> +				fprintf(stderr, "send_reply() failed.\n");
> +				return -1;
> +			}
> +			ptr += sizeof(*txn);
> +			break;
> +		}
> +		case BR_REPLY: {
> +			struct binder_transaction_data *txn =
> +				(struct binder_transaction_data *)ptr;
> +
> +			if (verbose) {
> +				printf("BR_REPLY data:\n");
> +				print_trans_data(txn);
> +			}
> +
> +			/* Client extracts the Manager fd, and responds */
> +			extract_fd_and_respond(txn);
> +			ptr += sizeof(*txn);
> +			break;
> +		}
> +		case BR_DEAD_BINDER:
> +			break;
> +		case BR_FAILED_REPLY:
> +			break;
> +		case BR_DEAD_REPLY:
> +			break;
> +		case BR_ERROR:
> +			ptr += sizeof(uint32_t);
> +			break;
> +		default:
> +			if (verbose)
> +				printf("%s Parsed unknown command: %d\n",
> +				       text, cmd);
> +			return -1;
> +		}
> +	}
> +
> +	return cmd;
> +}
> +
> +int main(int argc, char **argv)
> +{
> +	int opt, option, result, fd, count;
> +	uint32_t cmd;
> +	pid_t pid;
> +	char *driver = "/dev/binder";
> +	char *context;
> +	void *mapped;
> +	size_t mapsize = 2048;
> +	struct binder_write_read bwr;
> +	struct flat_binder_object obj;
> +	struct binder_transaction_data *txn;
> +	unsigned int readbuf[32];
> +	unsigned int writebuf[1024];
> +
> +	target = 0; /* Only need one target - the Manager */
> +	verbose = false;
> +
> +	while ((opt = getopt(argc, argv, "v")) != -1) {
> +		switch (opt) {
> +		case 'v':
> +			verbose = true;
> +			break;
> +		default:
> +			usage(argv[0]);
> +		}
> +	}
> +
> +	if ((argc - optind) != 1)
> +		usage(argv[0]);
> +
> +	if (!strcmp(argv[optind], "manager"))
> +		option = 1;
> +	else if (!strcmp(argv[optind], "client"))
> +		option = 2;
> +	else
> +		usage(argv[0]);
> +
> +	fd = open(driver, O_RDWR | O_CLOEXEC);
> +	if (fd < 0) {
> +		fprintf(stderr, "Cannot open %s error: %s\n", driver,
> +			strerror(errno));
> +		exit(1);
> +	}
> +
> +	/* Need this or "no VMA" error from kernel */
> +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
> +	if (mapped == MAP_FAILED) {
> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
> +		close(fd);
> +		exit(2);
> +	}
> +
> +	/* Get our context and pid */
> +	result = getcon(&context);
> +	if (result < 0) {
> +		fprintf(stderr, "Failed to obtain SELinux context\n");
> +		result = 3;
> +		goto brexit;
> +	}
> +	pid = getpid();
> +
> +	switch (option) {
> +	case 1: /* manager */
> +		if (verbose) {
> +			printf("Manager PID: %d Process context:\n\t%s\n",
> +			       pid, context);
> +		}
> +
> +		result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0);
> +		if (result < 0) {
> +			fprintf(stderr,
> +				"Failed to become context manager: %s\n",
> +				strerror(errno));
> +			result = 4;
> +			goto brexit;
> +		}
> +
> +		readbuf[0] = BC_ENTER_LOOPER;
> +		bwr.write_size = sizeof(readbuf[0]);
> +		bwr.write_consumed = 0;
> +		bwr.write_buffer = (uintptr_t)readbuf;
> +
> +		bwr.read_size = 0;
> +		bwr.read_consumed = 0;
> +		bwr.read_buffer = 0;
> +
> +		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +		if (result < 0) {
> +			fprintf(stderr,
> +				"Manager ioctl BINDER_WRITE_READ: %s\n",
> +				strerror(errno));
> +			result = 5;
> +			goto brexit;
> +		}
> +
> +		while (true) {
> +			bwr.read_size = sizeof(readbuf);
> +			bwr.read_consumed = 0;
> +			bwr.read_buffer = (uintptr_t)readbuf;
> +
> +			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +			if (result < 0) {
> +				fprintf(stderr,
> +					"Manager ioctl BINDER_WRITE_READ: %s\n",
> +					strerror(errno));
> +				result = 6;
> +				goto brexit;
> +			}
> +
> +			if (bwr.read_consumed == 0)
> +				continue;
> +
> +			if (verbose)
> +				printf("Manager read_consumed: %lld\n",
> +				       bwr.read_consumed);
> +
> +			cmd = binder_parse(fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, "Manager");
> +		}
> +		break;
> +
> +	case 2: /* client */
> +		if (verbose) {
> +			printf("Client PID: %d Process context:\n\t%s\n",
> +			       pid, context);
> +		}
> +
> +		writebuf[0] = BC_TRANSACTION;
> +		txn = (struct binder_transaction_data *)(&writebuf[1]);
> +		memset(txn, 0, sizeof(*txn));
> +		txn->target.handle = target;
> +		txn->cookie = 0;
> +		txn->code = 0;
> +		txn->flags = TF_ACCEPT_FDS;
> +
> +		memset(&obj, 0, sizeof(struct flat_binder_object));
> +		obj.hdr.type = BINDER_TYPE_WEAK_BINDER;
> +		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> +		obj.binder = target;
> +		obj.handle = 0;
> +		obj.cookie = 0;
> +
> +		txn->data_size = sizeof(struct flat_binder_object);
> +		txn->data.ptr.buffer = (uintptr_t)&obj;
> +		txn->data.ptr.offsets = (uintptr_t)&obj +
> +					sizeof(struct flat_binder_object);
> +		txn->offsets_size = sizeof(binder_size_t);
> +
> +		memset(&bwr, 0, sizeof(bwr));
> +		bwr.write_buffer = (unsigned long)writebuf;
> +		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> +
> +		/* Expect client to get max two responses:
> +		 *	1) From the above BC_TRANSACTION
> +		 *	2) The responding BC_REPLY from send_reply()
> +		 * unless an error.
> +		 */
> +		count = 0;
> +		while (count != 2) {
> +			bwr.read_size = sizeof(readbuf);
> +			bwr.read_consumed = 0;
> +			bwr.read_buffer = (uintptr_t)readbuf;
> +
> +			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> +			if (result < 0) {
> +				fprintf(stderr,
> +					"Client ioctl BINDER_WRITE_READ: %s\n",
> +					strerror(errno));
> +				result = 8;
> +				goto brexit;
> +			}
> +
> +			if (verbose)
> +				printf("Client read_consumed: %lld\n",
> +				       bwr.read_consumed);
> +
> +			cmd = binder_parse(fd, (uintptr_t)readbuf,
> +					   bwr.read_consumed, "Client");
> +
> +			if (cmd == BR_FAILED_REPLY ||
> +			    cmd == BR_DEAD_REPLY ||
> +			    cmd == BR_DEAD_BINDER) {
> +				result = 9;
> +				goto brexit;
> +			}
> +			count++;
> +		}
> +		break;
> +
> +	default:
> +		result = -1;
> +	}
> +
> +brexit:
> +	free(context);
> +	munmap(mapped, mapsize);
> +	close(fd);
> +
> +	return result;
> +}
>
Stephen Smalley May 15, 2018, 1:43 p.m. UTC | #2
On 05/15/2018 09:36 AM, Stephen Smalley wrote:
> On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote:
>> Add binder tests. See tests/binder/test_binder.c for details on
>> message flows to test security_binder*() functions.
>>
>> Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
>> ---
>>  README.md                   |   8 +
>>  defconfig                   |   8 +
>>  policy/Makefile             |   2 +-
>>  policy/test_binder.te       |  83 +++++++
>>  tests/Makefile              |   2 +-
>>  tests/binder/Makefile       |   7 +
>>  tests/binder/check_binder.c |  80 +++++++
>>  tests/binder/test           | 131 +++++++++++
>>  tests/binder/test_binder.c  | 543 ++++++++++++++++++++++++++++++++++++++++++++
>>  9 files changed, 862 insertions(+), 2 deletions(-)
>>  create mode 100644 policy/test_binder.te
>>  create mode 100644 tests/binder/Makefile
>>  create mode 100644 tests/binder/check_binder.c
>>  create mode 100644 tests/binder/test
>>  create mode 100644 tests/binder/test_binder.c
>>
>> diff --git a/README.md b/README.md
>> index c9f3b2b..60a249e 100644
>> --- a/README.md
>> +++ b/README.md
>> @@ -141,6 +141,14 @@ directory or you can follow these broken-out steps:
>>  The broken-out steps allow you to run the tests multiple times without
>>  loading policy each time.
>>  
>> +Note that if leaving the test policy in-place for further testing, the
>> +policy build process changes a boolean:
>> +   On policy load:   setsebool allow_domain_fd_use=0
>> +   On policy unload: setsebool allow_domain_fd_use=1
>> +The consequence of this is that after a system reboot, the boolean
>> +defaults to true. Therefore if running the fdreceive or binder tests,
>> +reset the boolean to false, otherwise some tests will fail.
> 
> This isn't accurate - we aren't doing setsebool -P so the boolean change is not persistent across
> reboots.  It will persist across policy reloads however because the kernel preserves booleans across
> policy reloads.

Sorry, never mind - I misread the text above.  You are correct.

> 
>> +
>>  4) Review the test results.
>>  
>>  As each test script is run, the name of the script will be displayed followed
>> diff --git a/defconfig b/defconfig
>> index 7dce8bc..dc6ef30 100644
>> --- a/defconfig
>> +++ b/defconfig
>> @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m
>>  # This is enabled to test overlayfs SELinux integration.
>>  # It is not required for SELinux operation itself.
>>  CONFIG_OVERLAY_FS=m
>> +
>> +# Android binder implementations.
>> +# These are enabled to test the binder controls in
>> +# tests/binder; they are not required for SELinux operation itself.
>> +CONFIG_ANDROID=y
>> +CONFIG_ANDROID_BINDER_IPC=y
>> +CONFIG_ANDROID_BINDER_DEVICES="binder"
>> +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set
> 
> I don't think we need the last line.
> 
>> diff --git a/policy/Makefile b/policy/Makefile
>> index 8ed5e46..5a9d411 100644
>> --- a/policy/Makefile
>> +++ b/policy/Makefile
>> @@ -25,7 +25,7 @@ TARGETS = \
>>  	test_task_getsid.te test_task_setpgid.te test_task_setsched.te \
>>  	test_transition.te test_inet_socket.te test_unix_socket.te \
>>  	test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \
>> -	test_ibpkey.te test_atsecure.te
>> +	test_ibpkey.te test_atsecure.te test_binder.te
> 
> Likely need to make this conditional on the binder class being defined in the policy;
> see similar logic for e.g. cap_userns, icmp_socket, etc.  Otherwise policy won't build
> on earlier Fedora/RHEL before definition of the binder class.
> 
>>  
>>  ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true)
>>  TARGETS += test_bounds.te
>> diff --git a/policy/test_binder.te b/policy/test_binder.te
>> new file mode 100644
>> index 0000000..c4ad2ae
>> --- /dev/null
>> +++ b/policy/test_binder.te
>> @@ -0,0 +1,83 @@
>> +
>> +attribute binderdomain;
>> +
>> +#
>> +################################## Manager ###################################
>> +#
>> +type test_binder_mgr_t;
>> +domain_type(test_binder_mgr_t)
>> +unconfined_runs_test(test_binder_mgr_t)
>> +typeattribute test_binder_mgr_t testdomain;
>> +typeattribute test_binder_mgr_t binderdomain;
>> +allow test_binder_mgr_t self:binder { set_context_mgr call };
>> +allow test_binder_mgr_t device_t:chr_file { ioctl open read write map };
> 
> Wondering if we should define a .fc file with /dev/binder and a proper binder_device_t type
> and restorecon it before the test.  But not clear it is worth it.  Never mind.
> 
>> +allow test_binder_mgr_t self:capability { sys_nice };
> 
> Needed or just to suppress noise in the audit?
> 
>> +allow test_binder_client_t test_binder_mgr_t:fd use;
>> +
>> +#
>> +################################# Client ####################################
>> +#
>> +type test_binder_client_t;
>> +domain_type(test_binder_client_t)
>> +unconfined_runs_test(test_binder_client_t)
>> +typeattribute test_binder_client_t testdomain;
>> +typeattribute test_binder_client_t binderdomain;
>> +allow test_binder_client_t self:binder { call };
>> +allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate };
> 
> Are you actually exercising impersonate?  I largely didn't expect it to ever be used in Android itself,
> just included the check because I saw that it was technically possible as far as the kernel interface
> is concerned.
> 
>> +allow test_binder_client_t device_t:chr_file { ioctl open read write map };
>> +# For fstat:
>> +allow test_binder_client_t device_t:chr_file getattr;
>> +
>> +#
>> +############################## Client no call ################################
>> +#
>> +type test_binder_client_no_call_t;
>> +domain_type(test_binder_client_no_call_t)
>> +unconfined_runs_test(test_binder_client_no_call_t)
>> +typeattribute test_binder_client_no_call_t testdomain;
>> +typeattribute test_binder_client_no_call_t binderdomain;
>> +allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map };
>> +
>> +#
>> +############################ Client no transfer #############################
>> +#
>> +type test_binder_client_no_transfer_t;
>> +domain_type(test_binder_client_no_transfer_t)
>> +unconfined_runs_test(test_binder_client_no_transfer_t)
>> +typeattribute test_binder_client_no_transfer_t testdomain;
>> +typeattribute test_binder_client_no_transfer_t binderdomain;
>> +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call };
>> +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map };
>> +
>> +#
>> +########################## Manager no fd {use} ###############################
>> +#
>> +type test_binder_mgr_no_fd_t;
>> +domain_type(test_binder_mgr_no_fd_t)
>> +unconfined_runs_test(test_binder_mgr_no_fd_t)
>> +typeattribute test_binder_mgr_no_fd_t testdomain;
>> +typeattribute test_binder_mgr_no_fd_t binderdomain;
>> +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call };
>> +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
>> +allow test_binder_mgr_no_fd_t self:capability { sys_nice };
>> +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer };
>> +
>> +#
>> +########################### Client no impersonate ############################
>> +#
>> +type test_binder_client_no_im_t;
>> +domain_type(test_binder_client_no_im_t)
>> +unconfined_runs_test(test_binder_client_no_im_t)
>> +typeattribute test_binder_client_no_im_t testdomain;
>> +typeattribute test_binder_client_no_im_t binderdomain;
>> +allow test_binder_client_no_im_t self:binder { call };
>> +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer };
>> +allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map };
>> +allow test_binder_client_no_im_t test_binder_mgr_t:fd use;
>> +allow test_binder_client_no_im_t device_t:chr_file getattr;
>> +
>> +#
>> +############ Allow these domains to be entered from sysadm domain ############
>> +#
>> +miscfiles_domain_entry_test_files(binderdomain)
>> +userdom_sysadm_entry_spec_domtrans_to(binderdomain)
>> diff --git a/tests/Makefile b/tests/Makefile
>> index 27ed6eb..7607c22 100644
>> --- a/tests/Makefile
>> +++ b/tests/Makefile
>> @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \
>>  	task_setnice task_setscheduler task_getscheduler task_getsid \
>>  	task_getpgid task_setpgid file ioctl capable_file capable_net \
>>  	capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \
>> -        inet_socket overlay checkreqprot mqueue mac_admin atsecure
>> +        inet_socket overlay checkreqprot mqueue mac_admin atsecure binder
> 
> Likely needs to be conditional on binder class being defined in policy as with cap_userns,
> and also depends on e.g. linux/android/binder.h existing?  Otherwise will break build on earlier
> Fedora/RHEL releases.
> 
>>  
>>  ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true)
>>  ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1)
>> diff --git a/tests/binder/Makefile b/tests/binder/Makefile
>> new file mode 100644
>> index 0000000..a60eeb3
>> --- /dev/null
>> +++ b/tests/binder/Makefile
>> @@ -0,0 +1,7 @@
>> +TARGETS = check_binder test_binder
>> +
>> +LDLIBS += -lselinux
>> +
>> +all: $(TARGETS)
>> +clean:
>> +	rm -f $(TARGETS)
>> diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c
>> new file mode 100644
>> index 0000000..3d553a0
>> --- /dev/null
>> +++ b/tests/binder/check_binder.c
>> @@ -0,0 +1,80 @@
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <unistd.h>
>> +#include <fcntl.h>
>> +#include <errno.h>
>> +#include <stdbool.h>
>> +#include <sys/mman.h>
>> +#include <sys/ioctl.h>
>> +#include <linux/android/binder.h>
>> +
>> +static void usage(char *progname)
>> +{
>> +	fprintf(stderr,
>> +		"usage:  %s [-v]\n"
>> +		"Where:\n\t"
>> +		"-v Print binder version.\n", progname);
>> +	exit(-1);
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +	int opt, result, fd;
>> +	char *driver = "/dev/binder";
>> +	bool verbose;
>> +	void *mapped;
>> +	size_t mapsize = 1024;
>> +	struct binder_version vers;
>> +
>> +	while ((opt = getopt(argc, argv, "v")) != -1) {
>> +		switch (opt) {
>> +		case 'v':
>> +			verbose = true;
>> +			break;
>> +		default:
>> +			usage(argv[0]);
>> +		}
>> +	}
>> +
>> +	fd = open(driver, O_RDWR | O_CLOEXEC);
>> +	if (fd < 0) {
>> +		fprintf(stderr, "Cannot open: %s error: %s\n",
>> +			driver, strerror(errno));
>> +		exit(-1);
>> +	}
>> +
>> +	/* Need this or no VMA error from kernel */
>> +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
>> +	if (mapped == MAP_FAILED) {
>> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
>> +		close(fd);
>> +		exit(-1);
>> +	}
>> +
>> +	result = ioctl(fd, BINDER_VERSION, &vers);
>> +	if (result < 0) {
>> +		fprintf(stderr, "ioctl BINDER_VERSION: %s\n",
>> +			strerror(errno));
>> +		goto brexit;
>> +	}
>> +
>> +	if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
>> +		fprintf(stderr,
>> +			"Binder kernel version: %d differs from user space version: %d\n",
>> +			vers.protocol_version,
>> +			BINDER_CURRENT_PROTOCOL_VERSION);
>> +		result = -1;
>> +		goto brexit;
>> +	}
>> +
>> +	if (verbose)
>> +		fprintf(stderr, "Binder kernel version: %d\n",
>> +			vers.protocol_version);
>> +
>> +brexit:
>> +	munmap(mapped, mapsize);
>> +	close(fd);
>> +
>> +	return result;
>> +}
>> diff --git a/tests/binder/test b/tests/binder/test
>> new file mode 100644
>> index 0000000..434ae32
>> --- /dev/null
>> +++ b/tests/binder/test
>> @@ -0,0 +1,131 @@
>> +#!/usr/bin/perl
>> +use Test::More;
>> +
>> +BEGIN {
>> +    $basedir = $0;
>> +    $basedir =~ s|(.*)/[^/]*|$1|;
>> +
>> +    # allow binder info to be shown
>> +    $v = $ARGV[0];
>> +    if ($v) {
>> +        if ( $v ne "-v" ) {
>> +            plan skip_all => "Invalid option (use -v)";
>> +        }
>> +    }
>> +
>> +    # check if binder driver available and the kernel/userspace versions.
>> +    if ( system("$basedir/check_binder 2> /dev/null") != 0 ) {
>> +        plan skip_all =>
>> +          "Binder not supported or kernel/userspace versions differ";
>> +    }
>> +    else {
>> +        plan tests => 6;
>> +    }
>> +}
>> +
>> +if ($v) {
> 
> Duplicating the test cases for $v and !$v seems prone to inconsistency in the future;
> can't we just embed $v into the command string being executed?
> 
>> +    if ( ( $pid = fork() ) == 0 ) {
>> +        exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager";
>> +    }
>> +
>> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
>> +
>> +    # Verify that authorized client can transact with the manager.
>> +    $result =
>> +      system "runcon -t test_binder_client_t $basedir/test_binder -v client";
>> +    ok( $result eq 0 );
> 
> This test is failing for me (with or without -v):
> # ./test -v
> 1..6
> Manager PID: 5608 Process context:
> 	unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023
> Client PID: 5609 Process context:
> 	unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023
> Client read_consumed: 28
> Manager read_consumed: 72
> Client command: BR_NOOP
> Manager command: BR_NOOP
> Client command: BR_INCREFS
> Manager command: BR_TRANSACTION
> Client command: BR_TRANSACTION_COMPLETE
> BR_TRANSACTION data:
> 	handle: 0
> 	cookie: 0
> 	code: 0
> 	flag: TF_ACCEPT_FDS
> 	sender pid: 5609
> 	sender euid: 0
> 	data_size: 24
> 	offsets_size: 8
> Sending BC_REPLY
> Manager read_consumed: 8
> Manager command: BR_NOOP
> Manager command: BR_TRANSACTION_COMPLETE
> Client read_consumed: 72
> Client command: BR_NOOP
> Client command: BR_REPLY
> BR_REPLY data:
> 	handle: 0
> 	cookie: 0
> 	code: 0
> 	flag: TF_ACCEPT_FDS
> 	sender pid: 0
> 	sender euid: 0
> 	data_size: 24
> 	offsets_size: 8
> Retrieved Managers fd: 4 st_dev: 6
> Client read_consumed: 8
> Client using Managers FD command: BR_NOOP
> Client using Managers FD command: BR_FAILED_REPLY
> Client using Managers received FD failed response
> Manager read_consumed: 4
> Manager command: BR_NOOP
> not ok 1
> #   Failed test at ./test line 36.

Just realized that I was testing with a kernel that still had Casey's stacking support enabled.
Will re-try without that.

> 
> 
>> +
>> +    # Verify that client cannot call manager (no call perm).
>> +    $result = system
>> +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Verify that client cannot communicate with manager (no impersonate perm).
>> +    $result = system
>> +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1";
>> +    ok( $result >> 8 eq 102 );
>> +
>> +    # Verify that client cannot communicate with manager (no transfer perm).
>> +    $result = system
>> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Kill the manager.
>> +    kill TERM, $pid;
>> +
>> +    # Verify that client cannot become a manager (no set_context_mgr perm).
>> +    $result =
>> +      system
>> +      "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1";
>> +    ok( $result >> 8 eq 4 );
>> +
>> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
>> +    if ( ( $pid = fork() ) == 0 ) {
>> +        exec
>> +          "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager";
>> +    }
>> +
>> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
>> +
>> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
>> +# domain and binder will return BR_FAILED_REPLY.
>> +    $result =
>> +      system
>> +      "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Kill the manager
>> +    kill TERM, $pid;
>> +}
>> +else {
>> +    if ( ( $pid = fork() ) == 0 ) {
>> +        exec "runcon -t test_binder_mgr_t $basedir/test_binder manager";
>> +    }
>> +
>> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
>> +
>> +    # Verify that authorized client can transact with the manager.
>> +    $result =
>> +      system "runcon -t test_binder_client_t $basedir/test_binder client";
>> +    ok( $result eq 0 );
>> +
>> +    # Verify that client cannot call manager (no call perm).
>> +    $result = system
>> +      "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Verify that client cannot communicate with manager (no impersonate perm).
>> +    $result = system
>> +      "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1";
>> +    ok( $result >> 8 eq 102 );
>> +
>> +    # Verify that client cannot communicate with manager (no transfer perm).
>> +    $result = system
>> +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Kill the manager.
>> +    kill TERM, $pid;
>> +
>> +    # Verify that client cannot become a manager (no set_context_mgr perm).
>> +    $result =
>> +      system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1";
>> +    ok( $result >> 8 eq 4 );
>> +
>> +# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
>> +    if ( ( $pid = fork() ) == 0 ) {
>> +        exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager";
>> +    }
>> +
>> +    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
>> +
>> +# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
>> +# domain and binder will return BR_FAILED_REPLY.
>> +    $result =
>> +      system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1";
>> +    ok( $result >> 8 eq 9 );
>> +
>> +    # Kill the manager
>> +    kill TERM, $pid;
>> +}
>> +exit;
>> diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c
>> new file mode 100644
>> index 0000000..8881cce
>> --- /dev/null
>> +++ b/tests/binder/test_binder.c
>> @@ -0,0 +1,543 @@
>> +/*
>> + * This is a simple binder client/server(manager) that only uses the
>> + * raw ioctl commands. It does not rely on a 'service manager' as in
>> + * the Android world as it only uses one 'target' = 0.
>> + *
>> + * The transaction/reply flow is basically:
>> + *      Client                                  Manager
>> + *     ========                                =========
>> + *
>> + *                                        Becomes context manager
>> + *   Send transaction
>> + *   requesting file
>> + *   descriptor from
>> + *    the Manager
>> + *         --------------------------------------->
>> + *                                       Manager replies with its
>> + *                                        binder file descriptor
>> + *         <--------------------------------------
>> + *     Check fd valid, if so send
>> + *  TF_ONE_WAY transaction to Manager
>> + *  using the received fd
>> + *          --------------------------------------->
>> + *                                        End of tests so Manager
>> + *                                          waits to be killed
>> + *   Client exits
>> + *
>> + * Using binder test policy the following will be validated:
>> + *    security_binder_set_context_mgr() binder { set_context_mgr }
>> + *    security_binder_transaction()     binder { call impersonate }
>> + *    security_binder_transfer_binder() binder { transfer }
>> + *    security_binder_transfer_file()   fd { use }
>> + *
>> + * TODO security_binder_transfer_file() uses BPF if configured in kernel.
>> + */
>> +
>> +#include <errno.h>
>> +#include <fcntl.h>
>> +#include <inttypes.h>
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +#include <string.h>
>> +#include <unistd.h>
>> +#include <sys/stat.h>
>> +#include <stdbool.h>
>> +#include <sys/mman.h>
>> +#include <sys/ioctl.h>
>> +#include <selinux/selinux.h>
>> +#include <linux/android/binder.h>
>> +
>> +static uint32_t target; /* This will be set to '0' as only need one target */
>> +static bool verbose;
>> +
>> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text);
>> +
>> +static void usage(char *progname)
>> +{
>> +	fprintf(stderr,
>> +		"usage:  %s [-v] manager | client\n"
>> +		"Where:\n\t"
>> +		"-v       Print context and command information.\n\t"
>> +		"manager  Act as binder context manager.\n\t"
>> +		"client   Act as binder client.\n"
>> +		"\nNote: Ensure this boolean command is run when "
>> +		"testing after a reboot:\n\t"
>> +		"setsebool allow_domain_fd_use=0\n", progname);
>> +	exit(1);
>> +}
>> +
>> +static const char *cmd_name(uint32_t cmd)
>> +{
>> +	switch (cmd) {
>> +	case BR_NOOP:
>> +		return "BR_NOOP";
>> +	case BR_TRANSACTION_COMPLETE:
>> +		return "BR_TRANSACTION_COMPLETE";
>> +	case BR_INCREFS:
>> +		return "BR_INCREFS";
>> +	case BR_ACQUIRE:
>> +		return "BR_ACQUIRE";
>> +	case BR_RELEASE:
>> +		return "BR_RELEASE";
>> +	case BR_DECREFS:
>> +		return "BR_DECREFS";
>> +	case BR_TRANSACTION:
>> +		return "BR_TRANSACTION";
>> +	case BR_REPLY:
>> +		return "BR_REPLY";
>> +	case BR_FAILED_REPLY:
>> +		return "BR_FAILED_REPLY";
>> +	case BR_DEAD_REPLY:
>> +		return "BR_DEAD_REPLY";
>> +	case BR_DEAD_BINDER:
>> +		return "BR_DEAD_BINDER";
>> +	case BR_ERROR:
>> +		return "BR_ERROR";
>> +	/* fallthrough */
>> +	default:
>> +		return "Unknown command";
>> +	}
>> +}
>> +
>> +void print_trans_data(struct binder_transaction_data *txn)
>> +{
>> +	printf("\thandle: %ld\n", (unsigned long)txn->target.handle);
>> +	printf("\tcookie: %lld\n", txn->cookie);
>> +	printf("\tcode: %u\n", txn->code);
>> +	switch (txn->flags) {
>> +	case TF_ONE_WAY:
>> +		printf("\tflag: TF_ONE_WAY\n");
>> +		break;
>> +	case TF_ROOT_OBJECT:
>> +		printf("\tflag: TF_ROOT_OBJECT\n");
>> +		break;
>> +	case TF_STATUS_CODE:
>> +		printf("\tflag: TF_STATUS_CODE\n");
>> +		break;
>> +	case TF_ACCEPT_FDS:
>> +		printf("\tflag: TF_ACCEPT_FDS\n");
>> +		break;
>> +	default:
>> +		printf("Unknown flag: %u\n", txn->flags);
>> +	}
>> +	printf("\tsender pid: %u\n", txn->sender_pid);
>> +	printf("\tsender euid: %u\n", txn->sender_euid);
>> +	printf("\tdata_size: %llu\n", txn->data_size);
>> +	printf("\toffsets_size: %llu\n", txn->offsets_size);
>> +}
>> +
>> +
>> +static int send_reply(int fd, struct binder_transaction_data *txn_in)
>> +{
>> +	int result;
>> +	unsigned int writebuf[1024];
>> +	struct flat_binder_object obj;
>> +	struct binder_write_read bwr;
>> +	struct binder_transaction_data *txn;
>> +
>> +	if (txn_in->flags == TF_ONE_WAY) {
>> +		if (verbose)
>> +			printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
>> +		return 0;
>> +	}
>> +
>> +	if (verbose)
>> +		printf("Sending BC_REPLY\n");
>> +
>> +	writebuf[0] = BC_REPLY;
>> +	txn = (struct binder_transaction_data *)(&writebuf[1]);
>> +
>> +	memset(txn, 0, sizeof(*txn));
>> +	txn->target.handle = txn_in->target.handle;
>> +	txn->cookie = txn_in->cookie;
>> +	txn->code = txn_in->code;
>> +	txn->flags = TF_ACCEPT_FDS;
>> +
>> +	memset(&obj, 0, sizeof(struct flat_binder_object));
>> +	obj.hdr.type = BINDER_TYPE_FD;
>> +	obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
>> +	obj.binder = txn->target.handle;
>> +	obj.cookie = txn->cookie;
>> +	/* The binder fd is used for testing as it allows policy to set
>> +	 * whether the Client/Manager can be allowed access (fd use) or
>> +	 * not. For example test_binder_mgr_t has:
>> +	 *        allow test_binder_client_t test_binder_mgr_t:fd use;
>> +	 * whereas test_binder_mgr_no_fd_t does not allow this fd use.
>> +	 *
>> +	 * This also allows a check for the impersonate permission later as
>> +	 * the Client will use this (the Managers fd) to send a transaction.
>> +	 */
>> +	obj.handle = fd;
>> +
>> +	txn->data_size = sizeof(struct flat_binder_object);
>> +	txn->data.ptr.buffer = (uintptr_t)&obj;
>> +	txn->data.ptr.offsets = (uintptr_t)&obj +
>> +				sizeof(struct flat_binder_object);
>> +	txn->offsets_size = sizeof(binder_size_t);
>> +
>> +	memset(&bwr, 0, sizeof(bwr));
>> +	bwr.write_buffer = (unsigned long)writebuf;
>> +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
>> +
>> +	result = ioctl(fd, BINDER_WRITE_READ, &bwr);
>> +	if (result < 0) {
>> +		fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n",
>> +			__func__, strerror(errno));
>> +		return -1;
>> +	}
>> +
>> +	return result;
>> +}
>> +
>> +/* This retrieves the requested Managers file descriptor, then using this
>> + * sends a simple transaction to trigger the impersonate permission.
>> + */
>> +static void extract_fd_and_respond(struct binder_transaction_data *txn_in)
>> +{
>> +	int result;
>> +	uint32_t cmd;
>> +	struct stat sb;
>> +	struct binder_write_read bwr;
>> +	struct flat_binder_object *obj;
>> +	struct binder_transaction_data *txn;
>> +	unsigned int readbuf[32];
>> +	unsigned int writebuf[1024];
>> +	binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
>> +
>> +	/* Get the fbo that contains the Managers binder file descriptor. */
>> +	obj = (struct flat_binder_object *)
>> +	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
>> +
>> +	/* fstat this just to see if a valid fd */
>> +	result = fstat(obj->handle, &sb);
>> +	if (result < 0) {
>> +		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
>> +		exit(100);
>> +	}
>> +
>> +	if (verbose)
>> +		printf("Retrieved Managers fd: %d st_dev: %ld\n",
>> +		       obj->handle, sb.st_dev);
>> +
>> +	/* Send response using Managers fd to trigger impersonate check. */
>> +	writebuf[0] = BC_TRANSACTION;
>> +	txn = (struct binder_transaction_data *)(&writebuf[1]);
>> +	memset(txn, 0, sizeof(*txn));
>> +	txn->target.handle = target;
>> +	txn->cookie = 0;
>> +	txn->code = 0;
>> +	txn->flags = TF_ONE_WAY;
>> +
>> +	txn->data_size = 0;
>> +	txn->data.ptr.buffer = (uintptr_t)NULL;
>> +	txn->data.ptr.offsets = (uintptr_t)NULL;
>> +	txn->offsets_size = 0;
>> +
>> +	memset(&bwr, 0, sizeof(bwr));
>> +	bwr.write_buffer = (unsigned long)writebuf;
>> +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
>> +
>> +	bwr.read_size = sizeof(readbuf);
>> +	bwr.read_consumed = 0;
>> +	bwr.read_buffer = (uintptr_t)readbuf;
>> +
>> +	result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr);
>> +	if (result < 0) {
>> +		fprintf(stderr,
>> +			"CLIENT ioctl BINDER_WRITE_READ: %s\n",
>> +			strerror(errno));
>> +		exit(101);
>> +	}
>> +
>> +	if (verbose)
>> +		printf("Client read_consumed: %lld\n", bwr.read_consumed);
>> +
>> +	cmd = binder_parse(obj->handle, (uintptr_t)readbuf,
>> +			   bwr.read_consumed,
>> +			   "Client using Managers FD");
>> +
>> +	if (cmd == BR_FAILED_REPLY ||
>> +	    cmd == BR_DEAD_REPLY ||
>> +	    cmd == BR_DEAD_BINDER) {
>> +		fprintf(stderr,
>> +			"Client using Managers received FD failed response\n");
>> +		exit(102);
>> +	}
>> +}
>> +
>> +/* Parse response, reply as required and then return last CMD */
>> +static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text)
>> +{
>> +	uintptr_t end = ptr + (uintptr_t)size;
>> +	uint32_t cmd;
>> +
>> +	while (ptr < end) {
>> +		cmd = *(uint32_t *)ptr;
>> +		ptr += sizeof(uint32_t);
>> +
>> +		if (verbose)
>> +			printf("%s command: %s\n", text, cmd_name(cmd));
>> +
>> +		switch (cmd) {
>> +		case BR_NOOP:
>> +			break;
>> +		case BR_TRANSACTION_COMPLETE:
>> +			break;
>> +		case BR_INCREFS:
>> +		case BR_ACQUIRE:
>> +		case BR_RELEASE:
>> +		case BR_DECREFS:
>> +			ptr += sizeof(struct binder_ptr_cookie);
>> +			break;
>> +		case BR_TRANSACTION: {
>> +			struct binder_transaction_data *txn =
>> +				(struct binder_transaction_data *)ptr;
>> +
>> +			if (verbose) {
>> +				printf("BR_TRANSACTION data:\n");
>> +				print_trans_data(txn);
>> +			}
>> +
>> +			/* The manager sends reply that will contain its fd */
>> +			if (send_reply(fd, txn) < 0) {
>> +				fprintf(stderr, "send_reply() failed.\n");
>> +				return -1;
>> +			}
>> +			ptr += sizeof(*txn);
>> +			break;
>> +		}
>> +		case BR_REPLY: {
>> +			struct binder_transaction_data *txn =
>> +				(struct binder_transaction_data *)ptr;
>> +
>> +			if (verbose) {
>> +				printf("BR_REPLY data:\n");
>> +				print_trans_data(txn);
>> +			}
>> +
>> +			/* Client extracts the Manager fd, and responds */
>> +			extract_fd_and_respond(txn);
>> +			ptr += sizeof(*txn);
>> +			break;
>> +		}
>> +		case BR_DEAD_BINDER:
>> +			break;
>> +		case BR_FAILED_REPLY:
>> +			break;
>> +		case BR_DEAD_REPLY:
>> +			break;
>> +		case BR_ERROR:
>> +			ptr += sizeof(uint32_t);
>> +			break;
>> +		default:
>> +			if (verbose)
>> +				printf("%s Parsed unknown command: %d\n",
>> +				       text, cmd);
>> +			return -1;
>> +		}
>> +	}
>> +
>> +	return cmd;
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> +	int opt, option, result, fd, count;
>> +	uint32_t cmd;
>> +	pid_t pid;
>> +	char *driver = "/dev/binder";
>> +	char *context;
>> +	void *mapped;
>> +	size_t mapsize = 2048;
>> +	struct binder_write_read bwr;
>> +	struct flat_binder_object obj;
>> +	struct binder_transaction_data *txn;
>> +	unsigned int readbuf[32];
>> +	unsigned int writebuf[1024];
>> +
>> +	target = 0; /* Only need one target - the Manager */
>> +	verbose = false;
>> +
>> +	while ((opt = getopt(argc, argv, "v")) != -1) {
>> +		switch (opt) {
>> +		case 'v':
>> +			verbose = true;
>> +			break;
>> +		default:
>> +			usage(argv[0]);
>> +		}
>> +	}
>> +
>> +	if ((argc - optind) != 1)
>> +		usage(argv[0]);
>> +
>> +	if (!strcmp(argv[optind], "manager"))
>> +		option = 1;
>> +	else if (!strcmp(argv[optind], "client"))
>> +		option = 2;
>> +	else
>> +		usage(argv[0]);
>> +
>> +	fd = open(driver, O_RDWR | O_CLOEXEC);
>> +	if (fd < 0) {
>> +		fprintf(stderr, "Cannot open %s error: %s\n", driver,
>> +			strerror(errno));
>> +		exit(1);
>> +	}
>> +
>> +	/* Need this or "no VMA" error from kernel */
>> +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
>> +	if (mapped == MAP_FAILED) {
>> +		fprintf(stderr, "mmap error: %s\n", strerror(errno));
>> +		close(fd);
>> +		exit(2);
>> +	}
>> +
>> +	/* Get our context and pid */
>> +	result = getcon(&context);
>> +	if (result < 0) {
>> +		fprintf(stderr, "Failed to obtain SELinux context\n");
>> +		result = 3;
>> +		goto brexit;
>> +	}
>> +	pid = getpid();
>> +
>> +	switch (option) {
>> +	case 1: /* manager */
>> +		if (verbose) {
>> +			printf("Manager PID: %d Process context:\n\t%s\n",
>> +			       pid, context);
>> +		}
>> +
>> +		result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0);
>> +		if (result < 0) {
>> +			fprintf(stderr,
>> +				"Failed to become context manager: %s\n",
>> +				strerror(errno));
>> +			result = 4;
>> +			goto brexit;
>> +		}
>> +
>> +		readbuf[0] = BC_ENTER_LOOPER;
>> +		bwr.write_size = sizeof(readbuf[0]);
>> +		bwr.write_consumed = 0;
>> +		bwr.write_buffer = (uintptr_t)readbuf;
>> +
>> +		bwr.read_size = 0;
>> +		bwr.read_consumed = 0;
>> +		bwr.read_buffer = 0;
>> +
>> +		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
>> +		if (result < 0) {
>> +			fprintf(stderr,
>> +				"Manager ioctl BINDER_WRITE_READ: %s\n",
>> +				strerror(errno));
>> +			result = 5;
>> +			goto brexit;
>> +		}
>> +
>> +		while (true) {
>> +			bwr.read_size = sizeof(readbuf);
>> +			bwr.read_consumed = 0;
>> +			bwr.read_buffer = (uintptr_t)readbuf;
>> +
>> +			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
>> +			if (result < 0) {
>> +				fprintf(stderr,
>> +					"Manager ioctl BINDER_WRITE_READ: %s\n",
>> +					strerror(errno));
>> +				result = 6;
>> +				goto brexit;
>> +			}
>> +
>> +			if (bwr.read_consumed == 0)
>> +				continue;
>> +
>> +			if (verbose)
>> +				printf("Manager read_consumed: %lld\n",
>> +				       bwr.read_consumed);
>> +
>> +			cmd = binder_parse(fd, (uintptr_t)readbuf,
>> +					   bwr.read_consumed, "Manager");
>> +		}
>> +		break;
>> +
>> +	case 2: /* client */
>> +		if (verbose) {
>> +			printf("Client PID: %d Process context:\n\t%s\n",
>> +			       pid, context);
>> +		}
>> +
>> +		writebuf[0] = BC_TRANSACTION;
>> +		txn = (struct binder_transaction_data *)(&writebuf[1]);
>> +		memset(txn, 0, sizeof(*txn));
>> +		txn->target.handle = target;
>> +		txn->cookie = 0;
>> +		txn->code = 0;
>> +		txn->flags = TF_ACCEPT_FDS;
>> +
>> +		memset(&obj, 0, sizeof(struct flat_binder_object));
>> +		obj.hdr.type = BINDER_TYPE_WEAK_BINDER;
>> +		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
>> +		obj.binder = target;
>> +		obj.handle = 0;
>> +		obj.cookie = 0;
>> +
>> +		txn->data_size = sizeof(struct flat_binder_object);
>> +		txn->data.ptr.buffer = (uintptr_t)&obj;
>> +		txn->data.ptr.offsets = (uintptr_t)&obj +
>> +					sizeof(struct flat_binder_object);
>> +		txn->offsets_size = sizeof(binder_size_t);
>> +
>> +		memset(&bwr, 0, sizeof(bwr));
>> +		bwr.write_buffer = (unsigned long)writebuf;
>> +		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
>> +
>> +		/* Expect client to get max two responses:
>> +		 *	1) From the above BC_TRANSACTION
>> +		 *	2) The responding BC_REPLY from send_reply()
>> +		 * unless an error.
>> +		 */
>> +		count = 0;
>> +		while (count != 2) {
>> +			bwr.read_size = sizeof(readbuf);
>> +			bwr.read_consumed = 0;
>> +			bwr.read_buffer = (uintptr_t)readbuf;
>> +
>> +			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
>> +			if (result < 0) {
>> +				fprintf(stderr,
>> +					"Client ioctl BINDER_WRITE_READ: %s\n",
>> +					strerror(errno));
>> +				result = 8;
>> +				goto brexit;
>> +			}
>> +
>> +			if (verbose)
>> +				printf("Client read_consumed: %lld\n",
>> +				       bwr.read_consumed);
>> +
>> +			cmd = binder_parse(fd, (uintptr_t)readbuf,
>> +					   bwr.read_consumed, "Client");
>> +
>> +			if (cmd == BR_FAILED_REPLY ||
>> +			    cmd == BR_DEAD_REPLY ||
>> +			    cmd == BR_DEAD_BINDER) {
>> +				result = 9;
>> +				goto brexit;
>> +			}
>> +			count++;
>> +		}
>> +		break;
>> +
>> +	default:
>> +		result = -1;
>> +	}
>> +
>> +brexit:
>> +	free(context);
>> +	munmap(mapped, mapsize);
>> +	close(fd);
>> +
>> +	return result;
>> +}
>>
>
Jann Horn via Selinux May 15, 2018, 3:35 p.m. UTC | #3
On Tue, 2018-05-15 at 09:43 -0400, Stephen Smalley wrote:
> On 05/15/2018 09:36 AM, Stephen Smalley wrote:
> > On 05/15/2018 04:25 AM, Richard Haines via Selinux wrote:
> > > Add binder tests. See tests/binder/test_binder.c for details on
> > > message flows to test security_binder*() functions.
> > > 
> > > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
> > > ---
> > >  README.md                   |   8 +
> > >  defconfig                   |   8 +
> > >  policy/Makefile             |   2 +-
> > >  policy/test_binder.te       |  83 +++++++
> > >  tests/Makefile              |   2 +-
> > >  tests/binder/Makefile       |   7 +
> > >  tests/binder/check_binder.c |  80 +++++++
> > >  tests/binder/test           | 131 +++++++++++
> > >  tests/binder/test_binder.c  | 543
> > > ++++++++++++++++++++++++++++++++++++++++++++
> > >  9 files changed, 862 insertions(+), 2 deletions(-)
> > >  create mode 100644 policy/test_binder.te
> > >  create mode 100644 tests/binder/Makefile
> > >  create mode 100644 tests/binder/check_binder.c
> > >  create mode 100644 tests/binder/test
> > >  create mode 100644 tests/binder/test_binder.c
> > > 
> > > diff --git a/README.md b/README.md
> > > index c9f3b2b..60a249e 100644
> > > --- a/README.md
> > > +++ b/README.md
> > > @@ -141,6 +141,14 @@ directory or you can follow these broken-out 
> > > steps:
> > >  The broken-out steps allow you to run the tests multiple times
> > > without
> > >  loading policy each time.
> > >  
> > > +Note that if leaving the test policy in-place for further
> > > testing, the
> > > +policy build process changes a boolean:
> > > +   On policy load:   setsebool allow_domain_fd_use=0
> > > +   On policy unload: setsebool allow_domain_fd_use=1
> > > +The consequence of this is that after a system reboot, the
> > > boolean
> > > +defaults to true. Therefore if running the fdreceive or binder
> > > tests,
> > > +reset the boolean to false, otherwise some tests will fail.
> > 
> > This isn't accurate - we aren't doing setsebool -P so the boolean
> > change is not persistent across
> > reboots.  It will persist across policy reloads however because the
> > kernel preserves booleans across
> > policy reloads.
> 
> Sorry, never mind - I misread the text above.  You are correct.
> 
> > 
> > > +
> > >  4) Review the test results.
> > >  
> > >  As each test script is run, the name of the script will be
> > > displayed followed
> > > diff --git a/defconfig b/defconfig
> > > index 7dce8bc..dc6ef30 100644
> > > --- a/defconfig
> > > +++ b/defconfig
> > > @@ -51,3 +51,11 @@ CONFIG_CRYPTO_USER=m
> > >  # This is enabled to test overlayfs SELinux integration.
> > >  # It is not required for SELinux operation itself.
> > >  CONFIG_OVERLAY_FS=m
> > > +
> > > +# Android binder implementations.
> > > +# These are enabled to test the binder controls in
> > > +# tests/binder; they are not required for SELinux operation
> > > itself.
> > > +CONFIG_ANDROID=y
> > > +CONFIG_ANDROID_BINDER_IPC=y
> > > +CONFIG_ANDROID_BINDER_DEVICES="binder"
> > > +# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set
> > 
> > I don't think we need the last line.

It appears it is requred as if not there are complaints when building,
in fact I missed some out and should be:

# Android binder implementations.
# These are enabled to test the binder controls in
# tests/binder; they are not required for SELinux operation itself.
# The 'is not set' items MUST be included unless they are required by
 your configuration.
# CONFIG_ASHMEM is not set
# CONFIG_ION is not set
CONFIG_ANDROID_BINDER_DEVICES="binder"
CONFIG_ANDROID_BINDER_IPC=y
# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set

> > 
> > > diff --git a/policy/Makefile b/policy/Makefile
> > > index 8ed5e46..5a9d411 100644
> > > --- a/policy/Makefile
> > > +++ b/policy/Makefile
> > > @@ -25,7 +25,7 @@ TARGETS = \
> > >  	test_task_getsid.te test_task_setpgid.te
> > > test_task_setsched.te \
> > >  	test_transition.te test_inet_socket.te
> > > test_unix_socket.te \
> > >  	test_mmap.te test_overlayfs.te test_mqueue.te
> > > test_mac_admin.te \
> > > -	test_ibpkey.te test_atsecure.te
> > > +	test_ibpkey.te test_atsecure.te test_binder.te
> > 
> > Likely need to make this conditional on the binder class being
> > defined in the policy;
> > see similar logic for e.g. cap_userns, icmp_socket, etc.  Otherwise
> > policy won't build
> > on earlier Fedora/RHEL before definition of the binder class.
> > 

I'll fix

> > >  
> > >  ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true)
> > >  TARGETS += test_bounds.te
> > > diff --git a/policy/test_binder.te b/policy/test_binder.te
> > > new file mode 100644
> > > index 0000000..c4ad2ae
> > > --- /dev/null
> > > +++ b/policy/test_binder.te
> > > @@ -0,0 +1,83 @@
> > > +
> > > +attribute binderdomain;
> > > +
> > > +#
> > > +################################## Manager
> > > ###################################
> > > +#
> > > +type test_binder_mgr_t;
> > > +domain_type(test_binder_mgr_t)
> > > +unconfined_runs_test(test_binder_mgr_t)
> > > +typeattribute test_binder_mgr_t testdomain;
> > > +typeattribute test_binder_mgr_t binderdomain;
> > > +allow test_binder_mgr_t self:binder { set_context_mgr call };
> > > +allow test_binder_mgr_t device_t:chr_file { ioctl open read
> > > write map };
> > 
> > Wondering if we should define a .fc file with /dev/binder and a
> > proper binder_device_t type
> > and restorecon it before the test.  But not clear it is worth
> > it.  Never mind.
> > 
> > > +allow test_binder_mgr_t self:capability { sys_nice };
> > 
> > Needed or just to suppress noise in the audit?

The binder driver does something with nice, but tests work okay
without.

> > 
> > > +allow test_binder_client_t test_binder_mgr_t:fd use;
> > > +
> > > +#
> > > +################################# Client
> > > ####################################
> > > +#
> > > +type test_binder_client_t;
> > > +domain_type(test_binder_client_t)
> > > +unconfined_runs_test(test_binder_client_t)
> > > +typeattribute test_binder_client_t testdomain;
> > > +typeattribute test_binder_client_t binderdomain;
> > > +allow test_binder_client_t self:binder { call };
> > > +allow test_binder_client_t test_binder_mgr_t:binder { call
> > > transfer impersonate };
> > 
> > Are you actually exercising impersonate?  I largely didn't expect
> > it to ever be used in Android itself,
> > just included the check because I saw that it was technically
> > possible as far as the kernel interface
> > is concerned.

I think I am. The client requests the Managers fd, then uses it to send
message. I patched kernel to check this. Also I read your helpful note:

https://marc.info/?l=seandroid-list&m=141901419332280&w=2


> > 
> > > +allow test_binder_client_t device_t:chr_file { ioctl open read
> > > write map };
> > > +# For fstat:
> > > +allow test_binder_client_t device_t:chr_file getattr;
> > > +
> > > +#
> > > +############################## Client no call
> > > ################################
> > > +#
> > > +type test_binder_client_no_call_t;
> > > +domain_type(test_binder_client_no_call_t)
> > > +unconfined_runs_test(test_binder_client_no_call_t)
> > > +typeattribute test_binder_client_no_call_t testdomain;
> > > +typeattribute test_binder_client_no_call_t binderdomain;
> > > +allow test_binder_client_no_call_t device_t:chr_file { ioctl
> > > open read write map };
> > > +
> > > +#
> > > +############################ Client no transfer
> > > #############################
> > > +#
> > > +type test_binder_client_no_transfer_t;
> > > +domain_type(test_binder_client_no_transfer_t)
> > > +unconfined_runs_test(test_binder_client_no_transfer_t)
> > > +typeattribute test_binder_client_no_transfer_t testdomain;
> > > +typeattribute test_binder_client_no_transfer_t binderdomain;
> > > +allow test_binder_client_no_transfer_t test_binder_mgr_t:binder
> > > { call };
> > > +allow test_binder_client_no_transfer_t device_t:chr_file { ioctl
> > > open read write map };
> > > +
> > > +#
> > > +########################## Manager no fd {use}
> > > ###############################
> > > +#
> > > +type test_binder_mgr_no_fd_t;
> > > +domain_type(test_binder_mgr_no_fd_t)
> > > +unconfined_runs_test(test_binder_mgr_no_fd_t)
> > > +typeattribute test_binder_mgr_no_fd_t testdomain;
> > > +typeattribute test_binder_mgr_no_fd_t binderdomain;
> > > +allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call
> > > };
> > > +allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open
> > > read write map };
> > > +allow test_binder_mgr_no_fd_t self:capability { sys_nice };
> > > +allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call
> > > transfer };
> > > +
> > > +#
> > > +########################### Client no impersonate
> > > ############################
> > > +#
> > > +type test_binder_client_no_im_t;
> > > +domain_type(test_binder_client_no_im_t)
> > > +unconfined_runs_test(test_binder_client_no_im_t)
> > > +typeattribute test_binder_client_no_im_t testdomain;
> > > +typeattribute test_binder_client_no_im_t binderdomain;
> > > +allow test_binder_client_no_im_t self:binder { call };
> > > +allow test_binder_client_no_im_t test_binder_mgr_t:binder { call
> > > transfer };
> > > +allow test_binder_client_no_im_t device_t:chr_file { ioctl open
> > > read write map };
> > > +allow test_binder_client_no_im_t test_binder_mgr_t:fd use;
> > > +allow test_binder_client_no_im_t device_t:chr_file getattr;
> > > +
> > > +#
> > > +############ Allow these domains to be entered from sysadm
> > > domain ############
> > > +#
> > > +miscfiles_domain_entry_test_files(binderdomain)
> > > +userdom_sysadm_entry_spec_domtrans_to(binderdomain)
> > > diff --git a/tests/Makefile b/tests/Makefile
> > > index 27ed6eb..7607c22 100644
> > > --- a/tests/Makefile
> > > +++ b/tests/Makefile
> > > @@ -10,7 +10,7 @@ SUBDIRS:= domain_trans entrypoint execshare
> > > exectrace execute_no_trans \
> > >  	task_setnice task_setscheduler task_getscheduler
> > > task_getsid \
> > >  	task_getpgid task_setpgid file ioctl capable_file
> > > capable_net \
> > >  	capable_sys dyntrans dyntrace bounds nnp_nosuid mmap
> > > unix_socket \
> > > -        inet_socket overlay checkreqprot mqueue mac_admin
> > > atsecure
> > > +        inet_socket overlay checkreqprot mqueue mac_admin
> > > atsecure binder
> > 
> > Likely needs to be conditional on binder class being defined in
> > policy as with cap_userns,
> > and also depends on e.g. linux/android/binder.h
> > existing?  Otherwise will break build on earlier
> > Fedora/RHEL releases.
> > 

I'll fix

> > >  
> > >  ifeq ($(shell grep -q cap_userns
> > > $(POLDEV)/include/support/all_perms.spt && echo true),true)
> > >  ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1)
> > > diff --git a/tests/binder/Makefile b/tests/binder/Makefile
> > > new file mode 100644
> > > index 0000000..a60eeb3
> > > --- /dev/null
> > > +++ b/tests/binder/Makefile
> > > @@ -0,0 +1,7 @@
> > > +TARGETS = check_binder test_binder
> > > +
> > > +LDLIBS += -lselinux
> > > +
> > > +all: $(TARGETS)
> > > +clean:
> > > +	rm -f $(TARGETS)
> > > diff --git a/tests/binder/check_binder.c
> > > b/tests/binder/check_binder.c
> > > new file mode 100644
> > > index 0000000..3d553a0
> > > --- /dev/null
> > > +++ b/tests/binder/check_binder.c
> > > @@ -0,0 +1,80 @@
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +#include <unistd.h>
> > > +#include <fcntl.h>
> > > +#include <errno.h>
> > > +#include <stdbool.h>
> > > +#include <sys/mman.h>
> > > +#include <sys/ioctl.h>
> > > +#include <linux/android/binder.h>
> > > +
> > > +static void usage(char *progname)
> > > +{
> > > +	fprintf(stderr,
> > > +		"usage:  %s [-v]\n"
> > > +		"Where:\n\t"
> > > +		"-v Print binder version.\n", progname);
> > > +	exit(-1);
> > > +}
> > > +
> > > +int main(int argc, char **argv)
> > > +{
> > > +	int opt, result, fd;
> > > +	char *driver = "/dev/binder";
> > > +	bool verbose;
> > > +	void *mapped;
> > > +	size_t mapsize = 1024;
> > > +	struct binder_version vers;
> > > +
> > > +	while ((opt = getopt(argc, argv, "v")) != -1) {
> > > +		switch (opt) {
> > > +		case 'v':
> > > +			verbose = true;
> > > +			break;
> > > +		default:
> > > +			usage(argv[0]);
> > > +		}
> > > +	}
> > > +
> > > +	fd = open(driver, O_RDWR | O_CLOEXEC);
> > > +	if (fd < 0) {
> > > +		fprintf(stderr, "Cannot open: %s error: %s\n",
> > > +			driver, strerror(errno));
> > > +		exit(-1);
> > > +	}
> > > +
> > > +	/* Need this or no VMA error from kernel */
> > > +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd,
> > > 0);
> > > +	if (mapped == MAP_FAILED) {
> > > +		fprintf(stderr, "mmap error: %s\n",
> > > strerror(errno));
> > > +		close(fd);
> > > +		exit(-1);
> > > +	}
> > > +
> > > +	result = ioctl(fd, BINDER_VERSION, &vers);
> > > +	if (result < 0) {
> > > +		fprintf(stderr, "ioctl BINDER_VERSION: %s\n",
> > > +			strerror(errno));
> > > +		goto brexit;
> > > +	}
> > > +
> > > +	if (vers.protocol_version !=
> > > BINDER_CURRENT_PROTOCOL_VERSION) {
> > > +		fprintf(stderr,
> > > +			"Binder kernel version: %d differs from
> > > user space version: %d\n",
> > > +			vers.protocol_version,
> > > +			BINDER_CURRENT_PROTOCOL_VERSION);
> > > +		result = -1;
> > > +		goto brexit;
> > > +	}
> > > +
> > > +	if (verbose)
> > > +		fprintf(stderr, "Binder kernel version: %d\n",
> > > +			vers.protocol_version);
> > > +
> > > +brexit:
> > > +	munmap(mapped, mapsize);
> > > +	close(fd);
> > > +
> > > +	return result;
> > > +}
> > > diff --git a/tests/binder/test b/tests/binder/test
> > > new file mode 100644
> > > index 0000000..434ae32
> > > --- /dev/null
> > > +++ b/tests/binder/test
> > > @@ -0,0 +1,131 @@
> > > +#!/usr/bin/perl
> > > +use Test::More;
> > > +
> > > +BEGIN {
> > > +    $basedir = $0;
> > > +    $basedir =~ s|(.*)/[^/]*|$1|;
> > > +
> > > +    # allow binder info to be shown
> > > +    $v = $ARGV[0];
> > > +    if ($v) {
> > > +        if ( $v ne "-v" ) {
> > > +            plan skip_all => "Invalid option (use -v)";
> > > +        }
> > > +    }
> > > +
> > > +    # check if binder driver available and the kernel/userspace
> > > versions.
> > > +    if ( system("$basedir/check_binder 2> /dev/null") != 0 ) {
> > > +        plan skip_all =>
> > > +          "Binder not supported or kernel/userspace versions
> > > differ";
> > > +    }
> > > +    else {
> > > +        plan tests => 6;
> > > +    }
> > > +}
> > > +
> > > +if ($v) {
> > 
> > Duplicating the test cases for $v and !$v seems prone to
> > inconsistency in the future;
> > can't we just embed $v into the command string being executed?

I'll find another way as I tried that but perl complained about
uninitialised variables (or something like that)

> > 
> > > +    if ( ( $pid = fork() ) == 0 ) {
> > > +        exec "runcon -t test_binder_mgr_t $basedir/test_binder
> > > -v manager";
> > > +    }
> > > +
> > > +    select( undef, undef, undef, 0.25 );    # Give it a moment
> > > to initialize.
> > > +
> > > +    # Verify that authorized client can transact with the
> > > manager.
> > > +    $result =
> > > +      system "runcon -t test_binder_client_t
> > > $basedir/test_binder -v client";
> > > +    ok( $result eq 0 );
> > 
> > This test is failing for me (with or without -v):
> > # ./test -v
> > 1..6
> > Manager PID: 5608 Process context:
> > 	unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023
> > Client PID: 5609 Process context:
> > 	unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023
> > Client read_consumed: 28
> > Manager read_consumed: 72
> > Client command: BR_NOOP
> > Manager command: BR_NOOP
> > Client command: BR_INCREFS
> > Manager command: BR_TRANSACTION
> > Client command: BR_TRANSACTION_COMPLETE
> > BR_TRANSACTION data:
> > 	handle: 0
> > 	cookie: 0
> > 	code: 0
> > 	flag: TF_ACCEPT_FDS
> > 	sender pid: 5609
> > 	sender euid: 0
> > 	data_size: 24
> > 	offsets_size: 8
> > Sending BC_REPLY
> > Manager read_consumed: 8
> > Manager command: BR_NOOP
> > Manager command: BR_TRANSACTION_COMPLETE
> > Client read_consumed: 72
> > Client command: BR_NOOP
> > Client command: BR_REPLY
> > BR_REPLY data:
> > 	handle: 0
> > 	cookie: 0
> > 	code: 0
> > 	flag: TF_ACCEPT_FDS
> > 	sender pid: 0
> > 	sender euid: 0
> > 	data_size: 24
> > 	offsets_size: 8
> > Retrieved Managers fd: 4 st_dev: 6
> > Client read_consumed: 8
> > Client using Managers FD command: BR_NOOP
> > Client using Managers FD command: BR_FAILED_REPLY
> > Client using Managers received FD failed response
> > Manager read_consumed: 4
> > Manager command: BR_NOOP
> > not ok 1
> > #   Failed test at ./test line 36.
> 
> Just realized that I was testing with a kernel that still had Casey's
> stacking support enabled.
> Will re-try without that.

I'm rebuilding on Fedora 28 so I'll test that as well.

> 
> > 
> > 
> > > +
> > > +    # Verify that client cannot call manager (no call perm).
> > > +    $result = system
> > > +"runcon -t test_binder_client_no_call_t $basedir/test_binder -v
> > > client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Verify that client cannot communicate with manager (no
> > > impersonate perm).
> > > +    $result = system
> > > +"runcon -t test_binder_client_no_im_t $basedir/test_binder -v
> > > client 2>&1";
> > > +    ok( $result >> 8 eq 102 );
> > > +
> > > +    # Verify that client cannot communicate with manager (no
> > > transfer perm).
> > > +    $result = system
> > > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder
> > > -v client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Kill the manager.
> > > +    kill TERM, $pid;
> > > +
> > > +    # Verify that client cannot become a manager (no
> > > set_context_mgr perm).
> > > +    $result =
> > > +      system
> > > +      "runcon -t test_binder_client_t $basedir/test_binder -v
> > > manager 2>&1";
> > > +    ok( $result >> 8 eq 4 );
> > > +
> > > +# Start manager to test that selinux_binder_transfer_file()
> > > fails when fd { use } is denied by policy.
> > > +    if ( ( $pid = fork() ) == 0 ) {
> > > +        exec
> > > +          "runcon -t test_binder_mgr_no_fd_t
> > > $basedir/test_binder -v manager";
> > > +    }
> > > +
> > > +    select( undef, undef, undef, 0.25 );    # Give it a moment
> > > to initialize.
> > > +
> > > +# Verify that authorized client can communicate with the server,
> > > however the fd passed will not be valid for manager
> > > +# domain and binder will return BR_FAILED_REPLY.
> > > +    $result =
> > > +      system
> > > +      "runcon -t test_binder_client_t $basedir/test_binder -v
> > > client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Kill the manager
> > > +    kill TERM, $pid;
> > > +}
> > > +else {
> > > +    if ( ( $pid = fork() ) == 0 ) {
> > > +        exec "runcon -t test_binder_mgr_t $basedir/test_binder
> > > manager";
> > > +    }
> > > +
> > > +    select( undef, undef, undef, 0.25 );    # Give it a moment
> > > to initialize.
> > > +
> > > +    # Verify that authorized client can transact with the
> > > manager.
> > > +    $result =
> > > +      system "runcon -t test_binder_client_t
> > > $basedir/test_binder client";
> > > +    ok( $result eq 0 );
> > > +
> > > +    # Verify that client cannot call manager (no call perm).
> > > +    $result = system
> > > +      "runcon -t test_binder_client_no_call_t
> > > $basedir/test_binder client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Verify that client cannot communicate with manager (no
> > > impersonate perm).
> > > +    $result = system
> > > +      "runcon -t test_binder_client_no_im_t $basedir/test_binder
> > > client 2>&1";
> > > +    ok( $result >> 8 eq 102 );
> > > +
> > > +    # Verify that client cannot communicate with manager (no
> > > transfer perm).
> > > +    $result = system
> > > +"runcon -t test_binder_client_no_transfer_t $basedir/test_binder
> > > client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Kill the manager.
> > > +    kill TERM, $pid;
> > > +
> > > +    # Verify that client cannot become a manager (no
> > > set_context_mgr perm).
> > > +    $result =
> > > +      system "runcon -t test_binder_client_t
> > > $basedir/test_binder manager 2>&1";
> > > +    ok( $result >> 8 eq 4 );
> > > +
> > > +# Start manager to test that selinux_binder_transfer_file()
> > > fails when fd { use } is denied by policy.
> > > +    if ( ( $pid = fork() ) == 0 ) {
> > > +        exec "runcon -t test_binder_mgr_no_fd_t
> > > $basedir/test_binder manager";
> > > +    }
> > > +
> > > +    select( undef, undef, undef, 0.25 );    # Give it a moment
> > > to initialize.
> > > +
> > > +# Verify that authorized client can communicate with the server,
> > > however the fd passed will not be valid for manager
> > > +# domain and binder will return BR_FAILED_REPLY.
> > > +    $result =
> > > +      system "runcon -t test_binder_client_t
> > > $basedir/test_binder client 2>&1";
> > > +    ok( $result >> 8 eq 9 );
> > > +
> > > +    # Kill the manager
> > > +    kill TERM, $pid;
> > > +}
> > > +exit;
> > > diff --git a/tests/binder/test_binder.c
> > > b/tests/binder/test_binder.c
> > > new file mode 100644
> > > index 0000000..8881cce
> > > --- /dev/null
> > > +++ b/tests/binder/test_binder.c
> > > @@ -0,0 +1,543 @@
> > > +/*
> > > + * This is a simple binder client/server(manager) that only uses
> > > the
> > > + * raw ioctl commands. It does not rely on a 'service manager'
> > > as in
> > > + * the Android world as it only uses one 'target' = 0.
> > > + *
> > > + * The transaction/reply flow is basically:
> > > + *      Client                                  Manager
> > > + *     ========                                =========
> > > + *
> > > + *                                        Becomes context
> > > manager
> > > + *   Send transaction
> > > + *   requesting file
> > > + *   descriptor from
> > > + *    the Manager
> > > + *         --------------------------------------->
> > > + *                                       Manager replies with
> > > its
> > > + *                                        binder file descriptor
> > > + *         <--------------------------------------
> > > + *     Check fd valid, if so send
> > > + *  TF_ONE_WAY transaction to Manager
> > > + *  using the received fd
> > > + *          --------------------------------------->
> > > + *                                        End of tests so
> > > Manager
> > > + *                                          waits to be killed
> > > + *   Client exits
> > > + *
> > > + * Using binder test policy the following will be validated:
> > > + *    security_binder_set_context_mgr() binder { set_context_mgr
> > > }
> > > + *    security_binder_transaction()     binder { call
> > > impersonate }
> > > + *    security_binder_transfer_binder() binder { transfer }
> > > + *    security_binder_transfer_file()   fd { use }
> > > + *
> > > + * TODO security_binder_transfer_file() uses BPF if configured
> > > in kernel.
> > > + */
> > > +
> > > +#include <errno.h>
> > > +#include <fcntl.h>
> > > +#include <inttypes.h>
> > > +#include <stdio.h>
> > > +#include <stdlib.h>
> > > +#include <string.h>
> > > +#include <unistd.h>
> > > +#include <sys/stat.h>
> > > +#include <stdbool.h>
> > > +#include <sys/mman.h>
> > > +#include <sys/ioctl.h>
> > > +#include <selinux/selinux.h>
> > > +#include <linux/android/binder.h>
> > > +
> > > +static uint32_t target; /* This will be set to '0' as only need
> > > one target */
> > > +static bool verbose;
> > > +
> > > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char
> > > *text);
> > > +
> > > +static void usage(char *progname)
> > > +{
> > > +	fprintf(stderr,
> > > +		"usage:  %s [-v] manager | client\n"
> > > +		"Where:\n\t"
> > > +		"-v       Print context and command
> > > information.\n\t"
> > > +		"manager  Act as binder context manager.\n\t"
> > > +		"client   Act as binder client.\n"
> > > +		"\nNote: Ensure this boolean command is run when
> > > "
> > > +		"testing after a reboot:\n\t"
> > > +		"setsebool allow_domain_fd_use=0\n", progname);
> > > +	exit(1);
> > > +}
> > > +
> > > +static const char *cmd_name(uint32_t cmd)
> > > +{
> > > +	switch (cmd) {
> > > +	case BR_NOOP:
> > > +		return "BR_NOOP";
> > > +	case BR_TRANSACTION_COMPLETE:
> > > +		return "BR_TRANSACTION_COMPLETE";
> > > +	case BR_INCREFS:
> > > +		return "BR_INCREFS";
> > > +	case BR_ACQUIRE:
> > > +		return "BR_ACQUIRE";
> > > +	case BR_RELEASE:
> > > +		return "BR_RELEASE";
> > > +	case BR_DECREFS:
> > > +		return "BR_DECREFS";
> > > +	case BR_TRANSACTION:
> > > +		return "BR_TRANSACTION";
> > > +	case BR_REPLY:
> > > +		return "BR_REPLY";
> > > +	case BR_FAILED_REPLY:
> > > +		return "BR_FAILED_REPLY";
> > > +	case BR_DEAD_REPLY:
> > > +		return "BR_DEAD_REPLY";
> > > +	case BR_DEAD_BINDER:
> > > +		return "BR_DEAD_BINDER";
> > > +	case BR_ERROR:
> > > +		return "BR_ERROR";
> > > +	/* fallthrough */
> > > +	default:
> > > +		return "Unknown command";
> > > +	}
> > > +}
> > > +
> > > +void print_trans_data(struct binder_transaction_data *txn)
> > > +{
> > > +	printf("\thandle: %ld\n", (unsigned long)txn-
> > > >target.handle);
> > > +	printf("\tcookie: %lld\n", txn->cookie);
> > > +	printf("\tcode: %u\n", txn->code);
> > > +	switch (txn->flags) {
> > > +	case TF_ONE_WAY:
> > > +		printf("\tflag: TF_ONE_WAY\n");
> > > +		break;
> > > +	case TF_ROOT_OBJECT:
> > > +		printf("\tflag: TF_ROOT_OBJECT\n");
> > > +		break;
> > > +	case TF_STATUS_CODE:
> > > +		printf("\tflag: TF_STATUS_CODE\n");
> > > +		break;
> > > +	case TF_ACCEPT_FDS:
> > > +		printf("\tflag: TF_ACCEPT_FDS\n");
> > > +		break;
> > > +	default:
> > > +		printf("Unknown flag: %u\n", txn->flags);
> > > +	}
> > > +	printf("\tsender pid: %u\n", txn->sender_pid);
> > > +	printf("\tsender euid: %u\n", txn->sender_euid);
> > > +	printf("\tdata_size: %llu\n", txn->data_size);
> > > +	printf("\toffsets_size: %llu\n", txn->offsets_size);
> > > +}
> > > +
> > > +
> > > +static int send_reply(int fd, struct binder_transaction_data
> > > *txn_in)
> > > +{
> > > +	int result;
> > > +	unsigned int writebuf[1024];
> > > +	struct flat_binder_object obj;
> > > +	struct binder_write_read bwr;
> > > +	struct binder_transaction_data *txn;
> > > +
> > > +	if (txn_in->flags == TF_ONE_WAY) {
> > > +		if (verbose)
> > > +			printf("No reply to BC_TRANSACTION as
> > > flags = TF_ONE_WAY\n");
> > > +		return 0;
> > > +	}
> > > +
> > > +	if (verbose)
> > > +		printf("Sending BC_REPLY\n");
> > > +
> > > +	writebuf[0] = BC_REPLY;
> > > +	txn = (struct binder_transaction_data *)(&writebuf[1]);
> > > +
> > > +	memset(txn, 0, sizeof(*txn));
> > > +	txn->target.handle = txn_in->target.handle;
> > > +	txn->cookie = txn_in->cookie;
> > > +	txn->code = txn_in->code;
> > > +	txn->flags = TF_ACCEPT_FDS;
> > > +
> > > +	memset(&obj, 0, sizeof(struct flat_binder_object));
> > > +	obj.hdr.type = BINDER_TYPE_FD;
> > > +	obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> > > +	obj.binder = txn->target.handle;
> > > +	obj.cookie = txn->cookie;
> > > +	/* The binder fd is used for testing as it allows policy
> > > to set
> > > +	 * whether the Client/Manager can be allowed access (fd
> > > use) or
> > > +	 * not. For example test_binder_mgr_t has:
> > > +	 *        allow test_binder_client_t
> > > test_binder_mgr_t:fd use;
> > > +	 * whereas test_binder_mgr_no_fd_t does not allow this
> > > fd use.
> > > +	 *
> > > +	 * This also allows a check for the impersonate
> > > permission later as
> > > +	 * the Client will use this (the Managers fd) to send a
> > > transaction.
> > > +	 */
> > > +	obj.handle = fd;
> > > +
> > > +	txn->data_size = sizeof(struct flat_binder_object);
> > > +	txn->data.ptr.buffer = (uintptr_t)&obj;
> > > +	txn->data.ptr.offsets = (uintptr_t)&obj +
> > > +				sizeof(struct
> > > flat_binder_object);
> > > +	txn->offsets_size = sizeof(binder_size_t);
> > > +
> > > +	memset(&bwr, 0, sizeof(bwr));
> > > +	bwr.write_buffer = (unsigned long)writebuf;
> > > +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> > > +
> > > +	result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> > > +	if (result < 0) {
> > > +		fprintf(stderr, "%s ioctl BINDER_WRITE_READ:
> > > %s\n",
> > > +			__func__, strerror(errno));
> > > +		return -1;
> > > +	}
> > > +
> > > +	return result;
> > > +}
> > > +
> > > +/* This retrieves the requested Managers file descriptor, then
> > > using this
> > > + * sends a simple transaction to trigger the impersonate
> > > permission.
> > > + */
> > > +static void extract_fd_and_respond(struct
> > > binder_transaction_data *txn_in)
> > > +{
> > > +	int result;
> > > +	uint32_t cmd;
> > > +	struct stat sb;
> > > +	struct binder_write_read bwr;
> > > +	struct flat_binder_object *obj;
> > > +	struct binder_transaction_data *txn;
> > > +	unsigned int readbuf[32];
> > > +	unsigned int writebuf[1024];
> > > +	binder_size_t *offs = (binder_size_t
> > > *)(uintptr_t)txn_in->data.ptr.offsets;
> > > +
> > > +	/* Get the fbo that contains the Managers binder file
> > > descriptor. */
> > > +	obj = (struct flat_binder_object *)
> > > +	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) +
> > > *offs);
> > > +
> > > +	/* fstat this just to see if a valid fd */
> > > +	result = fstat(obj->handle, &sb);
> > > +	if (result < 0) {
> > > +		fprintf(stderr, "Not a valid fd: %s\n",
> > > strerror(errno));
> > > +		exit(100);
> > > +	}
> > > +
> > > +	if (verbose)
> > > +		printf("Retrieved Managers fd: %d st_dev:
> > > %ld\n",
> > > +		       obj->handle, sb.st_dev);
> > > +
> > > +	/* Send response using Managers fd to trigger
> > > impersonate check. */
> > > +	writebuf[0] = BC_TRANSACTION;
> > > +	txn = (struct binder_transaction_data *)(&writebuf[1]);
> > > +	memset(txn, 0, sizeof(*txn));
> > > +	txn->target.handle = target;
> > > +	txn->cookie = 0;
> > > +	txn->code = 0;
> > > +	txn->flags = TF_ONE_WAY;
> > > +
> > > +	txn->data_size = 0;
> > > +	txn->data.ptr.buffer = (uintptr_t)NULL;
> > > +	txn->data.ptr.offsets = (uintptr_t)NULL;
> > > +	txn->offsets_size = 0;
> > > +
> > > +	memset(&bwr, 0, sizeof(bwr));
> > > +	bwr.write_buffer = (unsigned long)writebuf;
> > > +	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
> > > +
> > > +	bwr.read_size = sizeof(readbuf);
> > > +	bwr.read_consumed = 0;
> > > +	bwr.read_buffer = (uintptr_t)readbuf;
> > > +
> > > +	result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr);
> > > +	if (result < 0) {
> > > +		fprintf(stderr,
> > > +			"CLIENT ioctl BINDER_WRITE_READ: %s\n",
> > > +			strerror(errno));
> > > +		exit(101);
> > > +	}
> > > +
> > > +	if (verbose)
> > > +		printf("Client read_consumed: %lld\n",
> > > bwr.read_consumed);
> > > +
> > > +	cmd = binder_parse(obj->handle, (uintptr_t)readbuf,
> > > +			   bwr.read_consumed,
> > > +			   "Client using Managers FD");
> > > +
> > > +	if (cmd == BR_FAILED_REPLY ||
> > > +	    cmd == BR_DEAD_REPLY ||
> > > +	    cmd == BR_DEAD_BINDER) {
> > > +		fprintf(stderr,
> > > +			"Client using Managers received FD
> > > failed response\n");
> > > +		exit(102);
> > > +	}
> > > +}
> > > +
> > > +/* Parse response, reply as required and then return last CMD */
> > > +static int binder_parse(int fd, uintptr_t ptr, size_t size, char
> > > *text)
> > > +{
> > > +	uintptr_t end = ptr + (uintptr_t)size;
> > > +	uint32_t cmd;
> > > +
> > > +	while (ptr < end) {
> > > +		cmd = *(uint32_t *)ptr;
> > > +		ptr += sizeof(uint32_t);
> > > +
> > > +		if (verbose)
> > > +			printf("%s command: %s\n", text,
> > > cmd_name(cmd));
> > > +
> > > +		switch (cmd) {
> > > +		case BR_NOOP:
> > > +			break;
> > > +		case BR_TRANSACTION_COMPLETE:
> > > +			break;
> > > +		case BR_INCREFS:
> > > +		case BR_ACQUIRE:
> > > +		case BR_RELEASE:
> > > +		case BR_DECREFS:
> > > +			ptr += sizeof(struct binder_ptr_cookie);
> > > +			break;
> > > +		case BR_TRANSACTION: {
> > > +			struct binder_transaction_data *txn =
> > > +				(struct binder_transaction_data
> > > *)ptr;
> > > +
> > > +			if (verbose) {
> > > +				printf("BR_TRANSACTION
> > > data:\n");
> > > +				print_trans_data(txn);
> > > +			}
> > > +
> > > +			/* The manager sends reply that will
> > > contain its fd */
> > > +			if (send_reply(fd, txn) < 0) {
> > > +				fprintf(stderr, "send_reply()
> > > failed.\n");
> > > +				return -1;
> > > +			}
> > > +			ptr += sizeof(*txn);
> > > +			break;
> > > +		}
> > > +		case BR_REPLY: {
> > > +			struct binder_transaction_data *txn =
> > > +				(struct binder_transaction_data
> > > *)ptr;
> > > +
> > > +			if (verbose) {
> > > +				printf("BR_REPLY data:\n");
> > > +				print_trans_data(txn);
> > > +			}
> > > +
> > > +			/* Client extracts the Manager fd, and
> > > responds */
> > > +			extract_fd_and_respond(txn);
> > > +			ptr += sizeof(*txn);
> > > +			break;
> > > +		}
> > > +		case BR_DEAD_BINDER:
> > > +			break;
> > > +		case BR_FAILED_REPLY:
> > > +			break;
> > > +		case BR_DEAD_REPLY:
> > > +			break;
> > > +		case BR_ERROR:
> > > +			ptr += sizeof(uint32_t);
> > > +			break;
> > > +		default:
> > > +			if (verbose)
> > > +				printf("%s Parsed unknown
> > > command: %d\n",
> > > +				       text, cmd);
> > > +			return -1;
> > > +		}
> > > +	}
> > > +
> > > +	return cmd;
> > > +}
> > > +
> > > +int main(int argc, char **argv)
> > > +{
> > > +	int opt, option, result, fd, count;
> > > +	uint32_t cmd;
> > > +	pid_t pid;
> > > +	char *driver = "/dev/binder";
> > > +	char *context;
> > > +	void *mapped;
> > > +	size_t mapsize = 2048;
> > > +	struct binder_write_read bwr;
> > > +	struct flat_binder_object obj;
> > > +	struct binder_transaction_data *txn;
> > > +	unsigned int readbuf[32];
> > > +	unsigned int writebuf[1024];
> > > +
> > > +	target = 0; /* Only need one target - the Manager */
> > > +	verbose = false;
> > > +
> > > +	while ((opt = getopt(argc, argv, "v")) != -1) {
> > > +		switch (opt) {
> > > +		case 'v':
> > > +			verbose = true;
> > > +			break;
> > > +		default:
> > > +			usage(argv[0]);
> > > +		}
> > > +	}
> > > +
> > > +	if ((argc - optind) != 1)
> > > +		usage(argv[0]);
> > > +
> > > +	if (!strcmp(argv[optind], "manager"))
> > > +		option = 1;
> > > +	else if (!strcmp(argv[optind], "client"))
> > > +		option = 2;
> > > +	else
> > > +		usage(argv[0]);
> > > +
> > > +	fd = open(driver, O_RDWR | O_CLOEXEC);
> > > +	if (fd < 0) {
> > > +		fprintf(stderr, "Cannot open %s error: %s\n",
> > > driver,
> > > +			strerror(errno));
> > > +		exit(1);
> > > +	}
> > > +
> > > +	/* Need this or "no VMA" error from kernel */
> > > +	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd,
> > > 0);
> > > +	if (mapped == MAP_FAILED) {
> > > +		fprintf(stderr, "mmap error: %s\n",
> > > strerror(errno));
> > > +		close(fd);
> > > +		exit(2);
> > > +	}
> > > +
> > > +	/* Get our context and pid */
> > > +	result = getcon(&context);
> > > +	if (result < 0) {
> > > +		fprintf(stderr, "Failed to obtain SELinux
> > > context\n");
> > > +		result = 3;
> > > +		goto brexit;
> > > +	}
> > > +	pid = getpid();
> > > +
> > > +	switch (option) {
> > > +	case 1: /* manager */
> > > +		if (verbose) {
> > > +			printf("Manager PID: %d Process
> > > context:\n\t%s\n",
> > > +			       pid, context);
> > > +		}
> > > +
> > > +		result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0);
> > > +		if (result < 0) {
> > > +			fprintf(stderr,
> > > +				"Failed to become context
> > > manager: %s\n",
> > > +				strerror(errno));
> > > +			result = 4;
> > > +			goto brexit;
> > > +		}
> > > +
> > > +		readbuf[0] = BC_ENTER_LOOPER;
> > > +		bwr.write_size = sizeof(readbuf[0]);
> > > +		bwr.write_consumed = 0;
> > > +		bwr.write_buffer = (uintptr_t)readbuf;
> > > +
> > > +		bwr.read_size = 0;
> > > +		bwr.read_consumed = 0;
> > > +		bwr.read_buffer = 0;
> > > +
> > > +		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
> > > +		if (result < 0) {
> > > +			fprintf(stderr,
> > > +				"Manager ioctl
> > > BINDER_WRITE_READ: %s\n",
> > > +				strerror(errno));
> > > +			result = 5;
> > > +			goto brexit;
> > > +		}
> > > +
> > > +		while (true) {
> > > +			bwr.read_size = sizeof(readbuf);
> > > +			bwr.read_consumed = 0;
> > > +			bwr.read_buffer = (uintptr_t)readbuf;
> > > +
> > > +			result = ioctl(fd, BINDER_WRITE_READ,
> > > &bwr);
> > > +			if (result < 0) {
> > > +				fprintf(stderr,
> > > +					"Manager ioctl
> > > BINDER_WRITE_READ: %s\n",
> > > +					strerror(errno));
> > > +				result = 6;
> > > +				goto brexit;
> > > +			}
> > > +
> > > +			if (bwr.read_consumed == 0)
> > > +				continue;
> > > +
> > > +			if (verbose)
> > > +				printf("Manager read_consumed:
> > > %lld\n",
> > > +				       bwr.read_consumed);
> > > +
> > > +			cmd = binder_parse(fd,
> > > (uintptr_t)readbuf,
> > > +					   bwr.read_consumed,
> > > "Manager");
> > > +		}
> > > +		break;
> > > +
> > > +	case 2: /* client */
> > > +		if (verbose) {
> > > +			printf("Client PID: %d Process
> > > context:\n\t%s\n",
> > > +			       pid, context);
> > > +		}
> > > +
> > > +		writebuf[0] = BC_TRANSACTION;
> > > +		txn = (struct binder_transaction_data
> > > *)(&writebuf[1]);
> > > +		memset(txn, 0, sizeof(*txn));
> > > +		txn->target.handle = target;
> > > +		txn->cookie = 0;
> > > +		txn->code = 0;
> > > +		txn->flags = TF_ACCEPT_FDS;
> > > +
> > > +		memset(&obj, 0, sizeof(struct
> > > flat_binder_object));
> > > +		obj.hdr.type = BINDER_TYPE_WEAK_BINDER;
> > > +		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
> > > +		obj.binder = target;
> > > +		obj.handle = 0;
> > > +		obj.cookie = 0;
> > > +
> > > +		txn->data_size = sizeof(struct
> > > flat_binder_object);
> > > +		txn->data.ptr.buffer = (uintptr_t)&obj;
> > > +		txn->data.ptr.offsets = (uintptr_t)&obj +
> > > +					sizeof(struct
> > > flat_binder_object);
> > > +		txn->offsets_size = sizeof(binder_size_t);
> > > +
> > > +		memset(&bwr, 0, sizeof(bwr));
> > > +		bwr.write_buffer = (unsigned long)writebuf;
> > > +		bwr.write_size = sizeof(writebuf[0]) +
> > > sizeof(*txn);
> > > +
> > > +		/* Expect client to get max two responses:
> > > +		 *	1) From the above BC_TRANSACTION
> > > +		 *	2) The responding BC_REPLY from
> > > send_reply()
> > > +		 * unless an error.
> > > +		 */
> > > +		count = 0;
> > > +		while (count != 2) {
> > > +			bwr.read_size = sizeof(readbuf);
> > > +			bwr.read_consumed = 0;
> > > +			bwr.read_buffer = (uintptr_t)readbuf;
> > > +
> > > +			result = ioctl(fd, BINDER_WRITE_READ,
> > > &bwr);
> > > +			if (result < 0) {
> > > +				fprintf(stderr,
> > > +					"Client ioctl
> > > BINDER_WRITE_READ: %s\n",
> > > +					strerror(errno));
> > > +				result = 8;
> > > +				goto brexit;
> > > +			}
> > > +
> > > +			if (verbose)
> > > +				printf("Client read_consumed:
> > > %lld\n",
> > > +				       bwr.read_consumed);
> > > +
> > > +			cmd = binder_parse(fd,
> > > (uintptr_t)readbuf,
> > > +					   bwr.read_consumed,
> > > "Client");
> > > +
> > > +			if (cmd == BR_FAILED_REPLY ||
> > > +			    cmd == BR_DEAD_REPLY ||
> > > +			    cmd == BR_DEAD_BINDER) {
> > > +				result = 9;
> > > +				goto brexit;
> > > +			}
> > > +			count++;
> > > +		}
> > > +		break;
> > > +
> > > +	default:
> > > +		result = -1;
> > > +	}
> > > +
> > > +brexit:
> > > +	free(context);
> > > +	munmap(mapped, mapsize);
> > > +	close(fd);
> > > +
> > > +	return result;
> > > +}
> > > 
> 
>
Stephen Smalley May 15, 2018, 4:38 p.m. UTC | #4
On 05/15/2018 09:43 AM, Stephen Smalley wrote:
> On 05/15/2018 09:36 AM, Stephen Smalley wrote:
>> This test is failing for me (with or without -v):
>> # ./test -v
>> 1..6
>> Manager PID: 5608 Process context:
>> 	unconfined_u:unconfined_r:test_binder_mgr_t:s0-s0:c0.c1023
>> Client PID: 5609 Process context:
>> 	unconfined_u:unconfined_r:test_binder_client_t:s0-s0:c0.c1023
>> Client read_consumed: 28
>> Manager read_consumed: 72
>> Client command: BR_NOOP
>> Manager command: BR_NOOP
>> Client command: BR_INCREFS
>> Manager command: BR_TRANSACTION
>> Client command: BR_TRANSACTION_COMPLETE
>> BR_TRANSACTION data:
>> 	handle: 0
>> 	cookie: 0
>> 	code: 0
>> 	flag: TF_ACCEPT_FDS
>> 	sender pid: 5609
>> 	sender euid: 0
>> 	data_size: 24
>> 	offsets_size: 8
>> Sending BC_REPLY
>> Manager read_consumed: 8
>> Manager command: BR_NOOP
>> Manager command: BR_TRANSACTION_COMPLETE
>> Client read_consumed: 72
>> Client command: BR_NOOP
>> Client command: BR_REPLY
>> BR_REPLY data:
>> 	handle: 0
>> 	cookie: 0
>> 	code: 0
>> 	flag: TF_ACCEPT_FDS
>> 	sender pid: 0
>> 	sender euid: 0
>> 	data_size: 24
>> 	offsets_size: 8
>> Retrieved Managers fd: 4 st_dev: 6
>> Client read_consumed: 8
>> Client using Managers FD command: BR_NOOP
>> Client using Managers FD command: BR_FAILED_REPLY
>> Client using Managers received FD failed response
>> Manager read_consumed: 4
>> Manager command: BR_NOOP
>> not ok 1
>> #   Failed test at ./test line 36.
> 
> Just realized that I was testing with a kernel that still had Casey's stacking support enabled.
> Will re-try without that.

Still fails for me on F28 with stock/linus 4.17.0-rc5.  No AVC messages from the failing test itself,
just the other ones.
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..dc6ef30 100644
--- a/defconfig
+++ b/defconfig
@@ -51,3 +51,11 @@  CONFIG_CRYPTO_USER=m
 # This is enabled to test overlayfs SELinux integration.
 # It is not required for SELinux operation itself.
 CONFIG_OVERLAY_FS=m
+
+# Android binder implementations.
+# These are enabled to test the binder controls in
+# tests/binder; they are not required for SELinux operation itself.
+CONFIG_ANDROID=y
+CONFIG_ANDROID_BINDER_IPC=y
+CONFIG_ANDROID_BINDER_DEVICES="binder"
+# CONFIG_ANDROID_BINDER_IPC_SELFTEST is not set
diff --git a/policy/Makefile b/policy/Makefile
index 8ed5e46..5a9d411 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -25,7 +25,7 @@  TARGETS = \
 	test_task_getsid.te test_task_setpgid.te test_task_setsched.te \
 	test_transition.te test_inet_socket.te test_unix_socket.te \
 	test_mmap.te test_overlayfs.te test_mqueue.te test_mac_admin.te \
-	test_ibpkey.te test_atsecure.te
+	test_ibpkey.te test_atsecure.te test_binder.te
 
 ifeq ($(shell [ $(POL_VERS) -ge 24 ] && echo true),true)
 TARGETS += test_bounds.te
diff --git a/policy/test_binder.te b/policy/test_binder.te
new file mode 100644
index 0000000..c4ad2ae
--- /dev/null
+++ b/policy/test_binder.te
@@ -0,0 +1,83 @@ 
+
+attribute binderdomain;
+
+#
+################################## Manager ###################################
+#
+type test_binder_mgr_t;
+domain_type(test_binder_mgr_t)
+unconfined_runs_test(test_binder_mgr_t)
+typeattribute test_binder_mgr_t testdomain;
+typeattribute test_binder_mgr_t binderdomain;
+allow test_binder_mgr_t self:binder { set_context_mgr call };
+allow test_binder_mgr_t device_t:chr_file { ioctl open read write map };
+allow test_binder_mgr_t self:capability { sys_nice };
+allow test_binder_client_t test_binder_mgr_t:fd use;
+
+#
+################################# Client ####################################
+#
+type test_binder_client_t;
+domain_type(test_binder_client_t)
+unconfined_runs_test(test_binder_client_t)
+typeattribute test_binder_client_t testdomain;
+typeattribute test_binder_client_t binderdomain;
+allow test_binder_client_t self:binder { call };
+allow test_binder_client_t test_binder_mgr_t:binder { call transfer impersonate };
+allow test_binder_client_t device_t:chr_file { ioctl open read write map };
+# For fstat:
+allow test_binder_client_t device_t:chr_file getattr;
+
+#
+############################## Client no call ################################
+#
+type test_binder_client_no_call_t;
+domain_type(test_binder_client_no_call_t)
+unconfined_runs_test(test_binder_client_no_call_t)
+typeattribute test_binder_client_no_call_t testdomain;
+typeattribute test_binder_client_no_call_t binderdomain;
+allow test_binder_client_no_call_t device_t:chr_file { ioctl open read write map };
+
+#
+############################ Client no transfer #############################
+#
+type test_binder_client_no_transfer_t;
+domain_type(test_binder_client_no_transfer_t)
+unconfined_runs_test(test_binder_client_no_transfer_t)
+typeattribute test_binder_client_no_transfer_t testdomain;
+typeattribute test_binder_client_no_transfer_t binderdomain;
+allow test_binder_client_no_transfer_t test_binder_mgr_t:binder { call };
+allow test_binder_client_no_transfer_t device_t:chr_file { ioctl open read write map };
+
+#
+########################## Manager no fd {use} ###############################
+#
+type test_binder_mgr_no_fd_t;
+domain_type(test_binder_mgr_no_fd_t)
+unconfined_runs_test(test_binder_mgr_no_fd_t)
+typeattribute test_binder_mgr_no_fd_t testdomain;
+typeattribute test_binder_mgr_no_fd_t binderdomain;
+allow test_binder_mgr_no_fd_t self:binder { set_context_mgr call };
+allow test_binder_mgr_no_fd_t device_t:chr_file { ioctl open read write map };
+allow test_binder_mgr_no_fd_t self:capability { sys_nice };
+allow test_binder_client_t test_binder_mgr_no_fd_t:binder { call transfer };
+
+#
+########################### Client no impersonate ############################
+#
+type test_binder_client_no_im_t;
+domain_type(test_binder_client_no_im_t)
+unconfined_runs_test(test_binder_client_no_im_t)
+typeattribute test_binder_client_no_im_t testdomain;
+typeattribute test_binder_client_no_im_t binderdomain;
+allow test_binder_client_no_im_t self:binder { call };
+allow test_binder_client_no_im_t test_binder_mgr_t:binder { call transfer };
+allow test_binder_client_no_im_t device_t:chr_file { ioctl open read write map };
+allow test_binder_client_no_im_t test_binder_mgr_t:fd use;
+allow test_binder_client_no_im_t device_t:chr_file getattr;
+
+#
+############ Allow these domains to be entered from sysadm domain ############
+#
+miscfiles_domain_entry_test_files(binderdomain)
+userdom_sysadm_entry_spec_domtrans_to(binderdomain)
diff --git a/tests/Makefile b/tests/Makefile
index 27ed6eb..7607c22 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -10,7 +10,7 @@  SUBDIRS:= domain_trans entrypoint execshare exectrace execute_no_trans \
 	task_setnice task_setscheduler task_getscheduler task_getsid \
 	task_getpgid task_setpgid file ioctl capable_file capable_net \
 	capable_sys dyntrans dyntrace bounds nnp_nosuid mmap unix_socket \
-        inet_socket overlay checkreqprot mqueue mac_admin atsecure
+        inet_socket overlay checkreqprot mqueue mac_admin atsecure binder
 
 ifeq ($(shell grep -q cap_userns $(POLDEV)/include/support/all_perms.spt && echo true),true)
 ifneq ($(shell ./kvercmp $$(uname -r) 4.7),-1)
diff --git a/tests/binder/Makefile b/tests/binder/Makefile
new file mode 100644
index 0000000..a60eeb3
--- /dev/null
+++ b/tests/binder/Makefile
@@ -0,0 +1,7 @@ 
+TARGETS = check_binder test_binder
+
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/binder/check_binder.c b/tests/binder/check_binder.c
new file mode 100644
index 0000000..3d553a0
--- /dev/null
+++ b/tests/binder/check_binder.c
@@ -0,0 +1,80 @@ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <linux/android/binder.h>
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-v]\n"
+		"Where:\n\t"
+		"-v Print binder version.\n", progname);
+	exit(-1);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, result, fd;
+	char *driver = "/dev/binder";
+	bool verbose;
+	void *mapped;
+	size_t mapsize = 1024;
+	struct binder_version vers;
+
+	while ((opt = getopt(argc, argv, "v")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	fd = open(driver, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open: %s error: %s\n",
+			driver, strerror(errno));
+		exit(-1);
+	}
+
+	/* Need this or no VMA error from kernel */
+	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (mapped == MAP_FAILED) {
+		fprintf(stderr, "mmap error: %s\n", strerror(errno));
+		close(fd);
+		exit(-1);
+	}
+
+	result = ioctl(fd, BINDER_VERSION, &vers);
+	if (result < 0) {
+		fprintf(stderr, "ioctl BINDER_VERSION: %s\n",
+			strerror(errno));
+		goto brexit;
+	}
+
+	if (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
+		fprintf(stderr,
+			"Binder kernel version: %d differs from user space version: %d\n",
+			vers.protocol_version,
+			BINDER_CURRENT_PROTOCOL_VERSION);
+		result = -1;
+		goto brexit;
+	}
+
+	if (verbose)
+		fprintf(stderr, "Binder kernel version: %d\n",
+			vers.protocol_version);
+
+brexit:
+	munmap(mapped, mapsize);
+	close(fd);
+
+	return result;
+}
diff --git a/tests/binder/test b/tests/binder/test
new file mode 100644
index 0000000..434ae32
--- /dev/null
+++ b/tests/binder/test
@@ -0,0 +1,131 @@ 
+#!/usr/bin/perl
+use Test::More;
+
+BEGIN {
+    $basedir = $0;
+    $basedir =~ s|(.*)/[^/]*|$1|;
+
+    # allow binder info to be shown
+    $v = $ARGV[0];
+    if ($v) {
+        if ( $v ne "-v" ) {
+            plan skip_all => "Invalid option (use -v)";
+        }
+    }
+
+    # check if binder driver available and the kernel/userspace versions.
+    if ( system("$basedir/check_binder 2> /dev/null") != 0 ) {
+        plan skip_all =>
+          "Binder not supported or kernel/userspace versions differ";
+    }
+    else {
+        plan tests => 6;
+    }
+}
+
+if ($v) {
+    if ( ( $pid = fork() ) == 0 ) {
+        exec "runcon -t test_binder_mgr_t $basedir/test_binder -v manager";
+    }
+
+    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
+
+    # Verify that authorized client can transact with the manager.
+    $result =
+      system "runcon -t test_binder_client_t $basedir/test_binder -v client";
+    ok( $result eq 0 );
+
+    # Verify that client cannot call manager (no call perm).
+    $result = system
+"runcon -t test_binder_client_no_call_t $basedir/test_binder -v client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Verify that client cannot communicate with manager (no impersonate perm).
+    $result = system
+"runcon -t test_binder_client_no_im_t $basedir/test_binder -v client 2>&1";
+    ok( $result >> 8 eq 102 );
+
+    # Verify that client cannot communicate with manager (no transfer perm).
+    $result = system
+"runcon -t test_binder_client_no_transfer_t $basedir/test_binder -v client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Kill the manager.
+    kill TERM, $pid;
+
+    # Verify that client cannot become a manager (no set_context_mgr perm).
+    $result =
+      system
+      "runcon -t test_binder_client_t $basedir/test_binder -v manager 2>&1";
+    ok( $result >> 8 eq 4 );
+
+# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
+    if ( ( $pid = fork() ) == 0 ) {
+        exec
+          "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder -v manager";
+    }
+
+    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
+
+# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
+# domain and binder will return BR_FAILED_REPLY.
+    $result =
+      system
+      "runcon -t test_binder_client_t $basedir/test_binder -v client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Kill the manager
+    kill TERM, $pid;
+}
+else {
+    if ( ( $pid = fork() ) == 0 ) {
+        exec "runcon -t test_binder_mgr_t $basedir/test_binder manager";
+    }
+
+    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
+
+    # Verify that authorized client can transact with the manager.
+    $result =
+      system "runcon -t test_binder_client_t $basedir/test_binder client";
+    ok( $result eq 0 );
+
+    # Verify that client cannot call manager (no call perm).
+    $result = system
+      "runcon -t test_binder_client_no_call_t $basedir/test_binder client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Verify that client cannot communicate with manager (no impersonate perm).
+    $result = system
+      "runcon -t test_binder_client_no_im_t $basedir/test_binder client 2>&1";
+    ok( $result >> 8 eq 102 );
+
+    # Verify that client cannot communicate with manager (no transfer perm).
+    $result = system
+"runcon -t test_binder_client_no_transfer_t $basedir/test_binder client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Kill the manager.
+    kill TERM, $pid;
+
+    # Verify that client cannot become a manager (no set_context_mgr perm).
+    $result =
+      system "runcon -t test_binder_client_t $basedir/test_binder manager 2>&1";
+    ok( $result >> 8 eq 4 );
+
+# Start manager to test that selinux_binder_transfer_file() fails when fd { use } is denied by policy.
+    if ( ( $pid = fork() ) == 0 ) {
+        exec "runcon -t test_binder_mgr_no_fd_t $basedir/test_binder manager";
+    }
+
+    select( undef, undef, undef, 0.25 );    # Give it a moment to initialize.
+
+# Verify that authorized client can communicate with the server, however the fd passed will not be valid for manager
+# domain and binder will return BR_FAILED_REPLY.
+    $result =
+      system "runcon -t test_binder_client_t $basedir/test_binder client 2>&1";
+    ok( $result >> 8 eq 9 );
+
+    # Kill the manager
+    kill TERM, $pid;
+}
+exit;
diff --git a/tests/binder/test_binder.c b/tests/binder/test_binder.c
new file mode 100644
index 0000000..8881cce
--- /dev/null
+++ b/tests/binder/test_binder.c
@@ -0,0 +1,543 @@ 
+/*
+ * This is a simple binder client/server(manager) that only uses the
+ * raw ioctl commands. It does not rely on a 'service manager' as in
+ * the Android world as it only uses one 'target' = 0.
+ *
+ * The transaction/reply flow is basically:
+ *      Client                                  Manager
+ *     ========                                =========
+ *
+ *                                        Becomes context manager
+ *   Send transaction
+ *   requesting file
+ *   descriptor from
+ *    the Manager
+ *         --------------------------------------->
+ *                                       Manager replies with its
+ *                                        binder file descriptor
+ *         <--------------------------------------
+ *     Check fd valid, if so send
+ *  TF_ONE_WAY transaction to Manager
+ *  using the received fd
+ *          --------------------------------------->
+ *                                        End of tests so Manager
+ *                                          waits to be killed
+ *   Client exits
+ *
+ * Using binder test policy the following will be validated:
+ *    security_binder_set_context_mgr() binder { set_context_mgr }
+ *    security_binder_transaction()     binder { call impersonate }
+ *    security_binder_transfer_binder() binder { transfer }
+ *    security_binder_transfer_file()   fd { use }
+ *
+ * TODO security_binder_transfer_file() uses BPF if configured in kernel.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <selinux/selinux.h>
+#include <linux/android/binder.h>
+
+static uint32_t target; /* This will be set to '0' as only need one target */
+static bool verbose;
+
+static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text);
+
+static void usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-v] manager | client\n"
+		"Where:\n\t"
+		"-v       Print context and command information.\n\t"
+		"manager  Act as binder context manager.\n\t"
+		"client   Act as binder client.\n"
+		"\nNote: Ensure this boolean command is run when "
+		"testing after a reboot:\n\t"
+		"setsebool allow_domain_fd_use=0\n", progname);
+	exit(1);
+}
+
+static const char *cmd_name(uint32_t cmd)
+{
+	switch (cmd) {
+	case BR_NOOP:
+		return "BR_NOOP";
+	case BR_TRANSACTION_COMPLETE:
+		return "BR_TRANSACTION_COMPLETE";
+	case BR_INCREFS:
+		return "BR_INCREFS";
+	case BR_ACQUIRE:
+		return "BR_ACQUIRE";
+	case BR_RELEASE:
+		return "BR_RELEASE";
+	case BR_DECREFS:
+		return "BR_DECREFS";
+	case BR_TRANSACTION:
+		return "BR_TRANSACTION";
+	case BR_REPLY:
+		return "BR_REPLY";
+	case BR_FAILED_REPLY:
+		return "BR_FAILED_REPLY";
+	case BR_DEAD_REPLY:
+		return "BR_DEAD_REPLY";
+	case BR_DEAD_BINDER:
+		return "BR_DEAD_BINDER";
+	case BR_ERROR:
+		return "BR_ERROR";
+	/* fallthrough */
+	default:
+		return "Unknown command";
+	}
+}
+
+void print_trans_data(struct binder_transaction_data *txn)
+{
+	printf("\thandle: %ld\n", (unsigned long)txn->target.handle);
+	printf("\tcookie: %lld\n", txn->cookie);
+	printf("\tcode: %u\n", txn->code);
+	switch (txn->flags) {
+	case TF_ONE_WAY:
+		printf("\tflag: TF_ONE_WAY\n");
+		break;
+	case TF_ROOT_OBJECT:
+		printf("\tflag: TF_ROOT_OBJECT\n");
+		break;
+	case TF_STATUS_CODE:
+		printf("\tflag: TF_STATUS_CODE\n");
+		break;
+	case TF_ACCEPT_FDS:
+		printf("\tflag: TF_ACCEPT_FDS\n");
+		break;
+	default:
+		printf("Unknown flag: %u\n", txn->flags);
+	}
+	printf("\tsender pid: %u\n", txn->sender_pid);
+	printf("\tsender euid: %u\n", txn->sender_euid);
+	printf("\tdata_size: %llu\n", txn->data_size);
+	printf("\toffsets_size: %llu\n", txn->offsets_size);
+}
+
+
+static int send_reply(int fd, struct binder_transaction_data *txn_in)
+{
+	int result;
+	unsigned int writebuf[1024];
+	struct flat_binder_object obj;
+	struct binder_write_read bwr;
+	struct binder_transaction_data *txn;
+
+	if (txn_in->flags == TF_ONE_WAY) {
+		if (verbose)
+			printf("No reply to BC_TRANSACTION as flags = TF_ONE_WAY\n");
+		return 0;
+	}
+
+	if (verbose)
+		printf("Sending BC_REPLY\n");
+
+	writebuf[0] = BC_REPLY;
+	txn = (struct binder_transaction_data *)(&writebuf[1]);
+
+	memset(txn, 0, sizeof(*txn));
+	txn->target.handle = txn_in->target.handle;
+	txn->cookie = txn_in->cookie;
+	txn->code = txn_in->code;
+	txn->flags = TF_ACCEPT_FDS;
+
+	memset(&obj, 0, sizeof(struct flat_binder_object));
+	obj.hdr.type = BINDER_TYPE_FD;
+	obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+	obj.binder = txn->target.handle;
+	obj.cookie = txn->cookie;
+	/* The binder fd is used for testing as it allows policy to set
+	 * whether the Client/Manager can be allowed access (fd use) or
+	 * not. For example test_binder_mgr_t has:
+	 *        allow test_binder_client_t test_binder_mgr_t:fd use;
+	 * whereas test_binder_mgr_no_fd_t does not allow this fd use.
+	 *
+	 * This also allows a check for the impersonate permission later as
+	 * the Client will use this (the Managers fd) to send a transaction.
+	 */
+	obj.handle = fd;
+
+	txn->data_size = sizeof(struct flat_binder_object);
+	txn->data.ptr.buffer = (uintptr_t)&obj;
+	txn->data.ptr.offsets = (uintptr_t)&obj +
+				sizeof(struct flat_binder_object);
+	txn->offsets_size = sizeof(binder_size_t);
+
+	memset(&bwr, 0, sizeof(bwr));
+	bwr.write_buffer = (unsigned long)writebuf;
+	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
+
+	result = ioctl(fd, BINDER_WRITE_READ, &bwr);
+	if (result < 0) {
+		fprintf(stderr, "%s ioctl BINDER_WRITE_READ: %s\n",
+			__func__, strerror(errno));
+		return -1;
+	}
+
+	return result;
+}
+
+/* This retrieves the requested Managers file descriptor, then using this
+ * sends a simple transaction to trigger the impersonate permission.
+ */
+static void extract_fd_and_respond(struct binder_transaction_data *txn_in)
+{
+	int result;
+	uint32_t cmd;
+	struct stat sb;
+	struct binder_write_read bwr;
+	struct flat_binder_object *obj;
+	struct binder_transaction_data *txn;
+	unsigned int readbuf[32];
+	unsigned int writebuf[1024];
+	binder_size_t *offs = (binder_size_t *)(uintptr_t)txn_in->data.ptr.offsets;
+
+	/* Get the fbo that contains the Managers binder file descriptor. */
+	obj = (struct flat_binder_object *)
+	      (((char *)(uintptr_t)txn_in->data.ptr.buffer) + *offs);
+
+	/* fstat this just to see if a valid fd */
+	result = fstat(obj->handle, &sb);
+	if (result < 0) {
+		fprintf(stderr, "Not a valid fd: %s\n", strerror(errno));
+		exit(100);
+	}
+
+	if (verbose)
+		printf("Retrieved Managers fd: %d st_dev: %ld\n",
+		       obj->handle, sb.st_dev);
+
+	/* Send response using Managers fd to trigger impersonate check. */
+	writebuf[0] = BC_TRANSACTION;
+	txn = (struct binder_transaction_data *)(&writebuf[1]);
+	memset(txn, 0, sizeof(*txn));
+	txn->target.handle = target;
+	txn->cookie = 0;
+	txn->code = 0;
+	txn->flags = TF_ONE_WAY;
+
+	txn->data_size = 0;
+	txn->data.ptr.buffer = (uintptr_t)NULL;
+	txn->data.ptr.offsets = (uintptr_t)NULL;
+	txn->offsets_size = 0;
+
+	memset(&bwr, 0, sizeof(bwr));
+	bwr.write_buffer = (unsigned long)writebuf;
+	bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
+
+	bwr.read_size = sizeof(readbuf);
+	bwr.read_consumed = 0;
+	bwr.read_buffer = (uintptr_t)readbuf;
+
+	result = ioctl(obj->handle, BINDER_WRITE_READ, &bwr);
+	if (result < 0) {
+		fprintf(stderr,
+			"CLIENT ioctl BINDER_WRITE_READ: %s\n",
+			strerror(errno));
+		exit(101);
+	}
+
+	if (verbose)
+		printf("Client read_consumed: %lld\n", bwr.read_consumed);
+
+	cmd = binder_parse(obj->handle, (uintptr_t)readbuf,
+			   bwr.read_consumed,
+			   "Client using Managers FD");
+
+	if (cmd == BR_FAILED_REPLY ||
+	    cmd == BR_DEAD_REPLY ||
+	    cmd == BR_DEAD_BINDER) {
+		fprintf(stderr,
+			"Client using Managers received FD failed response\n");
+		exit(102);
+	}
+}
+
+/* Parse response, reply as required and then return last CMD */
+static int binder_parse(int fd, uintptr_t ptr, size_t size, char *text)
+{
+	uintptr_t end = ptr + (uintptr_t)size;
+	uint32_t cmd;
+
+	while (ptr < end) {
+		cmd = *(uint32_t *)ptr;
+		ptr += sizeof(uint32_t);
+
+		if (verbose)
+			printf("%s command: %s\n", text, cmd_name(cmd));
+
+		switch (cmd) {
+		case BR_NOOP:
+			break;
+		case BR_TRANSACTION_COMPLETE:
+			break;
+		case BR_INCREFS:
+		case BR_ACQUIRE:
+		case BR_RELEASE:
+		case BR_DECREFS:
+			ptr += sizeof(struct binder_ptr_cookie);
+			break;
+		case BR_TRANSACTION: {
+			struct binder_transaction_data *txn =
+				(struct binder_transaction_data *)ptr;
+
+			if (verbose) {
+				printf("BR_TRANSACTION data:\n");
+				print_trans_data(txn);
+			}
+
+			/* The manager sends reply that will contain its fd */
+			if (send_reply(fd, txn) < 0) {
+				fprintf(stderr, "send_reply() failed.\n");
+				return -1;
+			}
+			ptr += sizeof(*txn);
+			break;
+		}
+		case BR_REPLY: {
+			struct binder_transaction_data *txn =
+				(struct binder_transaction_data *)ptr;
+
+			if (verbose) {
+				printf("BR_REPLY data:\n");
+				print_trans_data(txn);
+			}
+
+			/* Client extracts the Manager fd, and responds */
+			extract_fd_and_respond(txn);
+			ptr += sizeof(*txn);
+			break;
+		}
+		case BR_DEAD_BINDER:
+			break;
+		case BR_FAILED_REPLY:
+			break;
+		case BR_DEAD_REPLY:
+			break;
+		case BR_ERROR:
+			ptr += sizeof(uint32_t);
+			break;
+		default:
+			if (verbose)
+				printf("%s Parsed unknown command: %d\n",
+				       text, cmd);
+			return -1;
+		}
+	}
+
+	return cmd;
+}
+
+int main(int argc, char **argv)
+{
+	int opt, option, result, fd, count;
+	uint32_t cmd;
+	pid_t pid;
+	char *driver = "/dev/binder";
+	char *context;
+	void *mapped;
+	size_t mapsize = 2048;
+	struct binder_write_read bwr;
+	struct flat_binder_object obj;
+	struct binder_transaction_data *txn;
+	unsigned int readbuf[32];
+	unsigned int writebuf[1024];
+
+	target = 0; /* Only need one target - the Manager */
+	verbose = false;
+
+	while ((opt = getopt(argc, argv, "v")) != -1) {
+		switch (opt) {
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			usage(argv[0]);
+		}
+	}
+
+	if ((argc - optind) != 1)
+		usage(argv[0]);
+
+	if (!strcmp(argv[optind], "manager"))
+		option = 1;
+	else if (!strcmp(argv[optind], "client"))
+		option = 2;
+	else
+		usage(argv[0]);
+
+	fd = open(driver, O_RDWR | O_CLOEXEC);
+	if (fd < 0) {
+		fprintf(stderr, "Cannot open %s error: %s\n", driver,
+			strerror(errno));
+		exit(1);
+	}
+
+	/* Need this or "no VMA" error from kernel */
+	mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, fd, 0);
+	if (mapped == MAP_FAILED) {
+		fprintf(stderr, "mmap error: %s\n", strerror(errno));
+		close(fd);
+		exit(2);
+	}
+
+	/* Get our context and pid */
+	result = getcon(&context);
+	if (result < 0) {
+		fprintf(stderr, "Failed to obtain SELinux context\n");
+		result = 3;
+		goto brexit;
+	}
+	pid = getpid();
+
+	switch (option) {
+	case 1: /* manager */
+		if (verbose) {
+			printf("Manager PID: %d Process context:\n\t%s\n",
+			       pid, context);
+		}
+
+		result = ioctl(fd, BINDER_SET_CONTEXT_MGR, 0);
+		if (result < 0) {
+			fprintf(stderr,
+				"Failed to become context manager: %s\n",
+				strerror(errno));
+			result = 4;
+			goto brexit;
+		}
+
+		readbuf[0] = BC_ENTER_LOOPER;
+		bwr.write_size = sizeof(readbuf[0]);
+		bwr.write_consumed = 0;
+		bwr.write_buffer = (uintptr_t)readbuf;
+
+		bwr.read_size = 0;
+		bwr.read_consumed = 0;
+		bwr.read_buffer = 0;
+
+		result = ioctl(fd, BINDER_WRITE_READ, &bwr);
+		if (result < 0) {
+			fprintf(stderr,
+				"Manager ioctl BINDER_WRITE_READ: %s\n",
+				strerror(errno));
+			result = 5;
+			goto brexit;
+		}
+
+		while (true) {
+			bwr.read_size = sizeof(readbuf);
+			bwr.read_consumed = 0;
+			bwr.read_buffer = (uintptr_t)readbuf;
+
+			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
+			if (result < 0) {
+				fprintf(stderr,
+					"Manager ioctl BINDER_WRITE_READ: %s\n",
+					strerror(errno));
+				result = 6;
+				goto brexit;
+			}
+
+			if (bwr.read_consumed == 0)
+				continue;
+
+			if (verbose)
+				printf("Manager read_consumed: %lld\n",
+				       bwr.read_consumed);
+
+			cmd = binder_parse(fd, (uintptr_t)readbuf,
+					   bwr.read_consumed, "Manager");
+		}
+		break;
+
+	case 2: /* client */
+		if (verbose) {
+			printf("Client PID: %d Process context:\n\t%s\n",
+			       pid, context);
+		}
+
+		writebuf[0] = BC_TRANSACTION;
+		txn = (struct binder_transaction_data *)(&writebuf[1]);
+		memset(txn, 0, sizeof(*txn));
+		txn->target.handle = target;
+		txn->cookie = 0;
+		txn->code = 0;
+		txn->flags = TF_ACCEPT_FDS;
+
+		memset(&obj, 0, sizeof(struct flat_binder_object));
+		obj.hdr.type = BINDER_TYPE_WEAK_BINDER;
+		obj.flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
+		obj.binder = target;
+		obj.handle = 0;
+		obj.cookie = 0;
+
+		txn->data_size = sizeof(struct flat_binder_object);
+		txn->data.ptr.buffer = (uintptr_t)&obj;
+		txn->data.ptr.offsets = (uintptr_t)&obj +
+					sizeof(struct flat_binder_object);
+		txn->offsets_size = sizeof(binder_size_t);
+
+		memset(&bwr, 0, sizeof(bwr));
+		bwr.write_buffer = (unsigned long)writebuf;
+		bwr.write_size = sizeof(writebuf[0]) + sizeof(*txn);
+
+		/* Expect client to get max two responses:
+		 *	1) From the above BC_TRANSACTION
+		 *	2) The responding BC_REPLY from send_reply()
+		 * unless an error.
+		 */
+		count = 0;
+		while (count != 2) {
+			bwr.read_size = sizeof(readbuf);
+			bwr.read_consumed = 0;
+			bwr.read_buffer = (uintptr_t)readbuf;
+
+			result = ioctl(fd, BINDER_WRITE_READ, &bwr);
+			if (result < 0) {
+				fprintf(stderr,
+					"Client ioctl BINDER_WRITE_READ: %s\n",
+					strerror(errno));
+				result = 8;
+				goto brexit;
+			}
+
+			if (verbose)
+				printf("Client read_consumed: %lld\n",
+				       bwr.read_consumed);
+
+			cmd = binder_parse(fd, (uintptr_t)readbuf,
+					   bwr.read_consumed, "Client");
+
+			if (cmd == BR_FAILED_REPLY ||
+			    cmd == BR_DEAD_REPLY ||
+			    cmd == BR_DEAD_BINDER) {
+				result = 9;
+				goto brexit;
+			}
+			count++;
+		}
+		break;
+
+	default:
+		result = -1;
+	}
+
+brexit:
+	free(context);
+	munmap(mapped, mapsize);
+	close(fd);
+
+	return result;
+}