[apparmor] RFC: cryptographic profile hashing

John Johansen john.johansen at canonical.com
Tue Jul 23 05:37:06 UTC 2013


On 07/22/2013 06:19 PM, Seth Arnold wrote:
> On Fri, Jul 19, 2013 at 10:27:57PM -0700, John Johansen wrote:
>> Below is a patch that has been hanging around for a long time but needs a
>> little more work to be completed. What it does is add a per profile sha1
>> hash to apparmorfs. So that kernel policy can be verified against the
>> policy in userspace.
>>
>> eg.
>>> cat /sys/kernel/security/apparmor/policy/profiles/bin.ping.7/sha1
>> 40463c310088fffff02d10300088ffff00194e3b
>>
>>
>> The hash value added is NOT the hash value that can be attained by running
>> sha1sum on the profile or cache file, because we aren't loading the
>> original profile text into the kernel, and the binary cache file may
>> contain multiple profiles, but the kernel doesn't see the profiles at the
>> file level but as a stream of profiles. Instead it hashes each individual
>> profile within the profile stream. This means we would need a userspace
>> tool capable of hashing the profiles within a binary cache. This would be
>> one of the things that needs to be done so that we can test and verify
>> everything is working as expect.
>>
>> we could move the hash to md5, or another hash or even make it selectable.
>> I think md5 would be sufficient as we are just using it provide a simple
>> way to verify what policy is loaded. And the plan is to make the full
>> binary policy available as well at some point so perhaps this isn't
>> even needed.  Except the kernel export binary may not exactly match
>> what is in userspace because the load stage may transform parts of it.
> 
> sha1 is fine, md5 isn't faster enough to justify switching to it, I
> think.
>
that was my feeling as well

>>
>> ---
>>
>> diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig
>> index 9b9013b..5da5e4f 100644
>> --- a/security/apparmor/Kconfig
>> +++ b/security/apparmor/Kconfig
>> @@ -29,3 +29,16 @@ config SECURITY_APPARMOR_BOOTPARAM_VALUE
>>  	  boot.
>>  
>>  	  If you are unsure how to answer this question, answer 1.
>> +
>> +config SECURITY_APPARMOR_HASH
>> +	bool "Cryptographic hash of loaded profiles"
> 
> I think "cryptographic" is overselling the feature. "Hash" alone would
> suit me.
> 
Hash is fine by me too, part of this is that the original patch offered
a choice dialog in menu config and you could choose the has you wanted.

In fact I thin "SHA1 Hash of loaded profiles" works well for the current
patch.

>> +	depends on SECURITY_APPARMOR
>> +	depends on CRYPTO
>> +	select CRYPTO_SHA1
>> +	default n
>> +
>> +	help
>> +	  This option selects whether cryptographic hashing is done
>> +	  against loaded profiles to present to user space via the apparmor
>> +	  filesystem so that userspace can validate the currently loaded
>> +	  profiles.
> 
> ... and I'm a little leery of "validate the currently loaded profiles".
> The hash is computed when the profile is loaded, not at the time the
> sha1 file is read, so it identifes the policy that was loaded, not the
> current policy. (They should be the same, barring bugs, of course...)
> 
Well they had better be the same thing or we have a series bug. Of course
I am fine saying "the loaded policy"

>> diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile
>> index 5706b74..adbef37 100644
>> --- a/security/apparmor/Makefile
>> +++ b/security/apparmor/Makefile
>> @@ -5,6 +5,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o
>>  apparmor-y := apparmorfs.o audit.o capability.o context.o ipc.o lib.o match.o \
>>                path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \
>>                resource.o sid.o file.o
>> +apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
>>  
>>  clean-files := capability_names.h rlim_names.h
>>  
>> diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
>> index 5325e90..abe6386 100644
>> --- a/security/apparmor/apparmorfs.c
>> +++ b/security/apparmor/apparmorfs.c
>> @@ -26,6 +26,7 @@
>>  #include "include/apparmorfs.h"
>>  #include "include/audit.h"
>>  #include "include/context.h"
>> +#include "include/crypto.h"
>>  #include "include/policy.h"
>>  #include "include/resource.h"
>>  
>> @@ -319,6 +320,33 @@ static const struct file_operations aa_fs_profattach_fops = {
>>  	.release	= aa_fs_seq_profile_release,
>>  };
>>  
>> +static int aa_fs_seq_hash_show(struct seq_file *seq, void *v)
>> +{
>> +	char *string = seq->private;
> 
> If this were "unsigned char" you wouldn't need the cast lower...
> 
yep

>> +	unsigned int i;
>> +
>> +	if (string) {
>> +	  for (i = 0; i < aa_hash_size(); i++)
>> +			seq_printf(seq, "%.2x", (unsigned char) string[i]);
>> +		seq_printf(seq, "\n");
>> +	}
> 
> The indentation of the 'for' line is incorrect.
> 
yep

> Will gcc haul out the aa_hash_size() function call from the for loop? If
> not, we should. (We could even hard-code the length of sha1 in the few
Hrmmm it will for the disabled case but not I think for the enabled case,
at least not without cross link analysis and discovery that the hash fns
don't touch any mem etc.

So yeah we should lift it out

> places it's used and get rid of the function and variable entirely...
> it'll never change at runtime, ksplice aside.)
> 
It rather not, while this patch doesn't provide alternative hashes, its
structured to do so easily, if at some point we choose to do so.

>> +
>> +	return 0;
>> +}
>> +
>> +static int aa_fs_seq_hash_open(struct inode *inode, struct file *file)
>> +{
>> +	return single_open(file, aa_fs_seq_hash_show, inode->i_private);
>> +}
>> +
>> +static const struct file_operations aa_fs_seq_hash_fops = {
>> +	.owner		= THIS_MODULE,
>> +	.open		= aa_fs_seq_hash_open,
>> +	.read		= seq_read,
>> +	.llseek		= seq_lseek,
>> +	.release	= single_release,
>> +};
>> +
>>  /** fns to setup dynamic per profile/namespace files **/
>>  void __aa_fs_profile_rmdir(struct aa_profile *profile)
>>  {
>> @@ -420,6 +448,15 @@ int __aa_fs_profile_mkdir(struct aa_profile *profile, struct dentry *parent)
>>  		goto fail;
>>  	profile->dents[AAFS_PROF_ATTACH] = dent;
>>  
>> +	if (profile->hash) {
>> +		dent = securityfs_create_file("sha1", S_IFREG | 0444, dir,
>> +					      &profile->hash,
>> +					      &aa_fs_seq_hash_fops);
>> +		if (IS_ERR(dent))
>> +			goto fail;
>> +		profile->dents[AAFS_PROF_HASH] = dent;
>> +	}
>> +
>>  	list_for_each_entry(child, &profile->base.profiles, base.list) {
>>  		error = __aa_fs_profile_mkdir(child, prof_child_dir(profile));
>>  		if (error)
>> diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c
>> new file mode 100644
>> index 0000000..8c2484d
>> --- /dev/null
>> +++ b/security/apparmor/crypto.c
>> @@ -0,0 +1,90 @@
>> +/*
>> + * AppArmor security module
>> + *
>> + * This file contains AppArmor policy loading interface function definitions.
>> + *
>> + * Copyright 2013 Canonical Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation, version 2 of the
>> + * License.
>> + *
>> + * Fns to provide a cryptographic checksum of policy that has been loaded
>> + * this can be compared to userspace policy compiles to verify loaded
>> + * policy is what it should be.
>> + */
>> +
>> +#include <linux/crypto.h>
>> +
>> +#include "include/apparmor.h"
>> +#include "include/crypto.h"
>> +
>> +static unsigned int apparmor_hash_size;
>> +
>> +static struct crypto_hash *apparmor_tfm;
>> +
>> +unsigned int aa_hash_size(void)
>> +{
>> +	return apparmor_hash_size;
>> +}
>> +
>> +int aa_calc_profile_hash(struct aa_profile *profile, void *start, size_t len)
>> +{
>> +	struct scatterlist sg;
>> +	struct hash_desc desc = {
>> +		.tfm = apparmor_tfm,
>> +		.flags = 0
>> +	};
>> +	int error = -ENOMEM;
>> +
>> +	if (!apparmor_tfm)
>> +		return 0;
>> +
>> +	sg_init_one(&sg, (u8 *) start, len);
>> +
>> +	profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL);
>> +	if (!profile->hash)
>> +		goto fail;
>> +
>> +	error = crypto_hash_init(&desc);
>> +	if (error)
>> +		goto fail;
>> +	error = crypto_hash_update(&desc, &sg, len);
>> +	if (error)
>> +		goto fail;
>> +	error = crypto_hash_final(&desc, profile->hash);
>> +	if (error)
>> +		goto fail;
>> +
>> +	return 0;
>> +
>> +fail:
>> +	kfree(profile->hash);
>> +	profile->hash = NULL;
>> +
>> +	return error;
>> +}
>> +
>> +static int __init init_profile_hash(void)
>> +{
>> +	struct crypto_hash *tfm;
>> +
>> +	if (!apparmor_initialized)
>> +		return 0;
>> +
>> +	tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
>> +	if (IS_ERR(tfm)) {
>> +		int error = PTR_ERR(tfm);
>> +		AA_ERROR("failed to setup profile sha1 hashing: %d\n", error);
>> +		return error;
>> +	}
>> +	apparmor_tfm = tfm;
>> +	apparmor_hash_size = crypto_hash_digestsize(apparmor_tfm);
>> +
>> +	aa_info_message("AppArmor cryptographic sha1 policy hashing Enabled");
>> +
>> +	return 0;
>> +}
>> +
>> +late_initcall(init_profile_hash);
>> diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h
>> index f91712c..414e568 100644
>> --- a/security/apparmor/include/apparmorfs.h
>> +++ b/security/apparmor/include/apparmorfs.h
>> @@ -82,6 +82,7 @@ enum aafs_prof_type {
>>  	AAFS_PROF_NAME,
>>  	AAFS_PROF_MODE,
>>  	AAFS_PROF_ATTACH,
>> +	AAFS_PROF_HASH,
>>  	AAFS_PROF_SIZEOF,
>>  };
>>  
>> diff --git a/security/apparmor/include/crypto.h b/security/apparmor/include/crypto.h
>> new file mode 100644
>> index 0000000..613944d
>> --- /dev/null
>> +++ b/security/apparmor/include/crypto.h
>> @@ -0,0 +1,35 @@
>> +/*
>> + * AppArmor security module
>> + *
>> + * This file contains AppArmor policy loading interface function definitions.
>> + *
>> + * Copyright 2013 Canonical Ltd.
>> + *
>> + * This program is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU General Public License as
>> + * published by the Free Software Foundation, version 2 of the
>> + * License.
>> + */
>> +
>> +#ifndef __APPARMOR_CRYPTO_H
>> +#define __APPARMOR_CRYPTO_H
>> +
>> +#include "policy.h"
>> +
>> +#ifdef CONFIG_SECURITY_APPARMOR_HASH
>> +unsigned int aa_hash_size(void);
>> +int aa_calc_profile_hash(struct aa_profile *profile, void *start, size_t len);
>> +#else
>> +static inline int aa_calc_profile_hash(struct aa_profile *profile, void *start,
>> +				       size_t len)
>> +{
>> +	return 0;
>> +}
>> +
>> +static inline unsigned int aa_hash_size(void)
>> +{
>> +	return 0;
>> +}
>> +#endif
>> +
>> +#endif /* __APPARMOR_CRYPTO_H */
>> diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
>> index 6283caf..e8460e4 100644
>> --- a/security/apparmor/include/policy.h
>> +++ b/security/apparmor/include/policy.h
>> @@ -218,6 +218,7 @@ struct aa_profile {
>>  	struct aa_caps caps;
>>  	struct aa_rlimit rlimits;
>>  
>> +	char *hash;
> 
> unsigned char instead? It stores a binary blob, not human-aimed
> characters...
> 
yep

>>  	char *dirname;
>>  	struct dentry *dents[AAFS_PROF_SIZEOF];
>>  };
>> diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
>> index f5b9977..49fff70 100644
>> --- a/security/apparmor/policy_unpack.c
>> +++ b/security/apparmor/policy_unpack.c
>> @@ -24,6 +24,7 @@
>>  #include "include/apparmor.h"
>>  #include "include/audit.h"
>>  #include "include/context.h"
>> +#include "include/crypto.h"
>>  #include "include/match.h"
>>  #include "include/policy.h"
>>  #include "include/policy_unpack.h"
>> @@ -66,6 +67,7 @@ struct aa_ext {
>>  	u32 version;
>>  };
>>  
>> +
>>  /* audit callback for unpack fields */
>>  static void audit_cb(struct audit_buffer *ab, void *va)
>>  {
>> @@ -756,12 +758,14 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
>>  
>>  	*ns = NULL;
>>  	while (e.pos < e.end) {
>> +		void *start;
>>  		error = verify_header(&e, e.pos == e.start, ns);
>>  		if (error)
>>  			goto fail;
>>  
>>  		/* alignment adjust needed by dfa unpack */
>>  		e.adj = (e.pos - e.start) & 7;
>> +		start = e.pos;
>>  		profile = unpack_profile(&e);
>>  		if (IS_ERR(profile)) {
>>  			error = PTR_ERR(profile);
>> @@ -769,16 +773,17 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
>>  		}
>>  
>>  		error = verify_profile(profile);
>> -		if (error) {
>> -			aa_free_profile(profile);
>> -			goto fail;
>> -		}
>> +		if (error)
>> +			goto fail_profile;
> 
> I don't understand this change. Is this the correct thing to do before
> the aa_load_ent_alloc() call below? (Which appeared to be the turning
> point between free/fail vs put/fail...)
> 
So yes if we are going to fail there is no point in allocating the
load_ent.  In fact at this point we don't really need put_profile and
we could change these to calls to free_profile.

We know at this point we have the single reference count, either on
the load ent, or the ref we are about to create a load ent for

put profile really only becomes necessary as we start getting more
references in the replace_profiles routine that calls this.

>> +
>> +		error = aa_calc_profile_hash(profile, start, e.pos - start);
>> +		if (error)
>> +			goto fail_profile;
>>  
>>  		ent = aa_load_ent_alloc();
>>  		if (!ent) {
>>  			error = -ENOMEM;
>> -			aa_put_profile(profile);
>> -			goto fail;
>> +			goto fail_profile;
>>  		}
>>  
>>  		ent->new = profile;
>> @@ -787,6 +792,9 @@ int aa_unpack(void *udata, size_t size, struct list_head *lh, const char **ns)
>>  
>>  	return 0;
>>  
>> +fail_profile:
>> +	aa_put_profile(profile);
>> +
>>  fail:
>>  	list_for_each_entry_safe(ent, tmp, lh, list) {
>>  		list_del_init(&ent->list);
> 
> 
> Thanks
> 
> 
> 




More information about the AppArmor mailing list