[apparmor] [PATCH RFC] Add profile-based libapparmor query interface

Tyler Hicks tyhicks at canonical.com
Thu Mar 28 01:30:36 UTC 2013


On 2013-03-05 22:44:35, Tyler Hicks wrote:
> I've got an initial libapparmor patch to complement the kernel query
> interface patch that I recently sent out to the list. It is functional
> but it is quite ugly so I'm looking for suggestions on how we want this
> to look since there's not really a libapparmor precedence for an
> interface like this.
> 
> * I made this dead really as far as what information can be extracted
>   from the interface. The application using the interface will simply
>   know whether it should allow the action and whether it should audit
>   the action. My thoughts are that we can add a more complex interface
>   later when we need it. For D-Bus, I think this simple of an interface
>   is sufficient.
> 
> * I reused JJ's design from the aa_has_perm() function where the
>   application needs to allocate a query buffer of AA_SOME_HEADER_SIZE +
>   query size and then the query needs to be offset by
>   AA_SOME_HEADER_SIZE bytes in the buffer. Then, the libapparmor
>   function fills in the header and doesn't have to do any extra
>   allocations or copies. Certainly good from an efficiency point of
>   view, but not extremely user friendly. Something to worry about or
>   not?
> 
> * Setting two int return parameters to indicate allow and audit isn't a
>   final solution. I was thinking that I'd return a special return code
>   that will indicate error or allow and audit statuses. Then define some
>   simple macros (aa_query_profile_err(rc), aa_query_profile_allow(rc),
>   aa_query_profile_audit(rc)) in apparmor.h that can be used to
>   translate the return code. I'm open to other suggestions...
> 
> * Feel free to comment on any other parts of the patch, as well.
> 
> ---
> 
> Description: Add profile-based libapparmor query interface
>  Wrap the apparmorfs profile query interface with a very simple libapparmor
>  interface. This function takes a permission mask and query string consisting
>  of a profile name and a DFA match string separated by a NUL char. It sets two
>  output parameters indicating whether the action should be allowed and if the
>  action should be audited.
>  .
>  The allowed and audited output parameters take into account deny and quiet
>  permission masks returned in the kernel query. Additionally, the audited
>  ouput parameter takes into account whether the action is to be allowed or
>  not. If not, audited is set to true as long as there was no specific quiet
>  rules for the queried permission.
>  .
>  The function requires a static char array to be allocated and initialized to
>  the path of the apparmorfs .access file the first time it is called.
>  Otherwise, aa_find_mountpoint() would need to be called for every query which
>  would be inefficient. pthread_once() is used to ensure that aa_query_profile()
>  is thread-safe while the char array is being allocated and initialized.
> Author: Tyler Hicks <tyhicks at canonical.com>
> Index: apparmor-2.8.0/libraries/libapparmor/src/kernel_interface.c
> ===================================================================

It turns out that this patch had some nasty bugs that I've managed to
chase down. Details below.

