[apparmor] RFC: cryptographic profile hashing

John Johansen john.johansen at canonical.com
Sat Jul 20 05:27:57 UTC 2013


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.


---

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"
+	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.
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;
+	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");
+	}
+
+	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;
 	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;
+
+		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);



More information about the AppArmor mailing list