[apparmor] [PATCH 25/31] parser: Create initial interface for policy cache

John Johansen john.johansen at canonical.com
Wed Jan 28 20:16:32 UTC 2015


On 01/22/2015 07:08 PM, Tyler Hicks wrote:
> On 2015-01-22 10:15:48, John Johansen wrote:
>> On 12/05/2014 04:22 PM, Tyler Hicks wrote:
>>> This API has the same look-and-feel of the previous aa_match and
>>> aa_features APIs.
>>>
>>> The cache setup code was heavily dependent on globals set by CLI
>>> options. Options such as "skip the read cache", or "skip the write
>>> cache", or "don't clear the cache if it isn't valid", won't be useful
>>> for all aa_policy_cache API users so some of that logic was lifted out
>>> of the API. The constructor function still provides a bool parameter
>>> that specifies if the cache should be created or not.
>>>
>>> If the policy cache is invalid (currently meaning that the cache
>>> features file doesn't match the kernel features file), then a new
>>> aa_policy_cache object is still created but a call to
>>> aa_policy_cache_is_valid() will return false. The caller can then decide
>>> what to do (create a new valid cache, stop, etc.)
>>>
>>> Signed-off-by: Tyler Hicks <tyhicks at canonical.com>
>>
Acked-by: John Johansen <john.johansen at canonical.com>

>> comments inline
>>
>>> ---
>>>  parser/parser_main.c  |  56 +++++++++++------
>>>  parser/policy_cache.c | 171 +++++++++++++++++++++++++++++++++++++++-----------
>>>  parser/policy_cache.h |  12 +++-
>>>  3 files changed, 182 insertions(+), 57 deletions(-)
>>>
>>> diff --git a/parser/parser_main.c b/parser/parser_main.c
>>> index 5b0e215..2de8edd 100644
>>> --- a/parser/parser_main.c
>>> +++ b/parser/parser_main.c
>>> @@ -860,6 +860,7 @@ static void setup_flags(void)
>>>  
>>>  int main(int argc, char *argv[])
>>>  {
>>> +	aa_policy_cache *policy_cache;
>>>  	int retval, last_error;
>>>  	int i;
>>>  	int optind;
>>> @@ -892,29 +893,48 @@ int main(int argc, char *argv[])
>>>  
>>>  	setup_flags();
>>>  
>>> -	if (!cacheloc && asprintf(&cacheloc, "%s/cache", basedir) == -1) {
>>> -		PERROR(_("Memory allocation error."));
>>> -		return 1;
>>> -	}
>>> -
>>> -	if (force_clear_cache) {
>>> -		if (clear_cache_files(cacheloc)) {
>>> -			PERROR(_("Failed to clear cache files (%s): %s\n"),
>>> -			       cacheloc, strerror(errno));
>>> +	if ((!skip_cache && (write_cache || !skip_read_cache)) ||
>>> +	    force_clear_cache) {
>>> +		if (!cacheloc && asprintf(&cacheloc, "%s/cache", basedir) == -1) {
>>> +			PERROR(_("Memory allocation error."));
>>>  			return 1;
>>>  		}
>>>  
>>> -		return 0;
>>> -	}
>>> +		if (force_clear_cache) {
>>> +			if (clear_cache_files(cacheloc)) {
>>> +				PERROR(_("Failed to clear cache files (%s): %s\n"),
>>> +				       cacheloc, strerror(errno));
>>> +				return 1;
>>> +			}
>>> +
>>> +			return 0;
>>> +		}
>>
>> Not a criticism of the patch but a question of is this the behavior we want for --force_clear_cache?
>> That is if we succeed to abort with exit code 0 (ie. if force-clear-cache is specified we will never
>> process any profiles are anything
> 
> I'm not sure that I'm following what you're saying. This is the same
> behavior that we have in trunk today. If you specify
> --force-clear-cache, the only thing that happens is the cache files are
> cleared and the parser exits.
> 
> From main() of parser_main.c:
> 
>         if (force_clear_cache)
> 	                exit(clear_cache_files(cacheloc));
> 
>>>  
>>> -	if (create_cache_dir)
>>> -		pwarn(_("The --create-cache-dir option is deprecated. Please use --write-cache.\n"));
>>> +		if (create_cache_dir)
>>> +			pwarn(_("The --create-cache-dir option is deprecated. Please use --write-cache.\n"));
>>>  
>>> -	retval = setup_cache(features, cacheloc);
>>> -	if (retval) {
>>> -		PERROR(_("Failed setting up policy cache (%s): %s\n"),
>>> -		       cacheloc, strerror(errno));
>>> -		return 1;
>>> +		retval = aa_policy_cache_new(&policy_cache, features, cacheloc,
>>> +					     write_cache);
>>> +		if (retval) {
>>> +			if (errno != ENOENT) {
>>> +				PERROR(_("Failed setting up policy cache (%s): %s\n"),
>>> +				       cacheloc, strerror(errno));
>>> +				return 1;
>>> +			}
>>> +
>>> +			write_cache = 0;
>>> +			skip_read_cache = 0;
>>> +		} else if (!aa_policy_cache_is_valid(policy_cache)) {
>>> +			if (write_cache && cond_clear_cache &&
>>> +			    aa_policy_cache_create(policy_cache)) {
>>> +				skip_read_cache = 1;
>>> +			} else if (!write_cache || !cond_clear_cache) {
>>> +				if (show_cache)
>>> +					PERROR("Cache read/write disabled: Policy cache is invalid\n");
>>> +				write_cache = 0;
>>> +				skip_read_cache = 1;
>>> +			}
>>> +		}
>>
>> The way this is setup we change cache behavior
>>
>> If write_cache is specified the cache is always cleared at aa_policy_cache_new
>> and the following cond_clear_cache test is useless
> 
> I don't think this is correct.
> 
> The cache is only cleared, before consulting cond_clear_cache, if
> aa_features_new() fails when called by init_cache_features(). It only
> fails if there's not a valid features file due to the dir not existing,
> the .features file not existing, etc.
> 
gah, yep sorry I flipped a condition around aa_features_new


> If there is a valid .features file, aa_policy_cache_new() will return
> success. Then there's the check to aa_policy_cache_is_valid(). If that
> fails and write_cache and cond_clear_cache are true, then we clear the
> cache.
> 
>>
>> now whether we want to always clear the cache on write cache is the features
>> differ is another question
>>
>>>  	}
>>>  
>>>  	retval = last_error = 0;
>>> diff --git a/parser/policy_cache.c b/parser/policy_cache.c
>>> index 6c8d5ca..f5061b1 100644
>>> --- a/parser/policy_cache.c
>>> +++ b/parser/policy_cache.c
>>> @@ -35,6 +35,14 @@
>>>  
>>>  #define le16_to_cpu(x) ((uint16_t)(le16toh (*(uint16_t *) x)))
>>>  
>>> +struct aa_policy_cache {
>>> +	unsigned int ref_count;
>>> +	aa_features *features;
>>> +	aa_features *kernel_features;
>>> +	char *path;
>>> +	char *features_path;
>>> +};
>>> +
>>>  const char header_string[] = "\004\010\000version\000\002";
>>>  #define HEADER_STRING_SIZE 12
>>>  bool valid_cached_file_version(const char *cachename)
>>> @@ -107,37 +115,44 @@ int clear_cache_files(const char *path)
>>>  	return dirat_for_each(NULL, path, NULL, clear_cache_cb);
>>>  }
>>>  
>>> -int create_cache(const char *cachedir, const char *path, aa_features *features)
>>> +static int create_cache(aa_policy_cache *policy_cache, aa_features *features)
>>>  {
>>>  	struct stat stat_file;
>>>  	autofclose FILE * f = NULL;
>>>  
>>> -	if (clear_cache_files(cachedir) != 0)
>>> +	if (clear_cache_files(policy_cache->path) != 0)
>>>  		goto error;
>>>  
>>>  create_file:
>>> -	if (aa_features_write_to_file(features, path) == -1)
>>> +	if (aa_features_write_to_file(features,
>>> +				      policy_cache->features_path) == -1)
>>>  		goto error;
>>>  
>>> +	aa_features_unref(policy_cache->features);
>>> +	policy_cache->features = aa_features_ref(features);
>>>  	return 0;
>>>  
>>>  error:
>>>  	/* does the dir exist? */
>>> -	if (stat(cachedir, &stat_file) == -1) {
>>> -		if (mkdir(cachedir, 0700) == 0)
>>> +	if (stat(policy_cache->path, &stat_file) == -1) {
>>> +		if (mkdir(policy_cache->path, 0700) == 0)
>>>  			goto create_file;
>>>  		if (show_cache)
>>> -			PERROR(_("Can't create cache directory: %s\n"), cachedir);
>>> +			PERROR(_("Can't create cache directory: %s\n"),
>>> +			       policy_cache->path);
>>>  	} else if (!S_ISDIR(stat_file.st_mode)) {
>>>  		if (show_cache)
>>> -			PERROR(_("File in cache directory location: %s\n"), cachedir);
>>> +			PERROR(_("File in cache directory location: %s\n"),
>>> +			       policy_cache->path);
>>>  	} else {
>>>  		if (show_cache)
>>> -			PERROR(_("Can't update cache directory: %s\n"), cachedir);
>>> +			PERROR(_("Can't update cache directory: %s\n"),
>>> +			       policy_cache->path);
>>>  	}
>>>  
>>>  	if (show_cache)
>>> -		PERROR("Cache write disabled: cannot create %s\n", path);
>>> +		PERROR("Cache write disabled: cannot create %s\n",
>>> +		       policy_cache->features_path);
>>>  	write_cache = 0;
>>>  
>>>  	return -1;
>>> @@ -224,45 +239,127 @@ void install_cache(const char *cachetmpname, const char *cachename)
>>>  	}
>>>  }
>>>  
>>> -int setup_cache(aa_features *kernel_features, const char *cacheloc)
>>> +static int init_cache_features(aa_policy_cache *policy_cache,
>>> +			       aa_features *kernel_features, bool create)
>>>  {
>>> -	autofree char *cache_features_path = NULL;
>>> -	aa_features *cache_features;
>>> +	if (aa_features_new(&policy_cache->features,
>>> +			    policy_cache->features_path)) {
>>> +		policy_cache->features = NULL;
>>> +		if (!create || errno != ENOENT)
>>> +			return -1;
>>> +
>>> +		if (create_cache(policy_cache, kernel_features))
>>> +			return -1;
>> this could be just
>>   return create_cache(...
> 
> I've made this change in my local tree. Additionally, I carried the
> change into the "libapparmor: Move the aa_policy_cache API" patch in the
> second set of patches.
> 
>>
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>>  
>>> -	if (!cacheloc) {
>>> +/**
>>> + * aa_policy_cache_new - create a new policy_cache from a path
>>> + * @policy_cache: will point to the address of an allocated and initialized
>>> + *                aa_policy_cache_new object upon success
>>> + * @kernel_features: features representing the currently running kernel
>>> + * @path: path to the policy cache
>>> + * @create: true if the cache should be created if it doesn't already exist
>>> + *
>>> + * Returns: 0 on success, -1 on error with errno set and *@policy_cache
>>> + *          pointing to NULL
>>> + */
>>> +int aa_policy_cache_new(aa_policy_cache **policy_cache,
>>> +			aa_features *kernel_features, const char *path,
>>> +			bool create)
>>> +{
>>> +	aa_policy_cache *pc;
>>> +
>>> +	*policy_cache = NULL;
>>> +
>>> +	if (!path) {
>>>  		errno = EINVAL;
>>>  		return -1;
>>>  	}
>>>  
>>> -	/*
>>> -         * Deal with cache directory versioning:
>>> -         *  - If cache/.features is missing, create it if --write-cache.
>>> -         *  - If cache/.features exists, and does not match features_string,
>>> -         *    force cache reading/writing off.
>>> -         */
>>> -	if (asprintf(&cache_features_path, "%s/.features", cacheloc) == -1) {
>>> -		PERROR(_("Memory allocation error."));
>>> +	pc = (aa_policy_cache *) calloc(1, sizeof(*pc));
>>> +	if (!pc) {
>>>  		errno = ENOMEM;
>>>  		return -1;
>>>  	}
>>> +	aa_policy_cache_ref(pc);
>>>  
>>> -	if (!aa_features_new(&cache_features, cache_features_path)) {
>>> -		if (!aa_features_is_equal(kernel_features, cache_features)) {
>>> -			if (write_cache && cond_clear_cache) {
>>> -				if (create_cache(cacheloc, cache_features_path,
>>> -						 kernel_features))
>>> -					skip_read_cache = 1;
>>> -			} else {
>>> -				if (show_cache)
>>> -					PERROR("Cache read/write disabled: Police cache is invalid\n");
>>> -				write_cache = 0;
>>> -				skip_read_cache = 1;
>>> -			}
>>> -		}
>>> -		aa_features_unref(cache_features);
>>> -	} else if (write_cache) {
>>> -		create_cache(cacheloc, cache_features_path, kernel_features);
>>> +	pc->path = strdup(path);
>>> +	if (!pc->path) {
>>> +		aa_policy_cache_unref(pc);
>>> +		errno = ENOMEM;
>>> +		return -1;
>>> +	}
>>> +
>>> +	if (asprintf(&pc->features_path, "%s/.features", pc->path) == -1) {
>>> +		pc->features_path = NULL;
>>> +		aa_policy_cache_unref(pc);
>>> +		errno = ENOMEM;
>>> +		return -1;
>>> +	}
>>> +
>>> +	if (init_cache_features(pc, kernel_features, create)) {
>>> +		int save = errno;
>>> +
>>> +		aa_policy_cache_unref(pc);
>>> +		errno = save;
>>> +		return -1;
>>>  	}
>>>  
>>> +	pc->kernel_features = aa_features_ref(kernel_features);
>>> +	*policy_cache = pc;
>>> +
>>>  	return 0;
>>>  }
>>> +
>>> +/**
>>> + * aa_policy_cache_ref - increments the ref count of a policy_cache
>>> + * @policy_cache: the policy_cache
>>> + *
>>> + * Returns: the policy_cache
>>> + */
>>> +aa_policy_cache *aa_policy_cache_ref(aa_policy_cache *policy_cache)
>>> +{
>>> +	atomic_inc(&policy_cache->ref_count);
>>> +	return policy_cache;
>>> +}
>>> +
>>> +/**
>>> + * aa_policy_cache_unref - decrements the ref count and frees the policy_cache when 0
>>> + * @policy_cache: the policy_cache (can be NULL)
>>> + */
>>> +void aa_policy_cache_unref(aa_policy_cache *policy_cache)
>>> +{
>>> +	if (policy_cache && atomic_dec_and_test(&policy_cache->ref_count)) {
>>> +		free(policy_cache->features_path);
>>> +		free(policy_cache->path);
>>> +		free(policy_cache);
>>> +	}
>>> +}
>>> +
>>> +/**
>>> + * aa_policy_cache_is_valid - checks if the policy_cache is valid for the currently running kernel
>>> + * @policy_cache: the policy_cache
>>> + *
>>> + * Returns: true if the policy_cache is valid for the currently running kernel,
>>> + *          false if not
>>> + */
>>> +bool aa_policy_cache_is_valid(aa_policy_cache *policy_cache)
>>> +{
>>> +	return aa_features_is_equal(policy_cache->features,
>>> +				    policy_cache->kernel_features);
>> So this is just me griping about wanting to get away from the equality test
>>
>> this is currently equiv to what we have but not really a true validity test
>>
>> It is possible to have cache that has more features compiled into it than the
>> current kernel supports and the kernel will still enforce it correctly. It is
>> also possible the kernel won't and the cache must be cleared (it depends on the
>> feature).
>>
>> The reverse situation where the cache supports fewer features than the current
>> kernel (kernel backport situation). Is interesting in that we currently handle
>> it poorly. The compiler, though it doesn't support the full feature set, will
>> store the kernel feature set in .features file but set a different abi in each
>> of the compiled cache files. The abi lets us detect the cache file is obsolete
>> but only on a per cache file basis, Not a global cache valid basis. So if we
>> are in an upgrade situation it is possible that if the userspace only used
>> aa_policy_cache_is_valid() it would start loading old files that should be
>> removed.
>>
>> And we haven't even gotten into some of the other feature issues that would
>> let us eliminate duplication and compile times between different kernels,
>> but then storage is cheap now so maybe those aren't worth solving.
> 
> This is exactly why I named the function aa_policy_is_valid() instead of
> something like aa_policy_cache_features_equals_kernel_features(). We can
> change the check to whatever we want in the future. We can change the
> implementation from doing a dumb equality check and do the intelligent
> checks that you mentioned. Callers of this function won't know the
> difference.
> 
> I see the systemd support and intelligent cache handling as two
> different chunks of work. Hopefully we can come up with an API that
> allows for systemd support now and intelligent cache handling in the
> future. Otherwise, we need to make these intelligent caching changes
> first and then turn it all into a library.
> 
> Tyler
> 
okay, like I said just my griping about equality and wanting to make sure
that is not considered what is valid.

>>> +}
>>> +
>>> +/**
>>> + * aa_policy_cache_create - creates a valid policy_cache for the currently running kernel
>>> + * @policy_cache: the policy_cache
>>> + *
>>> + * Returns: 0 on success, -1 on error with errno set and features pointing to
>>> + *          NULL
>>> + */
>>> +int aa_policy_cache_create(aa_policy_cache *policy_cache)
>>> +{
>>> +	return create_cache(policy_cache, policy_cache->kernel_features);
>>> +}
>>> diff --git a/parser/policy_cache.h b/parser/policy_cache.h
>>> index 728de57..7192939 100644
>>> --- a/parser/policy_cache.h
>>> +++ b/parser/policy_cache.h
>>> @@ -42,12 +42,20 @@ void set_mru_tstamp(struct timespec t);
>>>  void update_mru_tstamp(FILE *file, const char *path);
>>>  bool valid_cached_file_version(const char *cachename);
>>>  int clear_cache_files(const char *path);
>>> -int create_cache(const char *cachedir, const char *path, const char *features);
>>>  char *cache_filename(const char *cachedir, const char *basename);
>>>  void valid_read_cache(const char *cachename);
>>>  int cache_hit(const char *cachename);
>>>  int setup_cache_tmp(const char **cachetmpname, const char *cachename);
>>>  void install_cache(const char *cachetmpname, const char *cachename);
>>> -int setup_cache(aa_features *kernel_features, const char *cacheloc);
>>> +
>>> +typedef struct aa_policy_cache aa_policy_cache;
>>> +
>>> +int aa_policy_cache_new(aa_policy_cache **policy_cache,
>>> +			aa_features *kernel_features, const char *path,
>>> +			bool create);
>>> +aa_policy_cache *aa_policy_cache_ref(aa_policy_cache *policy_cache);
>>> +void aa_policy_cache_unref(aa_policy_cache *policy_cache);
>>> +bool aa_policy_cache_is_valid(aa_policy_cache *policy_cache);
>>> +int aa_policy_cache_create(aa_policy_cache *policy_cache);
>>>  
>>>  #endif /* __AA_POLICY_CACHE_H */
>>>
>>
>>




More information about the AppArmor mailing list