> --- apparmor-2.8.0.orig/libraries/libapparmor/src/kernel_interface.c	2013-03-05 16:45:24.471541316 -0800
> +++ apparmor-2.8.0/libraries/libapparmor/src/kernel_interface.c	2013-03-05 19:43:18.524352019 -0800
> @@ -28,6 +28,8 @@
>  #include <limits.h>
>  #include <stdarg.h>
>  #include <mntent.h>
> +#include <inttypes.h>
> +#include <pthread.h>
>  
>  #include "apparmor.h"
>  
> @@ -677,3 +679,92 @@
>  {
>  	return aa_task_has_perm(aa_gettid(), mask, query, size);
>  }
> +
> +static pthread_once_t aafs_access_control = PTHREAD_ONCE_INIT;
> +static char *aafs_access = NULL;
> +
> +static void aafs_access_init_once(void)
> +{
> +	char *aafs;
> +	int ret;
> +
> +	ret = aa_find_mountpoint(&aafs);
> +	if (ret < 0)
> +		return;
> +
> +	ret = asprintf(&aafs_access, "%s/.access", aafs);
> +	if (ret < 0)
> +		aafs_access = NULL;
> +
> +	free(aafs);
> +}
> +
> +/**
> + * aa_query_profile - test if the profile being enforced allows access to query
> + * @mask: permission bits to query
> + * @query: binary query string, must be offset by AA_QUERY_CMD_PROFILE_SIZE
> + * @size: size of the query string must include AA_QUERY_CMD_PROFILE_SIZE
> + * @allowed: upon successful return, will be 1 if query is allowed and 0 if not
> + * @audited: upon successful return, will be 1 if query should be audited and 0
> + *           if not
> + *
> + * Returns: 0 on success else -1 and sets errno
> + */
> +int aa_query_profile(uint32_t mask, char *query, size_t size,
> +		     int *allowed, int *audited)
> +{
> +	char buf[128];
> +	uint32_t allow, deny, audit, quiet;
> +	int fd, ret, saved;
> +
> +	if (size <= AA_QUERY_CMD_PROFILE_SIZE) {
> +		errno = EINVAL;
> +		return -1;
> +	}
> +
> +	ret = pthread_once(&aafs_access_control, aafs_access_init_once);
> +	if (ret) {
> +		errno = EINVAL;
> +		return -1;
> +	} else if (!aafs_access) {
> +		errno = ENOMEM;
> +		return -1;
> +	}
> +
> +	fd = open(aafs_access, O_RDWR);
> +	if (fd == -1)
> +		return -1;
> +
> +	memcpy(query, AA_QUERY_CMD_PROFILE, AA_QUERY_CMD_PROFILE_SIZE);
> +	errno = 0;
> +	ret = write(fd, query, size);
> +	if (ret != size) {
> +		if (ret >= 0)
> +			errno = EPROTO;
> +		return -1;
> +	}
> +
> +	ret = read(fd, buf, sizeof(buf));
> +	saved = errno;
> +	(void)close(fd);
> +	errno = saved;
> +	if (ret < 0)

It would be good to make sure that we read all of the data that we
expect to. I'll make a #define with the length that we expect to read
from the kernel and make sure that we get at least that many bytes from
the read().

I'll also use that #define to set the size of buf[].

> +		return -1;
> +
> +	ret = sscanf(buf, "allow 0x%8" SCNu32 "\n"
> +			  "deny 0x%8"  SCNu32 "\n"
> +			  "audit 0x%8" SCNu32 "\n"
> +			  "quiet 0x%8" SCNu32 "\n",

These should all be SCNx32 since we're reading in hex representations.
SCNu32 results in the wrong values. :/

> +		     &allow, &deny, &audit, &quiet);
> +	if (ret != 4) {
> +		errno = EPROTONOSUPPORT;
> +		return -1;
> +	}
> +
> +	*allowed = mask & (allow & ~deny) ? 1 : 0;

This is wrong. mask could contain the same thing as, or a subset of,
(allow & ~deny) and *allowed will properly be set to 1. However, mask
could contain a single matching bit along with any other invalid bits
and *allowed will still be set to 1.

After making those changes, we need to make sure that aa_query_profile()
is not called with a mask value of 0 or *allowed will be set to 1.

> +	if (!(*allowed))
> +		audit = 0xFFFFFFFF;
> +	*audited = mask & (audit & ~quiet) ? 1 : 0;

Same problem here as above.

Patch to follow...

Tyler

> +
> +	return 0;
> +}
> Index: apparmor-2.8.0/libraries/libapparmor/src/apparmor.h
> ===================================================================
> --- apparmor-2.8.0.orig/libraries/libapparmor/src/apparmor.h	2013-03-05 16:45:24.471541316 -0800
> +++ apparmor-2.8.0/libraries/libapparmor/src/apparmor.h	2013-03-05 19:26:18.528327777 -0800
> @@ -87,6 +87,10 @@
>  			    size_t size);
>  extern int aa_has_perm(uint32_t mask, char *query, size_t size);
>  
> +#define AA_QUERY_CMD_PROFILE		"profile\0"
> +#define AA_QUERY_CMD_PROFILE_SIZE	8
> +extern int aa_query_profile(uint32_t mask, char *query, size_t size,
> +			    int *allow, int *audit);
>  
>  #define __macroarg_counter(Y...) __macroarg_count1 ( , ##Y)
>  #define __macroarg_count1(Y...) __macroarg_count2 (Y, 16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
> Index: apparmor-2.8.0/libraries/libapparmor/src/libapparmor.map
> ===================================================================
> --- apparmor-2.8.0.orig/libraries/libapparmor/src/libapparmor.map	2013-03-05 16:45:24.471541316 -0800
> +++ apparmor-2.8.0/libraries/libapparmor/src/libapparmor.map	2013-03-05 16:45:24.459535316 -0800
> @@ -39,6 +39,7 @@
>    global:
>          aa_task_has_perm;
>  	aa_has_perm;
> +	aa_query_profile;
>    local:
>  	*;
>  } APPARMOR_1.1;
> Index: apparmor-2.8.0/libraries/libapparmor/swig/SWIG/libapparmor.i
> ===================================================================
> --- apparmor-2.8.0.orig/libraries/libapparmor/swig/SWIG/libapparmor.i	2013-03-05 16:45:24.471541316 -0800
> +++ apparmor-2.8.0/libraries/libapparmor/swig/SWIG/libapparmor.i	2013-03-05 16:45:24.463537315 -0800
> @@ -30,3 +30,5 @@
>  extern int aa_task_has_perm(pid_t task, uint32_t mask, const char *query,
>  			    size_t size);
>  extern int aa_has_perm(uint32_t mask, const char *query, size_t size);
> +extern int aa_query_profile(uint32_t mask, char *query, size_t size,
> +			    int *allow, int *audit);
> Index: apparmor-2.8.0/libraries/libapparmor/src/Makefile.am
> ===================================================================
> --- apparmor-2.8.0.orig/libraries/libapparmor/src/Makefile.am	2013-03-05 16:45:24.411511315 -0800
> +++ apparmor-2.8.0/libraries/libapparmor/src/Makefile.am	2013-03-05 19:55:23.144369240 -0800
> @@ -24,7 +24,7 @@
>  noinst_HEADERS = grammar.h parser.h scanner.h af_protos.h
>  
>  libapparmor_la_SOURCES = grammar.y libaalogparse.c kernel_interface.c scanner.c
> -libapparmor_la_LDFLAGS = -version-info 1:2:0 -XCClinker -dynamic \
> +libapparmor_la_LDFLAGS = -version-info 1:2:0 -XCClinker -dynamic -pthread \
>  	-Wl,--version-script=$(top_srcdir)/src/libapparmor.map -Wl,-soname=libapparmor.so.1
>  
>  libimmunix_la_SOURCES = kernel_interface.c libimmunix_warning.c
> 
> -- 
> AppArmor mailing list
> AppArmor at lists.ubuntu.com
> Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 836 bytes
Desc: Digital signature
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20130327/17f3e397/attachment-0001.pgp>


More information about the AppArmor mailing list