From a3c9e45d18ace44d02574518144742a00c7a94ec Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Tue, 10 Sep 2013 09:51:50 -0400 Subject: security: remove erroneous comment about capabilities.o link ordering Back when we had half ass LSM stacking we had to link capabilities.o after bigger LSMs so that on initialization the bigger LSM would register first and the capabilities module would be the one stacked as the 'seconday'. Somewhere around 6f0f0fd496333777d53 (back in 2008) we finally removed the last of the kinda module stacking code but this comment in the makefile still lives today. Reported-by: Valdis Kletnieks Signed-off-by: Eric Paris Signed-off-by: James Morris --- security/Makefile | 1 - 1 file changed, 1 deletion(-) (limited to 'security') diff --git a/security/Makefile b/security/Makefile index c26c81e..a5918e0 100644 --- a/security/Makefile +++ b/security/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_MMU) += min_addr.o # Object file lists obj-$(CONFIG_SECURITY) += security.o capability.o obj-$(CONFIG_SECURITYFS) += inode.o -# Must precede capability.o in order to stack properly. obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_AUDIT) += lsm_audit.o -- cgit v1.1 From 61ea0c0ba904a55f55317d850c1072ff7835ac92 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:13 +0100 Subject: KEYS: Skip key state checks when checking for possession Skip key state checks (invalidation, revocation and expiration) when checking for possession. Without this, keys that have been marked invalid, revoked keys and expired keys are not given a possession attribute - which means the possessor is not granted any possession permits and cannot do anything with them unless they also have one a user, group or other permit. This causes failures in the keyutils test suite's revocation and expiration tests now that commit 96b5c8fea6c0861621051290d705ec2e971963f1 reduced the initial permissions granted to a key. The failures are due to accesses to revoked and expired keys being given EACCES instead of EKEYREVOKED or EKEYEXPIRED. Signed-off-by: David Howells --- security/keys/internal.h | 1 + security/keys/process_keys.c | 8 +++++--- security/keys/request_key.c | 6 ++++-- security/keys/request_key_auth.c | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index d4f1468..df971fe 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -124,6 +124,7 @@ extern key_ref_t search_my_process_keyrings(struct key_type *type, extern key_ref_t search_process_keyrings(struct key_type *type, const void *description, key_match_func_t match, + bool no_state_check, const struct cred *cred); extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 42defae..a3410d6 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -440,6 +440,7 @@ found: key_ref_t search_process_keyrings(struct key_type *type, const void *description, key_match_func_t match, + bool no_state_check, const struct cred *cred) { struct request_key_auth *rka; @@ -448,7 +449,7 @@ key_ref_t search_process_keyrings(struct key_type *type, might_sleep(); key_ref = search_my_process_keyrings(type, description, match, - false, cred); + no_state_check, cred); if (!IS_ERR(key_ref)) goto found; err = key_ref; @@ -468,7 +469,8 @@ key_ref_t search_process_keyrings(struct key_type *type, rka = cred->request_key_auth->payload.data; key_ref = search_process_keyrings(type, description, - match, rka->cred); + match, no_state_check, + rka->cred); up_read(&cred->request_key_auth->sem); @@ -675,7 +677,7 @@ try_again: /* check to see if we possess the key */ skey_ref = search_process_keyrings(key->type, key, lookup_user_key_possessed, - cred); + true, cred); if (!IS_ERR(skey_ref)) { key_put(key); diff --git a/security/keys/request_key.c b/security/keys/request_key.c index c411f9b..172115b 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -390,7 +390,8 @@ static int construct_alloc_key(struct key_type *type, * waited for locks */ mutex_lock(&key_construction_mutex); - key_ref = search_process_keyrings(type, description, type->match, cred); + key_ref = search_process_keyrings(type, description, type->match, + false, cred); if (!IS_ERR(key_ref)) goto key_already_present; @@ -539,7 +540,8 @@ struct key *request_key_and_link(struct key_type *type, dest_keyring, flags); /* search all the process keyrings for a key */ - key_ref = search_process_keyrings(type, description, type->match, cred); + key_ref = search_process_keyrings(type, description, type->match, + false, cred); if (!IS_ERR(key_ref)) { key = key_ref_to_ptr(key_ref); diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 85730d5..92077de 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -247,7 +247,7 @@ struct key *key_get_instantiation_authkey(key_serial_t target_id) &key_type_request_key_auth, (void *) (unsigned long) target_id, key_get_instantiation_authkey_match, - cred); + false, cred); if (IS_ERR(authkey_ref)) { authkey = ERR_CAST(authkey_ref); -- cgit v1.1 From a5b4bd2874d9032b42db8cc4880058576c561b06 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:14 +0100 Subject: KEYS: Use bool in make_key_ref() and is_key_possessed() Make make_key_ref() take a bool possession parameter and make is_key_possessed() return a bool. Signed-off-by: David Howells --- security/keys/keyring.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 6ece7f2..f784063 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -329,9 +329,10 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, struct keyring_list *keylist; struct timespec now; - unsigned long possessed, kflags; + unsigned long kflags; struct key *keyring, *key; key_ref_t key_ref; + bool possessed; long err; int sp, nkeys, kix; @@ -542,8 +543,8 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, key_perm_t perm) { struct keyring_list *klist; - unsigned long possessed; struct key *keyring, *key; + bool possessed; int nkeys, loop; keyring = key_ref_to_ptr(keyring_ref); -- cgit v1.1 From 7e55ca6dcd07b45619035df343c9614a3ab35034 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:14 +0100 Subject: KEYS: key_is_dead() should take a const key pointer argument key_is_dead() should take a const key pointer argument as it doesn't modify what it points to. Signed-off-by: David Howells --- security/keys/internal.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index df971fe..490aef5 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -203,7 +203,7 @@ extern struct key *key_get_instantiation_authkey(key_serial_t target_id); /* * Determine whether a key is dead. */ -static inline bool key_is_dead(struct key *key, time_t limit) +static inline bool key_is_dead(const struct key *key, time_t limit) { return key->flags & ((1 << KEY_FLAG_DEAD) | -- cgit v1.1 From 16feef4340172b7dbb9cba60850e78fa6388adf1 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:15 +0100 Subject: KEYS: Consolidate the concept of an 'index key' for key access Consolidate the concept of an 'index key' for accessing keys. The index key is the search term needed to find a key directly - basically the key type and the key description. We can add to that the description length. This will be useful when turning a keyring into an associative array rather than just a pointer block. Signed-off-by: David Howells --- security/keys/internal.h | 8 ++--- security/keys/key.c | 72 +++++++++++++++++++++++---------------------- security/keys/keyring.c | 37 +++++++++++------------ security/keys/request_key.c | 12 +++++--- 4 files changed, 67 insertions(+), 62 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index 490aef5..77441dd 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -89,19 +89,17 @@ extern struct key_type *key_type_lookup(const char *type); extern void key_type_put(struct key_type *ktype); extern int __key_link_begin(struct key *keyring, - const struct key_type *type, - const char *description, + const struct keyring_index_key *index_key, unsigned long *_prealloc); extern int __key_link_check_live_key(struct key *keyring, struct key *key); extern void __key_link(struct key *keyring, struct key *key, unsigned long *_prealloc); extern void __key_link_end(struct key *keyring, - struct key_type *type, + const struct keyring_index_key *index_key, unsigned long prealloc); extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct key_type *type, - const char *description, + const struct keyring_index_key *index_key, key_perm_t perm); extern struct key *keyring_search_instkey(struct key *keyring, diff --git a/security/keys/key.c b/security/keys/key.c index 8fb7c7b..7e6bc39 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -242,8 +242,8 @@ struct key *key_alloc(struct key_type *type, const char *desc, } } - desclen = strlen(desc) + 1; - quotalen = desclen + type->def_datalen; + desclen = strlen(desc); + quotalen = desclen + 1 + type->def_datalen; /* get hold of the key tracking for this user */ user = key_user_lookup(uid); @@ -277,7 +277,8 @@ struct key *key_alloc(struct key_type *type, const char *desc, goto no_memory_2; if (desc) { - key->description = kmemdup(desc, desclen, GFP_KERNEL); + key->index_key.desc_len = desclen; + key->index_key.description = kmemdup(desc, desclen + 1, GFP_KERNEL); if (!key->description) goto no_memory_3; } @@ -285,7 +286,7 @@ struct key *key_alloc(struct key_type *type, const char *desc, atomic_set(&key->usage, 1); init_rwsem(&key->sem); lockdep_set_class(&key->sem, &type->lock_class); - key->type = type; + key->index_key.type = type; key->user = user; key->quotalen = quotalen; key->datalen = type->def_datalen; @@ -489,8 +490,7 @@ int key_instantiate_and_link(struct key *key, } if (keyring) { - ret = __key_link_begin(keyring, key->type, key->description, - &prealloc); + ret = __key_link_begin(keyring, &key->index_key, &prealloc); if (ret < 0) goto error_free_preparse; } @@ -499,7 +499,7 @@ int key_instantiate_and_link(struct key *key, &prealloc); if (keyring) - __key_link_end(keyring, key->type, prealloc); + __key_link_end(keyring, &key->index_key, prealloc); error_free_preparse: if (key->type->preparse) @@ -548,8 +548,7 @@ int key_reject_and_link(struct key *key, ret = -EBUSY; if (keyring) - link_ret = __key_link_begin(keyring, key->type, - key->description, &prealloc); + link_ret = __key_link_begin(keyring, &key->index_key, &prealloc); mutex_lock(&key_construction_mutex); @@ -581,7 +580,7 @@ int key_reject_and_link(struct key *key, mutex_unlock(&key_construction_mutex); if (keyring) - __key_link_end(keyring, key->type, prealloc); + __key_link_end(keyring, &key->index_key, prealloc); /* wake up anyone waiting for a key to be constructed */ if (awaken) @@ -780,25 +779,27 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, key_perm_t perm, unsigned long flags) { - unsigned long prealloc; + struct keyring_index_key index_key = { + .description = description, + }; struct key_preparsed_payload prep; const struct cred *cred = current_cred(); - struct key_type *ktype; + unsigned long prealloc; struct key *keyring, *key = NULL; key_ref_t key_ref; int ret; /* look up the key type to see if it's one of the registered kernel * types */ - ktype = key_type_lookup(type); - if (IS_ERR(ktype)) { + index_key.type = key_type_lookup(type); + if (IS_ERR(index_key.type)) { key_ref = ERR_PTR(-ENODEV); goto error; } key_ref = ERR_PTR(-EINVAL); - if (!ktype->match || !ktype->instantiate || - (!description && !ktype->preparse)) + if (!index_key.type->match || !index_key.type->instantiate || + (!index_key.description && !index_key.type->preparse)) goto error_put_type; keyring = key_ref_to_ptr(keyring_ref); @@ -812,21 +813,22 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, memset(&prep, 0, sizeof(prep)); prep.data = payload; prep.datalen = plen; - prep.quotalen = ktype->def_datalen; - if (ktype->preparse) { - ret = ktype->preparse(&prep); + prep.quotalen = index_key.type->def_datalen; + if (index_key.type->preparse) { + ret = index_key.type->preparse(&prep); if (ret < 0) { key_ref = ERR_PTR(ret); goto error_put_type; } - if (!description) - description = prep.description; + if (!index_key.description) + index_key.description = prep.description; key_ref = ERR_PTR(-EINVAL); - if (!description) + if (!index_key.description) goto error_free_prep; } + index_key.desc_len = strlen(index_key.description); - ret = __key_link_begin(keyring, ktype, description, &prealloc); + ret = __key_link_begin(keyring, &index_key, &prealloc); if (ret < 0) { key_ref = ERR_PTR(ret); goto error_free_prep; @@ -844,9 +846,8 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, * key of the same type and description in the destination keyring and * update that instead if possible */ - if (ktype->update) { - key_ref = __keyring_search_one(keyring_ref, ktype, description, - 0); + if (index_key.type->update) { + key_ref = __keyring_search_one(keyring_ref, &index_key, 0); if (!IS_ERR(key_ref)) goto found_matching_key; } @@ -856,16 +857,17 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; perm |= KEY_USR_VIEW; - if (ktype->read) + if (index_key.type->read) perm |= KEY_POS_READ; - if (ktype == &key_type_keyring || ktype->update) + if (index_key.type == &key_type_keyring || + index_key.type->update) perm |= KEY_POS_WRITE; } /* allocate a new key */ - key = key_alloc(ktype, description, cred->fsuid, cred->fsgid, cred, - perm, flags); + key = key_alloc(index_key.type, index_key.description, + cred->fsuid, cred->fsgid, cred, perm, flags); if (IS_ERR(key)) { key_ref = ERR_CAST(key); goto error_link_end; @@ -882,12 +884,12 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); error_link_end: - __key_link_end(keyring, ktype, prealloc); + __key_link_end(keyring, &index_key, prealloc); error_free_prep: - if (ktype->preparse) - ktype->free_preparse(&prep); + if (index_key.type->preparse) + index_key.type->free_preparse(&prep); error_put_type: - key_type_put(ktype); + key_type_put(index_key.type); error: return key_ref; @@ -895,7 +897,7 @@ error: /* we found a matching key, so we're going to try to update it * - we can drop the locks first as we have the key pinned */ - __key_link_end(keyring, ktype, prealloc); + __key_link_end(keyring, &index_key, prealloc); key_ref = __key_update(key_ref, &prep); goto error_free_prep; diff --git a/security/keys/keyring.c b/security/keys/keyring.c index f784063..c7f59f9 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -538,8 +538,7 @@ EXPORT_SYMBOL(keyring_search); * to the returned key reference. */ key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct key_type *ktype, - const char *description, + const struct keyring_index_key *index_key, key_perm_t perm) { struct keyring_list *klist; @@ -558,9 +557,9 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, smp_rmb(); for (loop = 0; loop < nkeys ; loop++) { key = rcu_dereference(klist->keys[loop]); - if (key->type == ktype && + if (key->type == index_key->type && (!key->type->match || - key->type->match(key, description)) && + key->type->match(key, index_key->description)) && key_permission(make_key_ref(key, possessed), perm) == 0 && !(key->flags & ((1 << KEY_FLAG_INVALIDATED) | @@ -747,8 +746,8 @@ static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) /* * Preallocate memory so that a key can be linked into to a keyring. */ -int __key_link_begin(struct key *keyring, const struct key_type *type, - const char *description, unsigned long *_prealloc) +int __key_link_begin(struct key *keyring, const struct keyring_index_key *index_key, + unsigned long *_prealloc) __acquires(&keyring->sem) __acquires(&keyring_serialise_link_sem) { @@ -759,7 +758,8 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, size_t size; int loop, lru, ret; - kenter("%d,%s,%s,", key_serial(keyring), type->name, description); + kenter("%d,%s,%s,", + key_serial(keyring), index_key->type->name, index_key->description); if (keyring->type != &key_type_keyring) return -ENOTDIR; @@ -772,7 +772,7 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, /* serialise link/link calls to prevent parallel calls causing a cycle * when linking two keyring in opposite orders */ - if (type == &key_type_keyring) + if (index_key->type == &key_type_keyring) down_write(&keyring_serialise_link_sem); klist = rcu_dereference_locked_keyring(keyring); @@ -784,8 +784,8 @@ int __key_link_begin(struct key *keyring, const struct key_type *type, for (loop = klist->nkeys - 1; loop >= 0; loop--) { struct key *key = rcu_deref_link_locked(klist, loop, keyring); - if (key->type == type && - strcmp(key->description, description) == 0) { + if (key->type == index_key->type && + strcmp(key->description, index_key->description) == 0) { /* Found a match - we'll replace the link with * one to the new key. We record the slot * position. @@ -865,7 +865,7 @@ error_quota: key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); error_sem: - if (type == &key_type_keyring) + if (index_key->type == &key_type_keyring) up_write(&keyring_serialise_link_sem); error_krsem: up_write(&keyring->sem); @@ -957,16 +957,17 @@ void __key_link(struct key *keyring, struct key *key, * * Must be called with __key_link_begin() having being called. */ -void __key_link_end(struct key *keyring, struct key_type *type, +void __key_link_end(struct key *keyring, + const struct keyring_index_key *index_key, unsigned long prealloc) __releases(&keyring->sem) __releases(&keyring_serialise_link_sem) { - BUG_ON(type == NULL); - BUG_ON(type->name == NULL); - kenter("%d,%s,%lx", keyring->serial, type->name, prealloc); + BUG_ON(index_key->type == NULL); + BUG_ON(index_key->type->name == NULL); + kenter("%d,%s,%lx", keyring->serial, index_key->type->name, prealloc); - if (type == &key_type_keyring) + if (index_key->type == &key_type_keyring) up_write(&keyring_serialise_link_sem); if (prealloc) { @@ -1007,12 +1008,12 @@ int key_link(struct key *keyring, struct key *key) key_check(keyring); key_check(key); - ret = __key_link_begin(keyring, key->type, key->description, &prealloc); + ret = __key_link_begin(keyring, &key->index_key, &prealloc); if (ret == 0) { ret = __key_link_check_live_key(keyring, key); if (ret == 0) __key_link(keyring, key, &prealloc); - __key_link_end(keyring, key->type, prealloc); + __key_link_end(keyring, &key->index_key, prealloc); } return ret; diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 172115b..586cb79 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -352,6 +352,11 @@ static int construct_alloc_key(struct key_type *type, struct key_user *user, struct key **_key) { + const struct keyring_index_key index_key = { + .type = type, + .description = description, + .desc_len = strlen(description), + }; const struct cred *cred = current_cred(); unsigned long prealloc; struct key *key; @@ -379,8 +384,7 @@ static int construct_alloc_key(struct key_type *type, set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); if (dest_keyring) { - ret = __key_link_begin(dest_keyring, type, description, - &prealloc); + ret = __key_link_begin(dest_keyring, &index_key, &prealloc); if (ret < 0) goto link_prealloc_failed; } @@ -400,7 +404,7 @@ static int construct_alloc_key(struct key_type *type, mutex_unlock(&key_construction_mutex); if (dest_keyring) - __key_link_end(dest_keyring, type, prealloc); + __key_link_end(dest_keyring, &index_key, prealloc); mutex_unlock(&user->cons_lock); *_key = key; kleave(" = 0 [%d]", key_serial(key)); @@ -416,7 +420,7 @@ key_already_present: ret = __key_link_check_live_key(dest_keyring, key); if (ret == 0) __key_link(dest_keyring, key, &prealloc); - __key_link_end(dest_keyring, type, prealloc); + __key_link_end(dest_keyring, &index_key, prealloc); if (ret < 0) goto link_check_failed; } -- cgit v1.1 From 4bdf0bc300314141e5475e145acb8b5ad846f00d Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:15 +0100 Subject: KEYS: Introduce a search context structure Search functions pass around a bunch of arguments, each of which gets copied with each call. Introduce a search context structure to hold these. Whilst we're at it, create a search flag that indicates whether the search should be directly to the description or whether it should iterate through all keys looking for a non-description match. This will be useful when keyrings use a generic data struct with generic routines to manage their content as the search terms can just be passed through to the iterator callback function. Also, for future use, the data to be supplied to the match function is separated from the description pointer in the search context. This makes it clear which is being supplied. Signed-off-by: David Howells --- security/keys/internal.h | 40 +++++++------ security/keys/keyring.c | 70 +++++++++++------------ security/keys/proc.c | 17 ++++-- security/keys/process_keys.c | 117 +++++++++++++++++++-------------------- security/keys/request_key.c | 56 +++++++++---------- security/keys/request_key_auth.c | 14 +++-- security/keys/user_defined.c | 18 +++--- 7 files changed, 174 insertions(+), 158 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index 77441dd..f4bf938 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -107,23 +107,31 @@ extern struct key *keyring_search_instkey(struct key *keyring, typedef int (*key_match_func_t)(const struct key *, const void *); +struct keyring_search_context { + struct keyring_index_key index_key; + const struct cred *cred; + key_match_func_t match; + const void *match_data; + unsigned flags; +#define KEYRING_SEARCH_LOOKUP_TYPE 0x0001 /* [as type->def_lookup_type] */ +#define KEYRING_SEARCH_NO_STATE_CHECK 0x0002 /* Skip state checks */ +#define KEYRING_SEARCH_DO_STATE_CHECK 0x0004 /* Override NO_STATE_CHECK */ +#define KEYRING_SEARCH_NO_UPDATE_TIME 0x0008 /* Don't update times */ +#define KEYRING_SEARCH_NO_CHECK_PERM 0x0010 /* Don't check permissions */ +#define KEYRING_SEARCH_DETECT_TOO_DEEP 0x0020 /* Give an error on excessive depth */ + + /* Internal stuff */ + int skipped_ret; + bool possessed; + key_ref_t result; + struct timespec now; +}; + extern key_ref_t keyring_search_aux(key_ref_t keyring_ref, - const struct cred *cred, - struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check); - -extern key_ref_t search_my_process_keyrings(struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check, - const struct cred *cred); -extern key_ref_t search_process_keyrings(struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check, - const struct cred *cred); + struct keyring_search_context *ctx); + +extern key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx); +extern key_ref_t search_process_keyrings(struct keyring_search_context *ctx); extern struct key *find_keyring_by_name(const char *name, bool skip_perm_check); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index c7f59f9..b42f2d4 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -280,11 +280,7 @@ EXPORT_SYMBOL(keyring_alloc); /** * keyring_search_aux - Search a keyring tree for a key matching some criteria * @keyring_ref: A pointer to the keyring with possession indicator. - * @cred: The credentials to use for permissions checks. - * @type: The type of key to search for. - * @description: Parameter for @match. - * @match: Function to rule on whether or not a key is the one required. - * @no_state_check: Don't check if a matching key is bad + * @ctx: The keyring search context. * * Search the supplied keyring tree for a key that matches the criteria given. * The root keyring and any linked keyrings must grant Search permission to the @@ -314,11 +310,7 @@ EXPORT_SYMBOL(keyring_alloc); * @keyring_ref is propagated to the returned key reference. */ key_ref_t keyring_search_aux(key_ref_t keyring_ref, - const struct cred *cred, - struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check) + struct keyring_search_context *ctx) { struct { /* Need a separate keylist pointer for RCU purposes */ @@ -328,20 +320,18 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, } stack[KEYRING_SEARCH_MAX_DEPTH]; struct keyring_list *keylist; - struct timespec now; unsigned long kflags; struct key *keyring, *key; key_ref_t key_ref; - bool possessed; long err; int sp, nkeys, kix; keyring = key_ref_to_ptr(keyring_ref); - possessed = is_key_possessed(keyring_ref); + ctx->possessed = is_key_possessed(keyring_ref); key_check(keyring); /* top keyring must have search permission to begin the search */ - err = key_task_permission(keyring_ref, cred, KEY_SEARCH); + err = key_task_permission(keyring_ref, ctx->cred, KEY_SEARCH); if (err < 0) { key_ref = ERR_PTR(err); goto error; @@ -353,7 +343,7 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, rcu_read_lock(); - now = current_kernel_time(); + ctx->now = current_kernel_time(); err = -EAGAIN; sp = 0; @@ -361,16 +351,17 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, * are looking for */ key_ref = ERR_PTR(-EAGAIN); kflags = keyring->flags; - if (keyring->type == type && match(keyring, description)) { + if (keyring->type == ctx->index_key.type && + ctx->match(keyring, ctx->match_data)) { key = keyring; - if (no_state_check) + if (ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK) goto found; /* check it isn't negative and hasn't expired or been * revoked */ if (kflags & (1 << KEY_FLAG_REVOKED)) goto error_2; - if (key->expiry && now.tv_sec >= key->expiry) + if (key->expiry && ctx->now.tv_sec >= key->expiry) goto error_2; key_ref = ERR_PTR(key->type_data.reject_error); if (kflags & (1 << KEY_FLAG_NEGATIVE)) @@ -384,7 +375,7 @@ key_ref_t keyring_search_aux(key_ref_t keyring_ref, if (kflags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED) | (1 << KEY_FLAG_NEGATIVE)) || - (keyring->expiry && now.tv_sec >= keyring->expiry)) + (keyring->expiry && ctx->now.tv_sec >= keyring->expiry)) goto error_2; /* start processing a new keyring */ @@ -406,29 +397,29 @@ descend: kflags = key->flags; /* ignore keys not of this type */ - if (key->type != type) + if (key->type != ctx->index_key.type) continue; /* skip invalidated, revoked and expired keys */ - if (!no_state_check) { + if (!(ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK)) { if (kflags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED))) continue; - if (key->expiry && now.tv_sec >= key->expiry) + if (key->expiry && ctx->now.tv_sec >= key->expiry) continue; } /* keys that don't match */ - if (!match(key, description)) + if (!ctx->match(key, ctx->match_data)) continue; /* key must have search permissions */ - if (key_task_permission(make_key_ref(key, possessed), - cred, KEY_SEARCH) < 0) + if (key_task_permission(make_key_ref(key, ctx->possessed), + ctx->cred, KEY_SEARCH) < 0) continue; - if (no_state_check) + if (ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK) goto found; /* we set a different error code if we pass a negative key */ @@ -456,8 +447,8 @@ ascend: if (sp >= KEYRING_SEARCH_MAX_DEPTH) continue; - if (key_task_permission(make_key_ref(key, possessed), - cred, KEY_SEARCH) < 0) + if (key_task_permission(make_key_ref(key, ctx->possessed), + ctx->cred, KEY_SEARCH) < 0) continue; /* stack the current position */ @@ -489,12 +480,12 @@ not_this_keyring: /* we found a viable match */ found: atomic_inc(&key->usage); - key->last_used_at = now.tv_sec; - keyring->last_used_at = now.tv_sec; + key->last_used_at = ctx->now.tv_sec; + keyring->last_used_at = ctx->now.tv_sec; while (sp > 0) - stack[--sp].keyring->last_used_at = now.tv_sec; + stack[--sp].keyring->last_used_at = ctx->now.tv_sec; key_check(key); - key_ref = make_key_ref(key, possessed); + key_ref = make_key_ref(key, ctx->possessed); error_2: rcu_read_unlock(); error: @@ -514,11 +505,20 @@ key_ref_t keyring_search(key_ref_t keyring, struct key_type *type, const char *description) { - if (!type->match) + struct keyring_search_context ctx = { + .index_key.type = type, + .index_key.description = description, + .cred = current_cred(), + .match = type->match, + .match_data = description, + .flags = (type->def_lookup_type | + KEYRING_SEARCH_DO_STATE_CHECK), + }; + + if (!ctx.match) return ERR_PTR(-ENOKEY); - return keyring_search_aux(keyring, current->cred, - type, description, type->match, false); + return keyring_search_aux(keyring, &ctx); } EXPORT_SYMBOL(keyring_search); diff --git a/security/keys/proc.c b/security/keys/proc.c index 217b685..88e9a46 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -182,7 +182,6 @@ static void proc_keys_stop(struct seq_file *p, void *v) static int proc_keys_show(struct seq_file *m, void *v) { - const struct cred *cred = current_cred(); struct rb_node *_p = v; struct key *key = rb_entry(_p, struct key, serial_node); struct timespec now; @@ -191,15 +190,23 @@ static int proc_keys_show(struct seq_file *m, void *v) char xbuf[12]; int rc; + struct keyring_search_context ctx = { + .index_key.type = key->type, + .index_key.description = key->description, + .cred = current_cred(), + .match = lookup_user_key_possessed, + .match_data = key, + .flags = (KEYRING_SEARCH_NO_STATE_CHECK | + KEYRING_SEARCH_LOOKUP_DIRECT), + }; + key_ref = make_key_ref(key, 0); /* determine if the key is possessed by this process (a test we can * skip if the key does not indicate the possessor can view it */ if (key->perm & KEY_POS_VIEW) { - skey_ref = search_my_process_keyrings(key->type, key, - lookup_user_key_possessed, - true, cred); + skey_ref = search_my_process_keyrings(&ctx); if (!IS_ERR(skey_ref)) { key_ref_put(skey_ref); key_ref = make_key_ref(key, 1); @@ -211,7 +218,7 @@ static int proc_keys_show(struct seq_file *m, void *v) * - the caller holds a spinlock, and thus the RCU read lock, making our * access to __current_cred() safe */ - rc = key_task_permission(key_ref, cred, KEY_VIEW); + rc = key_task_permission(key_ref, ctx.cred, KEY_VIEW); if (rc < 0) return 0; diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index a3410d6..e68a3e0 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -319,11 +319,7 @@ void key_fsgid_changed(struct task_struct *tsk) * In the case of a successful return, the possession attribute is set on the * returned key reference. */ -key_ref_t search_my_process_keyrings(struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check, - const struct cred *cred) +key_ref_t search_my_process_keyrings(struct keyring_search_context *ctx) { key_ref_t key_ref, ret, err; @@ -339,10 +335,9 @@ key_ref_t search_my_process_keyrings(struct key_type *type, err = ERR_PTR(-EAGAIN); /* search the thread keyring first */ - if (cred->thread_keyring) { + if (ctx->cred->thread_keyring) { key_ref = keyring_search_aux( - make_key_ref(cred->thread_keyring, 1), - cred, type, description, match, no_state_check); + make_key_ref(ctx->cred->thread_keyring, 1), ctx); if (!IS_ERR(key_ref)) goto found; @@ -358,10 +353,9 @@ key_ref_t search_my_process_keyrings(struct key_type *type, } /* search the process keyring second */ - if (cred->process_keyring) { + if (ctx->cred->process_keyring) { key_ref = keyring_search_aux( - make_key_ref(cred->process_keyring, 1), - cred, type, description, match, no_state_check); + make_key_ref(ctx->cred->process_keyring, 1), ctx); if (!IS_ERR(key_ref)) goto found; @@ -379,11 +373,11 @@ key_ref_t search_my_process_keyrings(struct key_type *type, } /* search the session keyring */ - if (cred->session_keyring) { + if (ctx->cred->session_keyring) { rcu_read_lock(); key_ref = keyring_search_aux( - make_key_ref(rcu_dereference(cred->session_keyring), 1), - cred, type, description, match, no_state_check); + make_key_ref(rcu_dereference(ctx->cred->session_keyring), 1), + ctx); rcu_read_unlock(); if (!IS_ERR(key_ref)) @@ -402,10 +396,10 @@ key_ref_t search_my_process_keyrings(struct key_type *type, } } /* or search the user-session keyring */ - else if (cred->user->session_keyring) { + else if (ctx->cred->user->session_keyring) { key_ref = keyring_search_aux( - make_key_ref(cred->user->session_keyring, 1), - cred, type, description, match, no_state_check); + make_key_ref(ctx->cred->user->session_keyring, 1), + ctx); if (!IS_ERR(key_ref)) goto found; @@ -437,19 +431,14 @@ found: * * Return same as search_my_process_keyrings(). */ -key_ref_t search_process_keyrings(struct key_type *type, - const void *description, - key_match_func_t match, - bool no_state_check, - const struct cred *cred) +key_ref_t search_process_keyrings(struct keyring_search_context *ctx) { struct request_key_auth *rka; key_ref_t key_ref, ret = ERR_PTR(-EACCES), err; might_sleep(); - key_ref = search_my_process_keyrings(type, description, match, - no_state_check, cred); + key_ref = search_my_process_keyrings(ctx); if (!IS_ERR(key_ref)) goto found; err = key_ref; @@ -458,19 +447,21 @@ key_ref_t search_process_keyrings(struct key_type *type, * search the keyrings of the process mentioned there * - we don't permit access to request_key auth keys via this method */ - if (cred->request_key_auth && - cred == current_cred() && - type != &key_type_request_key_auth + if (ctx->cred->request_key_auth && + ctx->cred == current_cred() && + ctx->index_key.type != &key_type_request_key_auth ) { + const struct cred *cred = ctx->cred; + /* defend against the auth key being revoked */ down_read(&cred->request_key_auth->sem); - if (key_validate(cred->request_key_auth) == 0) { - rka = cred->request_key_auth->payload.data; + if (key_validate(ctx->cred->request_key_auth) == 0) { + rka = ctx->cred->request_key_auth->payload.data; - key_ref = search_process_keyrings(type, description, - match, no_state_check, - rka->cred); + ctx->cred = rka->cred; + key_ref = search_process_keyrings(ctx); + ctx->cred = cred; up_read(&cred->request_key_auth->sem); @@ -524,19 +515,23 @@ int lookup_user_key_possessed(const struct key *key, const void *target) key_ref_t lookup_user_key(key_serial_t id, unsigned long lflags, key_perm_t perm) { + struct keyring_search_context ctx = { + .match = lookup_user_key_possessed, + .flags = (KEYRING_SEARCH_NO_STATE_CHECK | + KEYRING_SEARCH_LOOKUP_DIRECT), + }; struct request_key_auth *rka; - const struct cred *cred; struct key *key; key_ref_t key_ref, skey_ref; int ret; try_again: - cred = get_current_cred(); + ctx.cred = get_current_cred(); key_ref = ERR_PTR(-ENOKEY); switch (id) { case KEY_SPEC_THREAD_KEYRING: - if (!cred->thread_keyring) { + if (!ctx.cred->thread_keyring) { if (!(lflags & KEY_LOOKUP_CREATE)) goto error; @@ -548,13 +543,13 @@ try_again: goto reget_creds; } - key = cred->thread_keyring; + key = ctx.cred->thread_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); break; case KEY_SPEC_PROCESS_KEYRING: - if (!cred->process_keyring) { + if (!ctx.cred->process_keyring) { if (!(lflags & KEY_LOOKUP_CREATE)) goto error; @@ -566,13 +561,13 @@ try_again: goto reget_creds; } - key = cred->process_keyring; + key = ctx.cred->process_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); break; case KEY_SPEC_SESSION_KEYRING: - if (!cred->session_keyring) { + if (!ctx.cred->session_keyring) { /* always install a session keyring upon access if one * doesn't exist yet */ ret = install_user_keyrings(); @@ -582,13 +577,13 @@ try_again: ret = join_session_keyring(NULL); else ret = install_session_keyring( - cred->user->session_keyring); + ctx.cred->user->session_keyring); if (ret < 0) goto error; goto reget_creds; - } else if (cred->session_keyring == - cred->user->session_keyring && + } else if (ctx.cred->session_keyring == + ctx.cred->user->session_keyring && lflags & KEY_LOOKUP_CREATE) { ret = join_session_keyring(NULL); if (ret < 0) @@ -597,32 +592,32 @@ try_again: } rcu_read_lock(); - key = rcu_dereference(cred->session_keyring); + key = rcu_dereference(ctx.cred->session_keyring); atomic_inc(&key->usage); rcu_read_unlock(); key_ref = make_key_ref(key, 1); break; case KEY_SPEC_USER_KEYRING: - if (!cred->user->uid_keyring) { + if (!ctx.cred->user->uid_keyring) { ret = install_user_keyrings(); if (ret < 0) goto error; } - key = cred->user->uid_keyring; + key = ctx.cred->user->uid_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); break; case KEY_SPEC_USER_SESSION_KEYRING: - if (!cred->user->session_keyring) { + if (!ctx.cred->user->session_keyring) { ret = install_user_keyrings(); if (ret < 0) goto error; } - key = cred->user->session_keyring; + key = ctx.cred->user->session_keyring; atomic_inc(&key->usage); key_ref = make_key_ref(key, 1); break; @@ -633,7 +628,7 @@ try_again: goto error; case KEY_SPEC_REQKEY_AUTH_KEY: - key = cred->request_key_auth; + key = ctx.cred->request_key_auth; if (!key) goto error; @@ -642,20 +637,20 @@ try_again: break; case KEY_SPEC_REQUESTOR_KEYRING: - if (!cred->request_key_auth) + if (!ctx.cred->request_key_auth) goto error; - down_read(&cred->request_key_auth->sem); + down_read(&ctx.cred->request_key_auth->sem); if (test_bit(KEY_FLAG_REVOKED, - &cred->request_key_auth->flags)) { + &ctx.cred->request_key_auth->flags)) { key_ref = ERR_PTR(-EKEYREVOKED); key = NULL; } else { - rka = cred->request_key_auth->payload.data; + rka = ctx.cred->request_key_auth->payload.data; key = rka->dest_keyring; atomic_inc(&key->usage); } - up_read(&cred->request_key_auth->sem); + up_read(&ctx.cred->request_key_auth->sem); if (!key) goto error; key_ref = make_key_ref(key, 1); @@ -675,9 +670,13 @@ try_again: key_ref = make_key_ref(key, 0); /* check to see if we possess the key */ - skey_ref = search_process_keyrings(key->type, key, - lookup_user_key_possessed, - true, cred); + ctx.index_key.type = key->type; + ctx.index_key.description = key->description; + ctx.index_key.desc_len = strlen(key->description); + ctx.match_data = key; + kdebug("check possessed"); + skey_ref = search_process_keyrings(&ctx); + kdebug("possessed=%p", skey_ref); if (!IS_ERR(skey_ref)) { key_put(key); @@ -717,14 +716,14 @@ try_again: goto invalid_key; /* check the permissions */ - ret = key_task_permission(key_ref, cred, perm); + ret = key_task_permission(key_ref, ctx.cred, perm); if (ret < 0) goto invalid_key; key->last_used_at = current_kernel_time().tv_sec; error: - put_cred(cred); + put_cred(ctx.cred); return key_ref; invalid_key: @@ -735,7 +734,7 @@ invalid_key: /* if we attempted to install a keyring, then it may have caused new * creds to be installed */ reget_creds: - put_cred(cred); + put_cred(ctx.cred); goto try_again; } diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 586cb79..ab75df4 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -345,38 +345,34 @@ static void construct_get_dest_keyring(struct key **_dest_keyring) * May return a key that's already under construction instead if there was a * race between two thread calling request_key(). */ -static int construct_alloc_key(struct key_type *type, - const char *description, +static int construct_alloc_key(struct keyring_search_context *ctx, struct key *dest_keyring, unsigned long flags, struct key_user *user, struct key **_key) { - const struct keyring_index_key index_key = { - .type = type, - .description = description, - .desc_len = strlen(description), - }; - const struct cred *cred = current_cred(); unsigned long prealloc; struct key *key; key_perm_t perm; key_ref_t key_ref; int ret; - kenter("%s,%s,,,", type->name, description); + kenter("%s,%s,,,", + ctx->index_key.type->name, ctx->index_key.description); *_key = NULL; mutex_lock(&user->cons_lock); perm = KEY_POS_VIEW | KEY_POS_SEARCH | KEY_POS_LINK | KEY_POS_SETATTR; perm |= KEY_USR_VIEW; - if (type->read) + if (ctx->index_key.type->read) perm |= KEY_POS_READ; - if (type == &key_type_keyring || type->update) + if (ctx->index_key.type == &key_type_keyring || + ctx->index_key.type->update) perm |= KEY_POS_WRITE; - key = key_alloc(type, description, cred->fsuid, cred->fsgid, cred, + key = key_alloc(ctx->index_key.type, ctx->index_key.description, + ctx->cred->fsuid, ctx->cred->fsgid, ctx->cred, perm, flags); if (IS_ERR(key)) goto alloc_failed; @@ -384,7 +380,7 @@ static int construct_alloc_key(struct key_type *type, set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); if (dest_keyring) { - ret = __key_link_begin(dest_keyring, &index_key, &prealloc); + ret = __key_link_begin(dest_keyring, &ctx->index_key, &prealloc); if (ret < 0) goto link_prealloc_failed; } @@ -394,8 +390,7 @@ static int construct_alloc_key(struct key_type *type, * waited for locks */ mutex_lock(&key_construction_mutex); - key_ref = search_process_keyrings(type, description, type->match, - false, cred); + key_ref = search_process_keyrings(ctx); if (!IS_ERR(key_ref)) goto key_already_present; @@ -404,7 +399,7 @@ static int construct_alloc_key(struct key_type *type, mutex_unlock(&key_construction_mutex); if (dest_keyring) - __key_link_end(dest_keyring, &index_key, prealloc); + __key_link_end(dest_keyring, &ctx->index_key, prealloc); mutex_unlock(&user->cons_lock); *_key = key; kleave(" = 0 [%d]", key_serial(key)); @@ -420,7 +415,7 @@ key_already_present: ret = __key_link_check_live_key(dest_keyring, key); if (ret == 0) __key_link(dest_keyring, key, &prealloc); - __key_link_end(dest_keyring, &index_key, prealloc); + __key_link_end(dest_keyring, &ctx->index_key, prealloc); if (ret < 0) goto link_check_failed; } @@ -449,8 +444,7 @@ alloc_failed: /* * Commence key construction. */ -static struct key *construct_key_and_link(struct key_type *type, - const char *description, +static struct key *construct_key_and_link(struct keyring_search_context *ctx, const char *callout_info, size_t callout_len, void *aux, @@ -469,8 +463,7 @@ static struct key *construct_key_and_link(struct key_type *type, construct_get_dest_keyring(&dest_keyring); - ret = construct_alloc_key(type, description, dest_keyring, flags, user, - &key); + ret = construct_alloc_key(ctx, dest_keyring, flags, user, &key); key_user_put(user); if (ret == 0) { @@ -534,18 +527,24 @@ struct key *request_key_and_link(struct key_type *type, struct key *dest_keyring, unsigned long flags) { - const struct cred *cred = current_cred(); + struct keyring_search_context ctx = { + .index_key.type = type, + .index_key.description = description, + .cred = current_cred(), + .match = type->match, + .match_data = description, + .flags = KEYRING_SEARCH_LOOKUP_DIRECT, + }; struct key *key; key_ref_t key_ref; int ret; kenter("%s,%s,%p,%zu,%p,%p,%lx", - type->name, description, callout_info, callout_len, aux, - dest_keyring, flags); + ctx.index_key.type->name, ctx.index_key.description, + callout_info, callout_len, aux, dest_keyring, flags); /* search all the process keyrings for a key */ - key_ref = search_process_keyrings(type, description, type->match, - false, cred); + key_ref = search_process_keyrings(&ctx); if (!IS_ERR(key_ref)) { key = key_ref_to_ptr(key_ref); @@ -568,9 +567,8 @@ struct key *request_key_and_link(struct key_type *type, if (!callout_info) goto error; - key = construct_key_and_link(type, description, callout_info, - callout_len, aux, dest_keyring, - flags); + key = construct_key_and_link(&ctx, callout_info, callout_len, + aux, dest_keyring, flags); } error: diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 92077de..8d09852 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -239,15 +239,17 @@ static int key_get_instantiation_authkey_match(const struct key *key, */ struct key *key_get_instantiation_authkey(key_serial_t target_id) { - const struct cred *cred = current_cred(); + struct keyring_search_context ctx = { + .index_key.type = &key_type_request_key_auth, + .cred = current_cred(), + .match = key_get_instantiation_authkey_match, + .match_data = (void *)(unsigned long)target_id, + .flags = KEYRING_SEARCH_LOOKUP_DIRECT, + }; struct key *authkey; key_ref_t authkey_ref; - authkey_ref = search_process_keyrings( - &key_type_request_key_auth, - (void *) (unsigned long) target_id, - key_get_instantiation_authkey_match, - false, cred); + authkey_ref = search_process_keyrings(&ctx); if (IS_ERR(authkey_ref)) { authkey = ERR_CAST(authkey_ref); diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c index 55dc889..faa2cae 100644 --- a/security/keys/user_defined.c +++ b/security/keys/user_defined.c @@ -25,14 +25,15 @@ static int logon_vet_description(const char *desc); * arbitrary blob of data as the payload */ struct key_type key_type_user = { - .name = "user", - .instantiate = user_instantiate, - .update = user_update, - .match = user_match, - .revoke = user_revoke, - .destroy = user_destroy, - .describe = user_describe, - .read = user_read, + .name = "user", + .def_lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT, + .instantiate = user_instantiate, + .update = user_update, + .match = user_match, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, }; EXPORT_SYMBOL_GPL(key_type_user); @@ -45,6 +46,7 @@ EXPORT_SYMBOL_GPL(key_type_user); */ struct key_type key_type_logon = { .name = "logon", + .def_lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT, .instantiate = user_instantiate, .update = user_update, .match = user_match, -- cgit v1.1 From d0a059cac6523b23ad7d743dec8783705aab1668 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:16 +0100 Subject: KEYS: Search for auth-key by name rather than target key ID Search for auth-key by name rather than by target key ID as, in a future patch, we'll by searching directly by index key in preference to iteration over all keys. Signed-off-by: David Howells --- security/keys/request_key_auth.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'security') diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 8d09852..7495a93 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -18,6 +18,7 @@ #include #include #include "internal.h" +#include static int request_key_auth_instantiate(struct key *, struct key_preparsed_payload *); @@ -222,33 +223,25 @@ error_alloc: } /* - * See if an authorisation key is associated with a particular key. - */ -static int key_get_instantiation_authkey_match(const struct key *key, - const void *_id) -{ - struct request_key_auth *rka = key->payload.data; - key_serial_t id = (key_serial_t)(unsigned long) _id; - - return rka->target_key->serial == id; -} - -/* * Search the current process's keyrings for the authorisation key for * instantiation of a key. */ struct key *key_get_instantiation_authkey(key_serial_t target_id) { + char description[16]; struct keyring_search_context ctx = { .index_key.type = &key_type_request_key_auth, + .index_key.description = description, .cred = current_cred(), - .match = key_get_instantiation_authkey_match, - .match_data = (void *)(unsigned long)target_id, + .match = user_match, + .match_data = description, .flags = KEYRING_SEARCH_LOOKUP_DIRECT, }; struct key *authkey; key_ref_t authkey_ref; + sprintf(description, "%x", target_id); + authkey_ref = search_process_keyrings(&ctx); if (IS_ERR(authkey_ref)) { -- cgit v1.1 From ccc3e6d9c9aea07a0b60b2b0bfc5b05a704b66d5 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:16 +0100 Subject: KEYS: Define a __key_get() wrapper to use rather than atomic_inc() Define a __key_get() wrapper to use rather than atomic_inc() on the key usage count as this makes it easier to hook in refcount error debugging. Signed-off-by: David Howells --- security/keys/key.c | 2 +- security/keys/keyring.c | 6 +++--- security/keys/process_keys.c | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'security') diff --git a/security/keys/key.c b/security/keys/key.c index 7e6bc39..1e23cc2 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -644,7 +644,7 @@ found: /* this races with key_put(), but that doesn't matter since key_put() * doesn't actually change the key */ - atomic_inc(&key->usage); + __key_get(key); error: spin_unlock(&key_serial_lock); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index b42f2d4..87eff32 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -479,7 +479,7 @@ not_this_keyring: /* we found a viable match */ found: - atomic_inc(&key->usage); + __key_get(key); key->last_used_at = ctx->now.tv_sec; keyring->last_used_at = ctx->now.tv_sec; while (sp > 0) @@ -573,7 +573,7 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, return ERR_PTR(-ENOKEY); found: - atomic_inc(&key->usage); + __key_get(key); keyring->last_used_at = key->last_used_at = current_kernel_time().tv_sec; rcu_read_unlock(); @@ -909,7 +909,7 @@ void __key_link(struct key *keyring, struct key *key, klist = rcu_dereference_locked_keyring(keyring); - atomic_inc(&key->usage); + __key_get(key); keyring->last_used_at = key->last_used_at = current_kernel_time().tv_sec; diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index e68a3e0..68548ea 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -235,7 +235,7 @@ int install_session_keyring_to_cred(struct cred *cred, struct key *keyring) if (IS_ERR(keyring)) return PTR_ERR(keyring); } else { - atomic_inc(&keyring->usage); + __key_get(keyring); } /* install the keyring */ @@ -544,7 +544,7 @@ try_again: } key = ctx.cred->thread_keyring; - atomic_inc(&key->usage); + __key_get(key); key_ref = make_key_ref(key, 1); break; @@ -562,7 +562,7 @@ try_again: } key = ctx.cred->process_keyring; - atomic_inc(&key->usage); + __key_get(key); key_ref = make_key_ref(key, 1); break; @@ -593,7 +593,7 @@ try_again: rcu_read_lock(); key = rcu_dereference(ctx.cred->session_keyring); - atomic_inc(&key->usage); + __key_get(key); rcu_read_unlock(); key_ref = make_key_ref(key, 1); break; @@ -606,7 +606,7 @@ try_again: } key = ctx.cred->user->uid_keyring; - atomic_inc(&key->usage); + __key_get(key); key_ref = make_key_ref(key, 1); break; @@ -618,7 +618,7 @@ try_again: } key = ctx.cred->user->session_keyring; - atomic_inc(&key->usage); + __key_get(key); key_ref = make_key_ref(key, 1); break; @@ -632,7 +632,7 @@ try_again: if (!key) goto error; - atomic_inc(&key->usage); + __key_get(key); key_ref = make_key_ref(key, 1); break; @@ -648,7 +648,7 @@ try_again: } else { rka = ctx.cred->request_key_auth->payload.data; key = rka->dest_keyring; - atomic_inc(&key->usage); + __key_get(key); } up_read(&ctx.cred->request_key_auth->sem); if (!key) -- cgit v1.1 From e57e8669f2ab8350d30f771dd2fdd5377f183db2 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:17 +0100 Subject: KEYS: Drop the permissions argument from __keyring_search_one() Drop the permissions argument from __keyring_search_one() as the only caller passes 0 here - which causes all checks to be skipped. Signed-off-by: David Howells --- security/keys/internal.h | 3 +-- security/keys/key.c | 2 +- security/keys/keyring.c | 9 +++------ 3 files changed, 5 insertions(+), 9 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index f4bf938..73950bf 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -99,8 +99,7 @@ extern void __key_link_end(struct key *keyring, unsigned long prealloc); extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct keyring_index_key *index_key, - key_perm_t perm); + const struct keyring_index_key *index_key); extern struct key *keyring_search_instkey(struct key *keyring, key_serial_t target_id); diff --git a/security/keys/key.c b/security/keys/key.c index 1e23cc2..7d716b8 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -847,7 +847,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, * update that instead if possible */ if (index_key.type->update) { - key_ref = __keyring_search_one(keyring_ref, &index_key, 0); + key_ref = __keyring_search_one(keyring_ref, &index_key); if (!IS_ERR(key_ref)) goto found_matching_key; } diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 87eff32..eeef1a0 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -531,15 +531,14 @@ EXPORT_SYMBOL(keyring_search); * RCU is used to make it unnecessary to lock the keyring key list here. * * Returns a pointer to the found key with usage count incremented if - * successful and returns -ENOKEY if not found. Revoked keys and keys not - * providing the requested permission are skipped over. + * successful and returns -ENOKEY if not found. Revoked and invalidated keys + * are skipped over. * * If successful, the possession indicator is propagated from the keyring ref * to the returned key reference. */ key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct keyring_index_key *index_key, - key_perm_t perm) + const struct keyring_index_key *index_key) { struct keyring_list *klist; struct key *keyring, *key; @@ -560,8 +559,6 @@ key_ref_t __keyring_search_one(key_ref_t keyring_ref, if (key->type == index_key->type && (!key->type->match || key->type->match(key, index_key->description)) && - key_permission(make_key_ref(key, possessed), - perm) == 0 && !(key->flags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED))) ) -- cgit v1.1 From b2a4df200d570b2c33a57e1ebfa5896e4bc81b69 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:18 +0100 Subject: KEYS: Expand the capacity of a keyring Expand the capacity of a keyring to be able to hold a lot more keys by using the previously added associative array implementation. Currently the maximum capacity is: (PAGE_SIZE - sizeof(header)) / sizeof(struct key *) which, on a 64-bit system, is a little more 500. However, since this is being used for the NFS uid mapper, we need more than that. The new implementation gives us effectively unlimited capacity. With some alterations, the keyutils testsuite runs successfully to completion after this patch is applied. The alterations are because (a) keyrings that are simply added to no longer appear ordered and (b) some of the errors have changed a bit. Signed-off-by: David Howells --- security/keys/Kconfig | 1 + security/keys/gc.c | 33 +- security/keys/internal.h | 17 +- security/keys/key.c | 35 +- security/keys/keyring.c | 1436 ++++++++++++++++++++++--------------------- security/keys/request_key.c | 12 +- 6 files changed, 792 insertions(+), 742 deletions(-) (limited to 'security') diff --git a/security/keys/Kconfig b/security/keys/Kconfig index a90d6d30..15e0dfe 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -4,6 +4,7 @@ config KEYS bool "Enable access key retention support" + select ASSOCIATIVE_ARRAY help This option provides support for retaining authentication tokens and access keys in the kernel. diff --git a/security/keys/gc.c b/security/keys/gc.c index d67c97b..cce621c 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -130,6 +130,13 @@ void key_gc_keytype(struct key_type *ktype) kleave(""); } +static int key_gc_keyring_func(const void *object, void *iterator_data) +{ + const struct key *key = object; + time_t *limit = iterator_data; + return key_is_dead(key, *limit); +} + /* * Garbage collect pointers from a keyring. * @@ -138,10 +145,9 @@ void key_gc_keytype(struct key_type *ktype) */ static void key_gc_keyring(struct key *keyring, time_t limit) { - struct keyring_list *klist; - int loop; + int result; - kenter("%x", key_serial(keyring)); + kenter("%x{%s}", keyring->serial, keyring->description ?: ""); if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | (1 << KEY_FLAG_REVOKED))) @@ -149,27 +155,17 @@ static void key_gc_keyring(struct key *keyring, time_t limit) /* scan the keyring looking for dead keys */ rcu_read_lock(); - klist = rcu_dereference(keyring->payload.subscriptions); - if (!klist) - goto unlock_dont_gc; - - loop = klist->nkeys; - smp_rmb(); - for (loop--; loop >= 0; loop--) { - struct key *key = rcu_dereference(klist->keys[loop]); - if (key_is_dead(key, limit)) - goto do_gc; - } - -unlock_dont_gc: + result = assoc_array_iterate(&keyring->keys, + key_gc_keyring_func, &limit); rcu_read_unlock(); + if (result == true) + goto do_gc; + dont_gc: kleave(" [no gc]"); return; do_gc: - rcu_read_unlock(); - keyring_gc(keyring, limit); kleave(" [gc]"); } @@ -392,7 +388,6 @@ found_unreferenced_key: */ found_keyring: spin_unlock(&key_serial_lock); - kdebug("scan keyring %d", key->serial); key_gc_keyring(key, limit); goto maybe_resched; diff --git a/security/keys/internal.h b/security/keys/internal.h index 73950bf..581c6f6 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -90,20 +90,23 @@ extern void key_type_put(struct key_type *ktype); extern int __key_link_begin(struct key *keyring, const struct keyring_index_key *index_key, - unsigned long *_prealloc); + struct assoc_array_edit **_edit); extern int __key_link_check_live_key(struct key *keyring, struct key *key); -extern void __key_link(struct key *keyring, struct key *key, - unsigned long *_prealloc); +extern void __key_link(struct key *key, struct assoc_array_edit **_edit); extern void __key_link_end(struct key *keyring, const struct keyring_index_key *index_key, - unsigned long prealloc); + struct assoc_array_edit *edit); -extern key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct keyring_index_key *index_key); +extern key_ref_t find_key_to_update(key_ref_t keyring_ref, + const struct keyring_index_key *index_key); extern struct key *keyring_search_instkey(struct key *keyring, key_serial_t target_id); +extern int iterate_over_keyring(const struct key *keyring, + int (*func)(const struct key *key, void *data), + void *data); + typedef int (*key_match_func_t)(const struct key *, const void *); struct keyring_search_context { @@ -119,6 +122,8 @@ struct keyring_search_context { #define KEYRING_SEARCH_NO_CHECK_PERM 0x0010 /* Don't check permissions */ #define KEYRING_SEARCH_DETECT_TOO_DEEP 0x0020 /* Give an error on excessive depth */ + int (*iterator)(const void *object, void *iterator_data); + /* Internal stuff */ int skipped_ret; bool possessed; diff --git a/security/keys/key.c b/security/keys/key.c index 7d716b8..a819b5c 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -409,7 +409,7 @@ static int __key_instantiate_and_link(struct key *key, struct key_preparsed_payload *prep, struct key *keyring, struct key *authkey, - unsigned long *_prealloc) + struct assoc_array_edit **_edit) { int ret, awaken; @@ -436,7 +436,7 @@ static int __key_instantiate_and_link(struct key *key, /* and link it into the destination keyring */ if (keyring) - __key_link(keyring, key, _prealloc); + __key_link(key, _edit); /* disable the authorisation key */ if (authkey) @@ -476,7 +476,7 @@ int key_instantiate_and_link(struct key *key, struct key *authkey) { struct key_preparsed_payload prep; - unsigned long prealloc; + struct assoc_array_edit *edit; int ret; memset(&prep, 0, sizeof(prep)); @@ -490,16 +490,15 @@ int key_instantiate_and_link(struct key *key, } if (keyring) { - ret = __key_link_begin(keyring, &key->index_key, &prealloc); + ret = __key_link_begin(keyring, &key->index_key, &edit); if (ret < 0) goto error_free_preparse; } - ret = __key_instantiate_and_link(key, &prep, keyring, authkey, - &prealloc); + ret = __key_instantiate_and_link(key, &prep, keyring, authkey, &edit); if (keyring) - __key_link_end(keyring, &key->index_key, prealloc); + __key_link_end(keyring, &key->index_key, edit); error_free_preparse: if (key->type->preparse) @@ -537,7 +536,7 @@ int key_reject_and_link(struct key *key, struct key *keyring, struct key *authkey) { - unsigned long prealloc; + struct assoc_array_edit *edit; struct timespec now; int ret, awaken, link_ret = 0; @@ -548,7 +547,7 @@ int key_reject_and_link(struct key *key, ret = -EBUSY; if (keyring) - link_ret = __key_link_begin(keyring, &key->index_key, &prealloc); + link_ret = __key_link_begin(keyring, &key->index_key, &edit); mutex_lock(&key_construction_mutex); @@ -570,7 +569,7 @@ int key_reject_and_link(struct key *key, /* and link it into the destination keyring */ if (keyring && link_ret == 0) - __key_link(keyring, key, &prealloc); + __key_link(key, &edit); /* disable the authorisation key */ if (authkey) @@ -580,7 +579,7 @@ int key_reject_and_link(struct key *key, mutex_unlock(&key_construction_mutex); if (keyring) - __key_link_end(keyring, &key->index_key, prealloc); + __key_link_end(keyring, &key->index_key, edit); /* wake up anyone waiting for a key to be constructed */ if (awaken) @@ -783,8 +782,8 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, .description = description, }; struct key_preparsed_payload prep; + struct assoc_array_edit *edit; const struct cred *cred = current_cred(); - unsigned long prealloc; struct key *keyring, *key = NULL; key_ref_t key_ref; int ret; @@ -828,7 +827,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, } index_key.desc_len = strlen(index_key.description); - ret = __key_link_begin(keyring, &index_key, &prealloc); + ret = __key_link_begin(keyring, &index_key, &edit); if (ret < 0) { key_ref = ERR_PTR(ret); goto error_free_prep; @@ -847,8 +846,8 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, * update that instead if possible */ if (index_key.type->update) { - key_ref = __keyring_search_one(keyring_ref, &index_key); - if (!IS_ERR(key_ref)) + key_ref = find_key_to_update(keyring_ref, &index_key); + if (key_ref) goto found_matching_key; } @@ -874,7 +873,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, } /* instantiate it and link it into the target keyring */ - ret = __key_instantiate_and_link(key, &prep, keyring, NULL, &prealloc); + ret = __key_instantiate_and_link(key, &prep, keyring, NULL, &edit); if (ret < 0) { key_put(key); key_ref = ERR_PTR(ret); @@ -884,7 +883,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); error_link_end: - __key_link_end(keyring, &index_key, prealloc); + __key_link_end(keyring, &index_key, edit); error_free_prep: if (index_key.type->preparse) index_key.type->free_preparse(&prep); @@ -897,7 +896,7 @@ error: /* we found a matching key, so we're going to try to update it * - we can drop the locks first as we have the key pinned */ - __key_link_end(keyring, &index_key, prealloc); + __key_link_end(keyring, &index_key, edit); key_ref = __key_update(key_ref, &prep); goto error_free_prep; diff --git a/security/keys/keyring.c b/security/keys/keyring.c index eeef1a0..f7cdea2 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -1,6 +1,6 @@ /* Keyring handling * - * Copyright (C) 2004-2005, 2008 Red Hat, Inc. All Rights Reserved. + * Copyright (C) 2004-2005, 2008, 2013 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) * * This program is free software; you can redistribute it and/or @@ -17,25 +17,11 @@ #include #include #include +#include +#include #include #include "internal.h" -#define rcu_dereference_locked_keyring(keyring) \ - (rcu_dereference_protected( \ - (keyring)->payload.subscriptions, \ - rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) - -#define rcu_deref_link_locked(klist, index, keyring) \ - (rcu_dereference_protected( \ - (klist)->keys[index], \ - rwsem_is_locked((struct rw_semaphore *)&(keyring)->sem))) - -#define MAX_KEYRING_LINKS \ - min_t(size_t, USHRT_MAX - 1, \ - ((PAGE_SIZE - sizeof(struct keyring_list)) / sizeof(struct key *))) - -#define KEY_LINK_FIXQUOTA 1UL - /* * When plumbing the depths of the key tree, this sets a hard limit * set on how deep we're willing to go. @@ -47,6 +33,28 @@ */ #define KEYRING_NAME_HASH_SIZE (1 << 5) +/* + * We mark pointers we pass to the associative array with bit 1 set if + * they're keyrings and clear otherwise. + */ +#define KEYRING_PTR_SUBTYPE 0x2UL + +static inline bool keyring_ptr_is_keyring(const struct assoc_array_ptr *x) +{ + return (unsigned long)x & KEYRING_PTR_SUBTYPE; +} +static inline struct key *keyring_ptr_to_key(const struct assoc_array_ptr *x) +{ + void *object = assoc_array_ptr_to_leaf(x); + return (struct key *)((unsigned long)object & ~KEYRING_PTR_SUBTYPE); +} +static inline void *keyring_key_to_ptr(struct key *key) +{ + if (key->type == &key_type_keyring) + return (void *)((unsigned long)key | KEYRING_PTR_SUBTYPE); + return key; +} + static struct list_head keyring_name_hash[KEYRING_NAME_HASH_SIZE]; static DEFINE_RWLOCK(keyring_name_lock); @@ -67,7 +75,6 @@ static inline unsigned keyring_hash(const char *desc) */ static int keyring_instantiate(struct key *keyring, struct key_preparsed_payload *prep); -static int keyring_match(const struct key *keyring, const void *criterion); static void keyring_revoke(struct key *keyring); static void keyring_destroy(struct key *keyring); static void keyring_describe(const struct key *keyring, struct seq_file *m); @@ -76,9 +83,9 @@ static long keyring_read(const struct key *keyring, struct key_type key_type_keyring = { .name = "keyring", - .def_datalen = sizeof(struct keyring_list), + .def_datalen = 0, .instantiate = keyring_instantiate, - .match = keyring_match, + .match = user_match, .revoke = keyring_revoke, .destroy = keyring_destroy, .describe = keyring_describe, @@ -127,6 +134,7 @@ static int keyring_instantiate(struct key *keyring, ret = -EINVAL; if (prep->datalen == 0) { + assoc_array_init(&keyring->keys); /* make the keyring available by name if it has one */ keyring_publish_name(keyring); ret = 0; @@ -136,15 +144,226 @@ static int keyring_instantiate(struct key *keyring, } /* - * Match keyrings on their name + * Multiply 64-bits by 32-bits to 96-bits and fold back to 64-bit. Ideally we'd + * fold the carry back too, but that requires inline asm. + */ +static u64 mult_64x32_and_fold(u64 x, u32 y) +{ + u64 hi = (u64)(u32)(x >> 32) * y; + u64 lo = (u64)(u32)(x) * y; + return lo + ((u64)(u32)hi << 32) + (u32)(hi >> 32); +} + +/* + * Hash a key type and description. + */ +static unsigned long hash_key_type_and_desc(const struct keyring_index_key *index_key) +{ + const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP; + const unsigned long level_mask = ASSOC_ARRAY_LEVEL_STEP_MASK; + const char *description = index_key->description; + unsigned long hash, type; + u32 piece; + u64 acc; + int n, desc_len = index_key->desc_len; + + type = (unsigned long)index_key->type; + + acc = mult_64x32_and_fold(type, desc_len + 13); + acc = mult_64x32_and_fold(acc, 9207); + for (;;) { + n = desc_len; + if (n <= 0) + break; + if (n > 4) + n = 4; + piece = 0; + memcpy(&piece, description, n); + description += n; + desc_len -= n; + acc = mult_64x32_and_fold(acc, piece); + acc = mult_64x32_and_fold(acc, 9207); + } + + /* Fold the hash down to 32 bits if need be. */ + hash = acc; + if (ASSOC_ARRAY_KEY_CHUNK_SIZE == 32) + hash ^= acc >> 32; + + /* Squidge all the keyrings into a separate part of the tree to + * ordinary keys by making sure the lowest level segment in the hash is + * zero for keyrings and non-zero otherwise. + */ + if (index_key->type != &key_type_keyring && (hash & level_mask) == 0) + return hash | (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1; + if (index_key->type == &key_type_keyring && (hash & level_mask) != 0) + return (hash + (hash << level_shift)) & ~level_mask; + return hash; +} + +/* + * Build the next index key chunk. + * + * On 32-bit systems the index key is laid out as: + * + * 0 4 5 9... + * hash desclen typeptr desc[] + * + * On 64-bit systems: + * + * 0 8 9 17... + * hash desclen typeptr desc[] + * + * We return it one word-sized chunk at a time. */ -static int keyring_match(const struct key *keyring, const void *description) +static unsigned long keyring_get_key_chunk(const void *data, int level) +{ + const struct keyring_index_key *index_key = data; + unsigned long chunk = 0; + long offset = 0; + int desc_len = index_key->desc_len, n = sizeof(chunk); + + level /= ASSOC_ARRAY_KEY_CHUNK_SIZE; + switch (level) { + case 0: + return hash_key_type_and_desc(index_key); + case 1: + return ((unsigned long)index_key->type << 8) | desc_len; + case 2: + if (desc_len == 0) + return (u8)((unsigned long)index_key->type >> + (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8)); + n--; + offset = 1; + default: + offset += sizeof(chunk) - 1; + offset += (level - 3) * sizeof(chunk); + if (offset >= desc_len) + return 0; + desc_len -= offset; + if (desc_len > n) + desc_len = n; + offset += desc_len; + do { + chunk <<= 8; + chunk |= ((u8*)index_key->description)[--offset]; + } while (--desc_len > 0); + + if (level == 2) { + chunk <<= 8; + chunk |= (u8)((unsigned long)index_key->type >> + (ASSOC_ARRAY_KEY_CHUNK_SIZE - 8)); + } + return chunk; + } +} + +static unsigned long keyring_get_object_key_chunk(const void *object, int level) +{ + const struct key *key = keyring_ptr_to_key(object); + return keyring_get_key_chunk(&key->index_key, level); +} + +static bool keyring_compare_object(const void *object, const void *data) { - return keyring->description && - strcmp(keyring->description, description) == 0; + const struct keyring_index_key *index_key = data; + const struct key *key = keyring_ptr_to_key(object); + + return key->index_key.type == index_key->type && + key->index_key.desc_len == index_key->desc_len && + memcmp(key->index_key.description, index_key->description, + index_key->desc_len) == 0; } /* + * Compare the index keys of a pair of objects and determine the bit position + * at which they differ - if they differ. + */ +static int keyring_diff_objects(const void *_a, const void *_b) +{ + const struct key *key_a = keyring_ptr_to_key(_a); + const struct key *key_b = keyring_ptr_to_key(_b); + const struct keyring_index_key *a = &key_a->index_key; + const struct keyring_index_key *b = &key_b->index_key; + unsigned long seg_a, seg_b; + int level, i; + + level = 0; + seg_a = hash_key_type_and_desc(a); + seg_b = hash_key_type_and_desc(b); + if ((seg_a ^ seg_b) != 0) + goto differ; + + /* The number of bits contributed by the hash is controlled by a + * constant in the assoc_array headers. Everything else thereafter we + * can deal with as being machine word-size dependent. + */ + level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8; + seg_a = a->desc_len; + seg_b = b->desc_len; + if ((seg_a ^ seg_b) != 0) + goto differ; + + /* The next bit may not work on big endian */ + level++; + seg_a = (unsigned long)a->type; + seg_b = (unsigned long)b->type; + if ((seg_a ^ seg_b) != 0) + goto differ; + + level += sizeof(unsigned long); + if (a->desc_len == 0) + goto same; + + i = 0; + if (((unsigned long)a->description | (unsigned long)b->description) & + (sizeof(unsigned long) - 1)) { + do { + seg_a = *(unsigned long *)(a->description + i); + seg_b = *(unsigned long *)(b->description + i); + if ((seg_a ^ seg_b) != 0) + goto differ_plus_i; + i += sizeof(unsigned long); + } while (i < (a->desc_len & (sizeof(unsigned long) - 1))); + } + + for (; i < a->desc_len; i++) { + seg_a = *(unsigned char *)(a->description + i); + seg_b = *(unsigned char *)(b->description + i); + if ((seg_a ^ seg_b) != 0) + goto differ_plus_i; + } + +same: + return -1; + +differ_plus_i: + level += i; +differ: + i = level * 8 + __ffs(seg_a ^ seg_b); + return i; +} + +/* + * Free an object after stripping the keyring flag off of the pointer. + */ +static void keyring_free_object(void *object) +{ + key_put(keyring_ptr_to_key(object)); +} + +/* + * Operations for keyring management by the index-tree routines. + */ +static const struct assoc_array_ops keyring_assoc_array_ops = { + .get_key_chunk = keyring_get_key_chunk, + .get_object_key_chunk = keyring_get_object_key_chunk, + .compare_object = keyring_compare_object, + .diff_objects = keyring_diff_objects, + .free_object = keyring_free_object, +}; + +/* * Clean up a keyring when it is destroyed. Unpublish its name if it had one * and dispose of its data. * @@ -155,9 +374,6 @@ static int keyring_match(const struct key *keyring, const void *description) */ static void keyring_destroy(struct key *keyring) { - struct keyring_list *klist; - int loop; - if (keyring->description) { write_lock(&keyring_name_lock); @@ -168,12 +384,7 @@ static void keyring_destroy(struct key *keyring) write_unlock(&keyring_name_lock); } - klist = rcu_access_pointer(keyring->payload.subscriptions); - if (klist) { - for (loop = klist->nkeys - 1; loop >= 0; loop--) - key_put(rcu_access_pointer(klist->keys[loop])); - kfree(klist); - } + assoc_array_destroy(&keyring->keys, &keyring_assoc_array_ops); } /* @@ -181,76 +392,88 @@ static void keyring_destroy(struct key *keyring) */ static void keyring_describe(const struct key *keyring, struct seq_file *m) { - struct keyring_list *klist; - if (keyring->description) seq_puts(m, keyring->description); else seq_puts(m, "[anon]"); if (key_is_instantiated(keyring)) { - rcu_read_lock(); - klist = rcu_dereference(keyring->payload.subscriptions); - if (klist) - seq_printf(m, ": %u/%u", klist->nkeys, klist->maxkeys); + if (keyring->keys.nr_leaves_on_tree != 0) + seq_printf(m, ": %lu", keyring->keys.nr_leaves_on_tree); else seq_puts(m, ": empty"); - rcu_read_unlock(); } } +struct keyring_read_iterator_context { + size_t qty; + size_t count; + key_serial_t __user *buffer; +}; + +static int keyring_read_iterator(const void *object, void *data) +{ + struct keyring_read_iterator_context *ctx = data; + const struct key *key = keyring_ptr_to_key(object); + int ret; + + kenter("{%s,%d},,{%zu/%zu}", + key->type->name, key->serial, ctx->count, ctx->qty); + + if (ctx->count >= ctx->qty) + return 1; + + ret = put_user(key->serial, ctx->buffer); + if (ret < 0) + return ret; + ctx->buffer++; + ctx->count += sizeof(key->serial); + return 0; +} + /* * Read a list of key IDs from the keyring's contents in binary form * - * The keyring's semaphore is read-locked by the caller. + * The keyring's semaphore is read-locked by the caller. This prevents someone + * from modifying it under us - which could cause us to read key IDs multiple + * times. */ static long keyring_read(const struct key *keyring, char __user *buffer, size_t buflen) { - struct keyring_list *klist; - struct key *key; - size_t qty, tmp; - int loop, ret; + struct keyring_read_iterator_context ctx; + unsigned long nr_keys; + int ret; - ret = 0; - klist = rcu_dereference_locked_keyring(keyring); - if (klist) { - /* calculate how much data we could return */ - qty = klist->nkeys * sizeof(key_serial_t); - - if (buffer && buflen > 0) { - if (buflen > qty) - buflen = qty; - - /* copy the IDs of the subscribed keys into the - * buffer */ - ret = -EFAULT; - - for (loop = 0; loop < klist->nkeys; loop++) { - key = rcu_deref_link_locked(klist, loop, - keyring); - - tmp = sizeof(key_serial_t); - if (tmp > buflen) - tmp = buflen; - - if (copy_to_user(buffer, - &key->serial, - tmp) != 0) - goto error; - - buflen -= tmp; - if (buflen == 0) - break; - buffer += tmp; - } - } + kenter("{%d},,%zu", key_serial(keyring), buflen); + + if (buflen & (sizeof(key_serial_t) - 1)) + return -EINVAL; + + nr_keys = keyring->keys.nr_leaves_on_tree; + if (nr_keys == 0) + return 0; - ret = qty; + /* Calculate how much data we could return */ + ctx.qty = nr_keys * sizeof(key_serial_t); + + if (!buffer || !buflen) + return ctx.qty; + + if (buflen > ctx.qty) + ctx.qty = buflen; + + /* Copy the IDs of the subscribed keys into the buffer */ + ctx.buffer = (key_serial_t __user *)buffer; + ctx.count = 0; + ret = assoc_array_iterate(&keyring->keys, keyring_read_iterator, &ctx); + if (ret < 0) { + kleave(" = %d [iterate]", ret); + return ret; } -error: - return ret; + kleave(" = %zu [ok]", ctx.count); + return ctx.count; } /* @@ -277,219 +500,360 @@ struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, } EXPORT_SYMBOL(keyring_alloc); -/** - * keyring_search_aux - Search a keyring tree for a key matching some criteria - * @keyring_ref: A pointer to the keyring with possession indicator. - * @ctx: The keyring search context. - * - * Search the supplied keyring tree for a key that matches the criteria given. - * The root keyring and any linked keyrings must grant Search permission to the - * caller to be searchable and keys can only be found if they too grant Search - * to the caller. The possession flag on the root keyring pointer controls use - * of the possessor bits in permissions checking of the entire tree. In - * addition, the LSM gets to forbid keyring searches and key matches. - * - * The search is performed as a breadth-then-depth search up to the prescribed - * limit (KEYRING_SEARCH_MAX_DEPTH). - * - * Keys are matched to the type provided and are then filtered by the match - * function, which is given the description to use in any way it sees fit. The - * match function may use any attributes of a key that it wishes to to - * determine the match. Normally the match function from the key type would be - * used. - * - * RCU is used to prevent the keyring key lists from disappearing without the - * need to take lots of locks. - * - * Returns a pointer to the found key and increments the key usage count if - * successful; -EAGAIN if no matching keys were found, or if expired or revoked - * keys were found; -ENOKEY if only negative keys were found; -ENOTDIR if the - * specified keyring wasn't a keyring. - * - * In the case of a successful return, the possession attribute from - * @keyring_ref is propagated to the returned key reference. +/* + * Iteration function to consider each key found. */ -key_ref_t keyring_search_aux(key_ref_t keyring_ref, - struct keyring_search_context *ctx) +static int keyring_search_iterator(const void *object, void *iterator_data) { - struct { - /* Need a separate keylist pointer for RCU purposes */ - struct key *keyring; - struct keyring_list *keylist; - int kix; - } stack[KEYRING_SEARCH_MAX_DEPTH]; - - struct keyring_list *keylist; - unsigned long kflags; - struct key *keyring, *key; - key_ref_t key_ref; - long err; - int sp, nkeys, kix; + struct keyring_search_context *ctx = iterator_data; + const struct key *key = keyring_ptr_to_key(object); + unsigned long kflags = key->flags; - keyring = key_ref_to_ptr(keyring_ref); - ctx->possessed = is_key_possessed(keyring_ref); - key_check(keyring); + kenter("{%d}", key->serial); - /* top keyring must have search permission to begin the search */ - err = key_task_permission(keyring_ref, ctx->cred, KEY_SEARCH); - if (err < 0) { - key_ref = ERR_PTR(err); - goto error; + /* ignore keys not of this type */ + if (key->type != ctx->index_key.type) { + kleave(" = 0 [!type]"); + return 0; } - key_ref = ERR_PTR(-ENOTDIR); - if (keyring->type != &key_type_keyring) - goto error; + /* skip invalidated, revoked and expired keys */ + if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) { + if (kflags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) { + ctx->result = ERR_PTR(-EKEYREVOKED); + kleave(" = %d [invrev]", ctx->skipped_ret); + goto skipped; + } - rcu_read_lock(); + if (key->expiry && ctx->now.tv_sec >= key->expiry) { + ctx->result = ERR_PTR(-EKEYEXPIRED); + kleave(" = %d [expire]", ctx->skipped_ret); + goto skipped; + } + } - ctx->now = current_kernel_time(); - err = -EAGAIN; - sp = 0; - - /* firstly we should check to see if this top-level keyring is what we - * are looking for */ - key_ref = ERR_PTR(-EAGAIN); - kflags = keyring->flags; - if (keyring->type == ctx->index_key.type && - ctx->match(keyring, ctx->match_data)) { - key = keyring; - if (ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK) - goto found; + /* keys that don't match */ + if (!ctx->match(key, ctx->match_data)) { + kleave(" = 0 [!match]"); + return 0; + } - /* check it isn't negative and hasn't expired or been - * revoked */ - if (kflags & (1 << KEY_FLAG_REVOKED)) - goto error_2; - if (key->expiry && ctx->now.tv_sec >= key->expiry) - goto error_2; - key_ref = ERR_PTR(key->type_data.reject_error); - if (kflags & (1 << KEY_FLAG_NEGATIVE)) - goto error_2; - goto found; + /* key must have search permissions */ + if (!(ctx->flags & KEYRING_SEARCH_NO_CHECK_PERM) && + key_task_permission(make_key_ref(key, ctx->possessed), + ctx->cred, KEY_SEARCH) < 0) { + ctx->result = ERR_PTR(-EACCES); + kleave(" = %d [!perm]", ctx->skipped_ret); + goto skipped; } - /* otherwise, the top keyring must not be revoked, expired, or - * negatively instantiated if we are to search it */ - key_ref = ERR_PTR(-EAGAIN); - if (kflags & ((1 << KEY_FLAG_INVALIDATED) | - (1 << KEY_FLAG_REVOKED) | - (1 << KEY_FLAG_NEGATIVE)) || - (keyring->expiry && ctx->now.tv_sec >= keyring->expiry)) - goto error_2; - - /* start processing a new keyring */ -descend: - kflags = keyring->flags; - if (kflags & ((1 << KEY_FLAG_INVALIDATED) | - (1 << KEY_FLAG_REVOKED))) - goto not_this_keyring; + if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) { + /* we set a different error code if we pass a negative key */ + if (kflags & (1 << KEY_FLAG_NEGATIVE)) { + ctx->result = ERR_PTR(key->type_data.reject_error); + kleave(" = %d [neg]", ctx->skipped_ret); + goto skipped; + } + } - keylist = rcu_dereference(keyring->payload.subscriptions); - if (!keylist) - goto not_this_keyring; + /* Found */ + ctx->result = make_key_ref(key, ctx->possessed); + kleave(" = 1 [found]"); + return 1; - /* iterate through the keys in this keyring first */ - nkeys = keylist->nkeys; - smp_rmb(); - for (kix = 0; kix < nkeys; kix++) { - key = rcu_dereference(keylist->keys[kix]); - kflags = key->flags; +skipped: + return ctx->skipped_ret; +} - /* ignore keys not of this type */ - if (key->type != ctx->index_key.type) - continue; +/* + * Search inside a keyring for a key. We can search by walking to it + * directly based on its index-key or we can iterate over the entire + * tree looking for it, based on the match function. + */ +static int search_keyring(struct key *keyring, struct keyring_search_context *ctx) +{ + if ((ctx->flags & KEYRING_SEARCH_LOOKUP_TYPE) == + KEYRING_SEARCH_LOOKUP_DIRECT) { + const void *object; + + object = assoc_array_find(&keyring->keys, + &keyring_assoc_array_ops, + &ctx->index_key); + return object ? ctx->iterator(object, ctx) : 0; + } + return assoc_array_iterate(&keyring->keys, ctx->iterator, ctx); +} - /* skip invalidated, revoked and expired keys */ - if (!(ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK)) { - if (kflags & ((1 << KEY_FLAG_INVALIDATED) | - (1 << KEY_FLAG_REVOKED))) - continue; +/* + * Search a tree of keyrings that point to other keyrings up to the maximum + * depth. + */ +static bool search_nested_keyrings(struct key *keyring, + struct keyring_search_context *ctx) +{ + struct { + struct key *keyring; + struct assoc_array_node *node; + int slot; + } stack[KEYRING_SEARCH_MAX_DEPTH]; - if (key->expiry && ctx->now.tv_sec >= key->expiry) - continue; - } + struct assoc_array_shortcut *shortcut; + struct assoc_array_node *node; + struct assoc_array_ptr *ptr; + struct key *key; + int sp = 0, slot; - /* keys that don't match */ - if (!ctx->match(key, ctx->match_data)) - continue; + kenter("{%d},{%s,%s}", + keyring->serial, + ctx->index_key.type->name, + ctx->index_key.description); - /* key must have search permissions */ - if (key_task_permission(make_key_ref(key, ctx->possessed), - ctx->cred, KEY_SEARCH) < 0) - continue; + if (ctx->index_key.description) + ctx->index_key.desc_len = strlen(ctx->index_key.description); - if (ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK) + /* Check to see if this top-level keyring is what we are looking for + * and whether it is valid or not. + */ + if (ctx->flags & KEYRING_SEARCH_LOOKUP_ITERATE || + keyring_compare_object(keyring, &ctx->index_key)) { + ctx->skipped_ret = 2; + ctx->flags |= KEYRING_SEARCH_DO_STATE_CHECK; + switch (ctx->iterator(keyring_key_to_ptr(keyring), ctx)) { + case 1: goto found; - - /* we set a different error code if we pass a negative key */ - if (kflags & (1 << KEY_FLAG_NEGATIVE)) { - err = key->type_data.reject_error; - continue; + case 2: + return false; + default: + break; } + } + ctx->skipped_ret = 0; + if (ctx->flags & KEYRING_SEARCH_NO_STATE_CHECK) + ctx->flags &= ~KEYRING_SEARCH_DO_STATE_CHECK; + + /* Start processing a new keyring */ +descend_to_keyring: + kdebug("descend to %d", keyring->serial); + if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) + goto not_this_keyring; + + /* Search through the keys in this keyring before its searching its + * subtrees. + */ + if (search_keyring(keyring, ctx)) goto found; - } - /* search through the keyrings nested in this one */ - kix = 0; -ascend: - nkeys = keylist->nkeys; - smp_rmb(); - for (; kix < nkeys; kix++) { - key = rcu_dereference(keylist->keys[kix]); - if (key->type != &key_type_keyring) - continue; + /* Then manually iterate through the keyrings nested in this one. + * + * Start from the root node of the index tree. Because of the way the + * hash function has been set up, keyrings cluster on the leftmost + * branch of the root node (root slot 0) or in the root node itself. + * Non-keyrings avoid the leftmost branch of the root entirely (root + * slots 1-15). + */ + ptr = ACCESS_ONCE(keyring->keys.root); + if (!ptr) + goto not_this_keyring; - /* recursively search nested keyrings - * - only search keyrings for which we have search permission + if (assoc_array_ptr_is_shortcut(ptr)) { + /* If the root is a shortcut, either the keyring only contains + * keyring pointers (everything clusters behind root slot 0) or + * doesn't contain any keyring pointers. */ - if (sp >= KEYRING_SEARCH_MAX_DEPTH) + shortcut = assoc_array_ptr_to_shortcut(ptr); + smp_read_barrier_depends(); + if ((shortcut->index_key[0] & ASSOC_ARRAY_FAN_MASK) != 0) + goto not_this_keyring; + + ptr = ACCESS_ONCE(shortcut->next_node); + node = assoc_array_ptr_to_node(ptr); + goto begin_node; + } + + node = assoc_array_ptr_to_node(ptr); + smp_read_barrier_depends(); + + ptr = node->slots[0]; + if (!assoc_array_ptr_is_meta(ptr)) + goto begin_node; + +descend_to_node: + /* Descend to a more distal node in this keyring's content tree and go + * through that. + */ + kdebug("descend"); + if (assoc_array_ptr_is_shortcut(ptr)) { + shortcut = assoc_array_ptr_to_shortcut(ptr); + smp_read_barrier_depends(); + ptr = ACCESS_ONCE(shortcut->next_node); + BUG_ON(!assoc_array_ptr_is_node(ptr)); + node = assoc_array_ptr_to_node(ptr); + } + +begin_node: + kdebug("begin_node"); + smp_read_barrier_depends(); + slot = 0; +ascend_to_node: + /* Go through the slots in a node */ + for (; slot < ASSOC_ARRAY_FAN_OUT; slot++) { + ptr = ACCESS_ONCE(node->slots[slot]); + + if (assoc_array_ptr_is_meta(ptr) && node->back_pointer) + goto descend_to_node; + + if (!keyring_ptr_is_keyring(ptr)) continue; - if (key_task_permission(make_key_ref(key, ctx->possessed), + key = keyring_ptr_to_key(ptr); + + if (sp >= KEYRING_SEARCH_MAX_DEPTH) { + if (ctx->flags & KEYRING_SEARCH_DETECT_TOO_DEEP) { + ctx->result = ERR_PTR(-ELOOP); + return false; + } + goto not_this_keyring; + } + + /* Search a nested keyring */ + if (!(ctx->flags & KEYRING_SEARCH_NO_CHECK_PERM) && + key_task_permission(make_key_ref(key, ctx->possessed), ctx->cred, KEY_SEARCH) < 0) continue; /* stack the current position */ stack[sp].keyring = keyring; - stack[sp].keylist = keylist; - stack[sp].kix = kix; + stack[sp].node = node; + stack[sp].slot = slot; sp++; /* begin again with the new keyring */ keyring = key; - goto descend; + goto descend_to_keyring; + } + + /* We've dealt with all the slots in the current node, so now we need + * to ascend to the parent and continue processing there. + */ + ptr = ACCESS_ONCE(node->back_pointer); + slot = node->parent_slot; + + if (ptr && assoc_array_ptr_is_shortcut(ptr)) { + shortcut = assoc_array_ptr_to_shortcut(ptr); + smp_read_barrier_depends(); + ptr = ACCESS_ONCE(shortcut->back_pointer); + slot = shortcut->parent_slot; + } + if (!ptr) + goto not_this_keyring; + node = assoc_array_ptr_to_node(ptr); + smp_read_barrier_depends(); + slot++; + + /* If we've ascended to the root (zero backpointer), we must have just + * finished processing the leftmost branch rather than the root slots - + * so there can't be any more keyrings for us to find. + */ + if (node->back_pointer) { + kdebug("ascend %d", slot); + goto ascend_to_node; } - /* the keyring we're looking at was disqualified or didn't contain a - * matching key */ + /* The keyring we're looking at was disqualified or didn't contain a + * matching key. + */ not_this_keyring: - if (sp > 0) { - /* resume the processing of a keyring higher up in the tree */ - sp--; - keyring = stack[sp].keyring; - keylist = stack[sp].keylist; - kix = stack[sp].kix + 1; - goto ascend; + kdebug("not_this_keyring %d", sp); + if (sp <= 0) { + kleave(" = false"); + return false; } - key_ref = ERR_PTR(err); - goto error_2; + /* Resume the processing of a keyring higher up in the tree */ + sp--; + keyring = stack[sp].keyring; + node = stack[sp].node; + slot = stack[sp].slot + 1; + kdebug("ascend to %d [%d]", keyring->serial, slot); + goto ascend_to_node; - /* we found a viable match */ + /* We found a viable match */ found: - __key_get(key); - key->last_used_at = ctx->now.tv_sec; - keyring->last_used_at = ctx->now.tv_sec; - while (sp > 0) - stack[--sp].keyring->last_used_at = ctx->now.tv_sec; + key = key_ref_to_ptr(ctx->result); key_check(key); - key_ref = make_key_ref(key, ctx->possessed); -error_2: + if (!(ctx->flags & KEYRING_SEARCH_NO_UPDATE_TIME)) { + key->last_used_at = ctx->now.tv_sec; + keyring->last_used_at = ctx->now.tv_sec; + while (sp > 0) + stack[--sp].keyring->last_used_at = ctx->now.tv_sec; + } + kleave(" = true"); + return true; +} + +/** + * keyring_search_aux - Search a keyring tree for a key matching some criteria + * @keyring_ref: A pointer to the keyring with possession indicator. + * @ctx: The keyring search context. + * + * Search the supplied keyring tree for a key that matches the criteria given. + * The root keyring and any linked keyrings must grant Search permission to the + * caller to be searchable and keys can only be found if they too grant Search + * to the caller. The possession flag on the root keyring pointer controls use + * of the possessor bits in permissions checking of the entire tree. In + * addition, the LSM gets to forbid keyring searches and key matches. + * + * The search is performed as a breadth-then-depth search up to the prescribed + * limit (KEYRING_SEARCH_MAX_DEPTH). + * + * Keys are matched to the type provided and are then filtered by the match + * function, which is given the description to use in any way it sees fit. The + * match function may use any attributes of a key that it wishes to to + * determine the match. Normally the match function from the key type would be + * used. + * + * RCU can be used to prevent the keyring key lists from disappearing without + * the need to take lots of locks. + * + * Returns a pointer to the found key and increments the key usage count if + * successful; -EAGAIN if no matching keys were found, or if expired or revoked + * keys were found; -ENOKEY if only negative keys were found; -ENOTDIR if the + * specified keyring wasn't a keyring. + * + * In the case of a successful return, the possession attribute from + * @keyring_ref is propagated to the returned key reference. + */ +key_ref_t keyring_search_aux(key_ref_t keyring_ref, + struct keyring_search_context *ctx) +{ + struct key *keyring; + long err; + + ctx->iterator = keyring_search_iterator; + ctx->possessed = is_key_possessed(keyring_ref); + ctx->result = ERR_PTR(-EAGAIN); + + keyring = key_ref_to_ptr(keyring_ref); + key_check(keyring); + + if (keyring->type != &key_type_keyring) + return ERR_PTR(-ENOTDIR); + + if (!(ctx->flags & KEYRING_SEARCH_NO_CHECK_PERM)) { + err = key_task_permission(keyring_ref, ctx->cred, KEY_SEARCH); + if (err < 0) + return ERR_PTR(err); + } + + rcu_read_lock(); + ctx->now = current_kernel_time(); + if (search_nested_keyrings(keyring, ctx)) + __key_get(key_ref_to_ptr(ctx->result)); rcu_read_unlock(); -error: - return key_ref; + return ctx->result; } /** @@ -499,7 +863,7 @@ error: * @description: The name of the keyring we want to find. * * As keyring_search_aux() above, but using the current task's credentials and - * type's default matching function. + * type's default matching function and preferred search method. */ key_ref_t keyring_search(key_ref_t keyring, struct key_type *type, @@ -523,58 +887,49 @@ key_ref_t keyring_search(key_ref_t keyring, EXPORT_SYMBOL(keyring_search); /* - * Search the given keyring only (no recursion). + * Search the given keyring for a key that might be updated. * * The caller must guarantee that the keyring is a keyring and that the - * permission is granted to search the keyring as no check is made here. - * - * RCU is used to make it unnecessary to lock the keyring key list here. + * permission is granted to modify the keyring as no check is made here. The + * caller must also hold a lock on the keyring semaphore. * * Returns a pointer to the found key with usage count incremented if - * successful and returns -ENOKEY if not found. Revoked and invalidated keys - * are skipped over. + * successful and returns NULL if not found. Revoked and invalidated keys are + * skipped over. * * If successful, the possession indicator is propagated from the keyring ref * to the returned key reference. */ -key_ref_t __keyring_search_one(key_ref_t keyring_ref, - const struct keyring_index_key *index_key) +key_ref_t find_key_to_update(key_ref_t keyring_ref, + const struct keyring_index_key *index_key) { - struct keyring_list *klist; struct key *keyring, *key; - bool possessed; - int nkeys, loop; + const void *object; keyring = key_ref_to_ptr(keyring_ref); - possessed = is_key_possessed(keyring_ref); - rcu_read_lock(); + kenter("{%d},{%s,%s}", + keyring->serial, index_key->type->name, index_key->description); - klist = rcu_dereference(keyring->payload.subscriptions); - if (klist) { - nkeys = klist->nkeys; - smp_rmb(); - for (loop = 0; loop < nkeys ; loop++) { - key = rcu_dereference(klist->keys[loop]); - if (key->type == index_key->type && - (!key->type->match || - key->type->match(key, index_key->description)) && - !(key->flags & ((1 << KEY_FLAG_INVALIDATED) | - (1 << KEY_FLAG_REVOKED))) - ) - goto found; - } - } + object = assoc_array_find(&keyring->keys, &keyring_assoc_array_ops, + index_key); - rcu_read_unlock(); - return ERR_PTR(-ENOKEY); + if (object) + goto found; + + kleave(" = NULL"); + return NULL; found: + key = keyring_ptr_to_key(object); + if (key->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) { + kleave(" = NULL [x]"); + return NULL; + } __key_get(key); - keyring->last_used_at = key->last_used_at = - current_kernel_time().tv_sec; - rcu_read_unlock(); - return make_key_ref(key, possessed); + kleave(" = {%d}", key->serial); + return make_key_ref(key, is_key_possessed(keyring_ref)); } /* @@ -637,6 +992,19 @@ out: return keyring; } +static int keyring_detect_cycle_iterator(const void *object, + void *iterator_data) +{ + struct keyring_search_context *ctx = iterator_data; + const struct key *key = keyring_ptr_to_key(object); + + kenter("{%d}", key->serial); + + BUG_ON(key != ctx->match_data); + ctx->result = ERR_PTR(-EDEADLK); + return 1; +} + /* * See if a cycle will will be created by inserting acyclic tree B in acyclic * tree A at the topmost level (ie: as a direct child of A). @@ -646,117 +1014,39 @@ out: */ static int keyring_detect_cycle(struct key *A, struct key *B) { - struct { - struct keyring_list *keylist; - int kix; - } stack[KEYRING_SEARCH_MAX_DEPTH]; - - struct keyring_list *keylist; - struct key *subtree, *key; - int sp, nkeys, kix, ret; + struct keyring_search_context ctx = { + .index_key = A->index_key, + .match_data = A, + .iterator = keyring_detect_cycle_iterator, + .flags = (KEYRING_SEARCH_LOOKUP_DIRECT | + KEYRING_SEARCH_NO_STATE_CHECK | + KEYRING_SEARCH_NO_UPDATE_TIME | + KEYRING_SEARCH_NO_CHECK_PERM | + KEYRING_SEARCH_DETECT_TOO_DEEP), + }; rcu_read_lock(); - - ret = -EDEADLK; - if (A == B) - goto cycle_detected; - - subtree = B; - sp = 0; - - /* start processing a new keyring */ -descend: - if (test_bit(KEY_FLAG_REVOKED, &subtree->flags)) - goto not_this_keyring; - - keylist = rcu_dereference(subtree->payload.subscriptions); - if (!keylist) - goto not_this_keyring; - kix = 0; - -ascend: - /* iterate through the remaining keys in this keyring */ - nkeys = keylist->nkeys; - smp_rmb(); - for (; kix < nkeys; kix++) { - key = rcu_dereference(keylist->keys[kix]); - - if (key == A) - goto cycle_detected; - - /* recursively check nested keyrings */ - if (key->type == &key_type_keyring) { - if (sp >= KEYRING_SEARCH_MAX_DEPTH) - goto too_deep; - - /* stack the current position */ - stack[sp].keylist = keylist; - stack[sp].kix = kix; - sp++; - - /* begin again with the new keyring */ - subtree = key; - goto descend; - } - } - - /* the keyring we're looking at was disqualified or didn't contain a - * matching key */ -not_this_keyring: - if (sp > 0) { - /* resume the checking of a keyring higher up in the tree */ - sp--; - keylist = stack[sp].keylist; - kix = stack[sp].kix + 1; - goto ascend; - } - - ret = 0; /* no cycles detected */ - -error: + search_nested_keyrings(B, &ctx); rcu_read_unlock(); - return ret; - -too_deep: - ret = -ELOOP; - goto error; - -cycle_detected: - ret = -EDEADLK; - goto error; -} - -/* - * Dispose of a keyring list after the RCU grace period, freeing the unlinked - * key - */ -static void keyring_unlink_rcu_disposal(struct rcu_head *rcu) -{ - struct keyring_list *klist = - container_of(rcu, struct keyring_list, rcu); - - if (klist->delkey != USHRT_MAX) - key_put(rcu_access_pointer(klist->keys[klist->delkey])); - kfree(klist); + return PTR_ERR(ctx.result) == -EAGAIN ? 0 : PTR_ERR(ctx.result); } /* * Preallocate memory so that a key can be linked into to a keyring. */ -int __key_link_begin(struct key *keyring, const struct keyring_index_key *index_key, - unsigned long *_prealloc) +int __key_link_begin(struct key *keyring, + const struct keyring_index_key *index_key, + struct assoc_array_edit **_edit) __acquires(&keyring->sem) __acquires(&keyring_serialise_link_sem) { - struct keyring_list *klist, *nklist; - unsigned long prealloc; - unsigned max; - time_t lowest_lru; - size_t size; - int loop, lru, ret; + struct assoc_array_edit *edit; + int ret; kenter("%d,%s,%s,", - key_serial(keyring), index_key->type->name, index_key->description); + keyring->serial, index_key->type->name, index_key->description); + + BUG_ON(index_key->desc_len == 0); if (keyring->type != &key_type_keyring) return -ENOTDIR; @@ -772,88 +1062,25 @@ int __key_link_begin(struct key *keyring, const struct keyring_index_key *index_ if (index_key->type == &key_type_keyring) down_write(&keyring_serialise_link_sem); - klist = rcu_dereference_locked_keyring(keyring); - - /* see if there's a matching key we can displace */ - lru = -1; - if (klist && klist->nkeys > 0) { - lowest_lru = TIME_T_MAX; - for (loop = klist->nkeys - 1; loop >= 0; loop--) { - struct key *key = rcu_deref_link_locked(klist, loop, - keyring); - if (key->type == index_key->type && - strcmp(key->description, index_key->description) == 0) { - /* Found a match - we'll replace the link with - * one to the new key. We record the slot - * position. - */ - klist->delkey = loop; - prealloc = 0; - goto done; - } - if (key->last_used_at < lowest_lru) { - lowest_lru = key->last_used_at; - lru = loop; - } - } - } - - /* If the keyring is full then do an LRU discard */ - if (klist && - klist->nkeys == klist->maxkeys && - klist->maxkeys >= MAX_KEYRING_LINKS) { - kdebug("LRU discard %d\n", lru); - klist->delkey = lru; - prealloc = 0; - goto done; - } - /* check that we aren't going to overrun the user's quota */ ret = key_payload_reserve(keyring, keyring->datalen + KEYQUOTA_LINK_BYTES); if (ret < 0) goto error_sem; - if (klist && klist->nkeys < klist->maxkeys) { - /* there's sufficient slack space to append directly */ - klist->delkey = klist->nkeys; - prealloc = KEY_LINK_FIXQUOTA; - } else { - /* grow the key list */ - max = 4; - if (klist) { - max += klist->maxkeys; - if (max > MAX_KEYRING_LINKS) - max = MAX_KEYRING_LINKS; - BUG_ON(max <= klist->maxkeys); - } - - size = sizeof(*klist) + sizeof(struct key *) * max; - - ret = -ENOMEM; - nklist = kmalloc(size, GFP_KERNEL); - if (!nklist) - goto error_quota; - - nklist->maxkeys = max; - if (klist) { - memcpy(nklist->keys, klist->keys, - sizeof(struct key *) * klist->nkeys); - nklist->delkey = klist->nkeys; - nklist->nkeys = klist->nkeys + 1; - klist->delkey = USHRT_MAX; - } else { - nklist->nkeys = 1; - nklist->delkey = 0; - } - - /* add the key into the new space */ - RCU_INIT_POINTER(nklist->keys[nklist->delkey], NULL); - prealloc = (unsigned long)nklist | KEY_LINK_FIXQUOTA; + /* Create an edit script that will insert/replace the key in the + * keyring tree. + */ + edit = assoc_array_insert(&keyring->keys, + &keyring_assoc_array_ops, + index_key, + NULL); + if (IS_ERR(edit)) { + ret = PTR_ERR(edit); + goto error_quota; } -done: - *_prealloc = prealloc; + *_edit = edit; kleave(" = 0"); return 0; @@ -893,60 +1120,12 @@ int __key_link_check_live_key(struct key *keyring, struct key *key) * holds at most one link to any given key of a particular type+description * combination. */ -void __key_link(struct key *keyring, struct key *key, - unsigned long *_prealloc) +void __key_link(struct key *key, struct assoc_array_edit **_edit) { - struct keyring_list *klist, *nklist; - struct key *discard; - - nklist = (struct keyring_list *)(*_prealloc & ~KEY_LINK_FIXQUOTA); - *_prealloc = 0; - - kenter("%d,%d,%p", keyring->serial, key->serial, nklist); - - klist = rcu_dereference_locked_keyring(keyring); - __key_get(key); - keyring->last_used_at = key->last_used_at = - current_kernel_time().tv_sec; - - /* there's a matching key we can displace or an empty slot in a newly - * allocated list we can fill */ - if (nklist) { - kdebug("reissue %hu/%hu/%hu", - nklist->delkey, nklist->nkeys, nklist->maxkeys); - - RCU_INIT_POINTER(nklist->keys[nklist->delkey], key); - - rcu_assign_pointer(keyring->payload.subscriptions, nklist); - - /* dispose of the old keyring list and, if there was one, the - * displaced key */ - if (klist) { - kdebug("dispose %hu/%hu/%hu", - klist->delkey, klist->nkeys, klist->maxkeys); - call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); - } - } else if (klist->delkey < klist->nkeys) { - kdebug("replace %hu/%hu/%hu", - klist->delkey, klist->nkeys, klist->maxkeys); - - discard = rcu_dereference_protected( - klist->keys[klist->delkey], - rwsem_is_locked(&keyring->sem)); - rcu_assign_pointer(klist->keys[klist->delkey], key); - /* The garbage collector will take care of RCU - * synchronisation */ - key_put(discard); - } else { - /* there's sufficient slack space to append directly */ - kdebug("append %hu/%hu/%hu", - klist->delkey, klist->nkeys, klist->maxkeys); - - RCU_INIT_POINTER(klist->keys[klist->delkey], key); - smp_wmb(); - klist->nkeys++; - } + assoc_array_insert_set_object(*_edit, keyring_key_to_ptr(key)); + assoc_array_apply_edit(*_edit); + *_edit = NULL; } /* @@ -956,23 +1135,20 @@ void __key_link(struct key *keyring, struct key *key, */ void __key_link_end(struct key *keyring, const struct keyring_index_key *index_key, - unsigned long prealloc) + struct assoc_array_edit *edit) __releases(&keyring->sem) __releases(&keyring_serialise_link_sem) { BUG_ON(index_key->type == NULL); - BUG_ON(index_key->type->name == NULL); - kenter("%d,%s,%lx", keyring->serial, index_key->type->name, prealloc); + kenter("%d,%s,", keyring->serial, index_key->type->name); if (index_key->type == &key_type_keyring) up_write(&keyring_serialise_link_sem); - if (prealloc) { - if (prealloc & KEY_LINK_FIXQUOTA) - key_payload_reserve(keyring, - keyring->datalen - - KEYQUOTA_LINK_BYTES); - kfree((struct keyring_list *)(prealloc & ~KEY_LINK_FIXQUOTA)); + if (edit) { + key_payload_reserve(keyring, + keyring->datalen - KEYQUOTA_LINK_BYTES); + assoc_array_cancel_edit(edit); } up_write(&keyring->sem); } @@ -999,20 +1175,24 @@ void __key_link_end(struct key *keyring, */ int key_link(struct key *keyring, struct key *key) { - unsigned long prealloc; + struct assoc_array_edit *edit; int ret; + kenter("{%d,%d}", keyring->serial, atomic_read(&keyring->usage)); + key_check(keyring); key_check(key); - ret = __key_link_begin(keyring, &key->index_key, &prealloc); + ret = __key_link_begin(keyring, &key->index_key, &edit); if (ret == 0) { + kdebug("begun {%d,%d}", keyring->serial, atomic_read(&keyring->usage)); ret = __key_link_check_live_key(keyring, key); if (ret == 0) - __key_link(keyring, key, &prealloc); - __key_link_end(keyring, &key->index_key, prealloc); + __key_link(key, &edit); + __key_link_end(keyring, &key->index_key, edit); } + kleave(" = %d {%d,%d}", ret, keyring->serial, atomic_read(&keyring->usage)); return ret; } EXPORT_SYMBOL(key_link); @@ -1036,90 +1216,36 @@ EXPORT_SYMBOL(key_link); */ int key_unlink(struct key *keyring, struct key *key) { - struct keyring_list *klist, *nklist; - int loop, ret; + struct assoc_array_edit *edit; + int ret; key_check(keyring); key_check(key); - ret = -ENOTDIR; if (keyring->type != &key_type_keyring) - goto error; + return -ENOTDIR; down_write(&keyring->sem); - klist = rcu_dereference_locked_keyring(keyring); - if (klist) { - /* search the keyring for the key */ - for (loop = 0; loop < klist->nkeys; loop++) - if (rcu_access_pointer(klist->keys[loop]) == key) - goto key_is_present; + edit = assoc_array_delete(&keyring->keys, &keyring_assoc_array_ops, + &key->index_key); + if (IS_ERR(edit)) { + ret = PTR_ERR(edit); + goto error; } - - up_write(&keyring->sem); ret = -ENOENT; - goto error; - -key_is_present: - /* we need to copy the key list for RCU purposes */ - nklist = kmalloc(sizeof(*klist) + - sizeof(struct key *) * klist->maxkeys, - GFP_KERNEL); - if (!nklist) - goto nomem; - nklist->maxkeys = klist->maxkeys; - nklist->nkeys = klist->nkeys - 1; - - if (loop > 0) - memcpy(&nklist->keys[0], - &klist->keys[0], - loop * sizeof(struct key *)); - - if (loop < nklist->nkeys) - memcpy(&nklist->keys[loop], - &klist->keys[loop + 1], - (nklist->nkeys - loop) * sizeof(struct key *)); - - /* adjust the user's quota */ - key_payload_reserve(keyring, - keyring->datalen - KEYQUOTA_LINK_BYTES); - - rcu_assign_pointer(keyring->payload.subscriptions, nklist); - - up_write(&keyring->sem); - - /* schedule for later cleanup */ - klist->delkey = loop; - call_rcu(&klist->rcu, keyring_unlink_rcu_disposal); + if (edit == NULL) + goto error; + assoc_array_apply_edit(edit); ret = 0; error: - return ret; -nomem: - ret = -ENOMEM; up_write(&keyring->sem); - goto error; + return ret; } EXPORT_SYMBOL(key_unlink); -/* - * Dispose of a keyring list after the RCU grace period, releasing the keys it - * links to. - */ -static void keyring_clear_rcu_disposal(struct rcu_head *rcu) -{ - struct keyring_list *klist; - int loop; - - klist = container_of(rcu, struct keyring_list, rcu); - - for (loop = klist->nkeys - 1; loop >= 0; loop--) - key_put(rcu_access_pointer(klist->keys[loop])); - - kfree(klist); -} - /** * keyring_clear - Clear a keyring * @keyring: The keyring to clear. @@ -1130,33 +1256,25 @@ static void keyring_clear_rcu_disposal(struct rcu_head *rcu) */ int keyring_clear(struct key *keyring) { - struct keyring_list *klist; + struct assoc_array_edit *edit; int ret; - ret = -ENOTDIR; - if (keyring->type == &key_type_keyring) { - /* detach the pointer block with the locks held */ - down_write(&keyring->sem); - - klist = rcu_dereference_locked_keyring(keyring); - if (klist) { - /* adjust the quota */ - key_payload_reserve(keyring, - sizeof(struct keyring_list)); - - rcu_assign_pointer(keyring->payload.subscriptions, - NULL); - } - - up_write(&keyring->sem); + if (keyring->type != &key_type_keyring) + return -ENOTDIR; - /* free the keys after the locks have been dropped */ - if (klist) - call_rcu(&klist->rcu, keyring_clear_rcu_disposal); + down_write(&keyring->sem); + edit = assoc_array_clear(&keyring->keys, &keyring_assoc_array_ops); + if (IS_ERR(edit)) { + ret = PTR_ERR(edit); + } else { + if (edit) + assoc_array_apply_edit(edit); + key_payload_reserve(keyring, 0); ret = 0; } + up_write(&keyring->sem); return ret; } EXPORT_SYMBOL(keyring_clear); @@ -1168,17 +1286,25 @@ EXPORT_SYMBOL(keyring_clear); */ static void keyring_revoke(struct key *keyring) { - struct keyring_list *klist; + struct assoc_array_edit *edit; - klist = rcu_dereference_locked_keyring(keyring); + edit = assoc_array_clear(&keyring->keys, &keyring_assoc_array_ops); + if (!IS_ERR(edit)) { + if (edit) + assoc_array_apply_edit(edit); + key_payload_reserve(keyring, 0); + } +} - /* adjust the quota */ - key_payload_reserve(keyring, 0); +static bool gc_iterator(void *object, void *iterator_data) +{ + struct key *key = keyring_ptr_to_key(object); + time_t *limit = iterator_data; - if (klist) { - rcu_assign_pointer(keyring->payload.subscriptions, NULL); - call_rcu(&klist->rcu, keyring_clear_rcu_disposal); - } + if (key_is_dead(key, *limit)) + return false; + key_get(key); + return true; } /* @@ -1191,88 +1317,12 @@ static void keyring_revoke(struct key *keyring) */ void keyring_gc(struct key *keyring, time_t limit) { - struct keyring_list *klist, *new; - struct key *key; - int loop, keep, max; - kenter("{%x,%s}", key_serial(keyring), keyring->description); down_write(&keyring->sem); - - klist = rcu_dereference_locked_keyring(keyring); - if (!klist) - goto no_klist; - - /* work out how many subscriptions we're keeping */ - keep = 0; - for (loop = klist->nkeys - 1; loop >= 0; loop--) - if (!key_is_dead(rcu_deref_link_locked(klist, loop, keyring), - limit)) - keep++; - - if (keep == klist->nkeys) - goto just_return; - - /* allocate a new keyring payload */ - max = roundup(keep, 4); - new = kmalloc(sizeof(struct keyring_list) + max * sizeof(struct key *), - GFP_KERNEL); - if (!new) - goto nomem; - new->maxkeys = max; - new->nkeys = 0; - new->delkey = 0; - - /* install the live keys - * - must take care as expired keys may be updated back to life - */ - keep = 0; - for (loop = klist->nkeys - 1; loop >= 0; loop--) { - key = rcu_deref_link_locked(klist, loop, keyring); - if (!key_is_dead(key, limit)) { - if (keep >= max) - goto discard_new; - RCU_INIT_POINTER(new->keys[keep++], key_get(key)); - } - } - new->nkeys = keep; - - /* adjust the quota */ - key_payload_reserve(keyring, - sizeof(struct keyring_list) + - KEYQUOTA_LINK_BYTES * keep); - - if (keep == 0) { - rcu_assign_pointer(keyring->payload.subscriptions, NULL); - kfree(new); - } else { - rcu_assign_pointer(keyring->payload.subscriptions, new); - } - - up_write(&keyring->sem); - - call_rcu(&klist->rcu, keyring_clear_rcu_disposal); - kleave(" [yes]"); - return; - -discard_new: - new->nkeys = keep; - keyring_clear_rcu_disposal(&new->rcu); + assoc_array_gc(&keyring->keys, &keyring_assoc_array_ops, + gc_iterator, &limit); up_write(&keyring->sem); - kleave(" [discard]"); - return; -just_return: - up_write(&keyring->sem); - kleave(" [no dead]"); - return; - -no_klist: - up_write(&keyring->sem); - kleave(" [no_klist]"); - return; - -nomem: - up_write(&keyring->sem); - kleave(" [oom]"); + kleave(""); } diff --git a/security/keys/request_key.c b/security/keys/request_key.c index ab75df4..df94827 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -351,7 +351,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx, struct key_user *user, struct key **_key) { - unsigned long prealloc; + struct assoc_array_edit *edit; struct key *key; key_perm_t perm; key_ref_t key_ref; @@ -380,7 +380,7 @@ static int construct_alloc_key(struct keyring_search_context *ctx, set_bit(KEY_FLAG_USER_CONSTRUCT, &key->flags); if (dest_keyring) { - ret = __key_link_begin(dest_keyring, &ctx->index_key, &prealloc); + ret = __key_link_begin(dest_keyring, &ctx->index_key, &edit); if (ret < 0) goto link_prealloc_failed; } @@ -395,11 +395,11 @@ static int construct_alloc_key(struct keyring_search_context *ctx, goto key_already_present; if (dest_keyring) - __key_link(dest_keyring, key, &prealloc); + __key_link(key, &edit); mutex_unlock(&key_construction_mutex); if (dest_keyring) - __key_link_end(dest_keyring, &ctx->index_key, prealloc); + __key_link_end(dest_keyring, &ctx->index_key, edit); mutex_unlock(&user->cons_lock); *_key = key; kleave(" = 0 [%d]", key_serial(key)); @@ -414,8 +414,8 @@ key_already_present: if (dest_keyring) { ret = __key_link_check_live_key(dest_keyring, key); if (ret == 0) - __key_link(dest_keyring, key, &prealloc); - __key_link_end(dest_keyring, &ctx->index_key, prealloc); + __key_link(key, &edit); + __key_link_end(dest_keyring, &ctx->index_key, edit); if (ret < 0) goto link_check_failed; } -- cgit v1.1 From ab3c3587f8cda9083209a61dbe3a4407d3cada10 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:18 +0100 Subject: KEYS: Implement a big key type that can save to tmpfs Implement a big key type that can save its contents to tmpfs and thus swapspace when memory is tight. This is useful for Kerberos ticket caches. Signed-off-by: David Howells Tested-by: Simo Sorce --- security/keys/Kconfig | 11 +++ security/keys/Makefile | 1 + security/keys/big_key.c | 204 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 security/keys/big_key.c (limited to 'security') diff --git a/security/keys/Kconfig b/security/keys/Kconfig index 15e0dfe..b563622 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -20,6 +20,17 @@ config KEYS If you are unsure as to whether this is required, answer N. +config BIG_KEYS + tristate "Large payload keys" + depends on KEYS + depends on TMPFS + help + This option provides support for holding large keys within the kernel + (for example Kerberos ticket caches). The data may be stored out to + swapspace by tmpfs. + + If you are unsure as to whether this is required, answer N. + config TRUSTED_KEYS tristate "TRUSTED KEYS" depends on KEYS && TCG_TPM diff --git a/security/keys/Makefile b/security/keys/Makefile index 504aaa0..c487c77 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -22,5 +22,6 @@ obj-$(CONFIG_SYSCTL) += sysctl.o # # Key types # +obj-$(CONFIG_BIG_KEYS) += big_key.o obj-$(CONFIG_TRUSTED_KEYS) += trusted.o obj-$(CONFIG_ENCRYPTED_KEYS) += encrypted-keys/ diff --git a/security/keys/big_key.c b/security/keys/big_key.c new file mode 100644 index 0000000..5f9defc --- /dev/null +++ b/security/keys/big_key.c @@ -0,0 +1,204 @@ +/* Large capacity key type + * + * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); + +/* + * If the data is under this limit, there's no point creating a shm file to + * hold it as the permanently resident metadata for the shmem fs will be at + * least as large as the data. + */ +#define BIG_KEY_FILE_THRESHOLD (sizeof(struct inode) + sizeof(struct dentry)) + +/* + * big_key defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_big_key = { + .name = "big_key", + .def_lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT, + .instantiate = big_key_instantiate, + .match = user_match, + .revoke = big_key_revoke, + .destroy = big_key_destroy, + .describe = big_key_describe, + .read = big_key_read, +}; + +/* + * Instantiate a big key + */ +int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep) +{ + struct path *path = (struct path *)&key->payload.data2; + struct file *file; + ssize_t written; + size_t datalen = prep->datalen; + int ret; + + ret = -EINVAL; + if (datalen <= 0 || datalen > 1024 * 1024 || !prep->data) + goto error; + + /* Set an arbitrary quota */ + ret = key_payload_reserve(key, 16); + if (ret < 0) + goto error; + + key->type_data.x[1] = datalen; + + if (datalen > BIG_KEY_FILE_THRESHOLD) { + /* Create a shmem file to store the data in. This will permit the data + * to be swapped out if needed. + * + * TODO: Encrypt the stored data with a temporary key. + */ + file = shmem_file_setup("", datalen, 0); + if (IS_ERR(file)) + goto err_quota; + + written = kernel_write(file, prep->data, prep->datalen, 0); + if (written != datalen) { + if (written >= 0) + ret = -ENOMEM; + goto err_fput; + } + + /* Pin the mount and dentry to the key so that we can open it again + * later + */ + *path = file->f_path; + path_get(path); + fput(file); + } else { + /* Just store the data in a buffer */ + void *data = kmalloc(datalen, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto err_quota; + } + + key->payload.data = memcpy(data, prep->data, prep->datalen); + } + return 0; + +err_fput: + fput(file); +err_quota: + key_payload_reserve(key, 0); +error: + return ret; +} + +/* + * dispose of the links from a revoked keyring + * - called with the key sem write-locked + */ +void big_key_revoke(struct key *key) +{ + struct path *path = (struct path *)&key->payload.data2; + + /* clear the quota */ + key_payload_reserve(key, 0); + if (key_is_instantiated(key) && key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) + vfs_truncate(path, 0); +} + +/* + * dispose of the data dangling from the corpse of a big_key key + */ +void big_key_destroy(struct key *key) +{ + if (key->type_data.x[1] > BIG_KEY_FILE_THRESHOLD) { + struct path *path = (struct path *)&key->payload.data2; + path_put(path); + path->mnt = NULL; + path->dentry = NULL; + } else { + kfree(key->payload.data); + key->payload.data = NULL; + } +} + +/* + * describe the big_key key + */ +void big_key_describe(const struct key *key, struct seq_file *m) +{ + unsigned long datalen = key->type_data.x[1]; + + seq_puts(m, key->description); + + if (key_is_instantiated(key)) + seq_printf(m, ": %lu [%s]", + datalen, + datalen > BIG_KEY_FILE_THRESHOLD ? "file" : "buff"); +} + +/* + * read the key data + * - the key's semaphore is read-locked + */ +long big_key_read(const struct key *key, char __user *buffer, size_t buflen) +{ + unsigned long datalen = key->type_data.x[1]; + long ret; + + if (!buffer || buflen < datalen) + return datalen; + + if (datalen > BIG_KEY_FILE_THRESHOLD) { + struct path *path = (struct path *)&key->payload.data2; + struct file *file; + loff_t pos; + + file = dentry_open(path, O_RDONLY, current_cred()); + if (IS_ERR(file)) + return PTR_ERR(file); + + pos = 0; + ret = vfs_read(file, buffer, datalen, &pos); + fput(file); + if (ret >= 0 && ret != datalen) + ret = -EIO; + } else { + ret = datalen; + if (copy_to_user(buffer, key->payload.data, datalen) != 0) + ret = -EFAULT; + } + + return ret; +} + +/* + * Module stuff + */ +static int __init big_key_init(void) +{ + return register_key_type(&key_type_big_key); +} + +static void __exit big_key_cleanup(void) +{ + unregister_key_type(&key_type_big_key); +} + +module_init(big_key_init); +module_exit(big_key_cleanup); -- cgit v1.1 From f36f8c75ae2e7d4da34f4c908cebdb4aa42c977e Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 24 Sep 2013 10:35:19 +0100 Subject: KEYS: Add per-user_namespace registers for persistent per-UID kerberos caches Add support for per-user_namespace registers of persistent per-UID kerberos caches held within the kernel. This allows the kerberos cache to be retained beyond the life of all a user's processes so that the user's cron jobs can work. The kerberos cache is envisioned as a keyring/key tree looking something like: struct user_namespace \___ .krb_cache keyring - The register \___ _krb.0 keyring - Root's Kerberos cache \___ _krb.5000 keyring - User 5000's Kerberos cache \___ _krb.5001 keyring - User 5001's Kerberos cache \___ tkt785 big_key - A ccache blob \___ tkt12345 big_key - Another ccache blob Or possibly: struct user_namespace \___ .krb_cache keyring - The register \___ _krb.0 keyring - Root's Kerberos cache \___ _krb.5000 keyring - User 5000's Kerberos cache \___ _krb.5001 keyring - User 5001's Kerberos cache \___ tkt785 keyring - A ccache \___ krbtgt/REDHAT.COM@REDHAT.COM big_key \___ http/REDHAT.COM@REDHAT.COM user \___ afs/REDHAT.COM@REDHAT.COM user \___ nfs/REDHAT.COM@REDHAT.COM user \___ krbtgt/KERNEL.ORG@KERNEL.ORG big_key \___ http/KERNEL.ORG@KERNEL.ORG big_key What goes into a particular Kerberos cache is entirely up to userspace. Kernel support is limited to giving you the Kerberos cache keyring that you want. The user asks for their Kerberos cache by: krb_cache = keyctl_get_krbcache(uid, dest_keyring); The uid is -1 or the user's own UID for the user's own cache or the uid of some other user's cache (requires CAP_SETUID). This permits rpc.gssd or whatever to mess with the cache. The cache returned is a keyring named "_krb." that the possessor can read, search, clear, invalidate, unlink from and add links to. Active LSMs get a chance to rule on whether the caller is permitted to make a link. Each uid's cache keyring is created when it first accessed and is given a timeout that is extended each time this function is called so that the keyring goes away after a while. The timeout is configurable by sysctl but defaults to three days. Each user_namespace struct gets a lazily-created keyring that serves as the register. The cache keyrings are added to it. This means that standard key search and garbage collection facilities are available. The user_namespace struct's register goes away when it does and anything left in it is then automatically gc'd. Signed-off-by: David Howells Tested-by: Simo Sorce cc: Serge E. Hallyn cc: Eric W. Biederman --- security/keys/Kconfig | 17 +++++ security/keys/Makefile | 1 + security/keys/compat.c | 3 + security/keys/internal.h | 9 +++ security/keys/keyctl.c | 3 + security/keys/persistent.c | 169 +++++++++++++++++++++++++++++++++++++++++++++ security/keys/sysctl.c | 11 +++ 7 files changed, 213 insertions(+) create mode 100644 security/keys/persistent.c (limited to 'security') diff --git a/security/keys/Kconfig b/security/keys/Kconfig index b563622..53d8748 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -20,6 +20,23 @@ config KEYS If you are unsure as to whether this is required, answer N. +config PERSISTENT_KEYRINGS + bool "Enable register of persistent per-UID keyrings" + depends on KEYS + help + This option provides a register of persistent per-UID keyrings, + primarily aimed at Kerberos key storage. The keyrings are persistent + in the sense that they stay around after all processes of that UID + have exited, not that they survive the machine being rebooted. + + A particular keyring may be accessed by either the user whose keyring + it is or by a process with administrative privileges. The active + LSMs gets to rule on which admin-level processes get to access the + cache. + + Keyrings are created and added into the register upon demand and get + removed if they expire (a default timeout is set upon creation). + config BIG_KEYS tristate "Large payload keys" depends on KEYS diff --git a/security/keys/Makefile b/security/keys/Makefile index c487c77..dfb3a7b 100644 --- a/security/keys/Makefile +++ b/security/keys/Makefile @@ -18,6 +18,7 @@ obj-y := \ obj-$(CONFIG_KEYS_COMPAT) += compat.o obj-$(CONFIG_PROC_FS) += proc.o obj-$(CONFIG_SYSCTL) += sysctl.o +obj-$(CONFIG_PERSISTENT_KEYRINGS) += persistent.o # # Key types diff --git a/security/keys/compat.c b/security/keys/compat.c index d65fa7f..bbd32c7 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -138,6 +138,9 @@ asmlinkage long compat_sys_keyctl(u32 option, case KEYCTL_INVALIDATE: return keyctl_invalidate_key(arg2); + case KEYCTL_GET_PERSISTENT: + return keyctl_get_persistent(arg2, arg3); + default: return -EOPNOTSUPP; } diff --git a/security/keys/internal.h b/security/keys/internal.h index 581c6f6..80b2aac 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -255,6 +255,15 @@ extern long keyctl_invalidate_key(key_serial_t); extern long keyctl_instantiate_key_common(key_serial_t, const struct iovec *, unsigned, size_t, key_serial_t); +#ifdef CONFIG_PERSISTENT_KEYRINGS +extern long keyctl_get_persistent(uid_t, key_serial_t); +extern unsigned persistent_keyring_expiry; +#else +static inline long keyctl_get_persistent(uid_t uid, key_serial_t destring) +{ + return -EOPNOTSUPP; +} +#endif /* * Debugging key validation diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 33cfd27b..cee72ce 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1667,6 +1667,9 @@ SYSCALL_DEFINE5(keyctl, int, option, unsigned long, arg2, unsigned long, arg3, case KEYCTL_INVALIDATE: return keyctl_invalidate_key((key_serial_t) arg2); + case KEYCTL_GET_PERSISTENT: + return keyctl_get_persistent((uid_t)arg2, (key_serial_t)arg3); + default: return -EOPNOTSUPP; } diff --git a/security/keys/persistent.c b/security/keys/persistent.c new file mode 100644 index 0000000..82f4957a --- /dev/null +++ b/security/keys/persistent.c @@ -0,0 +1,169 @@ +/* General persistent per-UID keyrings register + * + * Copyright (C) 2013 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include +#include "internal.h" + +unsigned persistent_keyring_expiry = 3 * 24 * 3600; /* Expire after 3 days of non-use */ + +/* + * Create the persistent keyring register for the current user namespace. + * + * Called with the namespace's sem locked for writing. + */ +static int key_create_persistent_register(struct user_namespace *ns) +{ + struct key *reg = keyring_alloc(".persistent_register", + KUIDT_INIT(0), KGIDT_INIT(0), + current_cred(), + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA, NULL); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + ns->persistent_keyring_register = reg; + return 0; +} + +/* + * Create the persistent keyring for the specified user. + * + * Called with the namespace's sem locked for writing. + */ +static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, + struct keyring_index_key *index_key) +{ + struct key *persistent; + key_ref_t reg_ref, persistent_ref; + + if (!ns->persistent_keyring_register) { + long err = key_create_persistent_register(ns); + if (err < 0) + return ERR_PTR(err); + } else { + reg_ref = make_key_ref(ns->persistent_keyring_register, true); + persistent_ref = find_key_to_update(reg_ref, index_key); + if (persistent_ref) + return persistent_ref; + } + + persistent = keyring_alloc(index_key->description, + uid, INVALID_GID, current_cred(), + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA, + ns->persistent_keyring_register); + if (IS_ERR(persistent)) + return ERR_CAST(persistent); + + return make_key_ref(persistent, true); +} + +/* + * Get the persistent keyring for a specific UID and link it to the nominated + * keyring. + */ +static long key_get_persistent(struct user_namespace *ns, kuid_t uid, + key_ref_t dest_ref) +{ + struct keyring_index_key index_key; + struct key *persistent; + key_ref_t reg_ref, persistent_ref; + char buf[32]; + long ret; + + /* Look in the register if it exists */ + index_key.type = &key_type_keyring; + index_key.description = buf; + index_key.desc_len = sprintf(buf, "_persistent.%u", from_kuid(ns, uid)); + + if (ns->persistent_keyring_register) { + reg_ref = make_key_ref(ns->persistent_keyring_register, true); + down_read(&ns->persistent_keyring_register_sem); + persistent_ref = find_key_to_update(reg_ref, &index_key); + up_read(&ns->persistent_keyring_register_sem); + + if (persistent_ref) + goto found; + } + + /* It wasn't in the register, so we'll need to create it. We might + * also need to create the register. + */ + down_write(&ns->persistent_keyring_register_sem); + persistent_ref = key_create_persistent(ns, uid, &index_key); + up_write(&ns->persistent_keyring_register_sem); + if (!IS_ERR(persistent_ref)) + goto found; + + return PTR_ERR(persistent_ref); + +found: + ret = key_task_permission(persistent_ref, current_cred(), KEY_LINK); + if (ret == 0) { + persistent = key_ref_to_ptr(persistent_ref); + ret = key_link(key_ref_to_ptr(dest_ref), persistent); + if (ret == 0) { + key_set_timeout(persistent, persistent_keyring_expiry); + ret = persistent->serial; + } + } + + key_ref_put(persistent_ref); + return ret; +} + +/* + * Get the persistent keyring for a specific UID and link it to the nominated + * keyring. + */ +long keyctl_get_persistent(uid_t _uid, key_serial_t destid) +{ + struct user_namespace *ns = current_user_ns(); + key_ref_t dest_ref; + kuid_t uid; + long ret; + + /* -1 indicates the current user */ + if (_uid == (uid_t)-1) { + uid = current_uid(); + } else { + uid = make_kuid(ns, _uid); + if (!uid_valid(uid)) + return -EINVAL; + + /* You can only see your own persistent cache if you're not + * sufficiently privileged. + */ + if (uid_eq(uid, current_uid()) && + uid_eq(uid, current_suid()) && + uid_eq(uid, current_euid()) && + uid_eq(uid, current_fsuid()) && + !ns_capable(ns, CAP_SETUID)) + return -EPERM; + } + + /* There must be a destination keyring */ + dest_ref = lookup_user_key(destid, KEY_LOOKUP_CREATE, KEY_WRITE); + if (IS_ERR(dest_ref)) + return PTR_ERR(dest_ref); + if (key_ref_to_ptr(dest_ref)->type != &key_type_keyring) { + ret = -ENOTDIR; + goto out_put_dest; + } + + ret = key_get_persistent(ns, uid, dest_ref); + +out_put_dest: + key_ref_put(dest_ref); + return ret; +} diff --git a/security/keys/sysctl.c b/security/keys/sysctl.c index ee32d18..8c0af08 100644 --- a/security/keys/sysctl.c +++ b/security/keys/sysctl.c @@ -61,5 +61,16 @@ ctl_table key_sysctls[] = { .extra1 = (void *) &zero, .extra2 = (void *) &max, }, +#ifdef CONFIG_PERSISTENT_KEYRINGS + { + .procname = "persistent_keyring_expiry", + .data = &persistent_keyring_expiry, + .maxlen = sizeof(unsigned), + .mode = 0644, + .proc_handler = proc_dointvec_minmax, + .extra1 = (void *) &zero, + .extra2 = (void *) &max, + }, +#endif { } }; -- cgit v1.1 From 008643b86c5f33c115c84ccdda1725cac3ad50ad Mon Sep 17 00:00:00 2001 From: David Howells Date: Fri, 30 Aug 2013 16:07:37 +0100 Subject: KEYS: Add a 'trusted' flag and a 'trusted only' flag Add KEY_FLAG_TRUSTED to indicate that a key either comes from a trusted source or had a cryptographic signature chain that led back to a trusted key the kernel already possessed. Add KEY_FLAGS_TRUSTED_ONLY to indicate that a keyring will only accept links to keys marked with KEY_FLAGS_TRUSTED. Signed-off-by: David Howells Reviewed-by: Kees Cook --- security/keys/key.c | 8 ++++++++ security/keys/keyring.c | 4 ++++ 2 files changed, 12 insertions(+) (limited to 'security') diff --git a/security/keys/key.c b/security/keys/key.c index a819b5c..d331ea9 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -300,6 +300,8 @@ struct key *key_alloc(struct key_type *type, const char *desc, if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) key->flags |= 1 << KEY_FLAG_IN_QUOTA; + if (flags & KEY_ALLOC_TRUSTED) + key->flags |= 1 << KEY_FLAG_TRUSTED; memset(&key->type_data, 0, sizeof(key->type_data)); @@ -813,6 +815,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, prep.data = payload; prep.datalen = plen; prep.quotalen = index_key.type->def_datalen; + prep.trusted = flags & KEY_ALLOC_TRUSTED; if (index_key.type->preparse) { ret = index_key.type->preparse(&prep); if (ret < 0) { @@ -827,6 +830,11 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, } index_key.desc_len = strlen(index_key.description); + key_ref = ERR_PTR(-EPERM); + if (!prep.trusted && test_bit(KEY_FLAG_TRUSTED_ONLY, &keyring->flags)) + goto error_free_prep; + flags |= prep.trusted ? KEY_ALLOC_TRUSTED : 0; + ret = __key_link_begin(keyring, &index_key, &edit); if (ret < 0) { key_ref = ERR_PTR(ret); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index f7cdea2..9b6f6e0 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -1183,6 +1183,10 @@ int key_link(struct key *keyring, struct key *key) key_check(keyring); key_check(key); + if (test_bit(KEY_FLAG_TRUSTED_ONLY, &keyring->flags) && + !test_bit(KEY_FLAG_TRUSTED, &key->flags)) + return -EPERM; + ret = __key_link_begin(keyring, &key->index_key, &edit); if (ret == 0) { kdebug("begun {%d,%d}", keyring->serial, atomic_read(&keyring->usage)); -- cgit v1.1 From c124bde28bce41f9e46e32d03d134a81e116d38c Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 4 Sep 2013 13:26:22 +0100 Subject: KEYS: initialize root uid and session keyrings early In order to create the integrity keyrings (eg. _evm, _ima), root's uid and session keyrings need to be initialized early. Signed-off-by: Mimi Zohar Signed-off-by: David Howells --- security/keys/process_keys.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'security') diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 68548ea..0cf8a13 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -857,3 +857,13 @@ void key_change_session_keyring(struct callback_head *twork) commit_creds(new); } + +/* + * Make sure that root's user and user-session keyrings exist. + */ +static int __init init_root_keyring(void) +{ + return install_user_keyrings(); +} + +late_initcall(init_root_keyring); -- cgit v1.1 From 0bbf87d852d243680ed7074110ccc1dea003b61a Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Sat, 28 Sep 2013 14:10:59 -0700 Subject: net ipv4: Convert ipv4.ip_local_port_range to be per netns v3 - Move sysctl_local_ports from a global variable into struct netns_ipv4. - Modify inet_get_local_port_range to take a struct net, and update all of the callers. - Move the initialization of sysctl_local_ports into sysctl_net_ipv4.c:ipv4_sysctl_init_net from inet_connection_sock.c v2: - Ensure indentation used tabs - Fixed ip.h so it applies cleanly to todays net-next v3: - Compile fixes of strange callers of inet_get_local_port_range. This patch now successfully passes an allmodconfig build. Removed manual inlining of inet_get_local_port_range in ipv4_local_port_range Originally-by: Samya Acked-by: Nicolas Dichtel Signed-off-by: "Eric W. Biederman" Signed-off-by: David S. Miller --- security/selinux/hooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a5091ec..568c769 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3929,7 +3929,7 @@ static int selinux_socket_bind(struct socket *sock, struct sockaddr *address, in if (snum) { int low, high; - inet_get_local_port_range(&low, &high); + inet_get_local_port_range(sock_net(sk), &low, &high); if (snum < max(PROT_SOCK, low) || snum > high) { err = sel_netport_sid(sk->sk_protocol, -- cgit v1.1 From efe4208f47f907b86f528788da711e8ab9dea44d Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 3 Oct 2013 15:42:29 -0700 Subject: ipv6: make lookups simpler and faster TCP listener refactoring, part 4 : To speed up inet lookups, we moved IPv4 addresses from inet to struct sock_common Now is time to do the same for IPv6, because it permits us to have fast lookups for all kind of sockets, including upcoming SYN_RECV. Getting IPv6 addresses in TCP lookups currently requires two extra cache lines, plus a dereference (and memory stall). inet6_sk(sk) does the dereference of inet_sk(__sk)->pinet6 This patch is way bigger than its IPv4 counter part, because for IPv4, we could add aliases (inet_daddr, inet_rcv_saddr), while on IPv6, it's not doable easily. inet6_sk(sk)->daddr becomes sk->sk_v6_daddr inet6_sk(sk)->rcv_saddr becomes sk->sk_v6_rcv_saddr And timewait socket also have tw->tw_v6_daddr & tw->tw_v6_rcv_saddr at the same offset. We get rid of INET6_TW_MATCH() as INET6_MATCH() is now the generic macro. Signed-off-by: Eric Dumazet Signed-off-by: David S. Miller --- security/lsm_audit.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 8d8d97d..80554fc 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -304,12 +304,11 @@ static void dump_common_audit_data(struct audit_buffer *ab, } case AF_INET6: { struct inet_sock *inet = inet_sk(sk); - struct ipv6_pinfo *inet6 = inet6_sk(sk); - print_ipv6_addr(ab, &inet6->rcv_saddr, + print_ipv6_addr(ab, &sk->sk_v6_rcv_saddr, inet->inet_sport, "laddr", "lport"); - print_ipv6_addr(ab, &inet6->daddr, + print_ipv6_addr(ab, &sk->sk_v6_daddr, inet->inet_dport, "faddr", "fport"); break; -- cgit v1.1 From c2bb06db59eaf92eb5ca9c6faed590597c6ceccb Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 9 Oct 2013 03:05:48 -0700 Subject: net: fix build errors if ipv6 is disabled CONFIG_IPV6=n is still a valid choice ;) It appears we can remove dead code. Reported-by: Wu Fengguang Signed-off-by: Eric Dumazet Signed-off-by: David S. Miller --- security/lsm_audit.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'security') diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 80554fc..234bc2a 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -302,6 +302,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, "faddr", "fport"); break; } +#if IS_ENABLED(CONFIG_IPV6) case AF_INET6: { struct inet_sock *inet = inet_sk(sk); @@ -313,6 +314,7 @@ static void dump_common_audit_data(struct audit_buffer *ab, "faddr", "fport"); break; } +#endif case AF_UNIX: u = unix_sk(sk); if (u->path.dentry) { -- cgit v1.1 From 795aa6ef6a1aba99050735eadd0c2341b789b53b Mon Sep 17 00:00:00 2001 From: Patrick McHardy Date: Thu, 10 Oct 2013 09:21:55 +0200 Subject: netfilter: pass hook ops to hookfn Pass the hook ops to the hookfn to allow for generic hook functions. This change is required by nf_tables. Signed-off-by: Patrick McHardy Signed-off-by: Pablo Neira Ayuso --- security/selinux/hooks.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 568c769..3f224d7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -4668,7 +4668,7 @@ static unsigned int selinux_ip_forward(struct sk_buff *skb, int ifindex, return NF_ACCEPT; } -static unsigned int selinux_ipv4_forward(unsigned int hooknum, +static unsigned int selinux_ipv4_forward(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, @@ -4678,7 +4678,7 @@ static unsigned int selinux_ipv4_forward(unsigned int hooknum, } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static unsigned int selinux_ipv6_forward(unsigned int hooknum, +static unsigned int selinux_ipv6_forward(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, @@ -4710,7 +4710,7 @@ static unsigned int selinux_ip_output(struct sk_buff *skb, return NF_ACCEPT; } -static unsigned int selinux_ipv4_output(unsigned int hooknum, +static unsigned int selinux_ipv4_output(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, @@ -4837,7 +4837,7 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, return NF_ACCEPT; } -static unsigned int selinux_ipv4_postroute(unsigned int hooknum, +static unsigned int selinux_ipv4_postroute(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, @@ -4847,7 +4847,7 @@ static unsigned int selinux_ipv4_postroute(unsigned int hooknum, } #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) -static unsigned int selinux_ipv6_postroute(unsigned int hooknum, +static unsigned int selinux_ipv6_postroute(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, -- cgit v1.1 From c0ab6e56dcb7ca9903d460247cb464e769ae6e77 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Fri, 11 Oct 2013 18:06:39 -0700 Subject: Smack: Implement lock security mode Linux file locking does not follow the same rules as other mechanisms. Even though it is a write operation a process can set a read lock on files which it has open only for read access. Two programs with read access to a file can use read locks to communicate. This is not acceptable in a Mandatory Access Control environment. Smack treats setting a read lock as the write operation that it is. Unfortunately, many programs assume that setting a read lock is a read operation. These programs are unhappy in the Smack environment. This patch introduces a new access mode (lock) to address this problem. A process with lock access to a file can set a read lock. A process with write access to a file can set a read lock or a write lock. This prevents a situation where processes are granted write access just so they can set read locks. Targeted for git://git.gitorious.org/smack-next/kernel.git Signed-off-by: Casey Schaufler --- security/smack/smack.h | 12 ++++++++---- security/smack/smack_access.c | 10 ++++++++++ security/smack/smack_lsm.c | 9 +++++++-- security/smack/smackfs.c | 10 ++++++++-- 4 files changed, 33 insertions(+), 8 deletions(-) (limited to 'security') diff --git a/security/smack/smack.h b/security/smack/smack.h index 076b8e8..364cc64 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -177,9 +177,13 @@ struct smk_port_label { #define SMACK_CIPSO_MAXCATNUM 184 /* 23 * 8 */ /* - * Flag for transmute access + * Flags for untraditional access modes. + * It shouldn't be necessary to avoid conflicts with definitions + * in fs.h, but do so anyway. */ -#define MAY_TRANSMUTE 64 +#define MAY_TRANSMUTE 0x00001000 /* Controls directory labeling */ +#define MAY_LOCK 0x00002000 /* Locks should be writes, but ... */ + /* * Just to make the common cases easier to deal with */ @@ -188,9 +192,9 @@ struct smk_port_label { #define MAY_NOT 0 /* - * Number of access types used by Smack (rwxat) + * Number of access types used by Smack (rwxatl) */ -#define SMK_NUM_ACCESS_TYPE 5 +#define SMK_NUM_ACCESS_TYPE 6 /* SMACK data */ struct smack_audit_data { diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index b3b59b1..14293cd 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -84,6 +84,8 @@ int log_policy = SMACK_AUDIT_DENIED; * * Do the object check first because that is more * likely to differ. + * + * Allowing write access implies allowing locking. */ int smk_access_entry(char *subject_label, char *object_label, struct list_head *rule_list) @@ -99,6 +101,11 @@ int smk_access_entry(char *subject_label, char *object_label, } } + /* + * MAY_WRITE implies MAY_LOCK. + */ + if ((may & MAY_WRITE) == MAY_WRITE) + may |= MAY_LOCK; return may; } @@ -245,6 +252,7 @@ out_audit: static inline void smack_str_from_perm(char *string, int access) { int i = 0; + if (access & MAY_READ) string[i++] = 'r'; if (access & MAY_WRITE) @@ -255,6 +263,8 @@ static inline void smack_str_from_perm(char *string, int access) string[i++] = 'a'; if (access & MAY_TRANSMUTE) string[i++] = 't'; + if (access & MAY_LOCK) + string[i++] = 'l'; string[i] = '\0'; } /** diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8825375..88d366e5 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1146,7 +1146,7 @@ static int smack_file_ioctl(struct file *file, unsigned int cmd, * @file: the object * @cmd: unused * - * Returns 0 if current has write access, error code otherwise + * Returns 0 if current has lock access, error code otherwise */ static int smack_file_lock(struct file *file, unsigned int cmd) { @@ -1154,7 +1154,7 @@ static int smack_file_lock(struct file *file, unsigned int cmd) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); smk_ad_setfield_u_fs_path(&ad, file->f_path); - return smk_curacc(file->f_security, MAY_WRITE, &ad); + return smk_curacc(file->f_security, MAY_LOCK, &ad); } /** @@ -1178,8 +1178,13 @@ static int smack_file_fcntl(struct file *file, unsigned int cmd, switch (cmd) { case F_GETLK: + break; case F_SETLK: case F_SETLKW: + smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); + smk_ad_setfield_u_fs_path(&ad, file->f_path); + rc = smk_curacc(file->f_security, MAY_LOCK, &ad); + break; case F_SETOWN: case F_SETSIG: smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_PATH); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 80f4b4a..160aa08e 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -139,7 +139,7 @@ const char *smack_cipso_option = SMACK_CIPSO_OPTION; * SMK_LOADLEN: Smack rule length */ #define SMK_OACCESS "rwxa" -#define SMK_ACCESS "rwxat" +#define SMK_ACCESS "rwxatl" #define SMK_OACCESSLEN (sizeof(SMK_OACCESS) - 1) #define SMK_ACCESSLEN (sizeof(SMK_ACCESS) - 1) #define SMK_OLOADLEN (SMK_LABELLEN + SMK_LABELLEN + SMK_OACCESSLEN) @@ -282,6 +282,10 @@ static int smk_perm_from_str(const char *string) case 'T': perm |= MAY_TRANSMUTE; break; + case 'l': + case 'L': + perm |= MAY_LOCK; + break; default: return perm; } @@ -452,7 +456,7 @@ static ssize_t smk_write_rules_list(struct file *file, const char __user *buf, /* * Minor hack for backward compatibility */ - if (count != SMK_OLOADLEN && count != SMK_LOADLEN) + if (count < SMK_OLOADLEN || count > SMK_LOADLEN) return -EINVAL; } else { if (count >= PAGE_SIZE) { @@ -592,6 +596,8 @@ static void smk_rule_show(struct seq_file *s, struct smack_rule *srp, int max) seq_putc(s, 'a'); if (srp->smk_access & MAY_TRANSMUTE) seq_putc(s, 't'); + if (srp->smk_access & MAY_LOCK) + seq_putc(s, 'l'); seq_putc(s, '\n'); } -- cgit v1.1 From 73ba353471e0b692f398f3d63018b7f46ccf1d3e Mon Sep 17 00:00:00 2001 From: Serge Hallyn Date: Wed, 23 Oct 2013 01:34:00 +0200 Subject: device_cgroup: remove can_attach It is really only wanting to duplicate a check which is already done by the cgroup subsystem. With this patch, user jdoe still cannot move pid 1 into a devices cgroup he owns, but now he can move his own other tasks into devices cgroups. Signed-off-by: Serge Hallyn Signed-off-by: Tejun Heo Cc: Aristeu Rozanski --- security/device_cgroup.c | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index c123628..7c2a0a7 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -63,16 +63,6 @@ static inline struct dev_cgroup *task_devcgroup(struct task_struct *task) struct cgroup_subsys devices_subsys; -static int devcgroup_can_attach(struct cgroup_subsys_state *new_css, - struct cgroup_taskset *set) -{ - struct task_struct *task = cgroup_taskset_first(set); - - if (current != task && !capable(CAP_SYS_ADMIN)) - return -EPERM; - return 0; -} - /* * called under devcgroup_mutex */ @@ -697,7 +687,6 @@ static struct cftype dev_cgroup_files[] = { struct cgroup_subsys devices_subsys = { .name = "devices", - .can_attach = devcgroup_can_attach, .css_alloc = devcgroup_css_alloc, .css_free = devcgroup_css_free, .css_online = devcgroup_online, -- cgit v1.1 From 089bc8e95ae07b1ada14069935c30fd88204c21c Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 10 Oct 2013 15:56:13 +0900 Subject: ima: fix script messages Fix checkpatch, lindent, etc, warnings/errors Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/digsig.c | 2 +- security/integrity/evm/evm_posix_acl.c | 3 ++- security/integrity/ima/ima_appraise.c | 6 +++--- security/integrity/ima/ima_fs.c | 3 +-- security/integrity/ima/ima_main.c | 10 +++++----- security/integrity/integrity.h | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) (limited to 'security') diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 0b759e1..198e609 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -28,7 +28,7 @@ static const char *keyring_name[INTEGRITY_KEYRING_MAX] = { }; int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, - const char *digest, int digestlen) + const char *digest, int digestlen) { if (id >= INTEGRITY_KEYRING_MAX) return -EINVAL; diff --git a/security/integrity/evm/evm_posix_acl.c b/security/integrity/evm/evm_posix_acl.c index b1753e9..46408b9 100644 --- a/security/integrity/evm/evm_posix_acl.c +++ b/security/integrity/evm/evm_posix_acl.c @@ -11,8 +11,9 @@ #include #include +#include -int posix_xattr_acl(char *xattr) +int posix_xattr_acl(const char *xattr) { int xattr_len = strlen(xattr); diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 2d4beca..e3230d6 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -55,7 +55,7 @@ static int ima_fix_xattr(struct dentry *dentry, enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, int func) { - switch(func) { + switch (func) { case MMAP_CHECK: return iint->ima_mmap_status; case BPRM_CHECK: @@ -71,7 +71,7 @@ enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, static void ima_set_cache_status(struct integrity_iint_cache *iint, int func, enum integrity_status status) { - switch(func) { + switch (func) { case MMAP_CHECK: iint->ima_mmap_status = status; break; @@ -90,7 +90,7 @@ static void ima_set_cache_status(struct integrity_iint_cache *iint, static void ima_cache_flags(struct integrity_iint_cache *iint, int func) { - switch(func) { + switch (func) { case MMAP_CHECK: iint->flags |= (IMA_MMAP_APPRAISED | IMA_APPRAISED); break; diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 38477c9..5f0fd11 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -88,8 +88,7 @@ static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) * against concurrent list-extension */ rcu_read_lock(); - qe = list_entry_rcu(qe->later.next, - struct ima_queue_entry, later); + qe = list_entry_rcu(qe->later.next, struct ima_queue_entry, later); rcu_read_unlock(); (*pos)++; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index e9508d5..0f359df 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -244,9 +244,9 @@ int ima_file_mmap(struct file *file, unsigned long prot) int ima_bprm_check(struct linux_binprm *bprm) { return process_measurement(bprm->file, - (strcmp(bprm->filename, bprm->interp) == 0) ? - bprm->filename : bprm->interp, - MAY_EXEC, BPRM_CHECK); + (strcmp(bprm->filename, bprm->interp) == 0) ? + bprm->filename : bprm->interp, + MAY_EXEC, BPRM_CHECK); } /** @@ -263,8 +263,8 @@ int ima_file_check(struct file *file, int mask) { ima_rdwr_violation_check(file); return process_measurement(file, NULL, - mask & (MAY_READ | MAY_WRITE | MAY_EXEC), - FILE_CHECK); + mask & (MAY_READ | MAY_WRITE | MAY_EXEC), + FILE_CHECK); } EXPORT_SYMBOL_GPL(ima_file_check); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index c42fb7a..f867316 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -89,7 +89,7 @@ struct integrity_iint_cache *integrity_iint_find(struct inode *inode); #ifdef CONFIG_INTEGRITY_SIGNATURE int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, - const char *digest, int digestlen); + const char *digest, int digestlen); #else -- cgit v1.1 From 08de59eb144d7c41351a467442f898d720f0f15f Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 17 Oct 2013 07:34:02 -0400 Subject: Revert "ima: policy for RAMFS" This reverts commit 4c2c392763a682354fac65b6a569adec4e4b5387. Everything in the initramfs should be measured and appraised, but until the initramfs has extended attribute support, at least measured. Signed-off-by: Mimi Zohar Cc: Stable Kernel --- security/integrity/ima/ima_policy.c | 1 - 1 file changed, 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 399433a..a9c3d3c 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -73,7 +73,6 @@ static struct ima_rule_entry default_rules[] = { {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = DEVPTS_SUPER_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = BINFMTFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, -- cgit v1.1 From c7c8bb237fdbff932b5e431aebee5ce862ea07d1 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 25 Apr 2013 10:43:56 +0300 Subject: ima: provide support for arbitrary hash algorithms In preparation of supporting more hash algorithms with larger hash sizes needed for signature verification, this patch replaces the 20 byte sized digest, with a more flexible structure. The new structure includes the hash algorithm, digest size, and digest. Changelog: - recalculate filedata hash for the measurement list, if the signature hash digest size is greater than 20 bytes. - use generic HASH_ALGO_ - make ima_calc_file_hash static - scripts lindent and checkpatch fixes Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/Kconfig | 1 + security/integrity/ima/ima.h | 7 ++--- security/integrity/ima/ima_api.c | 32 +++++++++++++++++------ security/integrity/ima/ima_appraise.c | 20 ++++++++------ security/integrity/ima/ima_crypto.c | 49 +++++++++++++++++++++++++++++------ security/integrity/ima/ima_main.c | 6 +++-- security/integrity/integrity.h | 15 ++++++++--- 7 files changed, 98 insertions(+), 32 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 39196ab..e6628e7 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -9,6 +9,7 @@ config IMA select CRYPTO_HMAC select CRYPTO_MD5 select CRYPTO_SHA1 + select CRYPTO_HASH_INFO select TCG_TPM if HAS_IOMEM && !UML select TCG_TIS if TCG_TPM && X86 select TCG_IBMVTPM if TCG_TPM && PPC64 diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index b3dd616..eb86032 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -39,7 +39,7 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; /* set during initialization */ extern int ima_initialized; extern int ima_used_chip; -extern char *ima_hash; +extern int ima_hash_algo; extern int ima_appraise; /* IMA inode template definition */ @@ -70,8 +70,9 @@ void ima_fs_cleanup(void); int ima_inode_alloc(struct inode *inode); int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode); -int ima_calc_file_hash(struct file *file, char *digest); -int ima_calc_buffer_hash(const void *data, int len, char *digest); +int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); +int ima_calc_buffer_hash(const void *data, int len, + struct ima_digest_data *hash); int ima_calc_boot_aggregate(char *digest); void ima_add_violation(struct inode *inode, const unsigned char *filename, const char *op, const char *cause); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 1c03e8f1..e531fe2 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -44,6 +44,7 @@ int ima_store_template(struct ima_template_entry *entry, const char *op = "add_template_measure"; const char *audit_cause = "hashing_error"; int result; + struct ima_digest_data hash; memset(entry->digest, 0, sizeof(entry->digest)); entry->template_name = IMA_TEMPLATE_NAME; @@ -51,14 +52,14 @@ int ima_store_template(struct ima_template_entry *entry, if (!violation) { result = ima_calc_buffer_hash(&entry->template, - entry->template_len, - entry->digest); + entry->template_len, &hash); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, entry->template_name, op, audit_cause, result, 0); return result; } + memcpy(entry->digest, hash.digest, hash.length); } result = ima_add_template_entry(entry, violation, op, inode); return result; @@ -147,8 +148,9 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, if (!(iint->flags & IMA_COLLECTED)) { u64 i_version = file_inode(file)->i_version; - iint->ima_xattr.type = IMA_XATTR_DIGEST; - result = ima_calc_file_hash(file, iint->ima_xattr.digest); + /* use default hash algorithm */ + iint->ima_hash.algo = ima_hash_algo; + result = ima_calc_file_hash(file, &iint->ima_hash); if (!result) { iint->version = i_version; iint->flags |= IMA_COLLECTED; @@ -196,7 +198,21 @@ void ima_store_measurement(struct integrity_iint_cache *iint, return; } memset(&entry->template, 0, sizeof(entry->template)); - memcpy(entry->template.digest, iint->ima_xattr.digest, IMA_DIGEST_SIZE); + if (iint->ima_hash.algo != ima_hash_algo) { + struct ima_digest_data hash; + + hash.algo = ima_hash_algo; + result = ima_calc_file_hash(file, &hash); + if (result) + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", "failed", + result, 0); + else + memcpy(entry->template.digest, hash.digest, + hash.length); + } else + memcpy(entry->template.digest, iint->ima_hash.digest, + iint->ima_hash.length); strcpy(entry->template.file_name, (strlen(filename) > IMA_EVENT_NAME_LEN_MAX) ? file->f_dentry->d_name.name : filename); @@ -212,14 +228,14 @@ void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename) { struct audit_buffer *ab; - char hash[(IMA_DIGEST_SIZE * 2) + 1]; + char hash[(iint->ima_hash.length * 2) + 1]; int i; if (iint->flags & IMA_AUDITED) return; - for (i = 0; i < IMA_DIGEST_SIZE; i++) - hex_byte_pack(hash + (i * 2), iint->ima_xattr.digest[i]); + for (i = 0; i < iint->ima_hash.length; i++) + hex_byte_pack(hash + (i * 2), iint->ima_hash.digest[i]); hash[i * 2] = '\0'; ab = audit_log_start(current->audit_context, GFP_KERNEL, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index e3230d6..3833b0fa 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -43,12 +43,12 @@ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) } static int ima_fix_xattr(struct dentry *dentry, - struct integrity_iint_cache *iint) + struct integrity_iint_cache *iint) { - iint->ima_xattr.type = IMA_XATTR_DIGEST; + iint->ima_hash.type = IMA_XATTR_DIGEST; return __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, - (u8 *)&iint->ima_xattr, - sizeof(iint->ima_xattr), 0); + &iint->ima_hash.type, + 1 + iint->ima_hash.length, 0); } /* Return specific func appraised cached result */ @@ -159,8 +159,12 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, status = INTEGRITY_FAIL; break; } - rc = memcmp(xattr_value->digest, iint->ima_xattr.digest, - IMA_DIGEST_SIZE); + if (rc - 1 == iint->ima_hash.length) + rc = memcmp(xattr_value->digest, + iint->ima_hash.digest, + iint->ima_hash.length); + else + rc = -EINVAL; if (rc) { cause = "invalid-hash"; status = INTEGRITY_FAIL; @@ -172,8 +176,8 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, iint->flags |= IMA_DIGSIG; rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, xattr_value->digest, rc - 1, - iint->ima_xattr.digest, - IMA_DIGEST_SIZE); + iint->ima_hash.digest, + iint->ima_hash.length); if (rc == -EOPNOTSUPP) { status = INTEGRITY_UNKNOWN; } else if (rc) { diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index a02e079..2fd1786 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "ima.h" static struct crypto_shash *ima_shash_tfm; @@ -28,10 +29,11 @@ int ima_init_crypto(void) { long rc; - ima_shash_tfm = crypto_alloc_shash(ima_hash, 0, 0); + ima_shash_tfm = crypto_alloc_shash(hash_algo_name[ima_hash_algo], 0, 0); if (IS_ERR(ima_shash_tfm)) { rc = PTR_ERR(ima_shash_tfm); - pr_err("Can not allocate %s (reason: %ld)\n", ima_hash, rc); + pr_err("Can not allocate %s (reason: %ld)\n", + hash_algo_name[ima_hash_algo], rc); return rc; } return 0; @@ -40,17 +42,19 @@ int ima_init_crypto(void) /* * Calculate the MD5/SHA1 file digest */ -int ima_calc_file_hash(struct file *file, char *digest) +static int ima_calc_file_hash_tfm(struct file *file, + struct ima_digest_data *hash, + struct crypto_shash *tfm) { loff_t i_size, offset = 0; char *rbuf; int rc, read = 0; struct { struct shash_desc shash; - char ctx[crypto_shash_descsize(ima_shash_tfm)]; + char ctx[crypto_shash_descsize(tfm)]; } desc; - desc.shash.tfm = ima_shash_tfm; + desc.shash.tfm = tfm; desc.shash.flags = 0; rc = crypto_shash_init(&desc.shash); @@ -85,17 +89,42 @@ int ima_calc_file_hash(struct file *file, char *digest) } kfree(rbuf); if (!rc) - rc = crypto_shash_final(&desc.shash, digest); + rc = crypto_shash_final(&desc.shash, hash->digest); if (read) file->f_mode &= ~FMODE_READ; out: return rc; } +int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) +{ + struct crypto_shash *tfm = ima_shash_tfm; + int rc; + + if (hash->algo != ima_hash_algo && hash->algo < HASH_ALGO__LAST) { + tfm = crypto_alloc_shash(hash_algo_name[hash->algo], 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[hash->algo], rc); + return rc; + } + } + + hash->length = crypto_shash_digestsize(tfm); + + rc = ima_calc_file_hash_tfm(file, hash, tfm); + + if (tfm != ima_shash_tfm) + crypto_free_shash(tfm); + + return rc; +} + /* * Calculate the hash of a given buffer */ -int ima_calc_buffer_hash(const void *data, int len, char *digest) +int ima_calc_buffer_hash(const void *buf, int len, struct ima_digest_data *hash) { struct { struct shash_desc shash; @@ -105,7 +134,11 @@ int ima_calc_buffer_hash(const void *data, int len, char *digest) desc.shash.tfm = ima_shash_tfm; desc.shash.flags = 0; - return crypto_shash_digest(&desc.shash, data, len, digest); + /* this function uses default algo */ + hash->algo = ima_hash_algo; + hash->length = crypto_shash_digestsize(ima_shash_tfm); + + return crypto_shash_digest(&desc.shash, buf, len, hash->digest); } static void __init ima_pcrread(int idx, u8 *pcr) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 0f359df..7708c21 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -24,6 +24,7 @@ #include #include #include +#include #include "ima.h" @@ -35,11 +36,12 @@ int ima_appraise = IMA_APPRAISE_ENFORCE; int ima_appraise; #endif -char *ima_hash = "sha1"; +int ima_hash_algo = HASH_ALGO_SHA1; + static int __init hash_setup(char *str) { if (strncmp(str, "md5", 3) == 0) - ima_hash = "md5"; + ima_hash_algo = HASH_ALGO_MD5; return 1; } __setup("ima_hash=", hash_setup); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index f867316..0b02ea8 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -59,20 +59,29 @@ enum evm_ima_xattr_type { struct evm_ima_xattr_data { u8 type; u8 digest[SHA1_DIGEST_SIZE]; -} __attribute__((packed)); +} __packed; + +#define IMA_MAX_DIGEST_SIZE 64 + +struct ima_digest_data { + u8 algo; + u8 length; + u8 type; + u8 digest[IMA_MAX_DIGEST_SIZE]; +} __packed; /* integrity data associated with an inode */ struct integrity_iint_cache { - struct rb_node rb_node; /* rooted in integrity_iint_tree */ + struct rb_node rb_node; /* rooted in integrity_iint_tree */ struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ unsigned long flags; - struct evm_ima_xattr_data ima_xattr; enum integrity_status ima_file_status:4; enum integrity_status ima_mmap_status:4; enum integrity_status ima_bprm_status:4; enum integrity_status ima_module_status:4; enum integrity_status evm_status:4; + struct ima_digest_data ima_hash; }; /* rbtree tree calls to lookup, insert, delete -- cgit v1.1 From d3634d0f426bdeb433cb288bdbb0a5e16cf3dbbf Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 25 Apr 2013 10:44:04 +0300 Subject: ima: read and use signature hash algorithm All files on the filesystem, currently, are hashed using the same hash algorithm. In preparation for files from different packages being signed using different hash algorithms, this patch adds support for reading the signature hash algorithm from the 'security.ima' extended attribute and calculates the appropriate file data hash based on it. Changelog: - fix scripts Lindent and checkpatch msgs - Mimi - fix md5 support for older version, which occupied 20 bytes in the xattr, not the expected 16 bytes. Fix the comparison to compare only the first 16 bytes. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/digsig_asymmetric.c | 11 --------- security/integrity/ima/ima.h | 29 +++++++++++++++++++--- security/integrity/ima/ima_api.c | 12 ++++++++- security/integrity/ima/ima_appraise.c | 45 ++++++++++++++++++++++++++++------ security/integrity/ima/ima_main.c | 11 +++++++-- security/integrity/integrity.h | 11 +++++++++ 6 files changed, 94 insertions(+), 25 deletions(-) (limited to 'security') diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index b475466..9eae480 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -20,17 +20,6 @@ #include "integrity.h" /* - * signature format v2 - for using with asymmetric keys - */ -struct signature_v2_hdr { - uint8_t version; /* signature format version */ - uint8_t hash_algo; /* Digest algorithm [enum pkey_hash_algo] */ - uint32_t keyid; /* IMA key identifier - not X509/PGP specific*/ - uint16_t sig_size; /* signature size */ - uint8_t sig[0]; /* signature payload */ -} __packed; - -/* * Request an asymmetric key. */ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid) diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index eb86032..efcdef2 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -99,7 +99,9 @@ static inline unsigned long ima_hash_key(u8 *digest) int ima_get_action(struct inode *inode, int mask, int function); int ima_must_measure(struct inode *inode, int mask, int function); int ima_collect_measurement(struct integrity_iint_cache *iint, - struct file *file); + struct file *file, + struct evm_ima_xattr_data **xattr_value, + int *xattr_len); void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename); void ima_audit_measurement(struct integrity_iint_cache *iint, @@ -132,17 +134,25 @@ void ima_delete_rules(void); #ifdef CONFIG_IMA_APPRAISE int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, - struct file *file, const unsigned char *filename); + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len); int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, int func); +void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_digest_data *hash); +int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value); #else static inline int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, struct file *file, - const unsigned char *filename) + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) { return INTEGRITY_UNKNOWN; } @@ -163,6 +173,19 @@ static inline enum integrity_status ima_get_cache_status(struct integrity_iint_c { return INTEGRITY_UNKNOWN; } + +static inline void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, + int xattr_len, + struct ima_digest_data *hash) +{ +} + +static inline int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value) +{ + return 0; +} + #endif /* LSM based policy rules require audit */ diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index e531fe2..1dba98e 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -139,17 +139,27 @@ int ima_must_measure(struct inode *inode, int mask, int function) * Return 0 on success, error code otherwise */ int ima_collect_measurement(struct integrity_iint_cache *iint, - struct file *file) + struct file *file, + struct evm_ima_xattr_data **xattr_value, + int *xattr_len) { struct inode *inode = file_inode(file); const char *filename = file->f_dentry->d_name.name; int result = 0; + if (xattr_value) + *xattr_len = ima_read_xattr(file->f_dentry, xattr_value); + if (!(iint->flags & IMA_COLLECTED)) { u64 i_version = file_inode(file)->i_version; /* use default hash algorithm */ iint->ima_hash.algo = ima_hash_algo; + + if (xattr_value) + ima_get_hash_algo(*xattr_value, *xattr_len, + &iint->ima_hash); + result = ima_calc_file_hash(file, &iint->ima_hash); if (!result) { iint->version = i_version; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 3833b0fa..00708a3 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -107,6 +107,34 @@ static void ima_cache_flags(struct integrity_iint_cache *iint, int func) } } +void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_digest_data *hash) +{ + struct signature_v2_hdr *sig; + + if (!xattr_value || xattr_len < 0 || xattr_len <= 1 + sizeof(*sig)) + return; + + sig = (typeof(sig)) xattr_value->digest; + + if (xattr_value->type != EVM_IMA_XATTR_DIGSIG || sig->version != 2) + return; + + hash->algo = sig->hash_algo; +} + +int ima_read_xattr(struct dentry *dentry, + struct evm_ima_xattr_data **xattr_value) +{ + struct inode *inode = dentry->d_inode; + + if (!inode->i_op->getxattr) + return 0; + + return vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)xattr_value, + 0, GFP_NOFS); +} + /* * ima_appraise_measurement - appraise file measurement * @@ -116,23 +144,22 @@ static void ima_cache_flags(struct integrity_iint_cache *iint, int func) * Return 0 on success, error code otherwise */ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, - struct file *file, const unsigned char *filename) + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) { struct dentry *dentry = file->f_dentry; struct inode *inode = dentry->d_inode; - struct evm_ima_xattr_data *xattr_value = NULL; enum integrity_status status = INTEGRITY_UNKNOWN; const char *op = "appraise_data"; char *cause = "unknown"; - int rc; + int rc = xattr_len; if (!ima_appraise) return 0; if (!inode->i_op->getxattr) return INTEGRITY_UNKNOWN; - rc = vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)&xattr_value, - 0, GFP_NOFS); if (rc <= 0) { if (rc && rc != -ENODATA) goto out; @@ -159,7 +186,10 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, status = INTEGRITY_FAIL; break; } - if (rc - 1 == iint->ima_hash.length) + if (xattr_len - 1 >= iint->ima_hash.length) + /* xattr length may be longer. md5 hash in previous + version occupied 20 bytes in xattr, instead of 16 + */ rc = memcmp(xattr_value->digest, iint->ima_hash.digest, iint->ima_hash.length); @@ -207,7 +237,6 @@ out: ima_cache_flags(iint, func); } ima_set_cache_status(iint, func, status); - kfree(xattr_value); return status; } @@ -223,7 +252,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) if (iint->flags & IMA_DIGSIG) return; - rc = ima_collect_measurement(iint, file); + rc = ima_collect_measurement(iint, file, NULL, NULL); if (rc < 0) return; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 7708c21..95b5df2 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -149,6 +149,8 @@ static int process_measurement(struct file *file, const char *filename, char *pathbuf = NULL; const char *pathname = NULL; int rc = -ENOMEM, action, must_appraise, _func; + struct evm_ima_xattr_data *xattr_value = NULL, **xattr_ptr = NULL; + int xattr_len = 0; if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; @@ -187,7 +189,10 @@ static int process_measurement(struct file *file, const char *filename, goto out_digsig; } - rc = ima_collect_measurement(iint, file); + if (action & IMA_APPRAISE_SUBMASK) + xattr_ptr = &xattr_value; + + rc = ima_collect_measurement(iint, file, xattr_ptr, &xattr_len); if (rc != 0) goto out_digsig; @@ -198,7 +203,8 @@ static int process_measurement(struct file *file, const char *filename, if (action & IMA_MEASURE) ima_store_measurement(iint, file, pathname); if (action & IMA_APPRAISE_SUBMASK) - rc = ima_appraise_measurement(_func, iint, file, pathname); + rc = ima_appraise_measurement(_func, iint, file, pathname, + xattr_value, xattr_len); if (action & IMA_AUDIT) ima_audit_measurement(iint, pathname); kfree(pathbuf); @@ -207,6 +213,7 @@ out_digsig: rc = -EACCES; out: mutex_unlock(&inode->i_mutex); + kfree(xattr_value); if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE)) return -EACCES; return 0; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 0b02ea8..ea23189 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -70,6 +70,17 @@ struct ima_digest_data { u8 digest[IMA_MAX_DIGEST_SIZE]; } __packed; +/* + * signature format v2 - for using with asymmetric keys + */ +struct signature_v2_hdr { + uint8_t version; /* signature format version */ + uint8_t hash_algo; /* Digest algorithm [enum pkey_hash_algo] */ + uint32_t keyid; /* IMA key identifier - not X509/PGP specific */ + uint16_t sig_size; /* signature size */ + uint8_t sig[0]; /* signature payload */ +} __packed; + /* integrity data associated with an inode */ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ -- cgit v1.1 From b1aaab22e263d0cca1effe319b7d2bf895444219 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 10 Oct 2013 16:12:03 +0900 Subject: ima: pass full xattr with the signature For possibility to use xattr type for new signature formats, pass full xattr to the signature verification function. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/digsig.c | 5 +++-- security/integrity/evm/evm_main.c | 4 ++-- security/integrity/ima/ima_appraise.c | 2 +- security/integrity/integrity.h | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 198e609..b4af4eb 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -44,9 +44,10 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, } } - switch (sig[0]) { + switch (sig[1]) { case 1: - return digsig_verify(keyring[id], sig, siglen, + /* v1 API expect signature without xattr type */ + return digsig_verify(keyring[id], sig + 1, siglen - 1, digest, digestlen); case 2: return asymmetric_verify(keyring[id], sig, siglen, diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index af9b685..336b3dd 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -123,7 +123,7 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, goto out; } - xattr_len = rc - 1; + xattr_len = rc; /* check value type */ switch (xattr_data->type) { @@ -143,7 +143,7 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, if (rc) break; rc = integrity_digsig_verify(INTEGRITY_KEYRING_EVM, - xattr_data->digest, xattr_len, + (const char *)xattr_data, xattr_len, calc.digest, sizeof(calc.digest)); if (!rc) { /* we probably want to replace rsa with hmac here */ diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 00708a3..e1865a6 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -205,7 +205,7 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, case EVM_IMA_XATTR_DIGSIG: iint->flags |= IMA_DIGSIG; rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, - xattr_value->digest, rc - 1, + (const char *)xattr_value, rc, iint->ima_hash.digest, iint->ima_hash.length); if (rc == -EOPNOTSUPP) { diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index ea23189..aead6b2 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -74,6 +74,7 @@ struct ima_digest_data { * signature format v2 - for using with asymmetric keys */ struct signature_v2_hdr { + uint8_t type; /* xattr type */ uint8_t version; /* signature format version */ uint8_t hash_algo; /* Digest algorithm [enum pkey_hash_algo] */ uint32_t keyid; /* IMA key identifier - not X509/PGP specific */ -- cgit v1.1 From a35c3fb6490cc1d3446e4781693408100113c4fb Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 25 Apr 2013 10:44:04 +0300 Subject: ima: use dynamically allocated hash storage For each inode in the IMA policy, an iint is allocated. To support larger hash digests, the iint digest size changed from 20 bytes to the maximum supported hash digest size. Instead of allocating the maximum size, which most likely is not needed, this patch dynamically allocates the needed hash storage. Changelog: - fix krealloc bug Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 2 ++ security/integrity/ima/ima_api.c | 57 +++++++++++++++++++++++------------ security/integrity/ima/ima_appraise.c | 16 +++++----- security/integrity/integrity.h | 4 +-- 4 files changed, 49 insertions(+), 30 deletions(-) (limited to 'security') diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 74522db..c49d3f1 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -70,6 +70,8 @@ struct integrity_iint_cache *integrity_iint_find(struct inode *inode) static void iint_free(struct integrity_iint_cache *iint) { + kfree(iint->ima_hash); + iint->ima_hash = NULL; iint->version = 0; iint->flags = 0UL; iint->ima_file_status = INTEGRITY_UNKNOWN; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 1dba98e..5a7942e 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -44,7 +44,10 @@ int ima_store_template(struct ima_template_entry *entry, const char *op = "add_template_measure"; const char *audit_cause = "hashing_error"; int result; - struct ima_digest_data hash; + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; memset(entry->digest, 0, sizeof(entry->digest)); entry->template_name = IMA_TEMPLATE_NAME; @@ -52,14 +55,14 @@ int ima_store_template(struct ima_template_entry *entry, if (!violation) { result = ima_calc_buffer_hash(&entry->template, - entry->template_len, &hash); + entry->template_len, &hash.hdr); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, entry->template_name, op, audit_cause, result, 0); return result; } - memcpy(entry->digest, hash.digest, hash.length); + memcpy(entry->digest, hash.hdr.digest, hash.hdr.length); } result = ima_add_template_entry(entry, violation, op, inode); return result; @@ -146,6 +149,10 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, struct inode *inode = file_inode(file); const char *filename = file->f_dentry->d_name.name; int result = 0; + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; if (xattr_value) *xattr_len = ima_read_xattr(file->f_dentry, xattr_value); @@ -154,16 +161,23 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, u64 i_version = file_inode(file)->i_version; /* use default hash algorithm */ - iint->ima_hash.algo = ima_hash_algo; + hash.hdr.algo = ima_hash_algo; if (xattr_value) - ima_get_hash_algo(*xattr_value, *xattr_len, - &iint->ima_hash); + ima_get_hash_algo(*xattr_value, *xattr_len, &hash.hdr); - result = ima_calc_file_hash(file, &iint->ima_hash); + result = ima_calc_file_hash(file, &hash.hdr); if (!result) { - iint->version = i_version; - iint->flags |= IMA_COLLECTED; + int length = sizeof(hash.hdr) + hash.hdr.length; + void *tmpbuf = krealloc(iint->ima_hash, length, + GFP_NOFS); + if (tmpbuf) { + iint->ima_hash = tmpbuf; + memcpy(iint->ima_hash, &hash, length); + iint->version = i_version; + iint->flags |= IMA_COLLECTED; + } else + result = -ENOMEM; } } if (result) @@ -208,21 +222,24 @@ void ima_store_measurement(struct integrity_iint_cache *iint, return; } memset(&entry->template, 0, sizeof(entry->template)); - if (iint->ima_hash.algo != ima_hash_algo) { - struct ima_digest_data hash; + if (iint->ima_hash->algo != ima_hash_algo) { + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; - hash.algo = ima_hash_algo; - result = ima_calc_file_hash(file, &hash); + hash.hdr.algo = ima_hash_algo; + result = ima_calc_file_hash(file, &hash.hdr); if (result) integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename, "collect_data", "failed", result, 0); else - memcpy(entry->template.digest, hash.digest, - hash.length); + memcpy(entry->template.digest, hash.hdr.digest, + hash.hdr.length); } else - memcpy(entry->template.digest, iint->ima_hash.digest, - iint->ima_hash.length); + memcpy(entry->template.digest, iint->ima_hash->digest, + iint->ima_hash->length); strcpy(entry->template.file_name, (strlen(filename) > IMA_EVENT_NAME_LEN_MAX) ? file->f_dentry->d_name.name : filename); @@ -238,14 +255,14 @@ void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename) { struct audit_buffer *ab; - char hash[(iint->ima_hash.length * 2) + 1]; + char hash[(iint->ima_hash->length * 2) + 1]; int i; if (iint->flags & IMA_AUDITED) return; - for (i = 0; i < iint->ima_hash.length; i++) - hex_byte_pack(hash + (i * 2), iint->ima_hash.digest[i]); + for (i = 0; i < iint->ima_hash->length; i++) + hex_byte_pack(hash + (i * 2), iint->ima_hash->digest[i]); hash[i * 2] = '\0'; ab = audit_log_start(current->audit_context, GFP_KERNEL, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index e1865a6..116630c 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -45,10 +45,10 @@ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) static int ima_fix_xattr(struct dentry *dentry, struct integrity_iint_cache *iint) { - iint->ima_hash.type = IMA_XATTR_DIGEST; + iint->ima_hash->type = IMA_XATTR_DIGEST; return __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, - &iint->ima_hash.type, - 1 + iint->ima_hash.length, 0); + &iint->ima_hash->type, + 1 + iint->ima_hash->length, 0); } /* Return specific func appraised cached result */ @@ -186,13 +186,13 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, status = INTEGRITY_FAIL; break; } - if (xattr_len - 1 >= iint->ima_hash.length) + if (xattr_len - 1 >= iint->ima_hash->length) /* xattr length may be longer. md5 hash in previous version occupied 20 bytes in xattr, instead of 16 */ rc = memcmp(xattr_value->digest, - iint->ima_hash.digest, - iint->ima_hash.length); + iint->ima_hash->digest, + iint->ima_hash->length); else rc = -EINVAL; if (rc) { @@ -206,8 +206,8 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, iint->flags |= IMA_DIGSIG; rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, rc, - iint->ima_hash.digest, - iint->ima_hash.length); + iint->ima_hash->digest, + iint->ima_hash->length); if (rc == -EOPNOTSUPP) { status = INTEGRITY_UNKNOWN; } else if (rc) { diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index aead6b2..5429ca5 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -67,7 +67,7 @@ struct ima_digest_data { u8 algo; u8 length; u8 type; - u8 digest[IMA_MAX_DIGEST_SIZE]; + u8 digest[0]; } __packed; /* @@ -93,7 +93,7 @@ struct integrity_iint_cache { enum integrity_status ima_bprm_status:4; enum integrity_status ima_module_status:4; enum integrity_status evm_status:4; - struct ima_digest_data ima_hash; + struct ima_digest_data *ima_hash; }; /* rbtree tree calls to lookup, insert, delete -- cgit v1.1 From 140d802240a4ba3351494b4ab199964b96f87493 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 11 Mar 2013 20:29:47 -0400 Subject: ima: differentiate between template hash and file data hash sizes The TPM v1.2 limits the template hash size to 20 bytes. This patch differentiates between the template hash size, as defined in the ima_template_entry, and the file data hash size, as defined in the ima_template_data. Subsequent patches add support for different file data hash algorithms. Change log: - hash digest definition in ima_store_template() should be TPM_DIGEST_SIZE Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_crypto.c | 4 ++-- security/integrity/ima/ima_fs.c | 10 +++++----- security/integrity/ima/ima_init.c | 2 +- security/integrity/ima/ima_queue.c | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index efcdef2..52393ed 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -49,7 +49,7 @@ struct ima_template_data { }; struct ima_template_entry { - u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ + u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ const char *template_name; int template_len; struct ima_template_data template; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 5a7942e..2cc5dcc 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -46,7 +46,7 @@ int ima_store_template(struct ima_template_entry *entry, int result; struct { struct ima_digest_data hdr; - char digest[IMA_MAX_DIGEST_SIZE]; + char digest[TPM_DIGEST_SIZE]; } hash; memset(entry->digest, 0, sizeof(entry->digest)); diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 2fd1786..872c669 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -155,7 +155,7 @@ static void __init ima_pcrread(int idx, u8 *pcr) */ int __init ima_calc_boot_aggregate(char *digest) { - u8 pcr_i[IMA_DIGEST_SIZE]; + u8 pcr_i[TPM_DIGEST_SIZE]; int rc, i; struct { struct shash_desc shash; @@ -173,7 +173,7 @@ int __init ima_calc_boot_aggregate(char *digest) for (i = TPM_PCR0; i < TPM_PCR8; i++) { ima_pcrread(i, pcr_i); /* now accumulate with current aggregate */ - rc = crypto_shash_update(&desc.shash, pcr_i, IMA_DIGEST_SIZE); + rc = crypto_shash_update(&desc.shash, pcr_i, TPM_DIGEST_SIZE); } if (!rc) crypto_shash_final(&desc.shash, digest); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 5f0fd11..c35cfb5 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -133,7 +133,7 @@ static int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, &pcr, sizeof pcr); /* 2nd: template digest */ - ima_putc(m, e->digest, IMA_DIGEST_SIZE); + ima_putc(m, e->digest, TPM_DIGEST_SIZE); /* 3rd: template name size */ namelen = strlen(e->template_name); @@ -167,11 +167,11 @@ static const struct file_operations ima_measurements_ops = { .release = seq_release, }; -static void ima_print_digest(struct seq_file *m, u8 *digest) +static void ima_print_digest(struct seq_file *m, u8 *digest, int size) { int i; - for (i = 0; i < IMA_DIGEST_SIZE; i++) + for (i = 0; i < size; i++) seq_printf(m, "%02x", *(digest + i)); } @@ -182,7 +182,7 @@ void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) switch (show) { case IMA_SHOW_ASCII: - ima_print_digest(m, entry->digest); + ima_print_digest(m, entry->digest, IMA_DIGEST_SIZE); seq_printf(m, " %s\n", entry->file_name); break; case IMA_SHOW_BINARY: @@ -212,7 +212,7 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); /* 2nd: SHA1 template hash */ - ima_print_digest(m, e->digest); + ima_print_digest(m, e->digest, TPM_DIGEST_SIZE); /* 3th: template name */ seq_printf(m, " %s ", e->template_name); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 162ea72..9d0243c 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -74,7 +74,7 @@ err_out: int __init ima_init(void) { - u8 pcr_i[IMA_DIGEST_SIZE]; + u8 pcr_i[TPM_DIGEST_SIZE]; int rc; ima_used_chip = 0; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index ff63fe0..e63ff33 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -50,7 +50,7 @@ static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value) key = ima_hash_key(digest_value); rcu_read_lock(); hlist_for_each_entry_rcu(qe, &ima_htable.queue[key], hnext) { - rc = memcmp(qe->entry->digest, digest_value, IMA_DIGEST_SIZE); + rc = memcmp(qe->entry->digest, digest_value, TPM_DIGEST_SIZE); if (rc == 0) { ret = qe; break; @@ -106,7 +106,7 @@ static int ima_pcr_extend(const u8 *hash) int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode) { - u8 digest[IMA_DIGEST_SIZE]; + u8 digest[TPM_DIGEST_SIZE]; const char *audit_cause = "hash_added"; char tpm_audit_cause[AUDIT_CAUSE_LEN_MAX]; int audit_info = 1; -- cgit v1.1 From 723326b927b675daf4223fe31d7428eca68f194b Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 4 Jul 2013 17:40:01 +0300 Subject: ima: provide dedicated hash algo allocation function This patch provides dedicated hash algo allocation and deallocation function which can be used by different clients. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_crypto.c | 43 +++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 14 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 872c669..e5d3ebf 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -39,6 +39,28 @@ int ima_init_crypto(void) return 0; } +static struct crypto_shash *ima_alloc_tfm(enum hash_algo algo) +{ + struct crypto_shash *tfm = ima_shash_tfm; + int rc; + + if (algo != ima_hash_algo && algo < HASH_ALGO__LAST) { + tfm = crypto_alloc_shash(hash_algo_name[algo], 0, 0); + if (IS_ERR(tfm)) { + rc = PTR_ERR(tfm); + pr_err("Can not allocate %s (reason: %d)\n", + hash_algo_name[algo], rc); + } + } + return tfm; +} + +static void ima_free_tfm(struct crypto_shash *tfm) +{ + if (tfm != ima_shash_tfm) + crypto_free_shash(tfm); +} + /* * Calculate the MD5/SHA1 file digest */ @@ -57,6 +79,8 @@ static int ima_calc_file_hash_tfm(struct file *file, desc.shash.tfm = tfm; desc.shash.flags = 0; + hash->length = crypto_shash_digestsize(tfm); + rc = crypto_shash_init(&desc.shash); if (rc != 0) return rc; @@ -98,25 +122,16 @@ out: int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) { - struct crypto_shash *tfm = ima_shash_tfm; + struct crypto_shash *tfm; int rc; - if (hash->algo != ima_hash_algo && hash->algo < HASH_ALGO__LAST) { - tfm = crypto_alloc_shash(hash_algo_name[hash->algo], 0, 0); - if (IS_ERR(tfm)) { - rc = PTR_ERR(tfm); - pr_err("Can not allocate %s (reason: %d)\n", - hash_algo_name[hash->algo], rc); - return rc; - } - } - - hash->length = crypto_shash_digestsize(tfm); + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); rc = ima_calc_file_hash_tfm(file, hash, tfm); - if (tfm != ima_shash_tfm) - crypto_free_shash(tfm); + ima_free_tfm(tfm); return rc; } -- cgit v1.1 From ea593993d361748e795f5eb783a5fb5144fb2df9 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Fri, 7 Jun 2013 12:16:24 +0200 Subject: ima: support arbitrary hash algorithms in ima_calc_buffer_hash ima_calc_buffer_hash will be used with different hash algorithms. This patch provides support for arbitrary hash algorithms in ima_calc_buffer_hash. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_api.c | 3 +++ security/integrity/ima/ima_crypto.c | 28 ++++++++++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 2cc5dcc..bc1d128 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "ima.h" static const char *IMA_TEMPLATE_NAME = "ima"; @@ -54,6 +55,8 @@ int ima_store_template(struct ima_template_entry *entry, entry->template_len = sizeof(entry->template); if (!violation) { + /* this function uses default algo */ + hash.hdr.algo = HASH_ALGO_SHA1; result = ima_calc_buffer_hash(&entry->template, entry->template_len, &hash.hdr); if (result < 0) { diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index e5d3ebf..e2be252 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -139,23 +139,39 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) /* * Calculate the hash of a given buffer */ -int ima_calc_buffer_hash(const void *buf, int len, struct ima_digest_data *hash) +static int ima_calc_buffer_hash_tfm(const void *buf, int len, + struct ima_digest_data *hash, + struct crypto_shash *tfm) { struct { struct shash_desc shash; - char ctx[crypto_shash_descsize(ima_shash_tfm)]; + char ctx[crypto_shash_descsize(tfm)]; } desc; - desc.shash.tfm = ima_shash_tfm; + desc.shash.tfm = tfm; desc.shash.flags = 0; - /* this function uses default algo */ - hash->algo = ima_hash_algo; - hash->length = crypto_shash_digestsize(ima_shash_tfm); + hash->length = crypto_shash_digestsize(tfm); return crypto_shash_digest(&desc.shash, buf, len, hash->digest); } +int ima_calc_buffer_hash(const void *buf, int len, struct ima_digest_data *hash) +{ + struct crypto_shash *tfm; + int rc; + + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + rc = ima_calc_buffer_hash_tfm(buf, len, hash, tfm); + + ima_free_tfm(tfm); + + return rc; +} + static void __init ima_pcrread(int idx, u8 *pcr) { if (!ima_used_chip) -- cgit v1.1 From 09ef54359c4ad49c01a12503b2c510b424ecf059 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Fri, 7 Jun 2013 12:16:25 +0200 Subject: ima: ima_calc_boot_agregate must use SHA1 With multiple hash algorithms, ima_hash_tfm is no longer guaranteed to be sha1. Need to force to use sha1. Changelog: - pass ima_digest_data to ima_calc_boot_aggregate() instead of char * (Roberto Sassu); - create an ima_digest_data structure in ima_add_boot_aggregate() (Roberto Sassu); - pass hash->algo to ima_alloc_tfm() (Roberto Sassu, reported by Dmitry). - "move hash definition in ima_add_boot_aggregate()" commit hunk to here. - sparse warning fix - Fengguang Wu Signed-off-by: Dmitry Kasatkin Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_crypto.c | 24 +++++++++++++++++++++--- security/integrity/ima/ima_init.c | 10 +++++++++- 3 files changed, 31 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 52393ed..e0e1cde 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -73,7 +73,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); int ima_calc_buffer_hash(const void *data, int len, struct ima_digest_data *hash); -int ima_calc_boot_aggregate(char *digest); +int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); void ima_add_violation(struct inode *inode, const unsigned char *filename, const char *op, const char *cause); int ima_init_crypto(void); diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index e2be252..22be23f 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -184,16 +184,17 @@ static void __init ima_pcrread(int idx, u8 *pcr) /* * Calculate the boot aggregate hash */ -int __init ima_calc_boot_aggregate(char *digest) +static int __init ima_calc_boot_aggregate_tfm(char *digest, + struct crypto_shash *tfm) { u8 pcr_i[TPM_DIGEST_SIZE]; int rc, i; struct { struct shash_desc shash; - char ctx[crypto_shash_descsize(ima_shash_tfm)]; + char ctx[crypto_shash_descsize(tfm)]; } desc; - desc.shash.tfm = ima_shash_tfm; + desc.shash.tfm = tfm; desc.shash.flags = 0; rc = crypto_shash_init(&desc.shash); @@ -210,3 +211,20 @@ int __init ima_calc_boot_aggregate(char *digest) crypto_shash_final(&desc.shash, digest); return rc; } + +int __init ima_calc_boot_aggregate(struct ima_digest_data *hash) +{ + struct crypto_shash *tfm; + int rc; + + tfm = ima_alloc_tfm(hash->algo); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + hash->length = crypto_shash_digestsize(tfm); + rc = ima_calc_boot_aggregate_tfm(hash->digest, tfm); + + ima_free_tfm(tfm); + + return rc; +} diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 9d0243c..77cd500 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "ima.h" /* name for boot aggregate entry */ @@ -46,6 +47,10 @@ static void __init ima_add_boot_aggregate(void) const char *audit_cause = "ENOMEM"; int result = -ENOMEM; int violation = 1; + struct { + struct ima_digest_data hdr; + char digest[TPM_DIGEST_SIZE]; + } hash; entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) @@ -56,12 +61,15 @@ static void __init ima_add_boot_aggregate(void) IMA_EVENT_NAME_LEN_MAX); if (ima_used_chip) { violation = 0; - result = ima_calc_boot_aggregate(entry->template.digest); + hash.hdr.algo = HASH_ALGO_SHA1; + result = ima_calc_boot_aggregate(&hash.hdr); if (result < 0) { audit_cause = "hashing_error"; kfree(entry); goto err_out; } + memcpy(entry->template.digest, hash.hdr.digest, + hash.hdr.length); } result = ima_store_template(entry, violation, NULL); if (result < 0) -- cgit v1.1 From 7d802a227b7f26c89f13dab09767e6b0aebd9c9f Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:26 +0200 Subject: ima: pass the file descriptor to ima_add_violation() Pass the file descriptor instead of the inode to ima_add_violation(), to make the latter consistent with ima_store_measurement() in preparation for the new template architecture. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_api.c | 3 ++- security/integrity/ima/ima_main.c | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e0e1cde..d7bec6f 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -74,7 +74,7 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); int ima_calc_buffer_hash(const void *data, int len, struct ima_digest_data *hash); int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); -void ima_add_violation(struct inode *inode, const unsigned char *filename, +void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause); int ima_init_crypto(void); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index bc1d128..98160a3 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -78,10 +78,11 @@ int ima_store_template(struct ima_template_entry *entry, * By extending the PCR with 0xFF's instead of with zeroes, the PCR * value is invalidated. */ -void ima_add_violation(struct inode *inode, const unsigned char *filename, +void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause) { struct ima_template_entry *entry; + struct inode *inode = file->f_dentry->d_inode; int violation = 1; int result; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 95b5df2..5e8b1f7 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -94,10 +94,9 @@ out: pathname = dentry->d_name.name; if (send_tomtou) - ima_add_violation(inode, pathname, - "invalid_pcr", "ToMToU"); + ima_add_violation(file, pathname, "invalid_pcr", "ToMToU"); if (send_writers) - ima_add_violation(inode, pathname, + ima_add_violation(file, pathname, "invalid_pcr", "open_writers"); kfree(pathbuf); } -- cgit v1.1 From 9803d413f41db86fdf0097f1af781fe2e68f474c Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:27 +0200 Subject: ima: pass the filename argument up to ima_add_template_entry() Pass the filename argument to ima_add_template_entry() in order to eliminate a dependency on template specific data (third argument of integrity_audit_msg). This change is required because, with the new template management mechanism, the generation of a new measurement entry will be performed by new specific functions (introduced in next patches) and the current IMA code will not be aware anymore of how data is stored in the entry payload. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 5 +++-- security/integrity/ima/ima_api.c | 9 +++++---- security/integrity/ima/ima_init.c | 3 ++- security/integrity/ima/ima_queue.c | 6 +++--- 4 files changed, 13 insertions(+), 10 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index d7bec6f..27d2ffb 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -69,7 +69,8 @@ int ima_fs_init(void); void ima_fs_cleanup(void); int ima_inode_alloc(struct inode *inode); int ima_add_template_entry(struct ima_template_entry *entry, int violation, - const char *op, struct inode *inode); + const char *op, struct inode *inode, + const unsigned char *filename); int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); int ima_calc_buffer_hash(const void *data, int len, struct ima_digest_data *hash); @@ -107,7 +108,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename); int ima_store_template(struct ima_template_entry *entry, int violation, - struct inode *inode); + struct inode *inode, const unsigned char *filename); void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); const char *ima_d_path(struct path *path, char **pathbuf); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 98160a3..a0fe504 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -40,7 +40,8 @@ static const char *IMA_TEMPLATE_NAME = "ima"; * Returns 0 on success, error code otherwise */ int ima_store_template(struct ima_template_entry *entry, - int violation, struct inode *inode) + int violation, struct inode *inode, + const unsigned char *filename) { const char *op = "add_template_measure"; const char *audit_cause = "hashing_error"; @@ -67,7 +68,7 @@ int ima_store_template(struct ima_template_entry *entry, } memcpy(entry->digest, hash.hdr.digest, hash.hdr.length); } - result = ima_add_template_entry(entry, violation, op, inode); + result = ima_add_template_entry(entry, violation, op, inode, filename); return result; } @@ -96,7 +97,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, } memset(&entry->template, 0, sizeof(entry->template)); strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); - result = ima_store_template(entry, violation, inode); + result = ima_store_template(entry, violation, inode, filename); if (result < 0) kfree(entry); err_out: @@ -248,7 +249,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint, (strlen(filename) > IMA_EVENT_NAME_LEN_MAX) ? file->f_dentry->d_name.name : filename); - result = ima_store_template(entry, violation, inode); + result = ima_store_template(entry, violation, inode, filename); if (!result || result == -EEXIST) iint->flags |= IMA_MEASURED; if (result < 0) diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 77cd500..d42fac3 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -71,7 +71,8 @@ static void __init ima_add_boot_aggregate(void) memcpy(entry->template.digest, hash.hdr.digest, hash.hdr.length); } - result = ima_store_template(entry, violation, NULL); + result = ima_store_template(entry, violation, NULL, + boot_aggregate_name); if (result < 0) kfree(entry); return; diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index e63ff33..d85e997 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -104,7 +104,8 @@ static int ima_pcr_extend(const u8 *hash) * and extend the pcr. */ int ima_add_template_entry(struct ima_template_entry *entry, int violation, - const char *op, struct inode *inode) + const char *op, struct inode *inode, + const unsigned char *filename) { u8 digest[TPM_DIGEST_SIZE]; const char *audit_cause = "hash_added"; @@ -141,8 +142,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, } out: mutex_unlock(&ima_extend_list_mutex); - integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, - entry->template.file_name, + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, audit_cause, result, audit_info); return result; } -- cgit v1.1 From 7bc5f447ce9d01e19394b5399bf1a4fcebf0f8dd Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:28 +0200 Subject: ima: define new function ima_alloc_init_template() to API Instead of allocating and initializing the template entry from multiple places (eg. boot aggregate, violation, and regular measurements), this patch defines a new function called ima_alloc_init_template(). The new function allocates and initializes the measurement entry with the inode digest and the filename. In respect to the current behavior, it truncates the file name passed in the 'filename' argument if the latter's size is greater than 255 bytes and the passed file descriptor is NULL. Changelog: - initialize 'hash' variable for non TPM case - Mimi - conform to expectation for 'iint' to be defined as a pointer. - Mimi - add missing 'file' dependency for recalculating file hash. - Mimi Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 3 ++ security/integrity/ima/ima_api.c | 88 ++++++++++++++++++++++++++------------- security/integrity/ima/ima_init.c | 24 ++++++----- 3 files changed, 76 insertions(+), 39 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 27d2ffb..da03d33 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -107,6 +107,9 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename); void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename); +int ima_alloc_init_template(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct ima_template_entry **entry); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode, const unsigned char *filename); void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index a0fe504..29dd43d 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -24,6 +24,62 @@ static const char *IMA_TEMPLATE_NAME = "ima"; /* + * ima_alloc_init_template - create and initialize a new template entry + */ +int ima_alloc_init_template(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct ima_template_entry **entry) +{ + struct ima_template_entry *e; + int result = 0; + + e = kzalloc(sizeof(**entry), GFP_NOFS); + if (!e) + return -ENOMEM; + + memset(&(e)->template, 0, sizeof(e->template)); + if (!iint) /* IMA measurement violation entry */ + goto out; + + if (iint->ima_hash->algo != ima_hash_algo) { + struct inode *inode; + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; + + if (!file) { + result = -EINVAL; + goto out_free; + } + + inode = file_inode(file); + hash.hdr.algo = ima_hash_algo; + hash.hdr.length = SHA1_DIGEST_SIZE; + result = ima_calc_file_hash(file, &hash.hdr); + if (result) { + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", + "failed", result, 0); + goto out_free; + } else + memcpy(e->template.digest, hash.hdr.digest, + hash.hdr.length); + } else + memcpy(e->template.digest, iint->ima_hash->digest, + iint->ima_hash->length); +out: + strcpy(e->template.file_name, + (strlen(filename) > IMA_EVENT_NAME_LEN_MAX && file != NULL) ? + file->f_dentry->d_name.name : filename); + *entry = e; + return 0; +out_free: + kfree(e); + return result; +} + +/* * ima_store_template - store ima template measurements * * Calculate the hash of a template entry, add the template entry @@ -90,13 +146,11 @@ void ima_add_violation(struct file *file, const unsigned char *filename, /* can overflow, only indicator */ atomic_long_inc(&ima_htable.violations); - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { + result = ima_alloc_init_template(NULL, file, filename, &entry); + if (result < 0) { result = -ENOMEM; goto err_out; } - memset(&entry->template, 0, sizeof(entry->template)); - strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); result = ima_store_template(entry, violation, inode, filename); if (result < 0) kfree(entry); @@ -220,34 +274,12 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (iint->flags & IMA_MEASURED) return; - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) { + result = ima_alloc_init_template(iint, file, filename, &entry); + if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, audit_cause, result, 0); return; } - memset(&entry->template, 0, sizeof(entry->template)); - if (iint->ima_hash->algo != ima_hash_algo) { - struct { - struct ima_digest_data hdr; - char digest[IMA_MAX_DIGEST_SIZE]; - } hash; - - hash.hdr.algo = ima_hash_algo; - result = ima_calc_file_hash(file, &hash.hdr); - if (result) - integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, - filename, "collect_data", "failed", - result, 0); - else - memcpy(entry->template.digest, hash.hdr.digest, - hash.hdr.length); - } else - memcpy(entry->template.digest, iint->ima_hash->digest, - iint->ima_hash->length); - strcpy(entry->template.file_name, - (strlen(filename) > IMA_EVENT_NAME_LEN_MAX) ? - file->f_dentry->d_name.name : filename); result = ima_store_template(entry, violation, inode, filename); if (!result || result == -EEXIST) diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index d42fac3..50e15e6 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -43,34 +43,36 @@ int ima_used_chip; static void __init ima_add_boot_aggregate(void) { struct ima_template_entry *entry; + struct integrity_iint_cache tmp_iint, *iint = &tmp_iint; const char *op = "add_boot_aggregate"; const char *audit_cause = "ENOMEM"; int result = -ENOMEM; - int violation = 1; + int violation = 0; struct { struct ima_digest_data hdr; char digest[TPM_DIGEST_SIZE]; } hash; - entry = kmalloc(sizeof(*entry), GFP_KERNEL); - if (!entry) - goto err_out; + memset(iint, 0, sizeof(*iint)); + memset(&hash, 0, sizeof(hash)); + iint->ima_hash = &hash.hdr; + iint->ima_hash->algo = HASH_ALGO_SHA1; + iint->ima_hash->length = SHA1_DIGEST_SIZE; - memset(&entry->template, 0, sizeof(entry->template)); - strncpy(entry->template.file_name, boot_aggregate_name, - IMA_EVENT_NAME_LEN_MAX); if (ima_used_chip) { - violation = 0; - hash.hdr.algo = HASH_ALGO_SHA1; result = ima_calc_boot_aggregate(&hash.hdr); if (result < 0) { audit_cause = "hashing_error"; kfree(entry); goto err_out; } - memcpy(entry->template.digest, hash.hdr.digest, - hash.hdr.length); } + + result = ima_alloc_init_template(iint, NULL, boot_aggregate_name, + &entry); + if (result < 0) + return; + result = ima_store_template(entry, violation, NULL, boot_aggregate_name); if (result < 0) -- cgit v1.1 From adf53a778a0a5a5dc9103509da4a9719046e5310 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:29 +0200 Subject: ima: new templates management mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original 'ima' template is fixed length, containing the filedata hash and pathname. The filedata hash is limited to 20 bytes (md5/sha1). The pathname is a null terminated string, limited to 255 characters. To overcome these limitations and to add additional file metadata, it is necessary to extend the current version of IMA by defining additional templates. The main reason to introduce this feature is that, each time a new template is defined, the functions that generate and display the measurement list would include the code for handling a new format and, thus, would significantly grow over time. This patch set solves this problem by separating the template management from the remaining IMA code. The core of this solution is the definition of two new data structures: a template descriptor, to determine which information should be included in the measurement list, and a template field, to generate and display data of a given type. To define a new template field, developers define the field identifier and implement two functions, init() and show(), respectively to generate and display measurement entries. Initially, this patch set defines the following template fields (support for additional data types will be added later):  - 'd': the digest of the event (i.e. the digest of a measured file),         calculated with the SHA1 or MD5 hash algorithm;  - 'n': the name of the event (i.e. the file name), with size up to         255 bytes;  - 'd-ng': the digest of the event, calculated with an arbitrary hash            algorithm (field format: [:]digest, where the digest            prefix is shown only if the hash algorithm is not SHA1 or MD5);  - 'n-ng': the name of the event, without size limitations. Defining a new template descriptor requires specifying the template format, a string of field identifiers separated by the '|' character. This patch set defines the following template descriptors:  - "ima": its format is 'd|n';  - "ima-ng" (default): its format is 'd-ng|n-ng' Further details about the new template architecture can be found in Documentation/security/IMA-templates.txt. Changelog: - don't defer calling ima_init_template() - Mimi - don't define ima_lookup_template_desc() until used - Mimi - squashed with documentation patch - Mimi Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/Makefile | 2 +- security/integrity/ima/ima.h | 29 +++++++++ security/integrity/ima/ima_init.c | 4 ++ security/integrity/ima/ima_template.c | 112 ++++++++++++++++++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 security/integrity/ima/ima_template.c (limited to 'security') diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 56dfee7..7fe4ae3 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -6,5 +6,5 @@ obj-$(CONFIG_IMA) += ima.o ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ - ima_policy.o + ima_policy.o ima_template.o ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index da03d33..c85718f 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -36,12 +36,39 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; #define IMA_HASH_BITS 9 #define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) +#define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16 +#define IMA_TEMPLATE_NUM_FIELDS_MAX 15 + /* set during initialization */ extern int ima_initialized; extern int ima_used_chip; extern int ima_hash_algo; extern int ima_appraise; +/* IMA template field data definition */ +struct ima_field_data { + u8 *data; + u32 len; +}; + +/* IMA template field definition */ +struct ima_template_field { + const char field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN]; + int (*field_init) (struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data); + void (*field_show) (struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +}; + +/* IMA template descriptor definition */ +struct ima_template_desc { + char *name; + char *fmt; + int num_fields; + struct ima_template_field **fields; +}; + /* IMA inode template definition */ struct ima_template_data { u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ @@ -79,6 +106,8 @@ void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause); int ima_init_crypto(void); +int ima_init_template(void); + /* * used to protect h_table and sha_table */ diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 50e15e6..f84aec5 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -99,6 +99,10 @@ int __init ima_init(void) rc = ima_init_crypto(); if (rc) return rc; + rc = ima_init_template(); + if (rc != 0) + return rc; + ima_add_boot_aggregate(); /* boot aggregate must be first entry */ ima_init_policy(); diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c new file mode 100644 index 0000000..7e86783 --- /dev/null +++ b/security/integrity/ima/ima_template.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: Roberto Sassu + * + * 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. + * + * File: ima_template.c + * Helpers to manage template descriptors. + */ +#include "ima.h" + +static struct ima_template_desc defined_templates[] = { +}; + +static struct ima_template_field supported_fields[] = { +}; + +static struct ima_template_field *ima_lookup_template_field( + const char *field_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(supported_fields); i++) + if (strncmp(supported_fields[i].field_id, field_id, + IMA_TEMPLATE_FIELD_ID_MAX_LEN) == 0) + return &supported_fields[i]; + return NULL; +} + +static int ima_template_fmt_size(char *template_fmt) +{ + char c; + int template_fmt_len = strlen(template_fmt); + int i = 0, j = 0; + + while (i < template_fmt_len) { + c = template_fmt[i]; + if (c == '|') + j++; + i++; + } + + return j + 1; +} + +static int template_desc_init_fields(char *template_fmt, + struct ima_template_field ***fields, + int *num_fields) +{ + char *c, *template_fmt_ptr = template_fmt; + int template_num_fields = ima_template_fmt_size(template_fmt); + int i, result = 0; + + if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) + return -EINVAL; + + *fields = kzalloc(template_num_fields * sizeof(*fields), GFP_KERNEL); + if (*fields == NULL) { + result = -ENOMEM; + goto out; + } + for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && + i < template_num_fields; i++) { + struct ima_template_field *f = ima_lookup_template_field(c); + + if (!f) { + result = -ENOENT; + goto out; + } + (*fields)[i] = f; + } + *num_fields = i; + return 0; +out: + kfree(*fields); + *fields = NULL; + return result; +} + +static int init_defined_templates(void) +{ + int i = 0; + int result = 0; + + /* Init defined templates. */ + for (i = 0; i < ARRAY_SIZE(defined_templates); i++) { + struct ima_template_desc *template = &defined_templates[i]; + + result = template_desc_init_fields(template->fmt, + &(template->fields), + &(template->num_fields)); + if (result < 0) + return result; + } + return result; +} + +int ima_init_template(void) +{ + int result; + + result = init_defined_templates(); + if (result < 0) + return result; + + return 0; +} -- cgit v1.1 From 3ce1217d6cd5dfa82a9db5c2a999cc1bb01490d9 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:30 +0200 Subject: ima: define template fields library and new helpers This patch defines a library containing two initial template fields, inode digest (d) and file name (n), the 'ima' template descriptor, whose format is 'd|n', and two helper functions, ima_write_template_field_data() and ima_show_template_field_data(). Changelog: - replace ima_eventname_init() parameter NULL checking with BUG_ON. (suggested by Mimi) - include "new template fields for inode digest (d) and file name (n)" definitions to fix a compiler warning. - Mimi - unnecessary to prefix static function names with 'ima_'. remove prefix to resolve Lindent formatting changes. - Mimi - abbreviated/removed inline comments - Mimi - always send the template field length - Mimi Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/Makefile | 2 +- security/integrity/ima/ima.h | 5 + security/integrity/ima/ima_fs.c | 4 +- security/integrity/ima/ima_template.c | 15 ++- security/integrity/ima/ima_template_lib.c | 193 ++++++++++++++++++++++++++++++ security/integrity/ima/ima_template_lib.h | 31 +++++ 6 files changed, 242 insertions(+), 8 deletions(-) create mode 100644 security/integrity/ima/ima_template_lib.c create mode 100644 security/integrity/ima/ima_template_lib.h (limited to 'security') diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 7fe4ae3..d79263d 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -6,5 +6,5 @@ obj-$(CONFIG_IMA) += ima.o ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ - ima_policy.o ima_template.o + ima_policy.o ima_template.o ima_template_lib.o ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index c85718f..e1f081d 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -39,6 +39,9 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; #define IMA_TEMPLATE_FIELD_ID_MAX_LEN 16 #define IMA_TEMPLATE_NUM_FIELDS_MAX 15 +#define IMA_TEMPLATE_IMA_NAME "ima" +#define IMA_TEMPLATE_IMA_FMT "d|n" + /* set during initialization */ extern int ima_initialized; extern int ima_used_chip; @@ -105,6 +108,8 @@ int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause); int ima_init_crypto(void); +void ima_putc(struct seq_file *m, void *data, int datalen); +void ima_print_digest(struct seq_file *m, u8 *digest, int size); int ima_init_template(void); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index c35cfb5..414862e 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -99,7 +99,7 @@ static void ima_measurements_stop(struct seq_file *m, void *v) { } -static void ima_putc(struct seq_file *m, void *data, int datalen) +void ima_putc(struct seq_file *m, void *data, int datalen) { while (datalen--) seq_putc(m, *(char *)data++); @@ -167,7 +167,7 @@ static const struct file_operations ima_measurements_ops = { .release = seq_release, }; -static void ima_print_digest(struct seq_file *m, u8 *digest, int size) +void ima_print_digest(struct seq_file *m, u8 *digest, int size) { int i; diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 7e86783..8100422 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -13,15 +13,20 @@ * Helpers to manage template descriptors. */ #include "ima.h" +#include "ima_template_lib.h" static struct ima_template_desc defined_templates[] = { + {.name = IMA_TEMPLATE_IMA_NAME,.fmt = IMA_TEMPLATE_IMA_FMT}, }; static struct ima_template_field supported_fields[] = { + {.field_id = "d",.field_init = ima_eventdigest_init, + .field_show = ima_show_template_digest}, + {.field_id = "n",.field_init = ima_eventname_init, + .field_show = ima_show_template_string}, }; -static struct ima_template_field *ima_lookup_template_field( - const char *field_id) +static struct ima_template_field *lookup_template_field(const char *field_id) { int i; @@ -32,7 +37,7 @@ static struct ima_template_field *ima_lookup_template_field( return NULL; } -static int ima_template_fmt_size(char *template_fmt) +static int template_fmt_size(char *template_fmt) { char c; int template_fmt_len = strlen(template_fmt); @@ -53,7 +58,7 @@ static int template_desc_init_fields(char *template_fmt, int *num_fields) { char *c, *template_fmt_ptr = template_fmt; - int template_num_fields = ima_template_fmt_size(template_fmt); + int template_num_fields = template_fmt_size(template_fmt); int i, result = 0; if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) @@ -66,7 +71,7 @@ static int template_desc_init_fields(char *template_fmt, } for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && i < template_num_fields; i++) { - struct ima_template_field *f = ima_lookup_template_field(c); + struct ima_template_field *f = lookup_template_field(c); if (!f) { result = -ENOENT; diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c new file mode 100644 index 0000000..e13fc7c --- /dev/null +++ b/security/integrity/ima/ima_template_lib.c @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: Roberto Sassu + * + * 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. + * + * File: ima_template_lib.c + * Library of supported template fields. + */ +#include "ima_template_lib.h" + +enum data_formats { DATA_FMT_DIGEST = 0, DATA_FMT_EVENT_NAME, DATA_FMT_STRING }; +static int ima_write_template_field_data(const void *data, const u32 datalen, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + u8 *buf, *buf_ptr; + u32 buflen; + + switch (datafmt) { + case DATA_FMT_EVENT_NAME: + buflen = IMA_EVENT_NAME_LEN_MAX + 1; + break; + case DATA_FMT_STRING: + buflen = datalen + 1; + break; + default: + buflen = datalen; + } + + buf = kzalloc(buflen, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, data, datalen); + + /* + * Replace all space characters with underscore for event names and + * strings. This avoid that, during the parsing of a measurements list, + * filenames with spaces or that end with the suffix ' (deleted)' are + * split into multiple template fields (the space is the delimitator + * character for measurements lists in ASCII format). + */ + if (datafmt == DATA_FMT_EVENT_NAME || datafmt == DATA_FMT_STRING) { + for (buf_ptr = buf; buf_ptr - buf < datalen; buf_ptr++) + if (*buf_ptr == ' ') + *buf_ptr = '_'; + } + + field_data->data = buf; + field_data->len = buflen; + return 0; +} + +static void ima_show_template_data_ascii(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + switch (datafmt) { + case DATA_FMT_DIGEST: + ima_print_digest(m, field_data->data, field_data->len); + break; + case DATA_FMT_STRING: + seq_printf(m, "%s", field_data->data); + break; + default: + break; + } +} + +static void ima_show_template_data_binary(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + ima_putc(m, &field_data->len, sizeof(u32)); + if (!field_data->len) + return; + ima_putc(m, field_data->data, field_data->len); +} + +static void ima_show_template_field_data(struct seq_file *m, + enum ima_show_type show, + enum data_formats datafmt, + struct ima_field_data *field_data) +{ + switch (show) { + case IMA_SHOW_ASCII: + ima_show_template_data_ascii(m, show, datafmt, field_data); + break; + case IMA_SHOW_BINARY: + ima_show_template_data_binary(m, show, datafmt, field_data); + break; + default: + break; + } +} + +void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_DIGEST, field_data); +} + +void ima_show_template_string(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data); +} + +/* + * This function writes the digest of an event. + */ +int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data) +{ + struct { + struct ima_digest_data hdr; + char digest[IMA_MAX_DIGEST_SIZE]; + } hash; + u8 *cur_digest = hash.hdr.digest; + u32 cur_digestsize = IMA_DIGEST_SIZE; + struct inode *inode; + int result; + + memset(&hash, 0, sizeof(hash)); + + if (!iint) /* recording a violation. */ + goto out; + + if (iint->ima_hash->algo == ima_hash_algo) { + cur_digest = iint->ima_hash->digest; + cur_digestsize = iint->ima_hash->length; + goto out; + } + + if (!file) /* missing info to re-calculate the digest */ + return -EINVAL; + + inode = file_inode(file); + hash.hdr.algo = ima_hash_algo; + result = ima_calc_file_hash(file, &hash.hdr); + if (result) { + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", + "failed", result, 0); + return result; + } +out: + return ima_write_template_field_data(cur_digest, cur_digestsize, + DATA_FMT_DIGEST, field_data); +} + +/* + * This function writes the name of an event. + */ +int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data) +{ + const char *cur_filename = NULL; + u32 cur_filename_len = 0; + + BUG_ON(filename == NULL && file == NULL); + + if (filename) { + cur_filename = filename; + cur_filename_len = strlen(filename); + + if (cur_filename_len <= IMA_EVENT_NAME_LEN_MAX) + goto out; + } + + if (file) { + cur_filename = file->f_dentry->d_name.name; + cur_filename_len = strlen(cur_filename); + } else + /* + * Truncate filename if the latter is too long and + * the file descriptor is not available. + */ + cur_filename_len = IMA_EVENT_NAME_LEN_MAX; +out: + return ima_write_template_field_data(cur_filename, cur_filename_len, + DATA_FMT_EVENT_NAME, field_data); +} diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h new file mode 100644 index 0000000..2cecc83 --- /dev/null +++ b/security/integrity/ima/ima_template_lib.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 Politecnico di Torino, Italy + * TORSEC group -- http://security.polito.it + * + * Author: Roberto Sassu + * + * 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. + * + * File: ima_template_lib.h + * Header for the library of supported template fields. + */ +#ifndef __LINUX_IMA_TEMPLATE_LIB_H +#define __LINUX_IMA_TEMPLATE_LIB_H + +#include +#include "ima.h" + +void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +void ima_show_template_string(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); +int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data); +int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data); +#endif /* __LINUX_IMA_TEMPLATE_LIB_H */ -- cgit v1.1 From 4d7aeee73f5304bf195aa2904f8eb1d7b2e8fe52 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:32 +0200 Subject: ima: define new template ima-ng and template fields d-ng and n-ng This patch adds support for the new template 'ima-ng', whose format is defined as 'd-ng|n-ng'. These new field definitions remove the size limitations of the original 'ima' template. Further, the 'd-ng' field prefixes the inode digest with the hash algorithim, when displaying the new larger digest sizes. Change log: - scripts/Lindent fixes - Mimi - "always true comparison" - reported by Fengguang Wu, resolved Dmitry - initialize hash_algo variable to HASH_ALGO__LAST - always prefix digest with hash algorithm - Mimi Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template.c | 7 +- security/integrity/ima/ima_template_lib.c | 152 ++++++++++++++++++++++++++---- security/integrity/ima/ima_template_lib.h | 8 ++ 3 files changed, 150 insertions(+), 17 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 8100422..bf38d1a 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -16,7 +16,8 @@ #include "ima_template_lib.h" static struct ima_template_desc defined_templates[] = { - {.name = IMA_TEMPLATE_IMA_NAME,.fmt = IMA_TEMPLATE_IMA_FMT}, + {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, + {.name = "ima-ng",.fmt = "d-ng|n-ng"}, }; static struct ima_template_field supported_fields[] = { @@ -24,6 +25,10 @@ static struct ima_template_field supported_fields[] = { .field_show = ima_show_template_digest}, {.field_id = "n",.field_init = ima_eventname_init, .field_show = ima_show_template_string}, + {.field_id = "d-ng",.field_init = ima_eventdigest_ng_init, + .field_show = ima_show_template_digest_ng}, + {.field_id = "n-ng",.field_init = ima_eventname_ng_init, + .field_show = ima_show_template_string}, }; static struct ima_template_field *lookup_template_field(const char *field_id) diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index e13fc7c..7d84144 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -12,9 +12,25 @@ * File: ima_template_lib.c * Library of supported template fields. */ +#include + #include "ima_template_lib.h" -enum data_formats { DATA_FMT_DIGEST = 0, DATA_FMT_EVENT_NAME, DATA_FMT_STRING }; +static bool ima_template_hash_algo_allowed(u8 algo) +{ + if (algo == HASH_ALGO_SHA1 || algo == HASH_ALGO_MD5) + return true; + + return false; +} + +enum data_formats { + DATA_FMT_DIGEST = 0, + DATA_FMT_DIGEST_WITH_ALGO, + DATA_FMT_EVENT_NAME, + DATA_FMT_STRING +}; + static int ima_write_template_field_data(const void *data, const u32 datalen, enum data_formats datafmt, struct ima_field_data *field_data) @@ -62,12 +78,22 @@ static void ima_show_template_data_ascii(struct seq_file *m, enum data_formats datafmt, struct ima_field_data *field_data) { + u8 *buf_ptr = field_data->data, buflen = field_data->len; + switch (datafmt) { + case DATA_FMT_DIGEST_WITH_ALGO: + buf_ptr = strnchr(field_data->data, buflen, ':'); + if (buf_ptr != field_data->data) + seq_printf(m, "%s", field_data->data); + + /* skip ':' and '\0' */ + buf_ptr += 2; + buflen -= buf_ptr - field_data->data; case DATA_FMT_DIGEST: - ima_print_digest(m, field_data->data, field_data->len); + ima_print_digest(m, buf_ptr, buflen); break; case DATA_FMT_STRING: - seq_printf(m, "%s", field_data->data); + seq_printf(m, "%s", buf_ptr); break; default: break; @@ -108,14 +134,59 @@ void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, ima_show_template_field_data(m, show, DATA_FMT_DIGEST, field_data); } +void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_DIGEST_WITH_ALGO, + field_data); +} + void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data) { ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data); } +static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo, + struct ima_field_data *field_data, + bool size_limit) +{ + /* + * digest formats: + * - DATA_FMT_DIGEST: digest + * - DATA_FMT_DIGEST_WITH_ALGO: [] + ':' + '\0' + digest, + * where is provided if the hash algoritm is not + * SHA1 or MD5 + */ + u8 buffer[CRYPTO_MAX_ALG_NAME + 2 + IMA_MAX_DIGEST_SIZE] = { 0 }; + enum data_formats fmt = DATA_FMT_DIGEST; + u32 offset = 0; + + if (!size_limit) { + fmt = DATA_FMT_DIGEST_WITH_ALGO; + if (hash_algo < HASH_ALGO__LAST) + offset += snprintf(buffer, CRYPTO_MAX_ALG_NAME + 1, + "%s", hash_algo_name[hash_algo]); + buffer[offset] = ':'; + offset += 2; + } + + if (digest) + memcpy(buffer + offset, digest, digestsize); + else + /* + * If digest is NULL, the event being recorded is a violation. + * Make room for the digest by increasing the offset of + * IMA_DIGEST_SIZE. + */ + offset += IMA_DIGEST_SIZE; + + return ima_write_template_field_data(buffer, offset + digestsize, + fmt, field_data); +} + /* - * This function writes the digest of an event. + * This function writes the digest of an event (with size limit). */ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, @@ -125,8 +196,8 @@ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, struct ima_digest_data hdr; char digest[IMA_MAX_DIGEST_SIZE]; } hash; - u8 *cur_digest = hash.hdr.digest; - u32 cur_digestsize = IMA_DIGEST_SIZE; + u8 *cur_digest = NULL; + u32 cur_digestsize = 0; struct inode *inode; int result; @@ -135,7 +206,7 @@ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, if (!iint) /* recording a violation. */ goto out; - if (iint->ima_hash->algo == ima_hash_algo) { + if (ima_template_hash_algo_allowed(iint->ima_hash->algo)) { cur_digest = iint->ima_hash->digest; cur_digestsize = iint->ima_hash->length; goto out; @@ -145,7 +216,8 @@ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, return -EINVAL; inode = file_inode(file); - hash.hdr.algo = ima_hash_algo; + hash.hdr.algo = ima_template_hash_algo_allowed(ima_hash_algo) ? + ima_hash_algo : HASH_ALGO_SHA1; result = ima_calc_file_hash(file, &hash.hdr); if (result) { integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, @@ -153,20 +225,46 @@ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, "failed", result, 0); return result; } + cur_digest = hash.hdr.digest; + cur_digestsize = hash.hdr.length; out: - return ima_write_template_field_data(cur_digest, cur_digestsize, - DATA_FMT_DIGEST, field_data); + return ima_eventdigest_init_common(cur_digest, cur_digestsize, -1, + field_data, true); } /* - * This function writes the name of an event. + * This function writes the digest of an event (without size limit). */ -int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, - const unsigned char *filename, - struct ima_field_data *field_data) +int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct ima_field_data *field_data) +{ + u8 *cur_digest = NULL, hash_algo = HASH_ALGO__LAST; + u32 cur_digestsize = 0; + + /* If iint is NULL, we are recording a violation. */ + if (!iint) + goto out; + + cur_digest = iint->ima_hash->digest; + cur_digestsize = iint->ima_hash->length; + + hash_algo = iint->ima_hash->algo; +out: + return ima_eventdigest_init_common(cur_digest, cur_digestsize, + hash_algo, field_data, false); +} + +static int ima_eventname_init_common(struct integrity_iint_cache *iint, + struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data, + bool size_limit) { const char *cur_filename = NULL; u32 cur_filename_len = 0; + enum data_formats fmt = size_limit ? + DATA_FMT_EVENT_NAME : DATA_FMT_STRING; BUG_ON(filename == NULL && file == NULL); @@ -174,7 +272,7 @@ int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, cur_filename = filename; cur_filename_len = strlen(filename); - if (cur_filename_len <= IMA_EVENT_NAME_LEN_MAX) + if (!size_limit || cur_filename_len <= IMA_EVENT_NAME_LEN_MAX) goto out; } @@ -189,5 +287,27 @@ int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, cur_filename_len = IMA_EVENT_NAME_LEN_MAX; out: return ima_write_template_field_data(cur_filename, cur_filename_len, - DATA_FMT_EVENT_NAME, field_data); + fmt, field_data); +} + +/* + * This function writes the name of an event (with size limit). + */ +int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data) +{ + return ima_eventname_init_common(iint, file, filename, + field_data, true); +} + +/* + * This function writes the name of an event (without size limit). + */ +int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data) +{ + return ima_eventname_init_common(iint, file, filename, + field_data, false); } diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index 2cecc83..16c5e78 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -20,6 +20,8 @@ void ima_show_template_digest(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, @@ -28,4 +30,10 @@ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct ima_field_data *field_data); +int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename, + struct ima_field_data *field_data); +int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct ima_field_data *field_data); #endif /* __LINUX_IMA_TEMPLATE_LIB_H */ -- cgit v1.1 From a71dc65d30a472409f05d247f4eab91b14acf2f5 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:33 +0200 Subject: ima: switch to new template management mechanism This patch performs the switch to the new template mechanism by modifying the functions ima_alloc_init_template(), ima_measurements_show() and ima_ascii_measurements_show(). The old function ima_template_show() was removed as it is no longer needed. Also, if the template descriptor used to generate a measurement entry is not 'ima', the whole length of field data stored for an entry is provided before the data itself through the binary_runtime_measurement interface. Changelog: - unnecessary to use strncmp() (Mimi Zohar) - create new variable 'field' in ima_alloc_init_template() (Roberto Sassu) - use GFP_NOFS flag in ima_alloc_init_template() (Roberto Sassu) - new variable 'num_fields' in ima_store_template() (Roberto Sassu, proposed by Mimi Zohar) - rename ima_calc_buffer_hash/template_hash() to ima_calc_field_array_hash(), something more generic (Mimi, requested by Dmitry) - sparse error fix - Fengguang Wu - fix lindent warnings - always include the field length in the template data length - include the template field length variable size in the template data length - include both the template field data and field length in the template digest calculation. Simplifies verifying the template digest. (Mimi) Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 19 ++++----- security/integrity/ima/ima_api.c | 75 ++++++++++++----------------------- security/integrity/ima/ima_crypto.c | 34 ++++++++++++---- security/integrity/ima/ima_fs.c | 54 ++++++++++++------------- security/integrity/ima/ima_template.c | 22 ++++++++++ 5 files changed, 107 insertions(+), 97 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e1f081d..72d013e 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -72,17 +72,11 @@ struct ima_template_desc { struct ima_template_field **fields; }; -/* IMA inode template definition */ -struct ima_template_data { - u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ - char file_name[IMA_EVENT_NAME_LEN_MAX + 1]; /* name + \0 */ -}; - struct ima_template_entry { u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ - const char *template_name; - int template_len; - struct ima_template_data template; + struct ima_template_desc *template_desc; /* template descriptor */ + u32 template_data_len; + struct ima_field_data template_data[0]; /* template related data */ }; struct ima_queue_entry { @@ -102,14 +96,16 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode, const unsigned char *filename); int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); -int ima_calc_buffer_hash(const void *data, int len, - struct ima_digest_data *hash); +int ima_calc_field_array_hash(struct ima_field_data *field_data, int num_fields, + struct ima_digest_data *hash); int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); void ima_add_violation(struct file *file, const unsigned char *filename, const char *op, const char *cause); int ima_init_crypto(void); void ima_putc(struct seq_file *m, void *data, int datalen); void ima_print_digest(struct seq_file *m, u8 *digest, int size); +struct ima_template_desc *ima_template_desc_current(void); +int ima_init_template(void); int ima_init_template(void); @@ -146,7 +142,6 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, struct ima_template_entry **entry); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode, const unsigned char *filename); -void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); const char *ima_d_path(struct path *path, char **pathbuf); /* rbtree tree calls to lookup, insert, delete diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 29dd43d..baa3481 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -21,8 +21,6 @@ #include #include "ima.h" -static const char *IMA_TEMPLATE_NAME = "ima"; - /* * ima_alloc_init_template - create and initialize a new template entry */ @@ -30,52 +28,32 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, struct ima_template_entry **entry) { - struct ima_template_entry *e; - int result = 0; + struct ima_template_desc *template_desc = ima_template_desc_current(); + int i, result = 0; - e = kzalloc(sizeof(**entry), GFP_NOFS); - if (!e) + *entry = kzalloc(sizeof(**entry) + template_desc->num_fields * + sizeof(struct ima_field_data), GFP_NOFS); + if (!*entry) return -ENOMEM; - memset(&(e)->template, 0, sizeof(e->template)); - if (!iint) /* IMA measurement violation entry */ - goto out; - - if (iint->ima_hash->algo != ima_hash_algo) { - struct inode *inode; - struct { - struct ima_digest_data hdr; - char digest[IMA_MAX_DIGEST_SIZE]; - } hash; + for (i = 0; i < template_desc->num_fields; i++) { + struct ima_template_field *field = template_desc->fields[i]; + u32 len; - if (!file) { - result = -EINVAL; - goto out_free; - } + result = field->field_init(iint, file, filename, + &((*entry)->template_data[i])); + if (result != 0) + goto out; - inode = file_inode(file); - hash.hdr.algo = ima_hash_algo; - hash.hdr.length = SHA1_DIGEST_SIZE; - result = ima_calc_file_hash(file, &hash.hdr); - if (result) { - integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, - filename, "collect_data", - "failed", result, 0); - goto out_free; - } else - memcpy(e->template.digest, hash.hdr.digest, - hash.hdr.length); - } else - memcpy(e->template.digest, iint->ima_hash->digest, - iint->ima_hash->length); -out: - strcpy(e->template.file_name, - (strlen(filename) > IMA_EVENT_NAME_LEN_MAX && file != NULL) ? - file->f_dentry->d_name.name : filename); - *entry = e; + len = (*entry)->template_data[i].len; + (*entry)->template_data_len += sizeof(len); + (*entry)->template_data_len += len; + } + (*entry)->template_desc = template_desc; return 0; -out_free: - kfree(e); +out: + kfree(*entry); + *entry = NULL; return result; } @@ -101,24 +79,23 @@ int ima_store_template(struct ima_template_entry *entry, { const char *op = "add_template_measure"; const char *audit_cause = "hashing_error"; + char *template_name = entry->template_desc->name; int result; struct { struct ima_digest_data hdr; char digest[TPM_DIGEST_SIZE]; } hash; - memset(entry->digest, 0, sizeof(entry->digest)); - entry->template_name = IMA_TEMPLATE_NAME; - entry->template_len = sizeof(entry->template); - if (!violation) { + int num_fields = entry->template_desc->num_fields; + /* this function uses default algo */ hash.hdr.algo = HASH_ALGO_SHA1; - result = ima_calc_buffer_hash(&entry->template, - entry->template_len, &hash.hdr); + result = ima_calc_field_array_hash(&entry->template_data[0], + num_fields, &hash.hdr); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, - entry->template_name, op, + template_name, op, audit_cause, result, 0); return result; } diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 22be23f..676e029 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -137,26 +137,46 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) } /* - * Calculate the hash of a given buffer + * Calculate the hash of template data */ -static int ima_calc_buffer_hash_tfm(const void *buf, int len, - struct ima_digest_data *hash, - struct crypto_shash *tfm) +static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data, + int num_fields, + struct ima_digest_data *hash, + struct crypto_shash *tfm) { struct { struct shash_desc shash; char ctx[crypto_shash_descsize(tfm)]; } desc; + int rc, i; desc.shash.tfm = tfm; desc.shash.flags = 0; hash->length = crypto_shash_digestsize(tfm); - return crypto_shash_digest(&desc.shash, buf, len, hash->digest); + rc = crypto_shash_init(&desc.shash); + if (rc != 0) + return rc; + + for (i = 0; i < num_fields; i++) { + rc = crypto_shash_update(&desc.shash, + (const u8 *) &field_data[i].len, + sizeof(field_data[i].len)); + rc = crypto_shash_update(&desc.shash, field_data[i].data, + field_data[i].len); + if (rc) + break; + } + + if (!rc) + rc = crypto_shash_final(&desc.shash, hash->digest); + + return rc; } -int ima_calc_buffer_hash(const void *buf, int len, struct ima_digest_data *hash) +int ima_calc_field_array_hash(struct ima_field_data *field_data, int num_fields, + struct ima_digest_data *hash) { struct crypto_shash *tfm; int rc; @@ -165,7 +185,7 @@ int ima_calc_buffer_hash(const void *buf, int len, struct ima_digest_data *hash) if (IS_ERR(tfm)) return PTR_ERR(tfm); - rc = ima_calc_buffer_hash_tfm(buf, len, hash, tfm); + rc = ima_calc_field_array_hash_tfm(field_data, num_fields, hash, tfm); ima_free_tfm(tfm); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 414862e..d47a7c8 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -110,6 +110,7 @@ void ima_putc(struct seq_file *m, void *data, int datalen) * char[20]=template digest * 32bit-le=template name size * char[n]=template name + * [eventdata length] * eventdata[n]=template specific data */ static int ima_measurements_show(struct seq_file *m, void *v) @@ -119,6 +120,7 @@ static int ima_measurements_show(struct seq_file *m, void *v) struct ima_template_entry *e; int namelen; u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + int i; /* get entry */ e = qe->entry; @@ -136,15 +138,22 @@ static int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, e->digest, TPM_DIGEST_SIZE); /* 3rd: template name size */ - namelen = strlen(e->template_name); + namelen = strlen(e->template_desc->name); ima_putc(m, &namelen, sizeof namelen); /* 4th: template name */ - ima_putc(m, (void *)e->template_name, namelen); + ima_putc(m, e->template_desc->name, namelen); + + /* 5th: template length (except for 'ima' template) */ + if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) != 0) + ima_putc(m, &e->template_data_len, + sizeof(e->template_data_len)); - /* 5th: template specific data */ - ima_template_show(m, (struct ima_template_data *)&e->template, - IMA_SHOW_BINARY); + /* 6th: template specific data */ + for (i = 0; i < e->template_desc->num_fields; i++) { + e->template_desc->fields[i]->field_show(m, IMA_SHOW_BINARY, + &e->template_data[i]); + } return 0; } @@ -175,33 +184,13 @@ void ima_print_digest(struct seq_file *m, u8 *digest, int size) seq_printf(m, "%02x", *(digest + i)); } -void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) -{ - struct ima_template_data *entry = e; - int namelen; - - switch (show) { - case IMA_SHOW_ASCII: - ima_print_digest(m, entry->digest, IMA_DIGEST_SIZE); - seq_printf(m, " %s\n", entry->file_name); - break; - case IMA_SHOW_BINARY: - ima_putc(m, entry->digest, IMA_DIGEST_SIZE); - - namelen = strlen(entry->file_name); - ima_putc(m, &namelen, sizeof namelen); - ima_putc(m, entry->file_name, namelen); - default: - break; - } -} - /* print in ascii */ static int ima_ascii_measurements_show(struct seq_file *m, void *v) { /* the list never shrinks, so we don't need a lock here */ struct ima_queue_entry *qe = v; struct ima_template_entry *e; + int i; /* get entry */ e = qe->entry; @@ -215,11 +204,18 @@ static int ima_ascii_measurements_show(struct seq_file *m, void *v) ima_print_digest(m, e->digest, TPM_DIGEST_SIZE); /* 3th: template name */ - seq_printf(m, " %s ", e->template_name); + seq_printf(m, " %s", e->template_desc->name); /* 4th: template specific data */ - ima_template_show(m, (struct ima_template_data *)&e->template, - IMA_SHOW_ASCII); + for (i = 0; i < e->template_desc->num_fields; i++) { + seq_puts(m, " "); + if (e->template_data[i].len == 0) + continue; + + e->template_desc->fields[i]->field_show(m, IMA_SHOW_ASCII, + &e->template_data[i]); + } + seq_puts(m, "\n"); return 0; } diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index bf38d1a..1c4cf19 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -31,6 +31,20 @@ static struct ima_template_field supported_fields[] = { .field_show = ima_show_template_string}, }; +static struct ima_template_desc *ima_template; + +static struct ima_template_desc *lookup_template_desc(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(defined_templates); i++) { + if (strcmp(defined_templates[i].name, name) == 0) + return defined_templates + i; + } + + return NULL; +} + static struct ima_template_field *lookup_template_field(const char *field_id) { int i; @@ -110,6 +124,14 @@ static int init_defined_templates(void) return result; } +struct ima_template_desc *ima_template_desc_current(void) +{ + if (!ima_template) + ima_template = lookup_template_desc(IMA_TEMPLATE_IMA_NAME); + + return ima_template; +} + int ima_init_template(void) { int result; -- cgit v1.1 From 5278aa52f35003ddafda80b0243b3693f935b134 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 7 Jun 2013 12:16:38 +0200 Subject: ima: add audit log support for larger hashes Different files might be signed based on different hash algorithms. This patch prefixes the audit log measurement hash with the hash algorithm. Changelog: - use generic HASH_ALGO defintions - use ':' as delimiter between the hash algorithm and the digest (Roberto Sassu) - always include the hash algorithm used when audit-logging a measurement Signed-off-by: Mimi Zohar Signed-off-by: Roberto Sassu Signed-off-by: Peter Moody --- security/integrity/ima/ima_api.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index baa3481..5fcc806 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -270,6 +270,8 @@ void ima_audit_measurement(struct integrity_iint_cache *iint, { struct audit_buffer *ab; char hash[(iint->ima_hash->length * 2) + 1]; + const char *algo_name = hash_algo_name[iint->ima_hash->algo]; + char algo_hash[sizeof(hash) + strlen(algo_name) + 2]; int i; if (iint->flags & IMA_AUDITED) @@ -287,7 +289,8 @@ void ima_audit_measurement(struct integrity_iint_cache *iint, audit_log_format(ab, "file="); audit_log_untrustedstring(ab, filename); audit_log_format(ab, " hash="); - audit_log_untrustedstring(ab, hash); + snprintf(algo_hash, sizeof(algo_hash), "%s:%s", algo_name, hash); + audit_log_untrustedstring(ab, algo_hash); audit_log_task_info(ab, current); audit_log_end(ab); -- cgit v1.1 From add1c05dceb495a45036d66cdcbb3b2306de26c1 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:39 +0200 Subject: ima: defer determining the appraisal hash algorithm for 'ima' template The same hash algorithm should be used for calculating the file data hash for the IMA measurement list, as for appraising the file data integrity. (The appraise hash algorithm is stored in the 'security.ima' extended attribute.) The exception is when the reference file data hash digest, stored in the extended attribute, is larger than the one supported by the template. In this case, the file data hash needs to be calculated twice, once for the measurement list and, again, for appraisal. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 5e8b1f7..0b11bb4 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -145,6 +145,7 @@ static int process_measurement(struct file *file, const char *filename, { struct inode *inode = file_inode(file); struct integrity_iint_cache *iint; + struct ima_template_desc *template_desc = ima_template_desc_current(); char *pathbuf = NULL; const char *pathname = NULL; int rc = -ENOMEM, action, must_appraise, _func; @@ -188,7 +189,10 @@ static int process_measurement(struct file *file, const char *filename, goto out_digsig; } - if (action & IMA_APPRAISE_SUBMASK) + if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) { + if (action & IMA_APPRAISE_SUBMASK) + xattr_ptr = &xattr_value; + } else xattr_ptr = &xattr_value; rc = ima_collect_measurement(iint, file, xattr_ptr, &xattr_len); -- cgit v1.1 From 4286587dccd43d4f81fa227e413ed7e909895342 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 7 Jun 2013 12:16:34 +0200 Subject: ima: add Kconfig default measurement list template This patch adds a Kconfig option to select the default IMA measurement list template. The 'ima' template limited the filedata hash to 20 bytes and the pathname to 255 charaters. The 'ima-ng' measurement list template permits larger hash digests and longer pathnames. Changelog: - keep 'select CRYPTO_HASH_INFO' in 'config IMA' section (Kconfig) (Roberto Sassu); - removed trailing whitespaces (Roberto Sassu). - Lindent fixes Signed-off-by: Mimi Zohar Signed-off-by: Roberto Sassu --- security/integrity/ima/Kconfig | 25 +++++++++++++++++++++++++ security/integrity/ima/ima_template.c | 4 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index e6628e7..de26cc8 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -46,6 +46,31 @@ config IMA_LSM_RULES help Disabling this option will disregard LSM based policy rules. +choice + prompt "Default template" + default IMA_NG_TEMPLATE + depends on IMA + help + Select the default IMA measurement template. + + The original 'ima' measurement list template contains a + hash, defined as 20 bytes, and a null terminated pathname, + limited to 255 characters. The 'ima-ng' measurement list + template permits both larger hash digests and longer + pathnames. + + config IMA_TEMPLATE + bool "ima" + config IMA_NG_TEMPLATE + bool "ima-ng (default)" +endchoice + +config IMA_DEFAULT_TEMPLATE + string + depends on IMA + default "ima" if IMA_TEMPLATE + default "ima-ng" if IMA_NG_TEMPLATE + config IMA_APPRAISE bool "Appraise integrity measurements" depends on IMA diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 1c4cf19..c28ff9b 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -127,8 +127,8 @@ static int init_defined_templates(void) struct ima_template_desc *ima_template_desc_current(void) { if (!ima_template) - ima_template = lookup_template_desc(IMA_TEMPLATE_IMA_NAME); - + ima_template = + lookup_template_desc(CONFIG_IMA_DEFAULT_TEMPLATE); return ima_template; } -- cgit v1.1 From 9b9d4ce592d283fc4c01da746c02a840c499bb7e Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 7 Jun 2013 12:16:35 +0200 Subject: ima: define kernel parameter 'ima_template=' to change configured default This patch allows users to specify from the kernel command line the template descriptor, among those defined, that will be used to generate and display measurement entries. If an user specifies a wrong template, IMA reverts to the template descriptor set in the kernel configuration. Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'security') diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index c28ff9b..0002214 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -12,6 +12,8 @@ * File: ima_template.c * Helpers to manage template descriptors. */ +#include + #include "ima.h" #include "ima_template_lib.h" @@ -32,6 +34,35 @@ static struct ima_template_field supported_fields[] = { }; static struct ima_template_desc *ima_template; +static struct ima_template_desc *lookup_template_desc(const char *name); + +static int __init ima_template_setup(char *str) +{ + struct ima_template_desc *template_desc; + int template_len = strlen(str); + + /* + * Verify that a template with the supplied name exists. + * If not, use CONFIG_IMA_DEFAULT_TEMPLATE. + */ + template_desc = lookup_template_desc(str); + if (!template_desc) + return 1; + + /* + * Verify whether the current hash algorithm is supported + * by the 'ima' template. + */ + if (template_len == 3 && strcmp(str, IMA_TEMPLATE_IMA_NAME) == 0 && + ima_hash_algo != HASH_ALGO_SHA1 && ima_hash_algo != HASH_ALGO_MD5) { + pr_err("IMA: template does not support hash alg\n"); + return 1; + } + + ima_template = template_desc; + return 1; +} +__setup("ima_template=", ima_template_setup); static struct ima_template_desc *lookup_template_desc(const char *name) { -- cgit v1.1 From e7a2ad7eb6f48ad80c70a22dd8167fb34b409466 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Fri, 7 Jun 2013 12:16:37 +0200 Subject: ima: enable support for larger default filedata hash algorithms The IMA measurement list contains two hashes - a template data hash and a filedata hash. The template data hash is committed to the TPM, which is limited, by the TPM v1.2 specification, to 20 bytes. The filedata hash is defined as 20 bytes as well. Now that support for variable length measurement list templates was added, the filedata hash is not limited to 20 bytes. This patch adds Kconfig support for defining larger default filedata hash algorithms and replacing the builtin default with one specified on the kernel command line. contains a list of hash algorithms. The Kconfig default hash algorithm is a subset of this list, but any hash algorithm included in the list can be specified at boot, using the 'ima_hash=' kernel command line option. Changelog v2: - update Kconfig Changelog: - support hashes that are configured - use generic HASH_ALGO_ definitions - add Kconfig support - hash_setup must be called only once (Dmitry) - removed trailing whitespaces (Roberto Sassu) Signed-off-by: Mimi Zohar Signed-off-by: Roberto Sassu --- security/integrity/ima/Kconfig | 35 +++++++++++++++++++++++++++++++++++ security/integrity/ima/ima_main.c | 26 ++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index de26cc8..351a58e 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -71,6 +71,41 @@ config IMA_DEFAULT_TEMPLATE default "ima" if IMA_TEMPLATE default "ima-ng" if IMA_NG_TEMPLATE +choice + prompt "Default integrity hash algorithm" + default IMA_DEFAULT_HASH_SHA1 + depends on IMA + help + Select the default hash algorithm used for the measurement + list, integrity appraisal and audit log. The compiled default + hash algorithm can be overwritten using the kernel command + line 'ima_hash=' option. + + config IMA_DEFAULT_HASH_SHA1 + bool "SHA1 (default)" + depends on CRYPTO_SHA1 + + config IMA_DEFAULT_HASH_SHA256 + bool "SHA256" + depends on CRYPTO_SHA256 && !IMA_TEMPLATE + + config IMA_DEFAULT_HASH_SHA512 + bool "SHA512" + depends on CRYPTO_SHA512 && !IMA_TEMPLATE + + config IMA_DEFAULT_HASH_WP512 + bool "WP512" + depends on CRYPTO_WP512 && !IMA_TEMPLATE +endchoice + +config IMA_DEFAULT_HASH + string + depends on IMA + default "sha1" if IMA_DEFAULT_HASH_SHA1 + default "sha256" if IMA_DEFAULT_HASH_SHA256 + default "sha512" if IMA_DEFAULT_HASH_SHA512 + default "wp512" if IMA_DEFAULT_HASH_WP512 + config IMA_APPRAISE bool "Appraise integrity measurements" depends on IMA diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 0b11bb4..14d4cb5 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -37,11 +37,32 @@ int ima_appraise; #endif int ima_hash_algo = HASH_ALGO_SHA1; +static int hash_setup_done; static int __init hash_setup(char *str) { - if (strncmp(str, "md5", 3) == 0) - ima_hash_algo = HASH_ALGO_MD5; + struct ima_template_desc *template_desc = ima_template_desc_current(); + int i; + + if (hash_setup_done) + return 1; + + if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) { + if (strncmp(str, "sha1", 4) == 0) + ima_hash_algo = HASH_ALGO_SHA1; + else if (strncmp(str, "md5", 3) == 0) + ima_hash_algo = HASH_ALGO_MD5; + goto out; + } + + for (i = 0; i < HASH_ALGO__LAST; i++) { + if (strcmp(str, hash_algo_name[i]) == 0) { + ima_hash_algo = i; + break; + } + } +out: + hash_setup_done = 1; return 1; } __setup("ima_hash=", hash_setup); @@ -306,6 +327,7 @@ static int __init init_ima(void) { int error; + hash_setup(CONFIG_IMA_DEFAULT_HASH); error = ima_init(); if (!error) ima_initialized = 1; -- cgit v1.1 From 3ea7a56067e663278470c04fd655adf809e72d4d Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Mon, 12 Aug 2013 11:22:51 +0300 Subject: ima: provide hash algo info in the xattr All files labeled with 'security.ima' hashes, are hashed using the same hash algorithm. Changing from one hash algorithm to another, requires relabeling the filesystem. This patch defines a new xattr type, which includes the hash algorithm, permitting different files to be hashed with different algorithms. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 61 +++++++++++++++++++++++++++-------- security/integrity/integrity.h | 13 +++++++- 2 files changed, 59 insertions(+), 15 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 116630c..734e946 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "ima.h" @@ -45,10 +46,22 @@ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) static int ima_fix_xattr(struct dentry *dentry, struct integrity_iint_cache *iint) { - iint->ima_hash->type = IMA_XATTR_DIGEST; - return __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, - &iint->ima_hash->type, - 1 + iint->ima_hash->length, 0); + int rc, offset; + u8 algo = iint->ima_hash->algo; + + if (algo <= HASH_ALGO_SHA1) { + offset = 1; + iint->ima_hash->xattr.sha1.type = IMA_XATTR_DIGEST; + } else { + offset = 0; + iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG; + iint->ima_hash->xattr.ng.algo = algo; + } + rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, + &iint->ima_hash->xattr.data[offset], + (sizeof(iint->ima_hash->xattr) - offset) + + iint->ima_hash->length, 0); + return rc; } /* Return specific func appraised cached result */ @@ -112,15 +125,31 @@ void ima_get_hash_algo(struct evm_ima_xattr_data *xattr_value, int xattr_len, { struct signature_v2_hdr *sig; - if (!xattr_value || xattr_len < 0 || xattr_len <= 1 + sizeof(*sig)) + if (!xattr_value || xattr_len < 2) return; - sig = (typeof(sig)) xattr_value->digest; - - if (xattr_value->type != EVM_IMA_XATTR_DIGSIG || sig->version != 2) - return; - - hash->algo = sig->hash_algo; + switch (xattr_value->type) { + case EVM_IMA_XATTR_DIGSIG: + sig = (typeof(sig))xattr_value; + if (sig->version != 2 || xattr_len <= sizeof(*sig)) + return; + hash->algo = sig->hash_algo; + break; + case IMA_XATTR_DIGEST_NG: + hash->algo = xattr_value->digest[0]; + break; + case IMA_XATTR_DIGEST: + /* this is for backward compatibility */ + if (xattr_len == 21) { + unsigned int zero = 0; + if (!memcmp(&xattr_value->digest[16], &zero, 4)) + hash->algo = HASH_ALGO_MD5; + else + hash->algo = HASH_ALGO_SHA1; + } else if (xattr_len == 17) + hash->algo = HASH_ALGO_MD5; + break; + } } int ima_read_xattr(struct dentry *dentry, @@ -153,7 +182,7 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, enum integrity_status status = INTEGRITY_UNKNOWN; const char *op = "appraise_data"; char *cause = "unknown"; - int rc = xattr_len; + int rc = xattr_len, hash_start = 0; if (!ima_appraise) return 0; @@ -180,17 +209,21 @@ int ima_appraise_measurement(int func, struct integrity_iint_cache *iint, goto out; } switch (xattr_value->type) { + case IMA_XATTR_DIGEST_NG: + /* first byte contains algorithm id */ + hash_start = 1; case IMA_XATTR_DIGEST: if (iint->flags & IMA_DIGSIG_REQUIRED) { cause = "IMA signature required"; status = INTEGRITY_FAIL; break; } - if (xattr_len - 1 >= iint->ima_hash->length) + if (xattr_len - sizeof(xattr_value->type) - hash_start >= + iint->ima_hash->length) /* xattr length may be longer. md5 hash in previous version occupied 20 bytes in xattr, instead of 16 */ - rc = memcmp(xattr_value->digest, + rc = memcmp(&xattr_value->digest[hash_start], iint->ima_hash->digest, iint->ima_hash->length); else diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 5429ca5..2fb5e53 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -54,6 +54,7 @@ enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, EVM_XATTR_HMAC, EVM_IMA_XATTR_DIGSIG, + IMA_XATTR_DIGEST_NG, }; struct evm_ima_xattr_data { @@ -66,7 +67,17 @@ struct evm_ima_xattr_data { struct ima_digest_data { u8 algo; u8 length; - u8 type; + union { + struct { + u8 unused; + u8 type; + } sha1; + struct { + u8 type; + u8 algo; + } ng; + u8 data[2]; + } xattr; u8 digest[0]; } __packed; -- cgit v1.1 From b5dfd8075bc26636d11c3d8888940198afbf5112 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Tue, 22 Oct 2013 11:47:45 -0700 Subject: Smack: Ptrace access check mode When the ptrace security hooks were split the addition of a mode parameter was not taken advantage of in the Smack ptrace access check. This changes the access check from always looking for read and write access to using the passed mode. This will make use of /proc much happier. Targeted for git://git.gitorious.org/smack-next/kernel.git Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 88d366e5..b0be893 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -185,7 +185,7 @@ static int smack_ptrace_access_check(struct task_struct *ctp, unsigned int mode) smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); smk_ad_setfield_u_tsk(&ad, ctp); - rc = smk_curacc(skp->smk_known, MAY_READWRITE, &ad); + rc = smk_curacc(skp->smk_known, mode, &ad); return rc; } -- cgit v1.1 From dd0c6e86f66080869ca0a48c78fb9bfbe4cf156f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 8 Oct 2013 05:37:18 -0700 Subject: apparmor: fix capability to not use the current task, during reporting Mediation is based off of the cred but auditing includes the current task which may not be related to the actual request. Signed-off-by: John Johansen --- security/apparmor/capability.c | 15 +++++---------- security/apparmor/domain.c | 2 +- security/apparmor/include/capability.h | 5 ++--- security/apparmor/include/ipc.h | 4 ++-- security/apparmor/ipc.c | 9 ++++----- security/apparmor/lsm.c | 2 +- 6 files changed, 15 insertions(+), 22 deletions(-) (limited to 'security') diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 84d1f5f..1101c6f 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -53,8 +53,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) /** * audit_caps - audit a capability - * @profile: profile confining task (NOT NULL) - * @task: task capability test was performed against (NOT NULL) + * @profile: profile being tested for confinement (NOT NULL) * @cap: capability tested * @error: error code returned by test * @@ -63,8 +62,7 @@ static void audit_cb(struct audit_buffer *ab, void *va) * * Returns: 0 or sa->error on success, error code on failure */ -static int audit_caps(struct aa_profile *profile, struct task_struct *task, - int cap, int error) +static int audit_caps(struct aa_profile *profile, int cap, int error) { struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -73,7 +71,6 @@ static int audit_caps(struct aa_profile *profile, struct task_struct *task, sa.type = LSM_AUDIT_DATA_CAP; sa.aad = &aad; sa.u.cap = cap; - sa.aad->tsk = task; sa.aad->op = OP_CAPABLE; sa.aad->error = error; @@ -124,8 +121,7 @@ static int profile_capable(struct aa_profile *profile, int cap) /** * aa_capable - test permission to use capability - * @task: task doing capability test against (NOT NULL) - * @profile: profile confining @task (NOT NULL) + * @profile: profile being tested against (NOT NULL) * @cap: capability to be tested * @audit: whether an audit record should be generated * @@ -133,8 +129,7 @@ static int profile_capable(struct aa_profile *profile, int cap) * * Returns: 0 on success, or else an error code. */ -int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, - int audit) +int aa_capable(struct aa_profile *profile, int cap, int audit) { int error = profile_capable(profile, cap); @@ -144,5 +139,5 @@ int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, return error; } - return audit_caps(profile, task, cap, error); + return audit_caps(profile, cap, error); } diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 26c607c..e5538a1 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -75,7 +75,7 @@ static int may_change_ptraced_domain(struct task_struct *task, if (!tracer || unconfined(tracerp)) goto out; - error = aa_may_ptrace(tracer, tracerp, to_profile, PTRACE_MODE_ATTACH); + error = aa_may_ptrace(tracerp, to_profile, PTRACE_MODE_ATTACH); out: rcu_read_unlock(); diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index 2e7c9d6..fc3fa38 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -4,7 +4,7 @@ * This file contains AppArmor capability mediation definitions. * * Copyright (C) 1998-2008 Novell/SUSE - * Copyright 2009-2010 Canonical Ltd. + * Copyright 2009-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 @@ -38,8 +38,7 @@ struct aa_caps { extern struct aa_fs_entry aa_fs_entry_caps[]; -int aa_capable(struct task_struct *task, struct aa_profile *profile, int cap, - int audit); +int aa_capable(struct aa_profile *profile, int cap, int audit); static inline void aa_free_cap_rules(struct aa_caps *caps) { diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index aeda0fbc..288ca76 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -19,8 +19,8 @@ struct aa_profile; -int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, - struct aa_profile *tracee, unsigned int mode); +int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, + unsigned int mode); int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, unsigned int mode); diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index c51d226..777ac1c 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -54,15 +54,14 @@ static int aa_audit_ptrace(struct aa_profile *profile, /** * aa_may_ptrace - test if tracer task can trace the tracee - * @tracer_task: task who will do the tracing (NOT NULL) * @tracer: profile of the task doing the tracing (NOT NULL) * @tracee: task to be traced * @mode: whether PTRACE_MODE_READ || PTRACE_MODE_ATTACH * * Returns: %0 else error code if permission denied or error */ -int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, - struct aa_profile *tracee, unsigned int mode) +int aa_may_ptrace(struct aa_profile *tracer, struct aa_profile *tracee, + unsigned int mode) { /* TODO: currently only based on capability, not extended ptrace * rules, @@ -72,7 +71,7 @@ int aa_may_ptrace(struct task_struct *tracer_task, struct aa_profile *tracer, if (unconfined(tracer) || tracer == tracee) return 0; /* log this capability request */ - return aa_capable(tracer_task, tracer, CAP_SYS_PTRACE, 1); + return aa_capable(tracer, CAP_SYS_PTRACE, 1); } /** @@ -101,7 +100,7 @@ int aa_ptrace(struct task_struct *tracer, struct task_struct *tracee, if (!unconfined(tracer_p)) { struct aa_profile *tracee_p = aa_get_task_profile(tracee); - error = aa_may_ptrace(tracer, tracer_p, tracee_p, mode); + error = aa_may_ptrace(tracer_p, tracee_p, mode); error = aa_audit_ptrace(tracer_p, tracee_p, error); aa_put_profile(tracee_p); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index fb99e18..4257b7e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -145,7 +145,7 @@ static int apparmor_capable(const struct cred *cred, struct user_namespace *ns, if (!error) { profile = aa_cred_profile(cred); if (!unconfined(profile)) - error = aa_capable(current, profile, cap, audit); + error = aa_capable(profile, cap, audit); } return error; } -- cgit v1.1 From 61e3fb8acaea0ca4303ef123bae7edf8435dc2b7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 8 Oct 2013 05:37:26 -0700 Subject: apparmor: remove tsk field from the apparmor_audit_struct Now that aa_capabile no longer sets the task field it can be removed and the lsm_audit version of the field can be used. Signed-off-by: John Johansen --- security/apparmor/audit.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'security') diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 031d2d9..e32c448 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -111,7 +111,7 @@ static const char *const aa_audit_type[] = { static void audit_pre(struct audit_buffer *ab, void *ca) { struct common_audit_data *sa = ca; - struct task_struct *tsk = sa->aad->tsk ? sa->aad->tsk : current; + struct task_struct *tsk = sa->u.tsk ? sa->u.tsk : current; if (aa_g_audit_header) { audit_log_format(ab, "apparmor="); @@ -149,12 +149,6 @@ static void audit_pre(struct audit_buffer *ab, void *ca) audit_log_format(ab, " name="); audit_log_untrustedstring(ab, sa->aad->name); } - - if (sa->aad->tsk) { - audit_log_format(ab, " pid=%d comm=", tsk->pid); - audit_log_untrustedstring(ab, tsk->comm); - } - } /** @@ -212,7 +206,7 @@ int aa_audit(int type, struct aa_profile *profile, gfp_t gfp, if (sa->aad->type == AUDIT_APPARMOR_KILL) (void)send_sig_info(SIGKILL, NULL, - sa->aad->tsk ? sa->aad->tsk : current); + sa->u.tsk ? sa->u.tsk : current); if (sa->aad->type == AUDIT_APPARMOR_ALLOWED) return complain_error(sa->aad->error); -- cgit v1.1 From 4a7fc3018f05f4305723b508b12f3be13b7c4875 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 8 Oct 2013 05:39:02 -0700 Subject: apparmor: remove parent task info from audit logging The reporting of the parent task info is a vestage from old versions of apparmor. The need for this information was removed by unique null- profiles before apparmor was upstreamed so remove this info from logging. Signed-off-by: John Johansen --- security/apparmor/audit.c | 6 ------ security/apparmor/include/audit.h | 1 - 2 files changed, 7 deletions(-) (limited to 'security') diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index e32c448..89c7865 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -111,7 +111,6 @@ static const char *const aa_audit_type[] = { static void audit_pre(struct audit_buffer *ab, void *ca) { struct common_audit_data *sa = ca; - struct task_struct *tsk = sa->u.tsk ? sa->u.tsk : current; if (aa_g_audit_header) { audit_log_format(ab, "apparmor="); @@ -132,11 +131,6 @@ static void audit_pre(struct audit_buffer *ab, void *ca) if (sa->aad->profile) { struct aa_profile *profile = sa->aad->profile; - pid_t pid; - rcu_read_lock(); - pid = rcu_dereference(tsk->real_parent)->pid; - rcu_read_unlock(); - audit_log_format(ab, " parent=%d", pid); if (profile->ns != root_ns) { audit_log_format(ab, " namespace="); audit_log_untrustedstring(ab, profile->ns->base.hname); diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 30e8d76..ba3dfd1 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -109,7 +109,6 @@ struct apparmor_audit_data { void *profile; const char *name; const char *info; - struct task_struct *tsk; union { void *target; struct { -- cgit v1.1 From 51775fe736f053329faf0f5de7c679ee4cb0023d Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Tue, 8 Oct 2013 05:46:03 -0700 Subject: apparmor: remove the "task" arg from may_change_ptraced_domain() Unless task == current ptrace_parent(task) is not safe even under rcu_read_lock() and most of the current users are not right. So may_change_ptraced_domain(task) looks wrong as well. However it is always called with task == current so the code is actually fine. Remove this argument to make this fact clear. Note: perhaps we should simply kill ptrace_parent(), it buys almost nothing. And it is obviously racy, perhaps this should be fixed. Signed-off-by: Oleg Nesterov Signed-off-by: John Johansen --- security/apparmor/domain.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'security') diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index e5538a1..452567d 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -50,23 +50,21 @@ void aa_free_domain_entries(struct aa_domain *domain) /** * may_change_ptraced_domain - check if can change profile on ptraced task - * @task: task we want to change profile of (NOT NULL) * @to_profile: profile to change to (NOT NULL) * - * Check if the task is ptraced and if so if the tracing task is allowed + * Check if current is ptraced and if so if the tracing task is allowed * to trace the new domain * * Returns: %0 or error if change not allowed */ -static int may_change_ptraced_domain(struct task_struct *task, - struct aa_profile *to_profile) +static int may_change_ptraced_domain(struct aa_profile *to_profile) { struct task_struct *tracer; struct aa_profile *tracerp = NULL; int error = 0; rcu_read_lock(); - tracer = ptrace_parent(task); + tracer = ptrace_parent(current); if (tracer) /* released below */ tracerp = aa_get_task_profile(tracer); @@ -477,7 +475,7 @@ int apparmor_bprm_set_creds(struct linux_binprm *bprm) } if (bprm->unsafe & (LSM_UNSAFE_PTRACE | LSM_UNSAFE_PTRACE_CAP)) { - error = may_change_ptraced_domain(current, new_profile); + error = may_change_ptraced_domain(new_profile); if (error) { aa_put_profile(new_profile); goto audit; @@ -690,7 +688,7 @@ int aa_change_hat(const char *hats[], int count, u64 token, bool permtest) } } - error = may_change_ptraced_domain(current, hat); + error = may_change_ptraced_domain(hat); if (error) { info = "ptraced"; error = -EPERM; @@ -829,7 +827,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec, } /* check if tracing task is allowed to trace target domain */ - error = may_change_ptraced_domain(current, target); + error = may_change_ptraced_domain(target); if (error) { info = "ptrace prevents transition"; goto audit; -- cgit v1.1 From 2eaf6b5dcafda2b8c22930eff7f48a364fce1741 Mon Sep 17 00:00:00 2001 From: Josh Boyer Date: Wed, 30 Oct 2013 11:15:23 +0000 Subject: KEYS: Make BIG_KEYS boolean Having the big_keys functionality as a module is very marginally useful. The userspace code that would use this functionality will get odd error messages from the keys layer if the module isn't loaded. The code itself is fairly small, so just have this as a boolean option and not a tristate. Signed-off-by: Josh Boyer Signed-off-by: David Howells --- security/keys/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/keys/Kconfig b/security/keys/Kconfig index 53d8748..a4f3f8c 100644 --- a/security/keys/Kconfig +++ b/security/keys/Kconfig @@ -38,7 +38,7 @@ config PERSISTENT_KEYRINGS removed if they expire (a default timeout is set upon creation). config BIG_KEYS - tristate "Large payload keys" + bool "Large payload keys" depends on KEYS depends on TMPFS help -- cgit v1.1 From 74792b0001ee85b845dc82c1a716c6052c2db9de Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 30 Oct 2013 11:15:24 +0000 Subject: KEYS: Fix a race between negating a key and reading the error set key_reject_and_link() marking a key as negative and setting the error with which it was negated races with keyring searches and other things that read that error. The fix is to switch the order in which the assignments are done in key_reject_and_link() and to use memory barriers. Kudos to Dave Wysochanski and Scott Mayhew for tracking this down. This may be the cause of: BUG: unable to handle kernel NULL pointer dereference at 0000000000000070 IP: [] wait_for_key_construction+0x31/0x80 PGD c6b2c3067 PUD c59879067 PMD 0 Oops: 0000 [#1] SMP last sysfs file: /sys/devices/system/cpu/cpu3/cache/index2/shared_cpu_map CPU 0 Modules linked in: ... Pid: 13359, comm: amqzxma0 Not tainted 2.6.32-358.20.1.el6.x86_64 #1 IBM System x3650 M3 -[7945PSJ]-/00J6159 RIP: 0010:[] wait_for_key_construction+0x31/0x80 RSP: 0018:ffff880c6ab33758 EFLAGS: 00010246 RAX: ffffffff81219080 RBX: 0000000000000000 RCX: 0000000000000002 RDX: ffffffff81219060 RSI: 0000000000000000 RDI: 0000000000000000 RBP: ffff880c6ab33768 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000001 R11: 0000000000000000 R12: ffff880adfcbce40 R13: ffffffffa03afb84 R14: ffff880adfcbce40 R15: ffff880adfcbce43 FS: 00007f29b8042700(0000) GS:ffff880028200000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 0000000000000070 CR3: 0000000c613dc000 CR4: 00000000000007f0 DR0: 0000000000000000 DR1: 0000000000000000 DR2: 0000000000000000 DR3: 0000000000000000 DR6: 00000000ffff0ff0 DR7: 0000000000000400 Process amqzxma0 (pid: 13359, threadinfo ffff880c6ab32000, task ffff880c610deae0) Stack: ffff880adfcbce40 0000000000000000 ffff880c6ab337b8 ffffffff81219695 0000000000000000 ffff880a000000d0 ffff880c6ab337a8 000000000000000f ffffffffa03afb93 000000000000000f ffff88186c7882c0 0000000000000014 Call Trace: [] request_key+0x65/0xa0 [] nfs_idmap_request_key+0xc5/0x170 [nfs] [] nfs_idmap_lookup_id+0x34/0x80 [nfs] [] nfs_map_group_to_gid+0x75/0xa0 [nfs] [] decode_getfattr_attrs+0xbdd/0xfb0 [nfs] [] ? __dequeue_entity+0x30/0x50 [] ? __switch_to+0x26e/0x320 [] decode_getfattr+0x83/0xe0 [nfs] [] ? nfs4_xdr_dec_getattr+0x0/0xa0 [nfs] [] nfs4_xdr_dec_getattr+0x8f/0xa0 [nfs] [] rpcauth_unwrap_resp+0x84/0xb0 [sunrpc] [] ? nfs4_xdr_dec_getattr+0x0/0xa0 [nfs] [] call_decode+0x1b3/0x800 [sunrpc] [] ? wake_bit_function+0x0/0x50 [] ? call_decode+0x0/0x800 [sunrpc] [] __rpc_execute+0x77/0x350 [sunrpc] [] ? bit_waitqueue+0x17/0xd0 [] rpc_execute+0x61/0xa0 [sunrpc] [] rpc_run_task+0x75/0x90 [sunrpc] [] rpc_call_sync+0x42/0x70 [sunrpc] [] _nfs4_call_sync+0x30/0x40 [nfs] [] _nfs4_proc_getattr+0xac/0xc0 [nfs] [] ? futex_wait+0x227/0x380 [] nfs4_proc_getattr+0x56/0x80 [nfs] [] __nfs_revalidate_inode+0xe3/0x220 [nfs] [] nfs_revalidate_mapping+0x4e/0x170 [nfs] [] nfs_file_read+0x77/0x130 [nfs] [] do_sync_read+0xfa/0x140 [] ? autoremove_wake_function+0x0/0x40 [] ? apic_timer_interrupt+0xe/0x20 [] ? common_interrupt+0xe/0x13 [] ? selinux_file_permission+0xfb/0x150 [] ? security_file_permission+0x16/0x20 [] vfs_read+0xb5/0x1a0 [] sys_read+0x51/0x90 [] ? __audit_syscall_exit+0x265/0x290 [] system_call_fastpath+0x16/0x1b Signed-off-by: David Howells cc: Dave Wysochanski cc: Scott Mayhew --- security/keys/key.c | 3 ++- security/keys/keyring.c | 1 + security/keys/request_key.c | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/keys/key.c b/security/keys/key.c index d331ea9..55d110f 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -557,9 +557,10 @@ int key_reject_and_link(struct key *key, if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { /* mark the key as being negatively instantiated */ atomic_inc(&key->user->nikeys); + key->type_data.reject_error = -error; + smp_wmb(); set_bit(KEY_FLAG_NEGATIVE, &key->flags); set_bit(KEY_FLAG_INSTANTIATED, &key->flags); - key->type_data.reject_error = -error; now = current_kernel_time(); key->expiry = now.tv_sec + timeout; key_schedule_gc(key->expiry + key_gc_delay); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 9b6f6e0..8c05ebd 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -551,6 +551,7 @@ static int keyring_search_iterator(const void *object, void *iterator_data) if (ctx->flags & KEYRING_SEARCH_DO_STATE_CHECK) { /* we set a different error code if we pass a negative key */ if (kflags & (1 << KEY_FLAG_NEGATIVE)) { + smp_rmb(); ctx->result = ERR_PTR(key->type_data.reject_error); kleave(" = %d [neg]", ctx->skipped_ret); goto skipped; diff --git a/security/keys/request_key.c b/security/keys/request_key.c index df94827..3814119 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -596,8 +596,10 @@ int wait_for_key_construction(struct key *key, bool intr) intr ? TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE); if (ret < 0) return ret; - if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) + if (test_bit(KEY_FLAG_NEGATIVE, &key->flags)) { + smp_rmb(); return key->type_data.reject_error; + } return key_validate(key); } EXPORT_SYMBOL(wait_for_key_construction); -- cgit v1.1 From 034faeb9ef390d58239e1dce748143f6b35a0d9b Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 30 Oct 2013 11:15:24 +0000 Subject: KEYS: Fix keyring quota misaccounting on key replacement and unlink If a key is displaced from a keyring by a matching one, then four more bytes of quota are allocated to the keyring - despite the fact that the keyring does not change in size. Further, when a key is unlinked from a keyring, the four bytes of quota allocated the link isn't recovered and returned to the user's pool. The first can be tested by repeating: keyctl add big_key a fred @s cat /proc/key-users (Don't put it in a shell loop otherwise the garbage collector won't have time to clear the displaced keys, thus affecting the result). This was causing the kerberos keyring to run out of room fairly quickly. The second can be tested by: cat /proc/key-users a=`keyctl add user a a @s` cat /proc/key-users keyctl unlink $a sleep 1 # Give RCU a chance to delete the key cat /proc/key-users assuming no system activity that otherwise adds/removes keys, the amount of key data allocated should go up (say 40/20000 -> 47/20000) and then return to the original value at the end. Reported-by: Stephen Gallagher Signed-off-by: David Howells --- security/keys/keyring.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 8c05ebd..d80311e 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -1063,12 +1063,6 @@ int __key_link_begin(struct key *keyring, if (index_key->type == &key_type_keyring) down_write(&keyring_serialise_link_sem); - /* check that we aren't going to overrun the user's quota */ - ret = key_payload_reserve(keyring, - keyring->datalen + KEYQUOTA_LINK_BYTES); - if (ret < 0) - goto error_sem; - /* Create an edit script that will insert/replace the key in the * keyring tree. */ @@ -1078,17 +1072,25 @@ int __key_link_begin(struct key *keyring, NULL); if (IS_ERR(edit)) { ret = PTR_ERR(edit); - goto error_quota; + goto error_sem; + } + + /* If we're not replacing a link in-place then we're going to need some + * extra quota. + */ + if (!edit->dead_leaf) { + ret = key_payload_reserve(keyring, + keyring->datalen + KEYQUOTA_LINK_BYTES); + if (ret < 0) + goto error_cancel; } *_edit = edit; kleave(" = 0"); return 0; -error_quota: - /* undo the quota changes */ - key_payload_reserve(keyring, - keyring->datalen - KEYQUOTA_LINK_BYTES); +error_cancel: + assoc_array_cancel_edit(edit); error_sem: if (index_key->type == &key_type_keyring) up_write(&keyring_serialise_link_sem); @@ -1146,7 +1148,7 @@ void __key_link_end(struct key *keyring, if (index_key->type == &key_type_keyring) up_write(&keyring_serialise_link_sem); - if (edit) { + if (edit && !edit->dead_leaf) { key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); assoc_array_cancel_edit(edit); @@ -1243,6 +1245,7 @@ int key_unlink(struct key *keyring, struct key *key) goto error; assoc_array_apply_edit(edit); + key_payload_reserve(keyring, keyring->datalen - KEYQUOTA_LINK_BYTES); ret = 0; error: -- cgit v1.1 From d2b86970245b64652c4d7799e707dd8bd1533b64 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 30 Oct 2013 11:23:02 +0800 Subject: KEYS: fix error return code in big_key_instantiate() Fix to return a negative error code from the error handling case instead of 0, as done elsewhere in this function. Signed-off-by: Wei Yongjun Signed-off-by: David Howells --- security/keys/big_key.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/keys/big_key.c b/security/keys/big_key.c index 5f9defc..2cf5e62 100644 --- a/security/keys/big_key.c +++ b/security/keys/big_key.c @@ -71,8 +71,10 @@ int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep) * TODO: Encrypt the stored data with a temporary key. */ file = shmem_file_setup("", datalen, 0); - if (IS_ERR(file)) + if (IS_ERR(file)) { + ret = PTR_ERR(file); goto err_quota; + } written = kernel_write(file, prep->data, prep->datalen, 0); if (written != datalen) { -- cgit v1.1 From bcbc9b0cf6d8f340a1d166e414f4612b353f7a9b Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 23 Jul 2013 11:15:00 -0400 Subject: ima: extend the measurement list to include the file signature This patch defines a new template called 'ima-sig', which includes the file signature in the template data, in addition to the file's digest and pathname. A template is composed of a set of fields. Associated with each field is an initialization and display function. This patch defines a new template field called 'sig', the initialization function ima_eventsig_init(), and the display function ima_show_template_sig(). This patch modifies the .field_init() function definition to include the 'security.ima' extended attribute and length. Changelog: - remove unused code (Dmitry Kasatkin) - avoid calling ima_write_template_field_data() unnecesarily (Roberto Sassu) - rename DATA_FMT_SIG to DATA_FMT_HEX - cleanup ima_eventsig_init() based on Roberto's comments Signed-off-by: Mimi Zohar Signed-off-by: Dmitry Kasatkin Signed-off-by: Roberto Sassu --- security/integrity/ima/Kconfig | 3 +++ security/integrity/ima/ima.h | 10 +++++--- security/integrity/ima/ima_api.c | 14 ++++++++---- security/integrity/ima/ima_init.c | 2 +- security/integrity/ima/ima_main.c | 3 ++- security/integrity/ima/ima_template.c | 3 +++ security/integrity/ima/ima_template_lib.c | 38 +++++++++++++++++++++++++++++-- security/integrity/ima/ima_template_lib.h | 12 +++++++++- 8 files changed, 73 insertions(+), 12 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 351a58e..81a2797 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -63,6 +63,8 @@ choice bool "ima" config IMA_NG_TEMPLATE bool "ima-ng (default)" + config IMA_SIG_TEMPLATE + bool "ima-sig" endchoice config IMA_DEFAULT_TEMPLATE @@ -70,6 +72,7 @@ config IMA_DEFAULT_TEMPLATE depends on IMA default "ima" if IMA_TEMPLATE default "ima-ng" if IMA_NG_TEMPLATE + default "ima-sig" if IMA_SIG_TEMPLATE choice prompt "Default integrity hash algorithm" diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 72d013e..bf03c6a 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -59,7 +59,8 @@ struct ima_template_field { const char field_id[IMA_TEMPLATE_FIELD_ID_MAX_LEN]; int (*field_init) (struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, - struct ima_field_data *field_data); + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data); void (*field_show) (struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); }; @@ -134,12 +135,15 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, struct evm_ima_xattr_data **xattr_value, int *xattr_len); void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, - const unsigned char *filename); + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len); void ima_audit_measurement(struct integrity_iint_cache *iint, const unsigned char *filename); int ima_alloc_init_template(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, - struct ima_template_entry **entry); + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_template_entry **entry); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode, const unsigned char *filename); const char *ima_d_path(struct path *path, char **pathbuf); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 5fcc806..0e75408 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -26,7 +26,8 @@ */ int ima_alloc_init_template(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, - struct ima_template_entry **entry) + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_template_entry **entry) { struct ima_template_desc *template_desc = ima_template_desc_current(); int i, result = 0; @@ -41,6 +42,7 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, u32 len; result = field->field_init(iint, file, filename, + xattr_value, xattr_len, &((*entry)->template_data[i])); if (result != 0) goto out; @@ -123,7 +125,8 @@ void ima_add_violation(struct file *file, const unsigned char *filename, /* can overflow, only indicator */ atomic_long_inc(&ima_htable.violations); - result = ima_alloc_init_template(NULL, file, filename, &entry); + result = ima_alloc_init_template(NULL, file, filename, + NULL, 0, &entry); if (result < 0) { result = -ENOMEM; goto err_out; @@ -239,7 +242,9 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, * Must be called with iint->mutex held. */ void ima_store_measurement(struct integrity_iint_cache *iint, - struct file *file, const unsigned char *filename) + struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, + int xattr_len) { const char *op = "add_template_measure"; const char *audit_cause = "ENOMEM"; @@ -251,7 +256,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (iint->flags & IMA_MEASURED) return; - result = ima_alloc_init_template(iint, file, filename, &entry); + result = ima_alloc_init_template(iint, file, filename, + xattr_value, xattr_len, &entry); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, audit_cause, result, 0); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index f84aec5..15f34bd 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -69,7 +69,7 @@ static void __init ima_add_boot_aggregate(void) } result = ima_alloc_init_template(iint, NULL, boot_aggregate_name, - &entry); + NULL, 0, &entry); if (result < 0) return; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 14d4cb5..149ee11 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -225,7 +225,8 @@ static int process_measurement(struct file *file, const char *filename, pathname = (const char *)file->f_dentry->d_name.name; if (action & IMA_MEASURE) - ima_store_measurement(iint, file, pathname); + ima_store_measurement(iint, file, pathname, + xattr_value, xattr_len); if (action & IMA_APPRAISE_SUBMASK) rc = ima_appraise_measurement(_func, iint, file, pathname, xattr_value, xattr_len); diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 0002214..4e5da99 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -20,6 +20,7 @@ static struct ima_template_desc defined_templates[] = { {.name = IMA_TEMPLATE_IMA_NAME, .fmt = IMA_TEMPLATE_IMA_FMT}, {.name = "ima-ng",.fmt = "d-ng|n-ng"}, + {.name = "ima-sig",.fmt = "d-ng|n-ng|sig"}, }; static struct ima_template_field supported_fields[] = { @@ -31,6 +32,8 @@ static struct ima_template_field supported_fields[] = { .field_show = ima_show_template_digest_ng}, {.field_id = "n-ng",.field_init = ima_eventname_ng_init, .field_show = ima_show_template_string}, + {.field_id = "sig",.field_init = ima_eventsig_init, + .field_show = ima_show_template_sig}, }; static struct ima_template_desc *ima_template; diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 7d84144..6d66ad6 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -28,7 +28,8 @@ enum data_formats { DATA_FMT_DIGEST = 0, DATA_FMT_DIGEST_WITH_ALGO, DATA_FMT_EVENT_NAME, - DATA_FMT_STRING + DATA_FMT_STRING, + DATA_FMT_HEX }; static int ima_write_template_field_data(const void *data, const u32 datalen, @@ -90,6 +91,9 @@ static void ima_show_template_data_ascii(struct seq_file *m, buf_ptr += 2; buflen -= buf_ptr - field_data->data; case DATA_FMT_DIGEST: + case DATA_FMT_HEX: + if (!buflen) + break; ima_print_digest(m, buf_ptr, buflen); break; case DATA_FMT_STRING: @@ -147,6 +151,12 @@ void ima_show_template_string(struct seq_file *m, enum ima_show_type show, ima_show_template_field_data(m, show, DATA_FMT_STRING, field_data); } +void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data) +{ + ima_show_template_field_data(m, show, DATA_FMT_HEX, field_data); +} + static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo, struct ima_field_data *field_data, bool size_limit) @@ -190,6 +200,7 @@ static int ima_eventdigest_init_common(u8 *digest, u32 digestsize, u8 hash_algo, */ int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data) { struct { @@ -237,7 +248,8 @@ out: */ int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, - struct ima_field_data *field_data) + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data) { u8 *cur_digest = NULL, hash_algo = HASH_ALGO__LAST; u32 cur_digestsize = 0; @@ -295,6 +307,7 @@ out: */ int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data) { return ima_eventname_init_common(iint, file, filename, @@ -306,8 +319,29 @@ int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, */ int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data) { return ima_eventname_init_common(iint, file, filename, field_data, false); } + +/* + * ima_eventsig_init - include the file signature as part of the template data + */ +int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data) +{ + enum data_formats fmt = DATA_FMT_HEX; + int rc = 0; + + if ((!xattr_value) || (xattr_value->type != EVM_IMA_XATTR_DIGSIG)) + goto out; + + rc = ima_write_template_field_data(xattr_value, xattr_len, fmt, + field_data); +out: + return rc; +} diff --git a/security/integrity/ima/ima_template_lib.h b/security/integrity/ima/ima_template_lib.h index 16c5e78..63f6b52 100644 --- a/security/integrity/ima/ima_template_lib.h +++ b/security/integrity/ima/ima_template_lib.h @@ -24,16 +24,26 @@ void ima_show_template_digest_ng(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); void ima_show_template_string(struct seq_file *m, enum ima_show_type show, struct ima_field_data *field_data); +void ima_show_template_sig(struct seq_file *m, enum ima_show_type show, + struct ima_field_data *field_data); int ima_eventdigest_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data); int ima_eventname_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data); int ima_eventdigest_ng_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, - struct ima_field_data *field_data); + struct evm_ima_xattr_data *xattr_value, + int xattr_len, struct ima_field_data *field_data); int ima_eventname_ng_init(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, struct ima_field_data *field_data); +int ima_eventsig_init(struct integrity_iint_cache *iint, struct file *file, + const unsigned char *filename, + struct evm_ima_xattr_data *xattr_value, int xattr_len, + struct ima_field_data *field_data); #endif /* __LINUX_IMA_TEMPLATE_LIB_H */ -- cgit v1.1 From 217091dd7a7a1bdac027ddb7c5a25f6ac0b8e241 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Tue, 13 Aug 2013 08:47:43 -0400 Subject: ima: define '_ima' as a builtin 'trusted' keyring Require all keys added to the IMA keyring be signed by an existing trusted key on the system trusted keyring. Changelog: - define stub integrity_init_keyring() function (reported-by Fengguang Wu) - differentiate between regular and trusted keyring names. - replace printk with pr_info (D. Kasatkin) Signed-off-by: Mimi Zohar --- security/integrity/digsig.c | 30 +++++++++++++++++++++++++++++- security/integrity/ima/Kconfig | 8 ++++++++ security/integrity/ima/ima_appraise.c | 11 +++++++++++ security/integrity/integrity.h | 7 +++++++ 4 files changed, 55 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index b4af4eb..77ca965 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -13,7 +13,9 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include +#include #include +#include #include #include @@ -21,11 +23,19 @@ static struct key *keyring[INTEGRITY_KEYRING_MAX]; +#ifdef CONFIG_IMA_TRUSTED_KEYRING +static const char *keyring_name[INTEGRITY_KEYRING_MAX] = { + ".evm", + ".module", + ".ima", +}; +#else static const char *keyring_name[INTEGRITY_KEYRING_MAX] = { "_evm", "_module", "_ima", }; +#endif int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, const char *digest, int digestlen) @@ -35,7 +45,7 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, if (!keyring[id]) { keyring[id] = - request_key(&key_type_keyring, keyring_name[id], NULL); + request_key(&key_type_keyring, keyring_name[id], NULL); if (IS_ERR(keyring[id])) { int err = PTR_ERR(keyring[id]); pr_err("no %s keyring: %d\n", keyring_name[id], err); @@ -56,3 +66,21 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, return -EOPNOTSUPP; } + +int integrity_init_keyring(const unsigned int id) +{ + const struct cred *cred = current_cred(); + const struct user_struct *user = cred->user; + + keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0), + KGIDT_INIT(0), cred, + ((KEY_POS_ALL & ~KEY_POS_SETATTR) | + KEY_USR_VIEW | KEY_USR_READ), + KEY_ALLOC_NOT_IN_QUOTA, user->uid_keyring); + if (!IS_ERR(keyring[id])) + set_bit(KEY_FLAG_TRUSTED_ONLY, &keyring[id]->flags); + else + pr_info("Can't allocate %s keyring (%ld)\n", + keyring_name[id], PTR_ERR(keyring[id])); + return 0; +} diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 81a2797..dad8d4c 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -123,3 +123,11 @@ config IMA_APPRAISE For more information on integrity appraisal refer to: If unsure, say N. + +config IMA_TRUSTED_KEYRING + bool "Require all keys on the _ima keyring be signed" + depends on IMA_APPRAISE && SYSTEM_TRUSTED_KEYRING + default y + help + This option requires that all keys added to the _ima + keyring be signed by a key on the system trusted keyring. diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 734e946..46353ee 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -381,3 +381,14 @@ int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name) } return result; } + +#ifdef CONFIG_IMA_TRUSTED_KEYRING +static int __init init_ima_keyring(void) +{ + int ret; + + ret = integrity_init_keyring(INTEGRITY_KEYRING_IMA); + return 0; +} +late_initcall(init_ima_keyring); +#endif diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 2fb5e53..b9e7c13 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -137,12 +137,19 @@ static inline int integrity_digsig_verify(const unsigned int id, #ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen); + +int integrity_init_keyring(const unsigned int id); #else static inline int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen) { return -EOPNOTSUPP; } + +static int integrity_init_keyring(const unsigned int id) +{ + return 0; +} #endif #ifdef CONFIG_INTEGRITY_AUDIT -- cgit v1.1 From b805b198dc74b73aabb6969a3db734c71c05c88c Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Fri, 24 May 2013 12:09:50 -0400 Subject: selinux: apply selinux checks on new audit message types We use the read check to get the feature set (like AUDIT_GET) and the write check to set the features (like AUDIT_SET). Signed-off-by: Eric Paris Signed-off-by: Richard Guy Briggs Signed-off-by: Eric Paris --- security/selinux/nlmsgtab.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'security') diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 855e464..332ac8a 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -116,6 +116,8 @@ static struct nlmsg_perm nlmsg_audit_perms[] = { AUDIT_MAKE_EQUIV, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, { AUDIT_TTY_GET, NETLINK_AUDIT_SOCKET__NLMSG_READ }, { AUDIT_TTY_SET, NETLINK_AUDIT_SOCKET__NLMSG_TTY_AUDIT }, + { AUDIT_GET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_READ }, + { AUDIT_SET_FEATURE, NETLINK_AUDIT_SOCKET__NLMSG_WRITE }, }; -- cgit v1.1 From a20b62bdf7a1ed1a334eff3c4cafa97f5826006b Mon Sep 17 00:00:00 2001 From: Richard Guy Briggs Date: Tue, 1 Oct 2013 21:14:54 -0400 Subject: audit: suppress stock memalloc failure warnings since already managed Supress the stock memory allocation failure warnings for audit buffers since audit alreay takes care of memory allocation failure warnings, including rate-limiting, in audit_log_start(). Signed-off-by: Richard Guy Briggs Signed-off-by: Eric Paris --- security/lsm_audit.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 8d8d97d..b0f249d 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -396,7 +396,8 @@ void common_lsm_audit(struct common_audit_data *a, if (a == NULL) return; /* we use GFP_ATOMIC so we won't sleep */ - ab = audit_log_start(current->audit_context, GFP_ATOMIC, AUDIT_AVC); + ab = audit_log_start(current->audit_context, GFP_ATOMIC | __GFP_NOWARN, + AUDIT_AVC); if (ab == NULL) return; -- cgit v1.1 From fbf8c53f1a2ac7610ed124043600dc074992e71b Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 6 Nov 2013 14:01:51 +0000 Subject: KEYS: Fix UID check in keyctl_get_persistent() If the UID is specified by userspace when calling the KEYCTL_GET_PERSISTENT function and the process does not have the CAP_SETUID capability, then the function will return -EPERM if the current process's uid, suid, euid and fsuid all match the requested UID. This is incorrect. Fix it such that when a non-privileged caller requests a persistent keyring by a specific UID they can only request their own (ie. the specified UID matches either then process's UID or the process's EUID). This can be tested by logging in as the user and doing: keyctl get_persistent @p keyctl get_persistent @p `id -u` keyctl get_persistent @p 0 The first two should successfully print the same key ID. The third should do the same if called by UID 0 or indicate Operation Not Permitted otherwise. Signed-off-by: David Howells Acked-by: Stephen Gallagher --- security/keys/persistent.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 82f4957a..0ad3ee2 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -144,10 +144,8 @@ long keyctl_get_persistent(uid_t _uid, key_serial_t destid) /* You can only see your own persistent cache if you're not * sufficiently privileged. */ - if (uid_eq(uid, current_uid()) && - uid_eq(uid, current_suid()) && - uid_eq(uid, current_euid()) && - uid_eq(uid, current_fsuid()) && + if (!uid_eq(uid, current_uid()) && + !uid_eq(uid, current_euid()) && !ns_capable(ns, CAP_SETUID)) return -EPERM; } -- cgit v1.1 From 97826c821ec6724fc359d9b7840dc10af914c641 Mon Sep 17 00:00:00 2001 From: David Howells Date: Wed, 13 Nov 2013 16:51:06 +0000 Subject: KEYS: Fix error handling in big_key instantiation In the big_key_instantiate() function we return 0 if kernel_write() returns us an error rather than returning an error. This can potentially lead to dentry_open() giving a BUG when called from big_key_read() with an unset tmpfile path. ------------[ cut here ]------------ kernel BUG at fs/open.c:798! ... RIP: 0010:[] dentry_open+0xd1/0xe0 ... Call Trace: [] big_key_read+0x55/0x100 [] keyctl_read_key+0xb4/0xe0 [] SyS_keyctl+0xf8/0x1d0 [] system_call_fastpath+0x16/0x1b Signed-off-by: David Howells Reviewed-by: Stephen Gallagher --- security/keys/big_key.c | 1 + 1 file changed, 1 insertion(+) (limited to 'security') diff --git a/security/keys/big_key.c b/security/keys/big_key.c index 2cf5e62..7f44c32 100644 --- a/security/keys/big_key.c +++ b/security/keys/big_key.c @@ -78,6 +78,7 @@ int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep) written = kernel_write(file, prep->data, prep->datalen, 0); if (written != datalen) { + ret = written; if (written >= 0) ret = -ENOMEM; goto err_fput; -- cgit v1.1 From 62fe318256befbd1b4a6765e71d9c997f768fe79 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 14 Nov 2013 13:02:31 +0000 Subject: KEYS: Fix keyring content gc scanner Key pointers stored in the keyring are marked in bit 1 to indicate if they point to a keyring. We need to strip off this bit before using the pointer when iterating over the keyring for the purpose of looking for links to garbage collect. This means that expirable keyrings aren't correctly expiring because the checker is seeing their key pointer with 2 added to it. Since the fix for this involves knowing about the internals of the keyring, key_gc_keyring() is moved to keyring.c and merged into keyring_gc(). This can be tested by: echo 2 >/proc/sys/kernel/keys/gc_delay keyctl timeout `keyctl add keyring qwerty "" @s` 2 cat /proc/keys sleep 5; cat /proc/keys which should see a keyring called "qwerty" appear in the session keyring and then disappear after it expires, and: echo 2 >/proc/sys/kernel/keys/gc_delay a=`keyctl get_persistent @s` b=`keyctl add keyring 0 "" $a` keyctl add user a a $b keyctl timeout $b 2 cat /proc/keys sleep 5; cat /proc/keys which should see a keyring called "0" with a key called "a" in it appear in the user's persistent keyring (which will be attached to the session keyring) and then both the "0" keyring and the "a" key should disappear when the "0" keyring expires. Signed-off-by: David Howells Acked-by: Simo Sorce --- security/keys/gc.c | 42 +----------------------------------------- security/keys/keyring.c | 45 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 36 insertions(+), 51 deletions(-) (limited to 'security') diff --git a/security/keys/gc.c b/security/keys/gc.c index cce621c..d3222b6 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -130,46 +130,6 @@ void key_gc_keytype(struct key_type *ktype) kleave(""); } -static int key_gc_keyring_func(const void *object, void *iterator_data) -{ - const struct key *key = object; - time_t *limit = iterator_data; - return key_is_dead(key, *limit); -} - -/* - * Garbage collect pointers from a keyring. - * - * Not called with any locks held. The keyring's key struct will not be - * deallocated under us as only our caller may deallocate it. - */ -static void key_gc_keyring(struct key *keyring, time_t limit) -{ - int result; - - kenter("%x{%s}", keyring->serial, keyring->description ?: ""); - - if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | - (1 << KEY_FLAG_REVOKED))) - goto dont_gc; - - /* scan the keyring looking for dead keys */ - rcu_read_lock(); - result = assoc_array_iterate(&keyring->keys, - key_gc_keyring_func, &limit); - rcu_read_unlock(); - if (result == true) - goto do_gc; - -dont_gc: - kleave(" [no gc]"); - return; - -do_gc: - keyring_gc(keyring, limit); - kleave(" [gc]"); -} - /* * Garbage collect a list of unreferenced, detached keys */ @@ -388,7 +348,7 @@ found_unreferenced_key: */ found_keyring: spin_unlock(&key_serial_lock); - key_gc_keyring(key, limit); + keyring_gc(key, limit); goto maybe_resched; /* We found a dead key that is still referenced. Reset its type and diff --git a/security/keys/keyring.c b/security/keys/keyring.c index d80311e..69f0cb7 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -1304,7 +1304,7 @@ static void keyring_revoke(struct key *keyring) } } -static bool gc_iterator(void *object, void *iterator_data) +static bool keyring_gc_select_iterator(void *object, void *iterator_data) { struct key *key = keyring_ptr_to_key(object); time_t *limit = iterator_data; @@ -1315,22 +1315,47 @@ static bool gc_iterator(void *object, void *iterator_data) return true; } +static int keyring_gc_check_iterator(const void *object, void *iterator_data) +{ + const struct key *key = keyring_ptr_to_key(object); + time_t *limit = iterator_data; + + key_check(key); + return key_is_dead(key, *limit); +} + /* - * Collect garbage from the contents of a keyring, replacing the old list with - * a new one with the pointers all shuffled down. + * Garbage collect pointers from a keyring. * - * Dead keys are classed as oned that are flagged as being dead or are revoked, - * expired or negative keys that were revoked or expired before the specified - * limit. + * Not called with any locks held. The keyring's key struct will not be + * deallocated under us as only our caller may deallocate it. */ void keyring_gc(struct key *keyring, time_t limit) { - kenter("{%x,%s}", key_serial(keyring), keyring->description); + int result; + + kenter("%x{%s}", keyring->serial, keyring->description ?: ""); + if (keyring->flags & ((1 << KEY_FLAG_INVALIDATED) | + (1 << KEY_FLAG_REVOKED))) + goto dont_gc; + + /* scan the keyring looking for dead keys */ + rcu_read_lock(); + result = assoc_array_iterate(&keyring->keys, + keyring_gc_check_iterator, &limit); + rcu_read_unlock(); + if (result == true) + goto do_gc; + +dont_gc: + kleave(" [no gc]"); + return; + +do_gc: down_write(&keyring->sem); assoc_array_gc(&keyring->keys, &keyring_assoc_array_ops, - gc_iterator, &limit); + keyring_gc_select_iterator, &limit); up_write(&keyring->sem); - - kleave(""); + kleave(" [gc]"); } -- cgit v1.1 From 34ef7bd3823bf4401bf8f1f855e1bc77b82b1a43 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 23 Nov 2013 16:36:35 -0800 Subject: Revert "ima: define '_ima' as a builtin 'trusted' keyring" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 217091dd7a7a1bdac027ddb7c5a25f6ac0b8e241, which caused the following build error: security/integrity/digsig.c:70:5: error: redefinition of ‘integrity_init_keyring’ security/integrity/integrity.h:149:12: note: previous definition of ‘integrity_init_keyring’ w security/integrity/integrity.h:149:12: warning: ‘integrity_init_keyring’ defined but not used reported by Krzysztof Kolasa. Mimi says: "I made the classic mistake of requesting this patch to be upstreamed at the last second, rather than waiting until the next open window. At this point, the best course would probably be to revert the two commits and fix them for the next open window" Reported-by: Krzysztof Kolasa Acked-by: Mimi Zohar Signed-off-by: Linus Torvalds --- security/integrity/digsig.c | 30 +----------------------------- security/integrity/ima/Kconfig | 8 -------- security/integrity/ima/ima_appraise.c | 11 ----------- security/integrity/integrity.h | 7 ------- 4 files changed, 1 insertion(+), 55 deletions(-) (limited to 'security') diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index 77ca965..b4af4eb 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -13,9 +13,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include -#include #include -#include #include #include @@ -23,19 +21,11 @@ static struct key *keyring[INTEGRITY_KEYRING_MAX]; -#ifdef CONFIG_IMA_TRUSTED_KEYRING -static const char *keyring_name[INTEGRITY_KEYRING_MAX] = { - ".evm", - ".module", - ".ima", -}; -#else static const char *keyring_name[INTEGRITY_KEYRING_MAX] = { "_evm", "_module", "_ima", }; -#endif int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, const char *digest, int digestlen) @@ -45,7 +35,7 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, if (!keyring[id]) { keyring[id] = - request_key(&key_type_keyring, keyring_name[id], NULL); + request_key(&key_type_keyring, keyring_name[id], NULL); if (IS_ERR(keyring[id])) { int err = PTR_ERR(keyring[id]); pr_err("no %s keyring: %d\n", keyring_name[id], err); @@ -66,21 +56,3 @@ int integrity_digsig_verify(const unsigned int id, const char *sig, int siglen, return -EOPNOTSUPP; } - -int integrity_init_keyring(const unsigned int id) -{ - const struct cred *cred = current_cred(); - const struct user_struct *user = cred->user; - - keyring[id] = keyring_alloc(keyring_name[id], KUIDT_INIT(0), - KGIDT_INIT(0), cred, - ((KEY_POS_ALL & ~KEY_POS_SETATTR) | - KEY_USR_VIEW | KEY_USR_READ), - KEY_ALLOC_NOT_IN_QUOTA, user->uid_keyring); - if (!IS_ERR(keyring[id])) - set_bit(KEY_FLAG_TRUSTED_ONLY, &keyring[id]->flags); - else - pr_info("Can't allocate %s keyring (%ld)\n", - keyring_name[id], PTR_ERR(keyring[id])); - return 0; -} diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index dad8d4c..81a2797 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -123,11 +123,3 @@ config IMA_APPRAISE For more information on integrity appraisal refer to: If unsure, say N. - -config IMA_TRUSTED_KEYRING - bool "Require all keys on the _ima keyring be signed" - depends on IMA_APPRAISE && SYSTEM_TRUSTED_KEYRING - default y - help - This option requires that all keys added to the _ima - keyring be signed by a key on the system trusted keyring. diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 46353ee..734e946 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -381,14 +381,3 @@ int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name) } return result; } - -#ifdef CONFIG_IMA_TRUSTED_KEYRING -static int __init init_ima_keyring(void) -{ - int ret; - - ret = integrity_init_keyring(INTEGRITY_KEYRING_IMA); - return 0; -} -late_initcall(init_ima_keyring); -#endif diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index b9e7c13..2fb5e53 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -137,19 +137,12 @@ static inline int integrity_digsig_verify(const unsigned int id, #ifdef CONFIG_INTEGRITY_ASYMMETRIC_KEYS int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen); - -int integrity_init_keyring(const unsigned int id); #else static inline int asymmetric_verify(struct key *keyring, const char *sig, int siglen, const char *data, int datalen) { return -EOPNOTSUPP; } - -static int integrity_init_keyring(const unsigned int id) -{ - return 0; -} #endif #ifdef CONFIG_INTEGRITY_AUDIT -- cgit v1.1 From b6f8f16f41d92861621b043389ef49de1c52d613 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 8 Nov 2013 19:21:39 +0100 Subject: ima: do not include field length in template digest calc for ima template To maintain compatibility with userspace tools, the field length must not be included in the template digest calculation for the 'ima' template. Fixes commit: a71dc65 ima: switch to new template management mechanism Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 3 ++- security/integrity/ima/ima_api.c | 1 + security/integrity/ima/ima_crypto.c | 17 ++++++++++++----- 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index bf03c6a..a21cf70 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -97,7 +97,8 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, const char *op, struct inode *inode, const unsigned char *filename); int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash); -int ima_calc_field_array_hash(struct ima_field_data *field_data, int num_fields, +int ima_calc_field_array_hash(struct ima_field_data *field_data, + struct ima_template_desc *desc, int num_fields, struct ima_digest_data *hash); int __init ima_calc_boot_aggregate(struct ima_digest_data *hash); void ima_add_violation(struct file *file, const unsigned char *filename, diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 0e75408..8037484 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -94,6 +94,7 @@ int ima_store_template(struct ima_template_entry *entry, /* this function uses default algo */ hash.hdr.algo = HASH_ALGO_SHA1; result = ima_calc_field_array_hash(&entry->template_data[0], + entry->template_desc, num_fields, &hash.hdr); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 676e029..fdf60de 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -140,6 +140,7 @@ int ima_calc_file_hash(struct file *file, struct ima_digest_data *hash) * Calculate the hash of template data */ static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data, + struct ima_template_desc *td, int num_fields, struct ima_digest_data *hash, struct crypto_shash *tfm) @@ -160,9 +161,13 @@ static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data, return rc; for (i = 0; i < num_fields; i++) { - rc = crypto_shash_update(&desc.shash, - (const u8 *) &field_data[i].len, - sizeof(field_data[i].len)); + if (strcmp(td->name, IMA_TEMPLATE_IMA_NAME) != 0) { + rc = crypto_shash_update(&desc.shash, + (const u8 *) &field_data[i].len, + sizeof(field_data[i].len)); + if (rc) + break; + } rc = crypto_shash_update(&desc.shash, field_data[i].data, field_data[i].len); if (rc) @@ -175,7 +180,8 @@ static int ima_calc_field_array_hash_tfm(struct ima_field_data *field_data, return rc; } -int ima_calc_field_array_hash(struct ima_field_data *field_data, int num_fields, +int ima_calc_field_array_hash(struct ima_field_data *field_data, + struct ima_template_desc *desc, int num_fields, struct ima_digest_data *hash) { struct crypto_shash *tfm; @@ -185,7 +191,8 @@ int ima_calc_field_array_hash(struct ima_field_data *field_data, int num_fields, if (IS_ERR(tfm)) return PTR_ERR(tfm); - rc = ima_calc_field_array_hash_tfm(field_data, num_fields, hash, tfm); + rc = ima_calc_field_array_hash_tfm(field_data, desc, num_fields, + hash, tfm); ima_free_tfm(tfm); -- cgit v1.1 From 3e8e5503a33577d89bdb7469b851b11f507bbed6 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Fri, 8 Nov 2013 19:21:40 +0100 Subject: ima: do not send field length to userspace for digest of ima template This patch defines a new value for the 'ima_show_type' enumerator (IMA_SHOW_BINARY_NO_FIELD_LEN) to prevent that the field length is transmitted through the 'binary_runtime_measurements' interface for the digest field of the 'ima' template. Fixes commit: 3ce1217 ima: define template fields library and new helpers Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 3 ++- security/integrity/ima/ima_fs.c | 14 +++++++++++--- security/integrity/ima/ima_template_lib.c | 6 +++++- 3 files changed, 18 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index a21cf70..9636e17 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -26,7 +26,8 @@ #include "../integrity.h" -enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII }; +enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_BINARY_NO_FIELD_LEN, + IMA_SHOW_ASCII }; enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; /* digest size for IMA, fits SHA1 or MD5 */ diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index d47a7c8..db01125 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -120,6 +120,7 @@ static int ima_measurements_show(struct seq_file *m, void *v) struct ima_template_entry *e; int namelen; u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + bool is_ima_template = false; int i; /* get entry */ @@ -145,14 +146,21 @@ static int ima_measurements_show(struct seq_file *m, void *v) ima_putc(m, e->template_desc->name, namelen); /* 5th: template length (except for 'ima' template) */ - if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) != 0) + if (strcmp(e->template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) + is_ima_template = true; + + if (!is_ima_template) ima_putc(m, &e->template_data_len, sizeof(e->template_data_len)); /* 6th: template specific data */ for (i = 0; i < e->template_desc->num_fields; i++) { - e->template_desc->fields[i]->field_show(m, IMA_SHOW_BINARY, - &e->template_data[i]); + enum ima_show_type show = IMA_SHOW_BINARY; + struct ima_template_field *field = e->template_desc->fields[i]; + + if (is_ima_template && strcmp(field->field_id, "d") == 0) + show = IMA_SHOW_BINARY_NO_FIELD_LEN; + field->field_show(m, show, &e->template_data[i]); } return 0; } diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 6d66ad6..c38adcc 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -109,9 +109,12 @@ static void ima_show_template_data_binary(struct seq_file *m, enum data_formats datafmt, struct ima_field_data *field_data) { - ima_putc(m, &field_data->len, sizeof(u32)); + if (show != IMA_SHOW_BINARY_NO_FIELD_LEN) + ima_putc(m, &field_data->len, sizeof(u32)); + if (!field_data->len) return; + ima_putc(m, field_data->data, field_data->len); } @@ -125,6 +128,7 @@ static void ima_show_template_field_data(struct seq_file *m, ima_show_template_data_ascii(m, show, datafmt, field_data); break; case IMA_SHOW_BINARY: + case IMA_SHOW_BINARY_NO_FIELD_LEN: ima_show_template_data_binary(m, show, datafmt, field_data); break; default: -- cgit v1.1 From dbc335d2dc3c437649eb6b39f4e9aee2a13eb0af Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Mon, 25 Nov 2013 20:18:52 +0100 Subject: ima: make a copy of template_fmt in template_desc_init_fields() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch makes a copy of the 'template_fmt' function argument so that the latter will not be modified by strsep(), which does the splitting by replacing the given separator with '\0'.  IMA: No TPM chip found, activating TPM-bypass!  Unable to handle kernel pointer dereference at virtual kernel address 0000000000842000  Oops: 0004 [#1] SMP  Modules linked in:  CPU: 3 PID: 1 Comm: swapper/0 Not tainted 3.12.0-rc2-00098-g3ce1217d6cd5 #17  task: 000000003ffa0000 ti: 000000003ff84000 task.ti: 000000003ff84000  Krnl PSW : 0704e00180000000 000000000044bf88 (strsep+0x7c/0xa0)             R:0 T:1 IO:1 EX:1 Key:0 M:1 W:0 P:0 AS:3 CC:2 PM:0 EA:3  Krnl GPRS: 000000000000007c 000000000000007c 000000003ff87d90 0000000000821fd8             0000000000000000 000000000000007c 0000000000aa37e0 0000000000aa9008             0000000000000051 0000000000a114d8 0000000100000002 0000000000842bde             0000000000842bdf 00000000006f97f0 000000000040062c 000000003ff87cf0  Krnl Code: 000000000044bf7c: a7f4000a           brc     15,44bf90             000000000044bf80: b90200cc           ltgr    %r12,%r12            #000000000044bf84: a7840006           brc     8,44bf90            >000000000044bf88: 9200c000           mvi     0(%r12),0             000000000044bf8c: 41c0c001           la      %r12,1(%r12)             000000000044bf90: e3c020000024       stg     %r12,0(%r2)             000000000044bf96: b904002b           lgr     %r2,%r11             000000000044bf9a: ebbcf0700004       lmg     %r11,%r12,112(%r15)  Call Trace:  ([<00000000004005fe>] ima_init_template+0xa2/0x1bc)   [<0000000000a7c896>] ima_init+0x7a/0xa8   [<0000000000a7c938>] init_ima+0x24/0x40   [<00000000001000e8>] do_one_initcall+0x68/0x128   [<0000000000a4eb56>] kernel_init_freeable+0x20a/0x2b4   [<00000000006a1ff4>] kernel_init+0x30/0x178   [<00000000006b69fe>] kernel_thread_starter+0x6/0xc   [<00000000006b69f8>] kernel_thread_starter+0x0/0xc  Last Breaking-Event-Address:   [<000000000044bf42>] strsep+0x36/0xa0 Fixes commit: adf53a7 ima: new templates management mechanism Changelog v1: - make template_fmt 'const char *' (reported-by James Morris) - fix kstrdup memory leak (reported-by James Morris) Reported-by: Heiko Carstens Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar Tested-by: Heiko Carstens --- security/integrity/ima/ima_template.c | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 4e5da99..913e192 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -90,7 +90,7 @@ static struct ima_template_field *lookup_template_field(const char *field_id) return NULL; } -static int template_fmt_size(char *template_fmt) +static int template_fmt_size(const char *template_fmt) { char c; int template_fmt_len = strlen(template_fmt); @@ -106,23 +106,28 @@ static int template_fmt_size(char *template_fmt) return j + 1; } -static int template_desc_init_fields(char *template_fmt, +static int template_desc_init_fields(const char *template_fmt, struct ima_template_field ***fields, int *num_fields) { - char *c, *template_fmt_ptr = template_fmt; + char *c, *template_fmt_copy; int template_num_fields = template_fmt_size(template_fmt); int i, result = 0; if (template_num_fields > IMA_TEMPLATE_NUM_FIELDS_MAX) return -EINVAL; + /* copying is needed as strsep() modifies the original buffer */ + template_fmt_copy = kstrdup(template_fmt, GFP_KERNEL); + if (template_fmt_copy == NULL) + return -ENOMEM; + *fields = kzalloc(template_num_fields * sizeof(*fields), GFP_KERNEL); if (*fields == NULL) { result = -ENOMEM; goto out; } - for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && + for (i = 0; (c = strsep(&template_fmt_copy, "|")) != NULL && i < template_num_fields; i++) { struct ima_template_field *f = lookup_template_field(c); @@ -133,10 +138,12 @@ static int template_desc_init_fields(char *template_fmt, (*fields)[i] = f; } *num_fields = i; - return 0; out: - kfree(*fields); - *fields = NULL; + if (result < 0) { + kfree(*fields); + *fields = NULL; + } + kfree(template_fmt_copy); return result; } -- cgit v1.1 From af91706d5ddecb4a9858cca9e90d463037cfd498 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Wed, 27 Nov 2013 14:40:41 +0100 Subject: ima: store address of template_fmt_copy in a pointer before calling strsep This patch stores the address of the 'template_fmt_copy' variable in a new variable, called 'template_fmt_ptr', so that the latter is passed as an argument of strsep() instead of the former. This modification is needed in order to correctly free the memory area referenced by 'template_fmt_copy' (strsep() modifies the pointer of the passed string). Signed-off-by: Roberto Sassu Reported-by: Sebastian Ott Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- security/integrity/ima/ima_template.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 913e192..635695f 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -110,7 +110,7 @@ static int template_desc_init_fields(const char *template_fmt, struct ima_template_field ***fields, int *num_fields) { - char *c, *template_fmt_copy; + char *c, *template_fmt_copy, *template_fmt_ptr; int template_num_fields = template_fmt_size(template_fmt); int i, result = 0; @@ -127,7 +127,9 @@ static int template_desc_init_fields(const char *template_fmt, result = -ENOMEM; goto out; } - for (i = 0; (c = strsep(&template_fmt_copy, "|")) != NULL && + + template_fmt_ptr = template_fmt_copy; + for (i = 0; (c = strsep(&template_fmt_ptr, "|")) != NULL && i < template_num_fields; i++) { struct ima_template_field *f = lookup_template_field(c); -- cgit v1.1 From 2480f57fb3023eb047c5f2d6dfefef41ab9b893c Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 2 Dec 2013 11:24:18 +0000 Subject: KEYS: Pre-clear struct key on allocation The second word of key->payload does not get initialised in key_alloc(), but the big_key type is relying on it having been cleared. The problem comes when big_key fails to instantiate a large key and doesn't then set the payload. The big_key_destroy() op is called from the garbage collector and this assumes that the dentry pointer stored in the second word will be NULL if instantiation did not complete. Therefore just pre-clear the entire struct key on allocation rather than trying to be clever and only initialising to 0 only those bits that aren't otherwise initialised. The lack of initialisation can lead to a bug report like the following if big_key failed to initialise its file: general protection fault: 0000 [#1] SMP Modules linked in: ... CPU: 0 PID: 51 Comm: kworker/0:1 Not tainted 3.10.0-53.el7.x86_64 #1 Hardware name: Dell Inc. PowerEdge 1955/0HC513, BIOS 1.4.4 12/09/2008 Workqueue: events key_garbage_collector task: ffff8801294f5680 ti: ffff8801296e2000 task.ti: ffff8801296e2000 RIP: 0010:[] dput+0x21/0x2d0 ... Call Trace: [] path_put+0x16/0x30 [] big_key_destroy+0x44/0x60 [] key_gc_unused_keys.constprop.2+0x5b/0xe0 [] key_garbage_collector+0x1df/0x3c0 [] process_one_work+0x17b/0x460 [] worker_thread+0x11b/0x400 [] ? rescuer_thread+0x3e0/0x3e0 [] kthread+0xc0/0xd0 [] ? kthread_create_on_node+0x110/0x110 [] ret_from_fork+0x7c/0xb0 [] ? kthread_create_on_node+0x110/0x110 Reported-by: Patrik Kis Signed-off-by: David Howells Reviewed-by: Stephen Gallagher --- security/keys/key.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'security') diff --git a/security/keys/key.c b/security/keys/key.c index 55d110f..6e21c11 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -272,7 +272,7 @@ struct key *key_alloc(struct key_type *type, const char *desc, } /* allocate and initialise the key and its description */ - key = kmem_cache_alloc(key_jar, GFP_KERNEL); + key = kmem_cache_zalloc(key_jar, GFP_KERNEL); if (!key) goto no_memory_2; @@ -293,18 +293,12 @@ struct key *key_alloc(struct key_type *type, const char *desc, key->uid = uid; key->gid = gid; key->perm = perm; - key->flags = 0; - key->expiry = 0; - key->payload.data = NULL; - key->security = NULL; if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) key->flags |= 1 << KEY_FLAG_IN_QUOTA; if (flags & KEY_ALLOC_TRUSTED) key->flags |= 1 << KEY_FLAG_TRUSTED; - memset(&key->type_data, 0, sizeof(key->type_data)); - #ifdef KEY_DEBUGGING key->magic = KEY_DEBUG_MAGIC; #endif -- cgit v1.1 From d54e58b7f01552b0eb7d445f4b58de4499ad5ea6 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 2 Dec 2013 11:24:18 +0000 Subject: KEYS: Fix the keyring hash function The keyring hash function (used by the associative array) is supposed to clear the bottommost nibble of the index key (where the hash value resides) for keyrings and make sure it is non-zero for non-keyrings. This is done to make keyrings cluster together on one branch of the tree separately to other keys. Unfortunately, the wrong mask is used, so only the bottom two bits are examined and cleared and not the whole bottom nibble. This means that keys and keyrings can still be successfully searched for under most circumstances as the hash is consistent in its miscalculation, but if a keyring's associative array bottom node gets filled up then approx 75% of the keyrings will not be put into the 0 branch. The consequence of this is that a key in a keyring linked to by another keyring, ie. keyring A -> keyring B -> key may not be found if the search starts at keyring A and then descends into keyring B because search_nested_keyrings() only searches up the 0 branch (as it "knows" all keyrings must be there and not elsewhere in the tree). The fix is to use the right mask. This can be tested with: r=`keyctl newring sandbox @s` for ((i=0; i<=16; i++)); do keyctl newring ring$i $r; done for ((i=0; i<=16; i++)); do keyctl add user a$i a %:ring$i; done for ((i=0; i<=16; i++)); do keyctl search $r user a$i; done This creates a sandbox keyring, then creates 17 keyrings therein (labelled ring0..ring16). This causes the root node of the sandbox's associative array to overflow and for the tree to have extra nodes inserted. Each keyring then is given a user key (labelled aN for ringN) for us to search for. We then search for the user keys we added, starting from the sandbox. If working correctly, it should return the same ordered list of key IDs as for...keyctl add... did. Without this patch, it reports ENOKEY "Required key not available" for some of the keys. Just which keys get this depends as the kernel pointer to the key type forms part of the hash function. Reported-by: Nalin Dahyabhai Signed-off-by: David Howells Tested-by: Stephen Gallagher --- security/keys/keyring.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 69f0cb7..0adbc77 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -160,7 +160,7 @@ static u64 mult_64x32_and_fold(u64 x, u32 y) static unsigned long hash_key_type_and_desc(const struct keyring_index_key *index_key) { const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP; - const unsigned long level_mask = ASSOC_ARRAY_LEVEL_STEP_MASK; + const unsigned long fan_mask = ASSOC_ARRAY_FAN_MASK; const char *description = index_key->description; unsigned long hash, type; u32 piece; @@ -194,10 +194,10 @@ static unsigned long hash_key_type_and_desc(const struct keyring_index_key *inde * ordinary keys by making sure the lowest level segment in the hash is * zero for keyrings and non-zero otherwise. */ - if (index_key->type != &key_type_keyring && (hash & level_mask) == 0) + if (index_key->type != &key_type_keyring && (hash & fan_mask) == 0) return hash | (hash >> (ASSOC_ARRAY_KEY_CHUNK_SIZE - level_shift)) | 1; - if (index_key->type == &key_type_keyring && (hash & level_mask) != 0) - return (hash + (hash << level_shift)) & ~level_mask; + if (index_key->type == &key_type_keyring && (hash & fan_mask) != 0) + return (hash + (hash << level_shift)) & ~fan_mask; return hash; } -- cgit v1.1 From 23fd78d76415729b338ff1802a0066b4a62f7fb8 Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 2 Dec 2013 11:24:18 +0000 Subject: KEYS: Fix multiple key add into associative array If sufficient keys (or keyrings) are added into a keyring such that a node in the associative array's tree overflows (each node has a capacity N, currently 16) and such that all N+1 keys have the same index key segment for that level of the tree (the level'th nibble of the index key), then assoc_array_insert() calls ops->diff_objects() to indicate at which bit position the two index keys vary. However, __key_link_begin() passes a NULL object to assoc_array_insert() with the intention of supplying the correct pointer later before we commit the change. This means that keyring_diff_objects() is given a NULL pointer as one of its arguments which it does not expect. This results in an oops like the attached. With the previous patch to fix the keyring hash function, this can be forced much more easily by creating a keyring and only adding keyrings to it. Add any other sort of key and a different insertion path is taken - all 16+1 objects must want to cluster in the same node slot. This can be tested by: r=`keyctl newring sandbox @s` for ((i=0; i<=16; i++)); do keyctl newring ring$i $r; done This should work fine, but oopses when the 17th keyring is added. Since ops->diff_objects() is always called with the first pointer pointing to the object to be inserted (ie. the NULL pointer), we can fix the problem by changing the to-be-inserted object pointer to point to the index key passed into assoc_array_insert() instead. Whilst we're at it, we also switch the arguments so that they are the same as for ->compare_object(). BUG: unable to handle kernel NULL pointer dereference at 0000000000000088 IP: [] hash_key_type_and_desc+0x18/0xb0 ... RIP: 0010:[] hash_key_type_and_desc+0x18/0xb0 ... Call Trace: [] keyring_diff_objects+0x21/0xd2 [] assoc_array_insert+0x3b6/0x908 [] __key_link_begin+0x78/0xe5 [] key_create_or_update+0x17d/0x36a [] SyS_add_key+0x123/0x183 [] tracesys+0xdd/0xe2 Signed-off-by: David Howells Tested-by: Stephen Gallagher --- security/keys/keyring.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 0adbc77..3dd8445 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -279,12 +279,11 @@ static bool keyring_compare_object(const void *object, const void *data) * Compare the index keys of a pair of objects and determine the bit position * at which they differ - if they differ. */ -static int keyring_diff_objects(const void *_a, const void *_b) +static int keyring_diff_objects(const void *object, const void *data) { - const struct key *key_a = keyring_ptr_to_key(_a); - const struct key *key_b = keyring_ptr_to_key(_b); + const struct key *key_a = keyring_ptr_to_key(object); const struct keyring_index_key *a = &key_a->index_key; - const struct keyring_index_key *b = &key_b->index_key; + const struct keyring_index_key *b = data; unsigned long seg_a, seg_b; int level, i; -- cgit v1.1 From 9c5e45df215b4788f7a41c983ce862d08a083c2d Mon Sep 17 00:00:00 2001 From: David Howells Date: Mon, 2 Dec 2013 11:24:19 +0000 Subject: KEYS: Fix searching of nested keyrings If a keyring contains more than 16 keyrings (the capacity of a single node in the associative array) then those keyrings are split over multiple nodes arranged as a tree. If search_nested_keyrings() is called to search the keyring then it will attempt to manually walk over just the 0 branch of the associative array tree where all the keyring links are stored. This works provided the key is found before the algorithm steps from one node containing keyrings to a child node or if there are sufficiently few keyring links that the keyrings are all in one node. However, if the algorithm does need to step from a node to a child node, it doesn't change the node pointer unless a shortcut also gets transited. This means that the algorithm will keep scanning the same node over and over again without terminating and without returning. To fix this, move the internal-pointer-to-node translation from inside the shortcut transit handler so that it applies it to node arrival as well. This can be tested by: r=`keyctl newring sandbox @s` for ((i=0; i<=16; i++)); do keyctl newring ring$i $r; done for ((i=0; i<=16; i++)); do keyctl add user a$i a %:ring$i; done for ((i=0; i<=16; i++)); do keyctl search $r user a$i; done for ((i=17; i<=20; i++)); do keyctl search $r user a$i; done The searches should all complete successfully (or with an error for 17-20), but instead one or more of them will hang. Signed-off-by: David Howells Tested-by: Stephen Gallagher --- security/keys/keyring.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 3dd8445..d46cbc5 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -690,8 +690,8 @@ descend_to_node: smp_read_barrier_depends(); ptr = ACCESS_ONCE(shortcut->next_node); BUG_ON(!assoc_array_ptr_is_node(ptr)); - node = assoc_array_ptr_to_node(ptr); } + node = assoc_array_ptr_to_node(ptr); begin_node: kdebug("begin_node"); -- cgit v1.1 From c7277090927a5e71871e799a355ed2940f6c8fc6 Mon Sep 17 00:00:00 2001 From: Eric Paris Date: Mon, 2 Dec 2013 11:24:19 +0000 Subject: security: shmem: implement kernel private shmem inodes We have a problem where the big_key key storage implementation uses a shmem backed inode to hold the key contents. Because of this detail of implementation LSM checks are being done between processes trying to read the keys and the tmpfs backed inode. The LSM checks are already being handled on the key interface level and should not be enforced at the inode level (since the inode is an implementation detail, not a part of the security model) This patch implements a new function shmem_kernel_file_setup() which returns the equivalent to shmem_file_setup() only the underlying inode has S_PRIVATE set. This means that all LSM checks for the inode in question are skipped. It should only be used for kernel internal operations where the inode is not exposed to userspace without proper LSM checking. It is possible that some other users of shmem_file_setup() should use the new interface, but this has not been explored. Reproducing this bug is a little bit difficult. The steps I used on Fedora are: (1) Turn off selinux enforcing: setenforce 0 (2) Create a huge key k=`dd if=/dev/zero bs=8192 count=1 | keyctl padd big_key test-key @s` (3) Access the key in another context: runcon system_u:system_r:httpd_t:s0-s0:c0.c1023 keyctl print $k >/dev/null (4) Examine the audit logs: ausearch -m AVC -i --subject httpd_t | audit2allow If the last command's output includes a line that looks like: allow httpd_t user_tmpfs_t:file { open read }; There was an inode check between httpd and the tmpfs filesystem. With this patch no such denial will be seen. (NOTE! you should clear your audit log if you have tested for this previously) (Please return you box to enforcing) Signed-off-by: Eric Paris Signed-off-by: David Howells cc: Hugh Dickins cc: linux-mm@kvack.org --- security/keys/big_key.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/keys/big_key.c b/security/keys/big_key.c index 7f44c32..8137b27 100644 --- a/security/keys/big_key.c +++ b/security/keys/big_key.c @@ -70,7 +70,7 @@ int big_key_instantiate(struct key *key, struct key_preparsed_payload *prep) * * TODO: Encrypt the stored data with a temporary key. */ - file = shmem_file_setup("", datalen, 0); + file = shmem_kernel_file_setup("", datalen, 0); if (IS_ERR(file)) { ret = PTR_ERR(file); goto err_quota; -- cgit v1.1 From 09ae6345721afbb7cf3e0920209b140cbe7bff0d Mon Sep 17 00:00:00 2001 From: Christoph Paasch Date: Mon, 2 Dec 2013 00:05:20 +0100 Subject: ima: Do not free 'entry' before it is initialized 7bc5f447ce9d0 (ima: define new function ima_alloc_init_template() to API) moved the initialization of 'entry' in ima_add_boot_aggregate() a bit more below, after the if (ima_used_chip). So, 'entry' is not initialized while being inside this if-block. So, we should not attempt to free it. Found by Coverity (CID: 1131971) Fixes: 7bc5f447ce9d0 (ima: define new function ima_alloc_init_template() to API) Signed-off-by: Christoph Paasch Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_init.c | 1 - 1 file changed, 1 deletion(-) (limited to 'security') diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 15f34bd..76b8e2c 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -63,7 +63,6 @@ static void __init ima_add_boot_aggregate(void) result = ima_calc_boot_aggregate(&hash.hdr); if (result < 0) { audit_cause = "hashing_error"; - kfree(entry); goto err_out; } } -- cgit v1.1 From a7ed7c60e14df5b986f93549717235b882643e7e Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Mon, 2 Dec 2013 19:40:34 +0100 Subject: ima: properly free ima_template_entry structures The new templates management mechanism records information associated to an event into an array of 'ima_field_data' structures and makes it available through the 'template_data' field of the 'ima_template_entry' structure (the element of the measurements list created by IMA). Since 'ima_field_data' contains dynamically allocated data (which length varies depending on the data associated to a selected template field), it is not enough to just free the memory reserved for a 'ima_template_entry' structure if something goes wrong. This patch creates the new function ima_free_template_entry() which walks the array of 'ima_field_data' structures, frees the memory referenced by the 'data' pointer and finally the space reserved for the 'ima_template_entry' structure. Further, it replaces existing kfree() that have a pointer to an 'ima_template_entry' structure as argument with calls to the new function. Fixes: a71dc65: ima: switch to new template management mechanism Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 1 + security/integrity/ima/ima_api.c | 21 +++++++++++++++++---- security/integrity/ima/ima_init.c | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 9636e17..0356e1d 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -148,6 +148,7 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, int xattr_len, struct ima_template_entry **entry); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode, const unsigned char *filename); +void ima_free_template_entry(struct ima_template_entry *entry); const char *ima_d_path(struct path *path, char **pathbuf); /* rbtree tree calls to lookup, insert, delete diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 8037484..c38bbce 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -22,6 +22,19 @@ #include "ima.h" /* + * ima_free_template_entry - free an existing template entry + */ +void ima_free_template_entry(struct ima_template_entry *entry) +{ + int i; + + for (i = 0; i < entry->template_desc->num_fields; i++) + kfree(entry->template_data[i].data); + + kfree(entry); +} + +/* * ima_alloc_init_template - create and initialize a new template entry */ int ima_alloc_init_template(struct integrity_iint_cache *iint, @@ -37,6 +50,7 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, if (!*entry) return -ENOMEM; + (*entry)->template_desc = template_desc; for (i = 0; i < template_desc->num_fields; i++) { struct ima_template_field *field = template_desc->fields[i]; u32 len; @@ -51,10 +65,9 @@ int ima_alloc_init_template(struct integrity_iint_cache *iint, (*entry)->template_data_len += sizeof(len); (*entry)->template_data_len += len; } - (*entry)->template_desc = template_desc; return 0; out: - kfree(*entry); + ima_free_template_entry(*entry); *entry = NULL; return result; } @@ -134,7 +147,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, } result = ima_store_template(entry, violation, inode, filename); if (result < 0) - kfree(entry); + ima_free_template_entry(entry); err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, op, cause, result, 0); @@ -269,7 +282,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (!result || result == -EEXIST) iint->flags |= IMA_MEASURED; if (result < 0) - kfree(entry); + ima_free_template_entry(entry); } void ima_audit_measurement(struct integrity_iint_cache *iint, diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index 76b8e2c..3712276 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -75,7 +75,7 @@ static void __init ima_add_boot_aggregate(void) result = ima_store_template(entry, violation, NULL, boot_aggregate_name); if (result < 0) - kfree(entry); + ima_free_template_entry(entry); return; err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, -- cgit v1.1 From 0af901643fe3f1f8d44e41115d36609ee4bda2bf Mon Sep 17 00:00:00 2001 From: "Geyslan G. Bem" Date: Wed, 4 Dec 2013 16:10:24 -0500 Subject: selinux: fix possible memory leak Free 'ctx_str' when necessary. Signed-off-by: Geyslan G. Bem Signed-off-by: Paul Moore --- security/selinux/xfrm.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index a91d205..cf79a45 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -327,19 +327,22 @@ int selinux_xfrm_state_alloc_acquire(struct xfrm_state *x, return rc; ctx = kmalloc(sizeof(*ctx) + str_len, GFP_ATOMIC); - if (!ctx) - return -ENOMEM; + if (!ctx) { + rc = -ENOMEM; + goto out; + } ctx->ctx_doi = XFRM_SC_DOI_LSM; ctx->ctx_alg = XFRM_SC_ALG_SELINUX; ctx->ctx_sid = secid; ctx->ctx_len = str_len; memcpy(ctx->ctx_str, ctx_str, str_len); - kfree(ctx_str); x->security = ctx; atomic_inc(&selinux_xfrm_refcount); - return 0; +out: + kfree(ctx_str); + return rc; } /* -- cgit v1.1 From 47180068276a04ed31d24fe04c673138208b07a9 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Wed, 4 Dec 2013 16:10:45 -0500 Subject: selinux: handle TCP SYN-ACK packets correctly in selinux_ip_output() In selinux_ip_output() we always label packets based on the parent socket. While this approach works in almost all cases, it doesn't work in the case of TCP SYN-ACK packets when the correct label is not the label of the parent socket, but rather the label of the larval socket represented by the request_sock struct. Unfortunately, since the request_sock isn't queued on the parent socket until *after* the SYN-ACK packet is sent, we can't lookup the request_sock to determine the correct label for the packet; at this point in time the best we can do is simply pass/NF_ACCEPT the packet. It must be said that simply passing the packet without any explicit labeling action, while far from ideal, is not terrible as the SYN-ACK packet will inherit any IP option based labeling from the initial connection request so the label *should* be correct and all our access controls remain in place so we shouldn't have to worry about information leaks. Reported-by: Janak Desai Tested-by: Janak Desai Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 777ee98..877bab7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -53,6 +53,7 @@ #include /* for local_port_range[] */ #include #include /* struct or_callable used in sock_rcv_skb */ +#include #include #include #include @@ -4731,6 +4732,7 @@ static unsigned int selinux_ipv6_forward(unsigned int hooknum, static unsigned int selinux_ip_output(struct sk_buff *skb, u16 family) { + struct sock *sk; u32 sid; if (!netlbl_enabled()) @@ -4739,8 +4741,27 @@ static unsigned int selinux_ip_output(struct sk_buff *skb, /* we do this in the LOCAL_OUT path and not the POST_ROUTING path * because we want to make sure we apply the necessary labeling * before IPsec is applied so we can leverage AH protection */ - if (skb->sk) { - struct sk_security_struct *sksec = skb->sk->sk_security; + sk = skb->sk; + if (sk) { + struct sk_security_struct *sksec; + + if (sk->sk_state == TCP_LISTEN) + /* if the socket is the listening state then this + * packet is a SYN-ACK packet which means it needs to + * be labeled based on the connection/request_sock and + * not the parent socket. unfortunately, we can't + * lookup the request_sock yet as it isn't queued on + * the parent socket until after the SYN-ACK is sent. + * the "solution" is to simply pass the packet as-is + * as any IP option based labeling should be copied + * from the initial connection request (in the IP + * layer). it is far from ideal, but until we get a + * security label in the packet itself this is the + * best we can do. */ + return NF_ACCEPT; + + /* standard practice, label using the parent socket */ + sksec = sk->sk_security; sid = sksec->sid; } else sid = SECINITSID_KERNEL; -- cgit v1.1 From 446b802437f285de68ffb8d6fac3c44c3cab5b04 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Wed, 4 Dec 2013 16:10:51 -0500 Subject: selinux: handle TCP SYN-ACK packets correctly in selinux_ip_postroute() In selinux_ip_postroute() we perform access checks based on the packet's security label. For locally generated traffic we get the packet's security label from the associated socket; this works in all cases except for TCP SYN-ACK packets. In the case of SYN-ACK packet's the correct security label is stored in the connection's request_sock, not the server's socket. Unfortunately, at the point in time when selinux_ip_postroute() is called we can't query the request_sock directly, we need to recreate the label using the same logic that originally labeled the associated request_sock. See the inline comments for more explanation. Reported-by: Janak Desai Tested-by: Janak Desai Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 68 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 53 insertions(+), 15 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 877bab7..cc076a9 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3847,6 +3847,30 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) return 0; } +/** + * selinux_conn_sid - Determine the child socket label for a connection + * @sk_sid: the parent socket's SID + * @skb_sid: the packet's SID + * @conn_sid: the resulting connection SID + * + * If @skb_sid is valid then the user:role:type information from @sk_sid is + * combined with the MLS information from @skb_sid in order to create + * @conn_sid. If @skb_sid is not valid then then @conn_sid is simply a copy + * of @sk_sid. Returns zero on success, negative values on failure. + * + */ +static int selinux_conn_sid(u32 sk_sid, u32 skb_sid, u32 *conn_sid) +{ + int err = 0; + + if (skb_sid != SECSID_NULL) + err = security_sid_mls_copy(sk_sid, skb_sid, conn_sid); + else + *conn_sid = sk_sid; + + return err; +} + /* socket security operations */ static int socket_sockcreate_sid(const struct task_security_struct *tsec, @@ -4453,7 +4477,7 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, struct sk_security_struct *sksec = sk->sk_security; int err; u16 family = sk->sk_family; - u32 newsid; + u32 connsid; u32 peersid; /* handle mapped IPv4 packets arriving via IPv6 sockets */ @@ -4463,16 +4487,11 @@ static int selinux_inet_conn_request(struct sock *sk, struct sk_buff *skb, err = selinux_skb_peerlbl_sid(skb, family, &peersid); if (err) return err; - if (peersid == SECSID_NULL) { - req->secid = sksec->sid; - req->peer_secid = SECSID_NULL; - } else { - err = security_sid_mls_copy(sksec->sid, peersid, &newsid); - if (err) - return err; - req->secid = newsid; - req->peer_secid = peersid; - } + err = selinux_conn_sid(sksec->sid, peersid, &connsid); + if (err) + return err; + req->secid = connsid; + req->peer_secid = peersid; return selinux_netlbl_inet_conn_request(req, family); } @@ -4846,12 +4865,12 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, if (!secmark_active && !peerlbl_active) return NF_ACCEPT; - /* if the packet is being forwarded then get the peer label from the - * packet itself; otherwise check to see if it is from a local - * application or the kernel, if from an application get the peer label - * from the sending socket, otherwise use the kernel's sid */ sk = skb->sk; if (sk == NULL) { + /* Without an associated socket the packet is either coming + * from the kernel or it is being forwarded; check the packet + * to determine which and if the packet is being forwarded + * query the packet directly to determine the security label. */ if (skb->skb_iif) { secmark_perm = PACKET__FORWARD_OUT; if (selinux_skb_peerlbl_sid(skb, family, &peer_sid)) @@ -4860,7 +4879,26 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, secmark_perm = PACKET__SEND; peer_sid = SECINITSID_KERNEL; } + } else if (sk->sk_state == TCP_LISTEN) { + /* Locally generated packet but the associated socket is in the + * listening state which means this is a SYN-ACK packet. In + * this particular case the correct security label is assigned + * to the connection/request_sock but unfortunately we can't + * query the request_sock as it isn't queued on the parent + * socket until after the SYN-ACK packet is sent; the only + * viable choice is to regenerate the label like we do in + * selinux_inet_conn_request(). See also selinux_ip_output() + * for similar problems. */ + u32 skb_sid; + struct sk_security_struct *sksec = sk->sk_security; + if (selinux_skb_peerlbl_sid(skb, family, &skb_sid)) + return NF_DROP; + if (selinux_conn_sid(sksec->sid, skb_sid, &peer_sid)) + return NF_DROP; + secmark_perm = PACKET__SEND; } else { + /* Locally generated packet, fetch the security label from the + * associated socket. */ struct sk_security_struct *sksec = sk->sk_security; peer_sid = sksec->sid; secmark_perm = PACKET__SEND; -- cgit v1.1 From 817eff718dca4e54d5721211ddde0914428fbb7c Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Tue, 10 Dec 2013 14:57:54 -0500 Subject: selinux: look for IPsec labels on both inbound and outbound packets Previously selinux_skb_peerlbl_sid() would only check for labeled IPsec security labels on inbound packets, this patch enables it to check both inbound and outbound traffic for labeled IPsec security labels. Reported-by: Janak Desai Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 2 +- security/selinux/include/xfrm.h | 8 ++++--- security/selinux/xfrm.c | 51 +++++++++++++++++++++++++++++++++-------- 3 files changed, 47 insertions(+), 14 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index cc076a9..8b28123 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3829,7 +3829,7 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid) u32 nlbl_sid; u32 nlbl_type; - err = selinux_skb_xfrm_sid(skb, &xfrm_sid); + err = selinux_xfrm_skb_sid(skb, &xfrm_sid); if (unlikely(err)) return -EACCES; err = selinux_netlbl_skbuff_getsid(skb, family, &nlbl_type, &nlbl_sid); diff --git a/security/selinux/include/xfrm.h b/security/selinux/include/xfrm.h index 0dec76c..48c3cc9 100644 --- a/security/selinux/include/xfrm.h +++ b/security/selinux/include/xfrm.h @@ -39,6 +39,7 @@ int selinux_xfrm_sock_rcv_skb(u32 sk_sid, struct sk_buff *skb, int selinux_xfrm_postroute_last(u32 sk_sid, struct sk_buff *skb, struct common_audit_data *ad, u8 proto); int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall); +int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid); static inline void selinux_xfrm_notify_policyload(void) { @@ -79,11 +80,12 @@ static inline int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, static inline void selinux_xfrm_notify_policyload(void) { } -#endif -static inline int selinux_skb_xfrm_sid(struct sk_buff *skb, u32 *sid) +static inline int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid) { - return selinux_xfrm_decode_session(skb, sid, 0); + *sid = SECSID_NULL; + return 0; } +#endif #endif /* _SELINUX_XFRM_H_ */ diff --git a/security/selinux/xfrm.c b/security/selinux/xfrm.c index cf79a45..0462cb3 100644 --- a/security/selinux/xfrm.c +++ b/security/selinux/xfrm.c @@ -209,19 +209,26 @@ int selinux_xfrm_state_pol_flow_match(struct xfrm_state *x, NULL) ? 0 : 1); } -/* - * LSM hook implementation that checks and/or returns the xfrm sid for the - * incoming packet. - */ -int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall) +static u32 selinux_xfrm_skb_sid_egress(struct sk_buff *skb) { - u32 sid_session = SECSID_NULL; - struct sec_path *sp; + struct dst_entry *dst = skb_dst(skb); + struct xfrm_state *x; - if (skb == NULL) - goto out; + if (dst == NULL) + return SECSID_NULL; + x = dst->xfrm; + if (x == NULL || !selinux_authorizable_xfrm(x)) + return SECSID_NULL; + + return x->security->ctx_sid; +} + +static int selinux_xfrm_skb_sid_ingress(struct sk_buff *skb, + u32 *sid, int ckall) +{ + u32 sid_session = SECSID_NULL; + struct sec_path *sp = skb->sp; - sp = skb->sp; if (sp) { int i; @@ -248,6 +255,30 @@ out: } /* + * LSM hook implementation that checks and/or returns the xfrm sid for the + * incoming packet. + */ +int selinux_xfrm_decode_session(struct sk_buff *skb, u32 *sid, int ckall) +{ + if (skb == NULL) { + *sid = SECSID_NULL; + return 0; + } + return selinux_xfrm_skb_sid_ingress(skb, sid, ckall); +} + +int selinux_xfrm_skb_sid(struct sk_buff *skb, u32 *sid) +{ + int rc; + + rc = selinux_xfrm_skb_sid_ingress(skb, sid, 0); + if (rc == 0 && *sid == SECSID_NULL) + *sid = selinux_xfrm_skb_sid_egress(skb); + + return rc; +} + +/* * LSM hook implementation that allocs and transfers uctx spec to xfrm_policy. */ int selinux_xfrm_policy_alloc(struct xfrm_sec_ctx **ctxp, -- cgit v1.1 From c0828e50485932b7e019df377a6b0a8d1ebd3080 Mon Sep 17 00:00:00 2001 From: Paul Moore Date: Tue, 10 Dec 2013 14:58:01 -0500 Subject: selinux: process labeled IPsec TCP SYN-ACK packets properly in selinux_ip_postroute() Due to difficulty in arriving at the proper security label for TCP SYN-ACK packets in selinux_ip_postroute(), we need to check packets while/before they are undergoing XFRM transforms instead of waiting until afterwards so that we can determine the correct security label. Reported-by: Janak Desai Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 8b28123..6db2e58 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -4850,22 +4850,31 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, * as fast and as clean as possible. */ if (!selinux_policycap_netpeer) return selinux_ip_postroute_compat(skb, ifindex, family); + + secmark_active = selinux_secmark_enabled(); + peerlbl_active = selinux_peerlbl_enabled(); + if (!secmark_active && !peerlbl_active) + return NF_ACCEPT; + + sk = skb->sk; + #ifdef CONFIG_XFRM /* If skb->dst->xfrm is non-NULL then the packet is undergoing an IPsec * packet transformation so allow the packet to pass without any checks * since we'll have another chance to perform access control checks * when the packet is on it's final way out. * NOTE: there appear to be some IPv6 multicast cases where skb->dst - * is NULL, in this case go ahead and apply access control. */ - if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL) + * is NULL, in this case go ahead and apply access control. + * NOTE: if this is a local socket (skb->sk != NULL) that is in the + * TCP listening state we cannot wait until the XFRM processing + * is done as we will miss out on the SA label if we do; + * unfortunately, this means more work, but it is only once per + * connection. */ + if (skb_dst(skb) != NULL && skb_dst(skb)->xfrm != NULL && + !(sk != NULL && sk->sk_state == TCP_LISTEN)) return NF_ACCEPT; #endif - secmark_active = selinux_secmark_enabled(); - peerlbl_active = selinux_peerlbl_enabled(); - if (!secmark_active && !peerlbl_active) - return NF_ACCEPT; - sk = skb->sk; if (sk == NULL) { /* Without an associated socket the packet is either coming * from the kernel or it is being forwarded; check the packet @@ -4893,6 +4902,25 @@ static unsigned int selinux_ip_postroute(struct sk_buff *skb, int ifindex, struct sk_security_struct *sksec = sk->sk_security; if (selinux_skb_peerlbl_sid(skb, family, &skb_sid)) return NF_DROP; + /* At this point, if the returned skb peerlbl is SECSID_NULL + * and the packet has been through at least one XFRM + * transformation then we must be dealing with the "final" + * form of labeled IPsec packet; since we've already applied + * all of our access controls on this packet we can safely + * pass the packet. */ + if (skb_sid == SECSID_NULL) { + switch (family) { + case PF_INET: + if (IPCB(skb)->flags & IPSKB_XFRM_TRANSFORMED) + return NF_ACCEPT; + break; + case PF_INET6: + if (IP6CB(skb)->flags & IP6SKB_XFRM_TRANSFORMED) + return NF_ACCEPT; + default: + return NF_DROP_ERR(-ECONNREFUSED); + } + } if (selinux_conn_sid(sksec->sid, skb_sid, &peer_sid)) return NF_DROP; secmark_perm = PACKET__SEND; -- cgit v1.1 From 29b1deb2a48a9dd02b93597aa4c055a24c0e989f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 15 Dec 2013 11:17:45 -0800 Subject: Revert "selinux: consider filesystem subtype in policies" This reverts commit 102aefdda4d8275ce7d7100bc16c88c74272b260. Tom London reports that it causes sync() to hang on Fedora rawhide: https://bugzilla.redhat.com/show_bug.cgi?id=1033965 and Josh Boyer bisected it down to this commit. Reverting the commit in the rawhide kernel fixes the problem. Eric Paris root-caused it to incorrect subtype matching in that commit breaking fuse, and has a tentative patch, but by now we're better off retrying this in 3.14 rather than playing with it any more. Reported-by: Tom London Bisected-by: Josh Boyer Acked-by: Eric Paris Cc: James Morris Cc: Anand Avati Cc: Paul Moore Signed-off-by: Linus Torvalds --- security/selinux/hooks.c | 40 ++++++++++++++++++---------------------- security/selinux/ss/services.c | 42 ++++-------------------------------------- 2 files changed, 22 insertions(+), 60 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 794c3ca..98b1caa 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -95,10 +95,6 @@ #include "audit.h" #include "avc_ss.h" -#define SB_TYPE_FMT "%s%s%s" -#define SB_SUBTYPE(sb) (sb->s_subtype && sb->s_subtype[0]) -#define SB_TYPE_ARGS(sb) sb->s_type->name, SB_SUBTYPE(sb) ? "." : "", SB_SUBTYPE(sb) ? sb->s_subtype : "" - extern struct security_operations *security_ops; /* SECMARK reference count */ @@ -413,8 +409,8 @@ static int sb_finish_set_opts(struct super_block *sb) the first boot of the SELinux kernel before we have assigned xattr values to the filesystem. */ if (!root_inode->i_op->getxattr) { - printk(KERN_WARNING "SELinux: (dev %s, type "SB_TYPE_FMT") has no " - "xattr support\n", sb->s_id, SB_TYPE_ARGS(sb)); + printk(KERN_WARNING "SELinux: (dev %s, type %s) has no " + "xattr support\n", sb->s_id, sb->s_type->name); rc = -EOPNOTSUPP; goto out; } @@ -422,22 +418,22 @@ static int sb_finish_set_opts(struct super_block *sb) if (rc < 0 && rc != -ENODATA) { if (rc == -EOPNOTSUPP) printk(KERN_WARNING "SELinux: (dev %s, type " - SB_TYPE_FMT") has no security xattr handler\n", - sb->s_id, SB_TYPE_ARGS(sb)); + "%s) has no security xattr handler\n", + sb->s_id, sb->s_type->name); else printk(KERN_WARNING "SELinux: (dev %s, type " - SB_TYPE_FMT") getxattr errno %d\n", sb->s_id, - SB_TYPE_ARGS(sb), -rc); + "%s) getxattr errno %d\n", sb->s_id, + sb->s_type->name, -rc); goto out; } } if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) - printk(KERN_ERR "SELinux: initialized (dev %s, type "SB_TYPE_FMT"), unknown behavior\n", - sb->s_id, SB_TYPE_ARGS(sb)); + printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", + sb->s_id, sb->s_type->name); else - printk(KERN_DEBUG "SELinux: initialized (dev %s, type "SB_TYPE_FMT"), %s\n", - sb->s_id, SB_TYPE_ARGS(sb), + printk(KERN_DEBUG "SELinux: initialized (dev %s, type %s), %s\n", + sb->s_id, sb->s_type->name, labeling_behaviors[sbsec->behavior-1]); sbsec->flags |= SE_SBINITIALIZED; @@ -600,6 +596,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, const struct cred *cred = current_cred(); int rc = 0, i; struct superblock_security_struct *sbsec = sb->s_security; + const char *name = sb->s_type->name; struct inode *inode = sbsec->sb->s_root->d_inode; struct inode_security_struct *root_isec = inode->i_security; u32 fscontext_sid = 0, context_sid = 0, rootcontext_sid = 0; @@ -658,8 +655,8 @@ static int selinux_set_mnt_opts(struct super_block *sb, strlen(mount_options[i]), &sid); if (rc) { printk(KERN_WARNING "SELinux: security_context_to_sid" - "(%s) failed for (dev %s, type "SB_TYPE_FMT") errno=%d\n", - mount_options[i], sb->s_id, SB_TYPE_ARGS(sb), rc); + "(%s) failed for (dev %s, type %s) errno=%d\n", + mount_options[i], sb->s_id, name, rc); goto out; } switch (flags[i]) { @@ -806,8 +803,7 @@ out: out_double_mount: rc = -EINVAL; printk(KERN_WARNING "SELinux: mount invalid. Same superblock, different " - "security settings for (dev %s, type "SB_TYPE_FMT")\n", sb->s_id, - SB_TYPE_ARGS(sb)); + "security settings for (dev %s, type %s)\n", sb->s_id, name); goto out; } @@ -2480,8 +2476,8 @@ static int selinux_sb_remount(struct super_block *sb, void *data) rc = security_context_to_sid(mount_options[i], len, &sid); if (rc) { printk(KERN_WARNING "SELinux: security_context_to_sid" - "(%s) failed for (dev %s, type "SB_TYPE_FMT") errno=%d\n", - mount_options[i], sb->s_id, SB_TYPE_ARGS(sb), rc); + "(%s) failed for (dev %s, type %s) errno=%d\n", + mount_options[i], sb->s_id, sb->s_type->name, rc); goto out_free_opts; } rc = -EINVAL; @@ -2519,8 +2515,8 @@ out_free_secdata: return rc; out_bad_option: printk(KERN_WARNING "SELinux: unable to change security options " - "during remount (dev %s, type "SB_TYPE_FMT")\n", sb->s_id, - SB_TYPE_ARGS(sb)); + "during remount (dev %s, type=%s)\n", sb->s_id, + sb->s_type->name); goto out_free_opts; } diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index ee470a0..d106733 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2334,50 +2334,16 @@ int security_fs_use(struct super_block *sb) struct ocontext *c; struct superblock_security_struct *sbsec = sb->s_security; const char *fstype = sb->s_type->name; - const char *subtype = (sb->s_subtype && sb->s_subtype[0]) ? sb->s_subtype : NULL; - struct ocontext *base = NULL; read_lock(&policy_rwlock); - for (c = policydb.ocontexts[OCON_FSUSE]; c; c = c->next) { - char *sub; - int baselen; - - baselen = strlen(fstype); - - /* if base does not match, this is not the one */ - if (strncmp(fstype, c->u.name, baselen)) - continue; - - /* if there is no subtype, this is the one! */ - if (!subtype) - break; - - /* skip past the base in this entry */ - sub = c->u.name + baselen; - - /* entry is only a base. save it. keep looking for subtype */ - if (sub[0] == '\0') { - base = c; - continue; - } - - /* entry is not followed by a subtype, so it is not a match */ - if (sub[0] != '.') - continue; - - /* whew, we found a subtype of this fstype */ - sub++; /* move past '.' */ - - /* exact match of fstype AND subtype */ - if (!strcmp(subtype, sub)) + c = policydb.ocontexts[OCON_FSUSE]; + while (c) { + if (strcmp(fstype, c->u.name) == 0) break; + c = c->next; } - /* in case we had found an fstype match but no subtype match */ - if (!c) - c = base; - if (c) { sbsec->behavior = c->v.behavior; if (!c->sid[0]) { -- cgit v1.1 From 46d01d63221c3508421dd72ff9c879f61053cffc Mon Sep 17 00:00:00 2001 From: Chad Hanson Date: Mon, 23 Dec 2013 17:45:01 -0500 Subject: selinux: fix broken peer recv check Fix a broken networking check. Return an error if peer recv fails. If secmark is active and the packet recv succeeds the peer recv error is ignored. Signed-off-by: Chad Hanson Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 419491d..5db2646 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -4334,8 +4334,10 @@ static int selinux_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) } err = avc_has_perm(sk_sid, peer_sid, SECCLASS_PEER, PEER__RECV, &ad); - if (err) + if (err) { selinux_netlbl_err(skb, err, 0); + return err; + } } if (secmark_active) { -- cgit v1.1 From c0c1439541f5305b57a83d599af32b74182933fe Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Mon, 23 Dec 2013 17:45:01 -0500 Subject: selinux: selinux_setprocattr()->ptrace_parent() needs rcu_read_lock() selinux_setprocattr() does ptrace_parent(p) under task_lock(p), but task_struct->alloc_lock doesn't pin ->parent or ->ptrace, this looks confusing and triggers the "suspicious RCU usage" warning because ptrace_parent() does rcu_dereference_check(). And in theory this is wrong, spin_lock()->preempt_disable() doesn't necessarily imply rcu_read_lock() we need to access the ->parent. Reported-by: Evan McNabb Signed-off-by: Oleg Nesterov Cc: stable@vger.kernel.org Signed-off-by: Paul Moore --- security/selinux/hooks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 5db2646..6625699 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -5588,11 +5588,11 @@ static int selinux_setprocattr(struct task_struct *p, /* Check for ptracing, and update the task SID if ok. Otherwise, leave SID unchanged and fail. */ ptsid = 0; - task_lock(p); + rcu_read_lock(); tracer = ptrace_parent(p); if (tracer) ptsid = task_sid(tracer); - task_unlock(p); + rcu_read_unlock(); if (tracer) { error = avc_has_perm(ptsid, sid, SECCLASS_PROCESS, -- cgit v1.1 From 3dc91d4338d698ce77832985f9cb183d8eeaf6be Mon Sep 17 00:00:00 2001 From: Steven Rostedt Date: Thu, 9 Jan 2014 21:46:34 -0500 Subject: SELinux: Fix possible NULL pointer dereference in selinux_inode_permission() While running stress tests on adding and deleting ftrace instances I hit this bug: BUG: unable to handle kernel NULL pointer dereference at 0000000000000020 IP: selinux_inode_permission+0x85/0x160 PGD 63681067 PUD 7ddbe067 PMD 0 Oops: 0000 [#1] PREEMPT CPU: 0 PID: 5634 Comm: ftrace-test-mki Not tainted 3.13.0-rc4-test-00033-gd2a6dde-dirty #20 Hardware name: /DG965MQ, BIOS MQ96510J.86A.0372.2006.0605.1717 06/05/2006 task: ffff880078375800 ti: ffff88007ddb0000 task.ti: ffff88007ddb0000 RIP: 0010:[] [] selinux_inode_permission+0x85/0x160 RSP: 0018:ffff88007ddb1c48 EFLAGS: 00010246 RAX: 0000000000000000 RBX: 0000000000800000 RCX: ffff88006dd43840 RDX: 0000000000000001 RSI: 0000000000000081 RDI: ffff88006ee46000 RBP: ffff88007ddb1c88 R08: 0000000000000000 R09: ffff88007ddb1c54 R10: 6e6576652f6f6f66 R11: 0000000000000003 R12: 0000000000000000 R13: 0000000000000081 R14: ffff88006ee46000 R15: 0000000000000000 FS: 00007f217b5b6700(0000) GS:ffffffff81e21000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033^M CR2: 0000000000000020 CR3: 000000006a0fe000 CR4: 00000000000007f0 Call Trace: security_inode_permission+0x1c/0x30 __inode_permission+0x41/0xa0 inode_permission+0x18/0x50 link_path_walk+0x66/0x920 path_openat+0xa6/0x6c0 do_filp_open+0x43/0xa0 do_sys_open+0x146/0x240 SyS_open+0x1e/0x20 system_call_fastpath+0x16/0x1b Code: 84 a1 00 00 00 81 e3 00 20 00 00 89 d8 83 c8 02 40 f6 c6 04 0f 45 d8 40 f6 c6 08 74 71 80 cf 02 49 8b 46 38 4c 8d 4d cc 45 31 c0 <0f> b7 50 20 8b 70 1c 48 8b 41 70 89 d9 8b 78 04 e8 36 cf ff ff RIP selinux_inode_permission+0x85/0x160 CR2: 0000000000000020 Investigating, I found that the inode->i_security was NULL, and the dereference of it caused the oops. in selinux_inode_permission(): isec = inode->i_security; rc = avc_has_perm_noaudit(sid, isec->sid, isec->sclass, perms, 0, &avd); Note, the crash came from stressing the deletion and reading of debugfs files. I was not able to recreate this via normal files. But I'm not sure they are safe. It may just be that the race window is much harder to hit. What seems to have happened (and what I have traced), is the file is being opened at the same time the file or directory is being deleted. As the dentry and inode locks are not held during the path walk, nor is the inodes ref counts being incremented, there is nothing saving these structures from being discarded except for an rcu_read_lock(). The rcu_read_lock() protects against freeing of the inode, but it does not protect freeing of the inode_security_struct. Now if the freeing of the i_security happens with a call_rcu(), and the i_security field of the inode is not changed (it gets freed as the inode gets freed) then there will be no issue here. (Linus Torvalds suggested not setting the field to NULL such that we do not need to check if it is NULL in the permission check). Note, this is a hack, but it fixes the problem at hand. A real fix is to restructure the destroy_inode() to call all the destructor handlers from the RCU callback. But that is a major job to do, and requires a lot of work. For now, we just band-aid this bug with this fix (it works), and work on a more maintainable solution in the future. Link: http://lkml.kernel.org/r/20140109101932.0508dec7@gandalf.local.home Link: http://lkml.kernel.org/r/20140109182756.17abaaa8@gandalf.local.home Cc: stable@vger.kernel.org Signed-off-by: Steven Rostedt Signed-off-by: Linus Torvalds --- security/selinux/hooks.c | 20 ++++++++++++++++++-- security/selinux/include/objsec.h | 5 ++++- 2 files changed, 22 insertions(+), 3 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6625699..57b0b49 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -234,6 +234,14 @@ static int inode_alloc_security(struct inode *inode) return 0; } +static void inode_free_rcu(struct rcu_head *head) +{ + struct inode_security_struct *isec; + + isec = container_of(head, struct inode_security_struct, rcu); + kmem_cache_free(sel_inode_cache, isec); +} + static void inode_free_security(struct inode *inode) { struct inode_security_struct *isec = inode->i_security; @@ -244,8 +252,16 @@ static void inode_free_security(struct inode *inode) list_del_init(&isec->list); spin_unlock(&sbsec->isec_lock); - inode->i_security = NULL; - kmem_cache_free(sel_inode_cache, isec); + /* + * The inode may still be referenced in a path walk and + * a call to selinux_inode_permission() can be made + * after inode_free_security() is called. Ideally, the VFS + * wouldn't do this, but fixing that is a much harder + * job. For now, simply free the i_security via RCU, and + * leave the current inode->i_security pointer intact. + * The inode will be freed after the RCU grace period too. + */ + call_rcu(&isec->rcu, inode_free_rcu); } static int file_alloc_security(struct file *file) diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index b1dfe10..078e553 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -38,7 +38,10 @@ struct task_security_struct { struct inode_security_struct { struct inode *inode; /* back pointer to inode object */ - struct list_head list; /* list of inode_security_struct */ + union { + struct list_head list; /* list of inode_security_struct */ + struct rcu_head rcu; /* for freeing the inode_security_struct */ + }; u32 task_sid; /* SID of creating task */ u32 sid; /* SID of this object */ u16 sclass; /* security class of this object */ -- cgit v1.1