Message ID | 20190919174655.17348-4-richard_c_haines@btinternet.com (mailing list archive) |
---|---|
State | Accepted |
Headers | show |
Series | selinux-testsuite: Add BPF tests | expand |
On 9/19/19 1:46 PM, Richard Haines wrote: > Add BPF map & prog functions to test binder security_binder_transfer_file() Thanks, all three applied. > > Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> > --- > policy/Makefile | 2 +- > policy/test_binder_bpf.te | 73 ++++++++++++++++++++ > tests/binder/Makefile | 5 ++ > tests/binder/binder_common.c | 10 +-- > tests/binder/binder_common.h | 17 ++++- > tests/binder/client.c | 28 ++++++-- > tests/binder/manager.c | 2 +- > tests/binder/service_provider.c | 118 ++++++++++++++++++++++++-------- > tests/bpf/Makefile | 2 +- > tests/bpf/test | 84 ++++++++++++++++++++++- > 10 files changed, 296 insertions(+), 45 deletions(-) > create mode 100644 policy/test_binder_bpf.te > > diff --git a/policy/Makefile b/policy/Makefile > index 4ca5486..d72eb62 100644 > --- a/policy/Makefile > +++ b/policy/Makefile > @@ -72,7 +72,7 @@ TARGETS += test_sctp.te > endif > > ifeq ($(shell grep -q bpf $(POLDEV)/include/support/all_perms.spt && echo true),true) > -TARGETS += test_bpf.te test_fdreceive_bpf.te > +TARGETS += test_bpf.te test_fdreceive_bpf.te test_binder_bpf.te > endif > > ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6)) > diff --git a/policy/test_binder_bpf.te b/policy/test_binder_bpf.te > new file mode 100644 > index 0000000..c545846 > --- /dev/null > +++ b/policy/test_binder_bpf.te > @@ -0,0 +1,73 @@ > +####### Policy for testing BPF file descriptor transfers via binder ######### > + > +attribute binderbpfdomain; > + > +# > +################################## Manager ################################## > +# > +type test_binder_bpf_mgr_t; > +domain_type(test_binder_bpf_mgr_t) > +unconfined_runs_test(test_binder_bpf_mgr_t) > +typeattribute test_binder_bpf_mgr_t testdomain; > +typeattribute test_binder_bpf_mgr_t binderdomain; > +allow test_binder_bpf_mgr_t test_binder_bpf_client_t:binder { transfer }; > +allow test_binder_bpf_mgr_t test_binder_client_no_bpf_perm_t:binder { transfer }; > +allow test_binder_bpf_mgr_t device_t:chr_file { ioctl open read write }; > +allow_map(test_binder_bpf_mgr_t, device_t, chr_file) > +allow test_binder_bpf_mgr_t self:binder { set_context_mgr }; > +# For writing to flag file: > +allow test_binder_bpf_mgr_t test_file_t:fifo_file { rw_file_perms }; > + > +# > +########################### Service Provider ################################ > +# > +type test_binder_bpf_provider_t; > +domain_type(test_binder_bpf_provider_t) > +unconfined_runs_test(test_binder_bpf_provider_t) > +typeattribute test_binder_bpf_provider_t testdomain; > +typeattribute test_binder_bpf_provider_t binderbpfdomain; > +allow test_binder_bpf_provider_t test_binder_bpf_mgr_t:binder { call transfer }; > +allow test_binder_bpf_provider_t device_t:chr_file { ioctl open read write }; > +allow_map(test_binder_bpf_provider_t, device_t, chr_file) > +# For writing to flag file: > +allow test_binder_bpf_provider_t test_file_t:fifo_file { rw_file_perms }; > +# For testing BPF map fd transfer: > +allow test_binder_bpf_provider_t self:bpf { map_create map_read map_write prog_load prog_run }; > +allow test_binder_bpf_provider_t self:capability { sys_resource }; > +allow test_binder_bpf_provider_t self:process { setrlimit }; > + > +# > +################################# Client #################################### > +# > +type test_binder_bpf_client_t; > +domain_type(test_binder_bpf_client_t) > +unconfined_runs_test(test_binder_bpf_client_t) > +typeattribute test_binder_bpf_client_t testdomain; > +typeattribute test_binder_bpf_client_t binderbpfdomain; > +allow test_binder_bpf_client_t test_binder_bpf_provider_t:binder { call impersonate }; > +allow test_binder_bpf_client_t test_binder_bpf_mgr_t:binder { call }; > +allow test_binder_bpf_client_t test_binder_bpf_provider_t:fd { use }; > +allow test_binder_bpf_client_t device_t:chr_file { getattr ioctl open read write }; > +allow_map(test_binder_bpf_client_t, device_t, chr_file) > +# For testing BPF map fd transfer: > +allow test_binder_bpf_client_t test_binder_bpf_provider_t:bpf { map_read map_write prog_load prog_run }; > + > +# > +######################## Client no BPF perms ############################# > +# > +type test_binder_client_no_bpf_perm_t; > +domain_type(test_binder_client_no_bpf_perm_t) > +unconfined_runs_test(test_binder_client_no_bpf_perm_t) > +typeattribute test_binder_client_no_bpf_perm_t testdomain; > +typeattribute test_binder_client_no_bpf_perm_t binderbpfdomain; > +allow test_binder_client_no_bpf_perm_t test_binder_bpf_provider_t:binder { call impersonate }; > +allow test_binder_client_no_bpf_perm_t test_binder_bpf_mgr_t:binder { call }; > +allow test_binder_client_no_bpf_perm_t test_binder_bpf_provider_t:fd { use }; > +allow test_binder_client_no_bpf_perm_t device_t:chr_file { getattr ioctl open read write }; > +allow_map(test_binder_client_no_bpf_perm_t, device_t, chr_file) > + > +# > +########### Allow these domains to be entered from sysadm domain ############ > +# > +miscfiles_domain_entry_test_files(binderbpfdomain) > +userdom_sysadm_entry_spec_domtrans_to(binderbpfdomain) > diff --git a/tests/binder/Makefile b/tests/binder/Makefile > index 32f9a83..e78ad16 100644 > --- a/tests/binder/Makefile > +++ b/tests/binder/Makefile > @@ -10,6 +10,11 @@ CFLAGS += -DHAVE_BINDERFS > TARGETS += check_binderfs > endif > > +ifneq (,$(findstring -DHAVE_BPF,$(CFLAGS))) > + DEPS += ../bpf/bpf_common.c ../bpf/bpf_common.h > + LDLIBS += -lbpf > +endif > + > all: $(TARGETS) > > clean: > diff --git a/tests/binder/binder_common.c b/tests/binder/binder_common.c > index a240453..224238b 100644 > --- a/tests/binder/binder_common.c > +++ b/tests/binder/binder_common.c > @@ -3,13 +3,15 @@ > * the raw ioctl commands to test the SELinux binder permissions: > * set_context_mgr, call, transfer, impersonate. > * > + * If configured, the BPF permissions are also tested. > + * > * 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. > + * bpf { map_create map_read map_write }; > + * bpf { prog_load prog_run }; > */ > > #include "binder_common.h" > @@ -67,8 +69,8 @@ void print_trans_data(const struct binder_transaction_data *txn_in) > case TEST_SERVICE_GET: > printf("\tcode: TEST_SERVICE_GET\n"); > break; > - case TEST_SERVICE_SEND_CLIENT_SP_FD: > - printf("\tcode: TEST_SERVICE_SEND_CLIENT_SP_FD\n"); > + case TEST_SERVICE_SEND_FD: > + printf("\tcode: TEST_SERVICE_SEND_FD\n"); > break; > default: > printf("Unknown binder_transaction_data->code: %x\n", > diff --git a/tests/binder/binder_common.h b/tests/binder/binder_common.h > index bcf9e0c..30edc75 100644 > --- a/tests/binder/binder_common.h > +++ b/tests/binder/binder_common.h > @@ -15,6 +15,9 @@ > #if HAVE_BINDERFS > #include <linux/android/binderfs.h> > #endif > +#if HAVE_BPF > +#include "../bpf/bpf_common.h" > +#endif > > #define BINDER_DEV "/dev/binder" > #define BINDERFS_DEV "/dev/binderfs" > @@ -26,12 +29,20 @@ > /* These are the Binder txn->code values used by the Service Provider, Client > * and Manager to request/retrieve a binder handle or file descriptor. > */ > -#define TEST_SERVICE_ADD 240616 /* Sent by Service Provider */ > -#define TEST_SERVICE_GET 290317 /* Sent by Client */ > -#define TEST_SERVICE_SEND_CLIENT_SP_FD 120419 /* Sent by Client */ > +#define TEST_SERVICE_ADD 240616 /* Sent by Service Provider */ > +#define TEST_SERVICE_GET 290317 /* Sent by Client */ > +#define TEST_SERVICE_SEND_FD 311019 /* Sent by Client */ > > bool verbose; > > const char *cmd_name(uint32_t cmd); > void print_trans_data(const struct binder_transaction_data *txn_in); > int binder_write(int fd, void *data, size_t len); > + > +enum { > + BINDER_FD, > + BPF_MAP_FD, > + BPF_PROG_FD, > + BPF_TEST > +} fd_type; > +char *fd_type_str; > diff --git a/tests/binder/client.c b/tests/binder/client.c > index e4e2a61..4965563 100644 > --- a/tests/binder/client.c > +++ b/tests/binder/client.c > @@ -6,7 +6,7 @@ static int transactions_complete; > static void usage(char *progname) > { > fprintf(stderr, > - "usage: %s [-c] [-n] [-r replies] [-v]\n" > + "usage: %s [-c] [-n] [-r replies] [-m|-p] [-v]\n" > "Where:\n\t" > "-c Use the number of replies for the BR_TRANSACTION_COMPLETE" > " count.\n\t" > @@ -15,6 +15,8 @@ static void usage(char *progname) > " It can be the number of BR_TRANSACTION_COMPLETE if\n\t" > " the -c option is set or number of times to issue the\n\t" > " ioctl - BINDER_WRITE_READ command if -c not set.\n\t" > + "-m Service Provider sending BPF map fd.\n\t" > + "-p Service Provider sending BPF prog fd.\n\t" > "-v Print context and command information.\n\t" > "\nNote: Ensure this boolean command is run when " > "testing after a reboot:\n\t" > @@ -67,8 +69,12 @@ static void extract_fd_and_respond(const struct binder_transaction_data *txn_in) > } > > if (verbose) > - printf("Client retrieved Service Providers fd: %d st_dev: %ld\n", > - obj->fd, sb.st_dev); > + printf("Client retrieved %s fd: %d st_dev: %ld\n", > + fd_type_str, obj->fd, sb.st_dev); > + > + /* If testing BPF, then cannot do impersonate check */ > + if (fd_type > BINDER_FD) > + return; > > memset(&writebuf, 0, sizeof(writebuf)); > memset(readbuf, 0, sizeof(readbuf)); > @@ -141,7 +147,7 @@ static void request_service_provider_fd(int fd, uint32_t handle) > writebuf.cmd = BC_TRANSACTION; > writebuf.txn.target.handle = handle; > writebuf.txn.cookie = 0; > - writebuf.txn.code = TEST_SERVICE_SEND_CLIENT_SP_FD; > + writebuf.txn.code = TEST_SERVICE_SEND_FD; > writebuf.txn.flags = TF_ACCEPT_FDS; > > writebuf.txn.data_size = 0; > @@ -270,7 +276,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) > if (txn->code == TEST_SERVICE_GET) > extract_handle_and_acquire(fd, txn); > > - if (txn->code == TEST_SERVICE_SEND_CLIENT_SP_FD) > + if (txn->code == TEST_SERVICE_SEND_FD) > extract_fd_and_respond(txn); > > ptr += sizeof(*txn); > @@ -313,8 +319,10 @@ int main(int argc, char **argv) > unsigned int readbuf[32]; > > transactions_complete = 0; > + fd_type = BINDER_FD; > + fd_type_str = "SP"; > > - while ((opt = getopt(argc, argv, "cnr:v")) != -1) { > + while ((opt = getopt(argc, argv, "cnr:vmp")) != -1) { > switch (opt) { > case 'c': > use_transactions_complete = true; > @@ -328,6 +336,14 @@ int main(int argc, char **argv) > case 'v': > verbose = true; > break; > + case 'm': > + fd_type = BPF_MAP_FD; > + fd_type_str = "BPF map"; > + break; > + case 'p': > + fd_type = BPF_PROG_FD; > + fd_type_str = "BPF prog"; > + break; > default: > usage(argv[0]); > } > diff --git a/tests/binder/manager.c b/tests/binder/manager.c > index 9922183..8e5f446 100644 > --- a/tests/binder/manager.c > +++ b/tests/binder/manager.c > @@ -91,7 +91,7 @@ static void do_service_manager(int fd, struct binder_transaction_data *txn_in) > reply_with_handle(fd, txn_in); > > break; > - case TEST_SERVICE_SEND_CLIENT_SP_FD: > + case TEST_SERVICE_SEND_FD: > if (verbose) > printf("Manager Rx'ed SEND_CLIENT_YOUR_BINDER_FD for handle: %d\n", > txn_in->target.handle); > diff --git a/tests/binder/service_provider.c b/tests/binder/service_provider.c > index 2873af8..56d8a43 100644 > --- a/tests/binder/service_provider.c > +++ b/tests/binder/service_provider.c > @@ -6,11 +6,14 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size); > static void usage(char *progname) > { > fprintf(stderr, > - "usage: %s [-e expected_ctx] [-f file] [-n] [-v]\n" > + "usage: %s -e expected_ctx] [-f file] [-n] [-m|-p|-t] [-v]\n" > "Where:\n\t" > "-e Expected security context.\n\t" > "-f Write a line to the file when listening starts.\n\t" > "-n Use the /dev/binderfs name service.\n\t" > + "-m Use BPF map fd for transfer.\n\t" > + "-p Use BPF prog fd for transfer.\n\t" > + "-t Test if BPF enabled.\n\t" > "-v Print context and command information.\n\t" > "\nNote: Ensure this boolean command is run when " > "testing after a reboot:\n\t" > @@ -34,7 +37,7 @@ static void request_service_provider_fd(int fd, > } > > if (verbose) > - printf("Service Provider sending BC_REPLY with its FD\n"); > + printf("Service Provider sending BC_REPLY with an FD\n"); > > memset(writebuf, 0, sizeof(writebuf)); > memset(&bwr, 0, sizeof(bwr)); > @@ -56,17 +59,47 @@ static void request_service_provider_fd(int fd, > obj.pad_flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; > #endif > obj.cookie = txn->cookie; > - /* The Service Providers binder fd is used for testing as it allows > + > + /* > + * The Service Providers binder fd is used for testing as it allows > * policy to set whether the Service Provider and Client can be > * allowed access (fd use) or not. > * This also allows a check for the impersonate permission later as > * the Client will use the Service Provider fd to send a transaction. > + * > + * If a BPF fd is required, it is generated, however it cannot be > + * used to check the impersonate permission. > */ > - obj.fd = fd; > + switch (fd_type) { > + case BINDER_FD: > + obj.fd = fd; > + break; > +#if HAVE_BPF > + case BPF_MAP_FD: > + obj.fd = create_bpf_map(); > + if (obj.fd < 0) > + exit(70); > + break; > + case BPF_PROG_FD: > + obj.fd = create_bpf_prog(); > + if (obj.fd < 0) > + exit(71); > + break; > +#else > + case BPF_MAP_FD: > + case BPF_PROG_FD: > + fprintf(stderr, "BPF not supported - Service Provider\n"); > + exit(72); > + break; > +#endif > + default: > + fprintf(stderr, "Invalid fd_type: %d\n", fd_type); > + exit(73); > + } > > if (verbose) > - printf("Service Provider handle: %d and its FD: %d\n", > - txn->target.handle, fd); > + printf("Service Provider handle: %d and %s FD: %d\n", > + txn->target.handle, fd_type_str, obj.fd); > > txn->data_size = sizeof(obj); > txn->data.ptr.buffer = (binder_uintptr_t)&obj; > @@ -81,7 +114,7 @@ static void request_service_provider_fd(int fd, > fprintf(stderr, > "Service Provider ioctl BINDER_WRITE_READ error: %s\n", > strerror(errno)); > - exit(70); > + exit(74); > } > } > > @@ -119,7 +152,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) > print_trans_data(txn); > } > > - if (txn->code == TEST_SERVICE_SEND_CLIENT_SP_FD) > + if (txn->code == TEST_SERVICE_SEND_FD) > request_service_provider_fd(fd, txn); > > ptr += sizeof(*txn); > @@ -148,8 +181,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) > } > } > > - if (txn_ctx->transaction_data.code == > - TEST_SERVICE_SEND_CLIENT_SP_FD) > + if (txn_ctx->transaction_data.code == TEST_SERVICE_SEND_FD) > request_service_provider_fd(fd, > &txn_ctx->transaction_data); > > @@ -209,8 +241,10 @@ int main(int argc, char **argv) > unsigned int readbuf[32]; > > expected_ctx = NULL; > + fd_type = BINDER_FD; > + fd_type_str = "SP"; > > - while ((opt = getopt(argc, argv, "e:f:nv")) != -1) { > + while ((opt = getopt(argc, argv, "e:f:nvmpt")) != -1) { > switch (opt) { > case 'e': > expected_ctx = optarg; > @@ -224,11 +258,37 @@ int main(int argc, char **argv) > case 'v': > verbose = true; > break; > + case 'm': > + fd_type = BPF_MAP_FD; > + fd_type_str = "BPF map"; > + break; > + case 'p': > + fd_type = BPF_PROG_FD; > + fd_type_str = "BPF prog"; > + break; > + case 't': > + fd_type = BPF_TEST; > + break; > default: > usage(argv[0]); > } > } > > + > +#if HAVE_BPF > + if (fd_type == BPF_TEST) > + exit(0); > + > + /* If BPF enabed, then need to set limits */ > + if (fd_type == BPF_MAP_FD || fd_type == BPF_PROG_FD) > + bpf_setrlimit(); > +#else > + if (fd_type == BPF_TEST) { > + fprintf(stderr, "BPF not supported\n"); > + exit(-1); > + } > +#endif > + > /* Get our context and pid */ > result = getcon(&context); > if (result < 0) { > @@ -267,19 +327,6 @@ int main(int argc, char **argv) > exit(63); > } > > - if (flag_file) { > - flag_fd = fopen(flag_file, "w"); > - if (!flag_fd) { > - fprintf(stderr, > - "Service Provider failed to open %s: %s\n", > - flag_file, strerror(errno)); > - result = 64; > - goto brexit; > - } > - fprintf(flag_fd, "listening\n"); > - fclose(flag_fd); > - } > - > memset(&writebuf, 0, sizeof(writebuf)); > memset(&obj, 0, sizeof(obj)); > memset(readbuf, 0, sizeof(readbuf)); > @@ -322,7 +369,7 @@ int main(int argc, char **argv) > fprintf(stderr, > "Service Provider ioctl BINDER_WRITE_READ error: %s\n", > strerror(errno)); > - result = 65; > + result = 64; > goto brexit; > } > > @@ -338,7 +385,7 @@ int main(int argc, char **argv) > cmd == BR_DEAD_BINDER) { > fprintf(stderr, "Service Provider %s() failing command %s, exiting.\n", > __func__, cmd_name(cmd)); > - result = 66; > + result = 65; > goto brexit; > } > > @@ -358,10 +405,27 @@ int main(int argc, char **argv) > fprintf(stderr, > "Service Provider ioctl BINDER_WRITE_READ error: %s\n", > strerror(errno)); > - result = 67; > + result = 66; > goto brexit; > } > > + /* > + * Ensure the Manager and Service Provider have completed the > + * TEST_SERVICE_ADD sequence before the Client is allowed to start. > + */ > + if (flag_file) { > + flag_fd = fopen(flag_file, "w"); > + if (!flag_fd) { > + fprintf(stderr, > + "Service Provider failed to open %s: %s\n", > + flag_file, strerror(errno)); > + result = 67; > + goto brexit; > + } > + fprintf(flag_fd, "listening\n"); > + fclose(flag_fd); > + } > + > while (true) { > memset(readbuf, 0, sizeof(readbuf)); > bwr.read_size = sizeof(readbuf); > diff --git a/tests/bpf/Makefile b/tests/bpf/Makefile > index 3513179..6fb230d 100644 > --- a/tests/bpf/Makefile > +++ b/tests/bpf/Makefile > @@ -5,7 +5,7 @@ LDLIBS += -lselinux -lbpf > # export so that BPF_ENABLED entries get built correctly on local build > export CFLAGS += -DHAVE_BPF > > -BPF_ENABLED = ../fdreceive > +BPF_ENABLED = ../fdreceive ../binder > > all: $(TARGETS) > @set -e; for i in $(BPF_ENABLED); do $(MAKE) -C $$i all ; done > diff --git a/tests/bpf/test b/tests/bpf/test > index b49918d..4c768be 100755 > --- a/tests/bpf/test > +++ b/tests/bpf/test > @@ -4,11 +4,14 @@ use Test::More; > BEGIN { > $basedir = $0; > $basedir =~ s|(.*)/[^/]*|$1|; > - $fdr_basedir = "$basedir/../fdreceive/"; > + $fdr_basedir = "$basedir/../fdreceive/"; > + $binder_basedir = "$basedir/../binder/"; > > $test_bpf_count = 7; > $test_fdreceive_count = 4; > > + $test_count = $test_bpf_count + $test_fdreceive_count; > + > # allow info to be shown during tests > $v = $ARGV[0]; > if ($v) { > @@ -20,7 +23,15 @@ BEGIN { > $v = " "; > } > > - plan tests => $test_bpf_count + $test_fdreceive_count; > + # Test if Binder is supported > + $test_binder = 0; > + $result = system("$binder_basedir/check_binder $v 2>/dev/null"); > + if ( $result >> 8 eq 0 ) { > + $test_binder = 1; > + $test_count += 4; > + } > + > + plan tests => $test_count; > } > > # > @@ -104,4 +115,73 @@ kill KILL, $pid; > # Clean up. > system "rm -rf $basedir/test_sock $basedir/flag"; > > +# > +################ BPF Tests for binder ####################### > +# > +sub service_start { > + my ( $service, $runcon_args, $args ) = @_; > + my $pid; > + my $flag = $service . "_flag"; > + > + system("mkfifo $basedir/$flag"); > + > + if ( ( $pid = fork() ) == 0 ) { > + exec > +"runcon $runcon_args $binder_basedir/$service -f $basedir/$flag $args"; > + } > + > + # Wait for it to initialize. > + system("read -t 5 <>$basedir/$flag"); > + return $pid; > +} > + > +sub service_end { > + my ( $service, $pid ) = @_; > + my $flag = $service . "_flag"; > + > + kill KILL, $pid; > + waitpid $pid, 0; > + system("rm -f $basedir/$flag"); > +} > + > +if ($test_binder) { > + ### Test BPF map fd on transfer ################## > + $sm_pid = service_start( "manager", "-t test_binder_bpf_mgr_t", "$v" ); > + $sp_pid = > + service_start( "service_provider", "-t test_binder_bpf_provider_t", > + "-m $v" ); > + > + # Verify that the BPF map fd can be transferred. > + $result = > + system > + "runcon -t test_binder_bpf_client_t $binder_basedir/client $v -m -r 1"; > + ok( $result eq 0 ); > + > + # Verify BPF no map perms. > + $result = system > +"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $v -m -r 2 2>&1"; > + ok( $result >> 8 eq 141 ); > + > + ### Test BPF prog fd on transfer ################## > + service_end( "service_provider", $sp_pid ); > + $sp_pid = > + service_start( "service_provider", "-t test_binder_bpf_provider_t", > + "-p $v" ); > + > + # Verify that the BPF prog fd can be transferred. > + $result = > + system > + "runcon -t test_binder_bpf_client_t $binder_basedir/client $v -p -r 1"; > + ok( $result eq 0 ); > + > + # Verify BPF no prog perms. > + $result = system > +"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $v -p -r 2 2>&1"; > + ok( $result >> 8 eq 141 ); > + > + # Kill the service provider & manager. > + service_end( "service_provider", $sp_pid ); > + service_end( "manager", $sm_pid ); > +} > + > exit; >
diff --git a/policy/Makefile b/policy/Makefile index 4ca5486..d72eb62 100644 --- a/policy/Makefile +++ b/policy/Makefile @@ -72,7 +72,7 @@ TARGETS += test_sctp.te endif ifeq ($(shell grep -q bpf $(POLDEV)/include/support/all_perms.spt && echo true),true) -TARGETS += test_bpf.te test_fdreceive_bpf.te +TARGETS += test_bpf.te test_fdreceive_bpf.te test_binder_bpf.te endif ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6)) diff --git a/policy/test_binder_bpf.te b/policy/test_binder_bpf.te new file mode 100644 index 0000000..c545846 --- /dev/null +++ b/policy/test_binder_bpf.te @@ -0,0 +1,73 @@ +####### Policy for testing BPF file descriptor transfers via binder ######### + +attribute binderbpfdomain; + +# +################################## Manager ################################## +# +type test_binder_bpf_mgr_t; +domain_type(test_binder_bpf_mgr_t) +unconfined_runs_test(test_binder_bpf_mgr_t) +typeattribute test_binder_bpf_mgr_t testdomain; +typeattribute test_binder_bpf_mgr_t binderdomain; +allow test_binder_bpf_mgr_t test_binder_bpf_client_t:binder { transfer }; +allow test_binder_bpf_mgr_t test_binder_client_no_bpf_perm_t:binder { transfer }; +allow test_binder_bpf_mgr_t device_t:chr_file { ioctl open read write }; +allow_map(test_binder_bpf_mgr_t, device_t, chr_file) +allow test_binder_bpf_mgr_t self:binder { set_context_mgr }; +# For writing to flag file: +allow test_binder_bpf_mgr_t test_file_t:fifo_file { rw_file_perms }; + +# +########################### Service Provider ################################ +# +type test_binder_bpf_provider_t; +domain_type(test_binder_bpf_provider_t) +unconfined_runs_test(test_binder_bpf_provider_t) +typeattribute test_binder_bpf_provider_t testdomain; +typeattribute test_binder_bpf_provider_t binderbpfdomain; +allow test_binder_bpf_provider_t test_binder_bpf_mgr_t:binder { call transfer }; +allow test_binder_bpf_provider_t device_t:chr_file { ioctl open read write }; +allow_map(test_binder_bpf_provider_t, device_t, chr_file) +# For writing to flag file: +allow test_binder_bpf_provider_t test_file_t:fifo_file { rw_file_perms }; +# For testing BPF map fd transfer: +allow test_binder_bpf_provider_t self:bpf { map_create map_read map_write prog_load prog_run }; +allow test_binder_bpf_provider_t self:capability { sys_resource }; +allow test_binder_bpf_provider_t self:process { setrlimit }; + +# +################################# Client #################################### +# +type test_binder_bpf_client_t; +domain_type(test_binder_bpf_client_t) +unconfined_runs_test(test_binder_bpf_client_t) +typeattribute test_binder_bpf_client_t testdomain; +typeattribute test_binder_bpf_client_t binderbpfdomain; +allow test_binder_bpf_client_t test_binder_bpf_provider_t:binder { call impersonate }; +allow test_binder_bpf_client_t test_binder_bpf_mgr_t:binder { call }; +allow test_binder_bpf_client_t test_binder_bpf_provider_t:fd { use }; +allow test_binder_bpf_client_t device_t:chr_file { getattr ioctl open read write }; +allow_map(test_binder_bpf_client_t, device_t, chr_file) +# For testing BPF map fd transfer: +allow test_binder_bpf_client_t test_binder_bpf_provider_t:bpf { map_read map_write prog_load prog_run }; + +# +######################## Client no BPF perms ############################# +# +type test_binder_client_no_bpf_perm_t; +domain_type(test_binder_client_no_bpf_perm_t) +unconfined_runs_test(test_binder_client_no_bpf_perm_t) +typeattribute test_binder_client_no_bpf_perm_t testdomain; +typeattribute test_binder_client_no_bpf_perm_t binderbpfdomain; +allow test_binder_client_no_bpf_perm_t test_binder_bpf_provider_t:binder { call impersonate }; +allow test_binder_client_no_bpf_perm_t test_binder_bpf_mgr_t:binder { call }; +allow test_binder_client_no_bpf_perm_t test_binder_bpf_provider_t:fd { use }; +allow test_binder_client_no_bpf_perm_t device_t:chr_file { getattr ioctl open read write }; +allow_map(test_binder_client_no_bpf_perm_t, device_t, chr_file) + +# +########### Allow these domains to be entered from sysadm domain ############ +# +miscfiles_domain_entry_test_files(binderbpfdomain) +userdom_sysadm_entry_spec_domtrans_to(binderbpfdomain) diff --git a/tests/binder/Makefile b/tests/binder/Makefile index 32f9a83..e78ad16 100644 --- a/tests/binder/Makefile +++ b/tests/binder/Makefile @@ -10,6 +10,11 @@ CFLAGS += -DHAVE_BINDERFS TARGETS += check_binderfs endif +ifneq (,$(findstring -DHAVE_BPF,$(CFLAGS))) + DEPS += ../bpf/bpf_common.c ../bpf/bpf_common.h + LDLIBS += -lbpf +endif + all: $(TARGETS) clean: diff --git a/tests/binder/binder_common.c b/tests/binder/binder_common.c index a240453..224238b 100644 --- a/tests/binder/binder_common.c +++ b/tests/binder/binder_common.c @@ -3,13 +3,15 @@ * the raw ioctl commands to test the SELinux binder permissions: * set_context_mgr, call, transfer, impersonate. * + * If configured, the BPF permissions are also tested. + * * 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. + * bpf { map_create map_read map_write }; + * bpf { prog_load prog_run }; */ #include "binder_common.h" @@ -67,8 +69,8 @@ void print_trans_data(const struct binder_transaction_data *txn_in) case TEST_SERVICE_GET: printf("\tcode: TEST_SERVICE_GET\n"); break; - case TEST_SERVICE_SEND_CLIENT_SP_FD: - printf("\tcode: TEST_SERVICE_SEND_CLIENT_SP_FD\n"); + case TEST_SERVICE_SEND_FD: + printf("\tcode: TEST_SERVICE_SEND_FD\n"); break; default: printf("Unknown binder_transaction_data->code: %x\n", diff --git a/tests/binder/binder_common.h b/tests/binder/binder_common.h index bcf9e0c..30edc75 100644 --- a/tests/binder/binder_common.h +++ b/tests/binder/binder_common.h @@ -15,6 +15,9 @@ #if HAVE_BINDERFS #include <linux/android/binderfs.h> #endif +#if HAVE_BPF +#include "../bpf/bpf_common.h" +#endif #define BINDER_DEV "/dev/binder" #define BINDERFS_DEV "/dev/binderfs" @@ -26,12 +29,20 @@ /* These are the Binder txn->code values used by the Service Provider, Client * and Manager to request/retrieve a binder handle or file descriptor. */ -#define TEST_SERVICE_ADD 240616 /* Sent by Service Provider */ -#define TEST_SERVICE_GET 290317 /* Sent by Client */ -#define TEST_SERVICE_SEND_CLIENT_SP_FD 120419 /* Sent by Client */ +#define TEST_SERVICE_ADD 240616 /* Sent by Service Provider */ +#define TEST_SERVICE_GET 290317 /* Sent by Client */ +#define TEST_SERVICE_SEND_FD 311019 /* Sent by Client */ bool verbose; const char *cmd_name(uint32_t cmd); void print_trans_data(const struct binder_transaction_data *txn_in); int binder_write(int fd, void *data, size_t len); + +enum { + BINDER_FD, + BPF_MAP_FD, + BPF_PROG_FD, + BPF_TEST +} fd_type; +char *fd_type_str; diff --git a/tests/binder/client.c b/tests/binder/client.c index e4e2a61..4965563 100644 --- a/tests/binder/client.c +++ b/tests/binder/client.c @@ -6,7 +6,7 @@ static int transactions_complete; static void usage(char *progname) { fprintf(stderr, - "usage: %s [-c] [-n] [-r replies] [-v]\n" + "usage: %s [-c] [-n] [-r replies] [-m|-p] [-v]\n" "Where:\n\t" "-c Use the number of replies for the BR_TRANSACTION_COMPLETE" " count.\n\t" @@ -15,6 +15,8 @@ static void usage(char *progname) " It can be the number of BR_TRANSACTION_COMPLETE if\n\t" " the -c option is set or number of times to issue the\n\t" " ioctl - BINDER_WRITE_READ command if -c not set.\n\t" + "-m Service Provider sending BPF map fd.\n\t" + "-p Service Provider sending BPF prog fd.\n\t" "-v Print context and command information.\n\t" "\nNote: Ensure this boolean command is run when " "testing after a reboot:\n\t" @@ -67,8 +69,12 @@ static void extract_fd_and_respond(const struct binder_transaction_data *txn_in) } if (verbose) - printf("Client retrieved Service Providers fd: %d st_dev: %ld\n", - obj->fd, sb.st_dev); + printf("Client retrieved %s fd: %d st_dev: %ld\n", + fd_type_str, obj->fd, sb.st_dev); + + /* If testing BPF, then cannot do impersonate check */ + if (fd_type > BINDER_FD) + return; memset(&writebuf, 0, sizeof(writebuf)); memset(readbuf, 0, sizeof(readbuf)); @@ -141,7 +147,7 @@ static void request_service_provider_fd(int fd, uint32_t handle) writebuf.cmd = BC_TRANSACTION; writebuf.txn.target.handle = handle; writebuf.txn.cookie = 0; - writebuf.txn.code = TEST_SERVICE_SEND_CLIENT_SP_FD; + writebuf.txn.code = TEST_SERVICE_SEND_FD; writebuf.txn.flags = TF_ACCEPT_FDS; writebuf.txn.data_size = 0; @@ -270,7 +276,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) if (txn->code == TEST_SERVICE_GET) extract_handle_and_acquire(fd, txn); - if (txn->code == TEST_SERVICE_SEND_CLIENT_SP_FD) + if (txn->code == TEST_SERVICE_SEND_FD) extract_fd_and_respond(txn); ptr += sizeof(*txn); @@ -313,8 +319,10 @@ int main(int argc, char **argv) unsigned int readbuf[32]; transactions_complete = 0; + fd_type = BINDER_FD; + fd_type_str = "SP"; - while ((opt = getopt(argc, argv, "cnr:v")) != -1) { + while ((opt = getopt(argc, argv, "cnr:vmp")) != -1) { switch (opt) { case 'c': use_transactions_complete = true; @@ -328,6 +336,14 @@ int main(int argc, char **argv) case 'v': verbose = true; break; + case 'm': + fd_type = BPF_MAP_FD; + fd_type_str = "BPF map"; + break; + case 'p': + fd_type = BPF_PROG_FD; + fd_type_str = "BPF prog"; + break; default: usage(argv[0]); } diff --git a/tests/binder/manager.c b/tests/binder/manager.c index 9922183..8e5f446 100644 --- a/tests/binder/manager.c +++ b/tests/binder/manager.c @@ -91,7 +91,7 @@ static void do_service_manager(int fd, struct binder_transaction_data *txn_in) reply_with_handle(fd, txn_in); break; - case TEST_SERVICE_SEND_CLIENT_SP_FD: + case TEST_SERVICE_SEND_FD: if (verbose) printf("Manager Rx'ed SEND_CLIENT_YOUR_BINDER_FD for handle: %d\n", txn_in->target.handle); diff --git a/tests/binder/service_provider.c b/tests/binder/service_provider.c index 2873af8..56d8a43 100644 --- a/tests/binder/service_provider.c +++ b/tests/binder/service_provider.c @@ -6,11 +6,14 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size); static void usage(char *progname) { fprintf(stderr, - "usage: %s [-e expected_ctx] [-f file] [-n] [-v]\n" + "usage: %s -e expected_ctx] [-f file] [-n] [-m|-p|-t] [-v]\n" "Where:\n\t" "-e Expected security context.\n\t" "-f Write a line to the file when listening starts.\n\t" "-n Use the /dev/binderfs name service.\n\t" + "-m Use BPF map fd for transfer.\n\t" + "-p Use BPF prog fd for transfer.\n\t" + "-t Test if BPF enabled.\n\t" "-v Print context and command information.\n\t" "\nNote: Ensure this boolean command is run when " "testing after a reboot:\n\t" @@ -34,7 +37,7 @@ static void request_service_provider_fd(int fd, } if (verbose) - printf("Service Provider sending BC_REPLY with its FD\n"); + printf("Service Provider sending BC_REPLY with an FD\n"); memset(writebuf, 0, sizeof(writebuf)); memset(&bwr, 0, sizeof(bwr)); @@ -56,17 +59,47 @@ static void request_service_provider_fd(int fd, obj.pad_flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS; #endif obj.cookie = txn->cookie; - /* The Service Providers binder fd is used for testing as it allows + + /* + * The Service Providers binder fd is used for testing as it allows * policy to set whether the Service Provider and Client can be * allowed access (fd use) or not. * This also allows a check for the impersonate permission later as * the Client will use the Service Provider fd to send a transaction. + * + * If a BPF fd is required, it is generated, however it cannot be + * used to check the impersonate permission. */ - obj.fd = fd; + switch (fd_type) { + case BINDER_FD: + obj.fd = fd; + break; +#if HAVE_BPF + case BPF_MAP_FD: + obj.fd = create_bpf_map(); + if (obj.fd < 0) + exit(70); + break; + case BPF_PROG_FD: + obj.fd = create_bpf_prog(); + if (obj.fd < 0) + exit(71); + break; +#else + case BPF_MAP_FD: + case BPF_PROG_FD: + fprintf(stderr, "BPF not supported - Service Provider\n"); + exit(72); + break; +#endif + default: + fprintf(stderr, "Invalid fd_type: %d\n", fd_type); + exit(73); + } if (verbose) - printf("Service Provider handle: %d and its FD: %d\n", - txn->target.handle, fd); + printf("Service Provider handle: %d and %s FD: %d\n", + txn->target.handle, fd_type_str, obj.fd); txn->data_size = sizeof(obj); txn->data.ptr.buffer = (binder_uintptr_t)&obj; @@ -81,7 +114,7 @@ static void request_service_provider_fd(int fd, fprintf(stderr, "Service Provider ioctl BINDER_WRITE_READ error: %s\n", strerror(errno)); - exit(70); + exit(74); } } @@ -119,7 +152,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) print_trans_data(txn); } - if (txn->code == TEST_SERVICE_SEND_CLIENT_SP_FD) + if (txn->code == TEST_SERVICE_SEND_FD) request_service_provider_fd(fd, txn); ptr += sizeof(*txn); @@ -148,8 +181,7 @@ static int binder_parse(int fd, binder_uintptr_t ptr, binder_size_t size) } } - if (txn_ctx->transaction_data.code == - TEST_SERVICE_SEND_CLIENT_SP_FD) + if (txn_ctx->transaction_data.code == TEST_SERVICE_SEND_FD) request_service_provider_fd(fd, &txn_ctx->transaction_data); @@ -209,8 +241,10 @@ int main(int argc, char **argv) unsigned int readbuf[32]; expected_ctx = NULL; + fd_type = BINDER_FD; + fd_type_str = "SP"; - while ((opt = getopt(argc, argv, "e:f:nv")) != -1) { + while ((opt = getopt(argc, argv, "e:f:nvmpt")) != -1) { switch (opt) { case 'e': expected_ctx = optarg; @@ -224,11 +258,37 @@ int main(int argc, char **argv) case 'v': verbose = true; break; + case 'm': + fd_type = BPF_MAP_FD; + fd_type_str = "BPF map"; + break; + case 'p': + fd_type = BPF_PROG_FD; + fd_type_str = "BPF prog"; + break; + case 't': + fd_type = BPF_TEST; + break; default: usage(argv[0]); } } + +#if HAVE_BPF + if (fd_type == BPF_TEST) + exit(0); + + /* If BPF enabed, then need to set limits */ + if (fd_type == BPF_MAP_FD || fd_type == BPF_PROG_FD) + bpf_setrlimit(); +#else + if (fd_type == BPF_TEST) { + fprintf(stderr, "BPF not supported\n"); + exit(-1); + } +#endif + /* Get our context and pid */ result = getcon(&context); if (result < 0) { @@ -267,19 +327,6 @@ int main(int argc, char **argv) exit(63); } - if (flag_file) { - flag_fd = fopen(flag_file, "w"); - if (!flag_fd) { - fprintf(stderr, - "Service Provider failed to open %s: %s\n", - flag_file, strerror(errno)); - result = 64; - goto brexit; - } - fprintf(flag_fd, "listening\n"); - fclose(flag_fd); - } - memset(&writebuf, 0, sizeof(writebuf)); memset(&obj, 0, sizeof(obj)); memset(readbuf, 0, sizeof(readbuf)); @@ -322,7 +369,7 @@ int main(int argc, char **argv) fprintf(stderr, "Service Provider ioctl BINDER_WRITE_READ error: %s\n", strerror(errno)); - result = 65; + result = 64; goto brexit; } @@ -338,7 +385,7 @@ int main(int argc, char **argv) cmd == BR_DEAD_BINDER) { fprintf(stderr, "Service Provider %s() failing command %s, exiting.\n", __func__, cmd_name(cmd)); - result = 66; + result = 65; goto brexit; } @@ -358,10 +405,27 @@ int main(int argc, char **argv) fprintf(stderr, "Service Provider ioctl BINDER_WRITE_READ error: %s\n", strerror(errno)); - result = 67; + result = 66; goto brexit; } + /* + * Ensure the Manager and Service Provider have completed the + * TEST_SERVICE_ADD sequence before the Client is allowed to start. + */ + if (flag_file) { + flag_fd = fopen(flag_file, "w"); + if (!flag_fd) { + fprintf(stderr, + "Service Provider failed to open %s: %s\n", + flag_file, strerror(errno)); + result = 67; + goto brexit; + } + fprintf(flag_fd, "listening\n"); + fclose(flag_fd); + } + while (true) { memset(readbuf, 0, sizeof(readbuf)); bwr.read_size = sizeof(readbuf); diff --git a/tests/bpf/Makefile b/tests/bpf/Makefile index 3513179..6fb230d 100644 --- a/tests/bpf/Makefile +++ b/tests/bpf/Makefile @@ -5,7 +5,7 @@ LDLIBS += -lselinux -lbpf # export so that BPF_ENABLED entries get built correctly on local build export CFLAGS += -DHAVE_BPF -BPF_ENABLED = ../fdreceive +BPF_ENABLED = ../fdreceive ../binder all: $(TARGETS) @set -e; for i in $(BPF_ENABLED); do $(MAKE) -C $$i all ; done diff --git a/tests/bpf/test b/tests/bpf/test index b49918d..4c768be 100755 --- a/tests/bpf/test +++ b/tests/bpf/test @@ -4,11 +4,14 @@ use Test::More; BEGIN { $basedir = $0; $basedir =~ s|(.*)/[^/]*|$1|; - $fdr_basedir = "$basedir/../fdreceive/"; + $fdr_basedir = "$basedir/../fdreceive/"; + $binder_basedir = "$basedir/../binder/"; $test_bpf_count = 7; $test_fdreceive_count = 4; + $test_count = $test_bpf_count + $test_fdreceive_count; + # allow info to be shown during tests $v = $ARGV[0]; if ($v) { @@ -20,7 +23,15 @@ BEGIN { $v = " "; } - plan tests => $test_bpf_count + $test_fdreceive_count; + # Test if Binder is supported + $test_binder = 0; + $result = system("$binder_basedir/check_binder $v 2>/dev/null"); + if ( $result >> 8 eq 0 ) { + $test_binder = 1; + $test_count += 4; + } + + plan tests => $test_count; } # @@ -104,4 +115,73 @@ kill KILL, $pid; # Clean up. system "rm -rf $basedir/test_sock $basedir/flag"; +# +################ BPF Tests for binder ####################### +# +sub service_start { + my ( $service, $runcon_args, $args ) = @_; + my $pid; + my $flag = $service . "_flag"; + + system("mkfifo $basedir/$flag"); + + if ( ( $pid = fork() ) == 0 ) { + exec +"runcon $runcon_args $binder_basedir/$service -f $basedir/$flag $args"; + } + + # Wait for it to initialize. + system("read -t 5 <>$basedir/$flag"); + return $pid; +} + +sub service_end { + my ( $service, $pid ) = @_; + my $flag = $service . "_flag"; + + kill KILL, $pid; + waitpid $pid, 0; + system("rm -f $basedir/$flag"); +} + +if ($test_binder) { + ### Test BPF map fd on transfer ################## + $sm_pid = service_start( "manager", "-t test_binder_bpf_mgr_t", "$v" ); + $sp_pid = + service_start( "service_provider", "-t test_binder_bpf_provider_t", + "-m $v" ); + + # Verify that the BPF map fd can be transferred. + $result = + system + "runcon -t test_binder_bpf_client_t $binder_basedir/client $v -m -r 1"; + ok( $result eq 0 ); + + # Verify BPF no map perms. + $result = system +"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $v -m -r 2 2>&1"; + ok( $result >> 8 eq 141 ); + + ### Test BPF prog fd on transfer ################## + service_end( "service_provider", $sp_pid ); + $sp_pid = + service_start( "service_provider", "-t test_binder_bpf_provider_t", + "-p $v" ); + + # Verify that the BPF prog fd can be transferred. + $result = + system + "runcon -t test_binder_bpf_client_t $binder_basedir/client $v -p -r 1"; + ok( $result eq 0 ); + + # Verify BPF no prog perms. + $result = system +"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $v -p -r 2 2>&1"; + ok( $result >> 8 eq 141 ); + + # Kill the service provider & manager. + service_end( "service_provider", $sp_pid ); + service_end( "manager", $sm_pid ); +} + exit;
Add BPF map & prog functions to test binder security_binder_transfer_file() Signed-off-by: Richard Haines <richard_c_haines@btinternet.com> --- policy/Makefile | 2 +- policy/test_binder_bpf.te | 73 ++++++++++++++++++++ tests/binder/Makefile | 5 ++ tests/binder/binder_common.c | 10 +-- tests/binder/binder_common.h | 17 ++++- tests/binder/client.c | 28 ++++++-- tests/binder/manager.c | 2 +- tests/binder/service_provider.c | 118 ++++++++++++++++++++++++-------- tests/bpf/Makefile | 2 +- tests/bpf/test | 84 ++++++++++++++++++++++- 10 files changed, 296 insertions(+), 45 deletions(-) create mode 100644 policy/test_binder_bpf.te