All of lore.kernel.org
 help / color / mirror / Atom feed
From: Richard Haines <richard_c_haines@btinternet.com>
To: Stephen Smalley <sds@tycho.nsa.gov>, selinux@tycho.nsa.gov
Subject: Re: [RFC PATCH 1/1] selinux-testsuite: Add binder tests
Date: Tue, 15 May 2018 16:35:19 +0100	[thread overview]
Message-ID: <1526398519.4511.6.camel@btinternet.com> (raw)
In-Reply-To: <0193e640-4792-57b1-2408-a6944731baf4@tycho.nsa.gov>

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

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

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

Reply instructions:

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

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

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

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

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

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

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