Message ID | 20210726163700.2092768-5-roberto.sassu@huawei.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | integrity: Introduce DIGLIM | expand |
Em Mon, 26 Jul 2021 18:36:52 +0200 Roberto Sassu <roberto.sassu@huawei.com> escreveu: > Introduce the methods requires to manage the three objects defined. > > - digest_item methods: > - digest_add() > - digest_del() > - __digest_lookup() > - diglim_digest_get_info() > > - digest_list_item_ref methods: > - digest_list_ref_add() > - digest_list_ref_del() > > - digest_list_item methods: > - digest_list_add() > - digest_list_del() > > More information about these functions can be found in > Documentation/security/diglim/implementation.rst. > > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> > --- > .../security/diglim/implementation.rst | 9 + > MAINTAINERS | 2 + > include/linux/diglim.h | 28 + > security/integrity/Kconfig | 1 + > security/integrity/Makefile | 1 + > security/integrity/diglim/Kconfig | 11 + > security/integrity/diglim/Makefile | 8 + > security/integrity/diglim/diglim.h | 20 +- > security/integrity/diglim/methods.c | 499 ++++++++++++++++++ > 9 files changed, 578 insertions(+), 1 deletion(-) > create mode 100644 include/linux/diglim.h > create mode 100644 security/integrity/diglim/Kconfig > create mode 100644 security/integrity/diglim/Makefile > create mode 100644 security/integrity/diglim/methods.c > > diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst > index 6002049612a1..54af23b2f5f1 100644 > --- a/Documentation/security/diglim/implementation.rst > +++ b/Documentation/security/diglim/implementation.rst > @@ -200,3 +200,12 @@ Similarly: > the digest can be obtained by summing the address of the digest list buffer > with ``digest_offset`` (except for the digest lists, where the digest is > stored in the ``digest`` field of the ``digest_list_item`` structure). > + > + > +Methods > +------- > + > +This section introduces the methods requires to manage the three objects > +defined. > + > +.. kernel-doc:: security/integrity/diglim/methods.c > diff --git a/MAINTAINERS b/MAINTAINERS > index f7592d41367d..9e085a36654a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -5461,8 +5461,10 @@ F: Documentation/security/diglim/architecture.rst > F: Documentation/security/diglim/implementation.rst > F: Documentation/security/diglim/index.rst > F: Documentation/security/diglim/introduction.rst > +F: include/linux/diglim.h > F: include/uapi/linux/diglim.h > F: security/integrity/diglim/diglim.h > +F: security/integrity/diglim/methods.c > > DIOLAN U2C-12 I2C DRIVER > M: Guenter Roeck <linux@roeck-us.net> > diff --git a/include/linux/diglim.h b/include/linux/diglim.h > new file mode 100644 > index 000000000000..d4b4548a288b > --- /dev/null > +++ b/include/linux/diglim.h > @@ -0,0 +1,28 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@huawei.com> > + * > + * DIGLIM functions available for use by kernel subsystems. > + */ > + > +#ifndef __DIGLIM_H > +#define __DIGLIM_H > + > +#include <crypto/hash_info.h> > +#include <uapi/linux/diglim.h> > + > +#ifdef CONFIG_DIGLIM > +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > + enum compact_types type, u16 *modifiers, > + u8 *actions); > +#else > +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > + enum compact_types type, > + u16 *modifiers, u8 *actions) > +{ > + return -ENOENT; > +} > +#endif /*CONFIG_DIGLIM*/ > +#endif /*__DIGLIM_H*/ > diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig > index 71f0177e8716..8f94f4dcc052 100644 > --- a/security/integrity/Kconfig > +++ b/security/integrity/Kconfig > @@ -98,5 +98,6 @@ config INTEGRITY_AUDIT > > source "security/integrity/ima/Kconfig" > source "security/integrity/evm/Kconfig" > +source "security/integrity/diglim/Kconfig" > > endif # if INTEGRITY > diff --git a/security/integrity/Makefile b/security/integrity/Makefile > index 7ee39d66cf16..d6166550a6b8 100644 > --- a/security/integrity/Makefile > +++ b/security/integrity/Makefile > @@ -19,3 +19,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \ > platform_certs/keyring_handler.o > obj-$(CONFIG_IMA) += ima/ > obj-$(CONFIG_EVM) += evm/ > +obj-$(CONFIG_DIGLIM) += diglim/ > diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig > new file mode 100644 > index 000000000000..436a76a14337 > --- /dev/null > +++ b/security/integrity/diglim/Kconfig > @@ -0,0 +1,11 @@ > +# SPDX-License-Identifier: GPL-2.0-only > +# Digest Lists Integrity Module (DIGLIM) > +# > +config DIGLIM > + bool "Digest Lists Integrity Module (DIGLIM)" > + select SECURITYFS > + select CRYPTO > + select CRYPTO_HASH_INFO > + help > + DIGLIM provides reference values for file content and metadata, > + that can be used for measurement and appraisal with IMA. > diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile > new file mode 100644 > index 000000000000..b761ed8cfb3e > --- /dev/null > +++ b/security/integrity/diglim/Makefile > @@ -0,0 +1,8 @@ > +# SPDX-License-Identifier: GPL-2.0 > +# > +# Makefile for building Digest Lists Integrity Module (DIGLIM). > +# > + > +obj-$(CONFIG_DIGLIM) += diglim.o > + > +diglim-y := methods.o > diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h > index 578253d7e1d1..25851e7d4906 100644 > --- a/security/integrity/diglim/diglim.h > +++ b/security/integrity/diglim/diglim.h > @@ -20,7 +20,7 @@ > #include <linux/audit.h> > #include <crypto/hash_info.h> > #include <linux/hash_info.h> > -#include <uapi/linux/diglim.h> > +#include <linux/diglim.h> > > #define MAX_DIGEST_SIZE 64 > #define HASH_BITS 10 > @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) > return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; > } > > +extern struct h_table htable[COMPACT__LAST]; > + it sounds somewhat risky to use just "htable" for a var declared as external. > static inline struct compact_list_hdr *get_hdr( > struct digest_list_item *digest_list, > loff_t hdr_offset) > @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref) > > return ref->digest_list->buf + ref->digest_offset; > } > + > +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, > + enum compact_types type, u16 *modifiers, > + u8 *actions); > +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, > + enum compact_types type, > + struct digest_list_item *digest_list, > + loff_t digest_offset, loff_t hdr_offset); > +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types type, > + struct digest_list_item *digest_list, loff_t digest_offset, > + loff_t hdr_offset); > +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, > + loff_t size, u8 *buf, u8 actions, > + const char *label); > +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, > + struct digest_list_item *digest_list); > #endif /*__DIGLIM_INTERNAL_H*/ > diff --git a/security/integrity/diglim/methods.c b/security/integrity/diglim/methods.c > new file mode 100644 > index 000000000000..7ed61399cfe8 > --- /dev/null > +++ b/security/integrity/diglim/methods.c > @@ -0,0 +1,499 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2005,2006,2007,2008 IBM Corporation > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH > + * > + * Author: Roberto Sassu <roberto.sassu@huawei.com> > + * > + * Functions to manage digest lists. > + */ > + > +#include <linux/vmalloc.h> > +#include <linux/module.h> > +#include <linux/fault-inject.h> > + > +#include "diglim.h" > +#include "../integrity.h" > + > +/* Define a cache for each object type. */ > +static struct kmem_cache *digest_list_item_cache __read_mostly; > +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; > +static struct kmem_cache *digest_item_cache __read_mostly; > + > +/* Define a hash table for each digest type. */ > +struct h_table htable[COMPACT__LAST] = {{ > + .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT > +}}; > + > +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS > +static DECLARE_FAULT_ATTR(fail_diglim); > + > +static int __init fail_diglim_debugfs(void) > +{ > + struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL, > + &fail_diglim); > + > + return PTR_ERR_OR_ZERO(dir); > +} > + > +static inline bool should_fail_diglim(void) > +{ > + return should_fail(&fail_diglim, 1); > +} > + > +late_initcall(fail_diglim_debugfs); > +#else > +static inline bool should_fail_diglim(void) > +{ > + return false; > +} > +#endif I guess this is a matter of personal preference, but, IMO, it is a lot better to place the debugfs stuff on a separate source file, avoiding ugly #ifdefs in the middle of the code. Ok, the current code is too small to deserve a separate file, but if later patches would add more stuff, then I would opt to have this on a separate file. > + > +/** > + * __digest_lookup - lookup digest and return associated modifiers and actions > + * @digest: digest to lookup > + * @algo: digest algorithm > + * @type: type of digest to lookup (e.g. file, metadata) > + * @modifiers: modifiers (attributes) associated to the found digest > + * @actions: actions performed by IMA on the digest list containing the digest > + * > + * This function searches the given digest in the hash table depending on the > + * passed type and sets the modifiers and actions associated to the digest, if > + * the pointers are not NULL. > + * > + * This function is not intended for external use, as the returned digest item > + * could be freed at any time after it has been returned. > + * diglim_digest_get_info() should be used instead by external callers, as it > + * only returns the modifiers and the actions associated to the digest at the > + * time the digest is searched. > + * > + * RCU protects both the hash table and the linked list of references to the > + * digest lists containing the found digest. > + * > + * Return: a digest_item structure if the digest is found, NULL otherwise. > + */ > +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, > + enum compact_types type, u16 *modifiers, > + u8 *actions) > +{ > + struct digest_item *d = NULL; > + struct digest_list_item_ref *ref; > + int digest_len = hash_digest_size[algo]; > + unsigned int key = hash_key(digest); > + bool found = false; > + > + rcu_read_lock(); > + hlist_for_each_entry_rcu(d, &htable[type].queue[key], hnext) { > + list_for_each_entry_rcu(ref, &d->refs, list) { > + if (get_algo_ref(ref) != algo || > + memcmp(get_digest_ref(ref), digest, digest_len)) > + break; > + > + found = true; > + > + /* There is no need to scan all digest list refs. */ > + if (!modifiers || !actions) > + break; > + > + /* > + * The resulting modifiers and actions are the OR of the > + * modifiers and actions for each digest list. > + */ > + *modifiers |= get_hdr_ref(ref)->modifiers; > + *actions |= ref->digest_list->actions; > + } > + > + if (found) > + break; > + } > + > + rcu_read_unlock(); > + return d; > +} > + > +/** > + * diglim_digest_get_info - lookup digest and return modifiers and actions > + * @digest: digest to lookup > + * @algo: digest algorithm > + * @type: type of digest to lookup (e.g. file, metadata) > + * @modifiers: modifiers (attributes) associated to the found digest > + * @actions: actions performed by IMA on the digest lists containing the digest > + * > + * This function searches the given digest in the hash table depending on the > + * passed type and sets the modifiers and actions associated to the digest, if > + * the pointers are not NULL. > + * > + * This function is safe for external use, as it does not return pointers of > + * objects that can be freed without the caller notices it. > + * > + * Return: 0 if the digest is found, -ENOENT otherwise. > + */ > +int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > + enum compact_types type, u16 *modifiers, u8 *actions) > +{ > + struct digest_item *d; > + > + d = __digest_lookup(digest, algo, type, modifiers, actions); > + if (!d) > + return -ENOENT; > + > + return 0; > +} > + > +/** > + * digest_list_ref_add - add reference to a digest list > + * @d: digest a new reference is added to > + * @digest_list: digest list whose reference is being added > + * @digest_offset: offset of the digest in the buffer of the digest list > + * @hdr_offset: offset of the header within the digest list the digest refers to > + * > + * This function adds a new reference to an existing digest list for a given > + * digest. The reference is described by the digest_list_item_ref structure and > + * consists of a pointer of the digest list, the offset of the digest to the > + * beginning of the digest list buffer and the offset of the header the digest > + * refers to (each digest list might be composed of several digest blocks, each > + * prefixed by a header describing the attributes of those digests). > + * > + * Return: 0 if a new digest list reference was successfully added, a negative > + * value otherwise. > + */ > +static int digest_list_ref_add(struct digest_item *d, > + struct digest_list_item *digest_list, > + loff_t digest_offset, loff_t hdr_offset) > +{ > + struct digest_list_item_ref *new_ref = NULL; > + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); > + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); > + int digest_len = hash_digest_size[algo]; > + > + /* Allocate a new reference. */ > + if (!should_fail_diglim()) > + new_ref = kmem_cache_alloc(digest_list_item_ref_cache, > + GFP_KERNEL); > + if (!new_ref) { > + print_hex_dump(KERN_ERR, "digest list ref allocation failed: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + return -ENOMEM; > + } > + > + /* Set the new reference. */ > + new_ref->digest_list = digest_list; > + /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */ > + new_ref->digest_offset = digest_offset; > + new_ref->hdr_offset = hdr_offset; > + > + list_add_tail_rcu(&new_ref->list, &d->refs); > + > + print_hex_dump_debug("add digest list ref: ", DUMP_PREFIX_NONE, > + digest_len, 1, digest, digest_len, true); > + return 0; > +} > + > +/** > + * digest_list_ref_del - del reference to a digest list > + * @d: digest a reference is deleted from > + * @digest_list: digest list whose reference is being deleted > + * @digest_offset: offset of the digest in the buffer of the digest list > + * @hdr_offset: offset of the header within the digest list the digest refers to > + * > + * This function searches the reference to an already loaded digest list in the > + * linked list of references stored for each digest item. If the reference is > + * found (if not, it is a bug), the function deletes it from the linked list. > + */ > +static void digest_list_ref_del(struct digest_item *d, > + struct digest_list_item *digest_list, > + loff_t digest_offset, loff_t hdr_offset) > +{ > + struct digest_list_item_ref *ref; > + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); > + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); > + int digest_len = hash_digest_size[algo]; > + > + /* Search for a digest list reference. */ > + list_for_each_entry(ref, &d->refs, list) > + if (ref->digest_list == digest_list) > + break; > + > + if (!ref) { > + print_hex_dump(KERN_ERR, "digest list ref not found: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + return; > + } > + > + list_del_rcu(&ref->list); > + kmem_cache_free(digest_list_item_ref_cache, ref); > + > + print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE, > + digest_len, 1, digest, digest_len, true); > +} > + > +/** > + * digest_add - add a new digest > + * @digest: digest in binary form > + * @algo: digest algorithm > + * @type: digest type > + * @digest_list: digest list the new digest belongs to > + * @digest_offset: offset of the digest in the buffer of the digest list > + * @hdr_offset: offset of the header within the digest list the digest refers to > + * > + * This function first searches if the digest is already in the hash table for > + * the given type. The digest is searched by comparing the passed digest and > + * algorithm with the digest obtained from the first digest list reference > + * (buffer + digest_offset), or from the digest field of a digest list item, > + * for a digest list. > + * > + * If the digest exists, only a new reference is added (there might be multiple > + * references to the same digest list). > + * > + * If the digest is not found, a new digest item is allocated and a reference to > + * the passed digest list is added to that item. The digest item is finally > + * added to the hash table for the given type. > + * > + * Proper locking must be provided by the caller. > + * > + * Return: a new or the found digest item on success, an error pointer > + * otherwise. > + */ > +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, > + enum compact_types type, > + struct digest_list_item *digest_list, > + loff_t digest_offset, loff_t hdr_offset) > +{ > + int digest_len = hash_digest_size[algo]; > + struct digest_item *d; > + int ret; > + > + /* Search the digest. */ > + d = __digest_lookup(digest, algo, type, NULL, NULL); > + if (d) { > + /* > + * Add a new digest list reference to the existing digest item. > + */ > + ret = digest_list_ref_add(d, digest_list, digest_offset, > + hdr_offset); > + if (ret < 0) > + return ERR_PTR(ret); > + > + print_hex_dump_debug("digest add duplicate: ", DUMP_PREFIX_NONE, > + digest_len, 1, digest, digest_len, true); > + return d; > + } > + > + /* Allocate a new digest item. */ > + if (!should_fail_diglim()) > + d = kmem_cache_alloc(digest_item_cache, GFP_KERNEL); > + if (!d) { > + print_hex_dump_debug("digest allocation failed: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + return ERR_PTR(-ENOMEM); > + } > + > + INIT_LIST_HEAD(&d->refs); > + > + /* Add a new digest list reference to the new digest item. */ > + ret = digest_list_ref_add(d, digest_list, digest_offset, hdr_offset); > + if (ret < 0) { > + kmem_cache_free(digest_item_cache, d); > + return ERR_PTR(ret); > + } > + > + /* Add the new digest item to the hash table for the given type. */ > + hlist_add_head_rcu(&d->hnext, &htable[type].queue[hash_key(digest)]); > + htable[type].len++; > + > + print_hex_dump_debug("digest add: ", DUMP_PREFIX_NONE, digest_len, 1, > + digest, digest_len, true); > + return d; > +} > + > +/** > + * digest_del - delete a digest with one reference, or just a reference > + * @digest: digest in binary form > + * @algo: digest algorithm > + * @type: digest type > + * @digest_list: digest list the digest belongs to > + * @digest_offset: offset of the digest in the buffer of the digest list > + * @hdr_offset: offset of the header within the digest list the digest refers to > + * > + * This function is called when a digest list is being removed. The digest is > + * first searched in the hash table for the given type. If it is found (if not, > + * it is a bug, because digest lists can be deleted only if they were added > + * previously), a reference of the passed digest list is deleted from the linked > + * list of references of the digest item. > + * > + * If the last reference was deleted, the digest item is also deleted and > + * removed from the hash table. > + * > + * Proper locking must be provided by the caller. > + */ > +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types type, > + struct digest_list_item *digest_list, loff_t digest_offset, > + loff_t hdr_offset) > +{ > + struct digest_item *d; > + int digest_len = hash_digest_size[algo]; > + > + /* Search the digest. */ > + d = __digest_lookup(digest, algo, type, NULL, NULL); > + if (!d) { > + print_hex_dump(KERN_ERR, "digest not found: ", DUMP_PREFIX_NONE, > + digest_len, 1, digest, digest_len, true); > + return; > + } > + > + /* Delete a reference of the passed digest list. */ > + digest_list_ref_del(d, digest_list, digest_offset, hdr_offset); > + > + print_hex_dump_debug(!list_empty(&d->refs) ? > + "digest del duplicate: " : "digest del: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + > + /* Return if there are still references. */ > + if (!list_empty(&d->refs)) > + return; > + > + /* > + * Remove the digest item from the hash table and free it if there are > + * no more references left. > + */ > + hlist_del_rcu(&d->hnext); > + htable[type].len--; > + kmem_cache_free(digest_item_cache, d); > +} > + > +/** > + * digest_list_add - add a new digest list > + * @digest: digest of the digest list in binary form > + * @algo: digest algorithm > + * @size: digest list size > + * @buf: digest list buffer > + * @actions: actions (measure/appraise) performed by IMA on the digest list > + * @label: label to be used to identify the digest list > + * > + * This function allocates a new digest list item, which contains the buffer, > + * size, actions performed by IMA and a label. Each digest list item is > + * associated to a digest item representing the digest of the digest list. > + * > + * This function prevents the same digest list to be added multiple times by > + * searching its digest in the hash table for the COMPACT_DIGEST_LIST type. > + * > + * The passed buffer is copied in a new memory area, to avoid to reference > + * memory that could be freed by the caller. > + * > + * If allocation of a new digest list and the associated buffer was successful, > + * its digest is added to the hash table for the COMPACT_DIGEST_LIST type. > + * > + * Proper locking must be provided by the caller. > + * > + * Return: the digest item associated to the digest list item on success, an > + * error pointer otherwise. > + */ > +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, > + loff_t size, u8 *buf, u8 actions, > + const char *label) > +{ > + struct digest_item *d; > + struct digest_list_item *digest_list = NULL; > + int digest_len = hash_digest_size[algo]; > + > + /* Search the digest of the digest list. */ > + d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL); > + if (d) { > + print_hex_dump(KERN_ERR, "digest list already uploaded: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + return ERR_PTR(-EEXIST); > + } > + > + /* Allocate a new digest list. */ > + if (!should_fail_diglim()) > + digest_list = kmem_cache_alloc(digest_list_item_cache, > + GFP_KERNEL); > + if (!digest_list) { > + print_hex_dump(KERN_ERR, "digest list allocation failed: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + return ERR_PTR(-ENOMEM); > + } > + > + digest_list->buf = NULL; > + digest_list->size = size; > + > + if (!should_fail_diglim()) > + digest_list->buf = kmemdup(buf, size, GFP_KERNEL); > + if (!digest_list->buf) { > + print_hex_dump(KERN_ERR, "digest list allocation failed: ", > + DUMP_PREFIX_NONE, digest_len, 1, digest, > + digest_len, true); > + kmem_cache_free(digest_list_item_cache, digest_list); > + return ERR_PTR(-ENOMEM); > + } > + > + digest_list->actions = actions; > + memcpy(digest_list->digest, digest, hash_digest_size[algo]); > + digest_list->algo = algo; > + digest_list->label = label; > + > + /* Add the digest of the digest list to the hash table. */ > + d = digest_add(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); > + if (IS_ERR(d)) { > + kfree(digest_list->buf); > + kmem_cache_free(digest_list_item_cache, digest_list); > + } > + > + return d; > +} > + > +/** > + * digest_list_del - delete an existing digest list > + * @digest: digest of the digest list in binary form > + * @algo: digest algorithm > + * @actions: actions (measure/appraise) performed by IMA on the digest list > + * @digest_list: digest list to delete > + * > + * This function searches the digest of the digest list in the hash table for > + * the COMPACT_DIGEST_LIST type. If it is found, this function frees the buffer > + * and the digest list item allocated in digest_list_add(). > + * > + * This function will be executed only for digest lists that were previously > + * added. > + * > + * Proper locking must be provided by the caller. > + */ > +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, > + struct digest_list_item *digest_list) > +{ > + /* Delete the digest item associated to the digest list. */ > + digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); > + > + /* > + * Free the buffer and the digest list item allocated when the digest > + * list was added. > + */ > + kfree(digest_list->buf); > + kmem_cache_free(digest_list_item_cache, digest_list); > +} > + > +static int __init digest_list_cache_init(void) > +{ > + digest_list_item_cache = kmem_cache_create("digest_list_item_cache", > + sizeof(struct digest_list_item), > + 0, SLAB_PANIC, NULL); > + > + digest_list_item_ref_cache = kmem_cache_create( > + "digest_list_item_ref_cache", > + sizeof(struct digest_list_item_ref), 0, > + SLAB_PANIC, NULL); > + > + digest_item_cache = kmem_cache_create("digest_item_cache", > + sizeof(struct digest_item), 0, > + SLAB_PANIC, NULL); > + > + return 0; > +} > + > +late_initcall(digest_list_cache_init) Thanks, Mauro
> From: Mauro Carvalho Chehab [mailto:mchehab+huawei@kernel.org] > Sent: Wednesday, July 28, 2021 2:19 PM > Em Mon, 26 Jul 2021 18:36:52 +0200 > Roberto Sassu <roberto.sassu@huawei.com> escreveu: > > > Introduce the methods requires to manage the three objects defined. > > > > - digest_item methods: > > - digest_add() > > - digest_del() > > - __digest_lookup() > > - diglim_digest_get_info() > > > > - digest_list_item_ref methods: > > - digest_list_ref_add() > > - digest_list_ref_del() > > > > - digest_list_item methods: > > - digest_list_add() > > - digest_list_del() > > > > More information about these functions can be found in > > Documentation/security/diglim/implementation.rst. > > > > Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> > > --- > > .../security/diglim/implementation.rst | 9 + > > MAINTAINERS | 2 + > > include/linux/diglim.h | 28 + > > security/integrity/Kconfig | 1 + > > security/integrity/Makefile | 1 + > > security/integrity/diglim/Kconfig | 11 + > > security/integrity/diglim/Makefile | 8 + > > security/integrity/diglim/diglim.h | 20 +- > > security/integrity/diglim/methods.c | 499 ++++++++++++++++++ > > 9 files changed, 578 insertions(+), 1 deletion(-) > > create mode 100644 include/linux/diglim.h > > create mode 100644 security/integrity/diglim/Kconfig > > create mode 100644 security/integrity/diglim/Makefile > > create mode 100644 security/integrity/diglim/methods.c > > > > diff --git a/Documentation/security/diglim/implementation.rst > b/Documentation/security/diglim/implementation.rst > > index 6002049612a1..54af23b2f5f1 100644 > > --- a/Documentation/security/diglim/implementation.rst > > +++ b/Documentation/security/diglim/implementation.rst > > @@ -200,3 +200,12 @@ Similarly: > > the digest can be obtained by summing the address of the digest list buffer > > with ``digest_offset`` (except for the digest lists, where the digest is > > stored in the ``digest`` field of the ``digest_list_item`` structure). > > + > > + > > +Methods > > +------- > > + > > +This section introduces the methods requires to manage the three objects > > +defined. > > + > > +.. kernel-doc:: security/integrity/diglim/methods.c > > diff --git a/MAINTAINERS b/MAINTAINERS > > index f7592d41367d..9e085a36654a 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -5461,8 +5461,10 @@ F: > Documentation/security/diglim/architecture.rst > > F: Documentation/security/diglim/implementation.rst > > F: Documentation/security/diglim/index.rst > > F: Documentation/security/diglim/introduction.rst > > +F: include/linux/diglim.h > > F: include/uapi/linux/diglim.h > > F: security/integrity/diglim/diglim.h > > +F: security/integrity/diglim/methods.c > > > > DIOLAN U2C-12 I2C DRIVER > > M: Guenter Roeck <linux@roeck-us.net> > > diff --git a/include/linux/diglim.h b/include/linux/diglim.h > > new file mode 100644 > > index 000000000000..d4b4548a288b > > --- /dev/null > > +++ b/include/linux/diglim.h > > @@ -0,0 +1,28 @@ > > +/* SPDX-License-Identifier: GPL-2.0 */ > > +/* > > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH > > + * > > + * Author: Roberto Sassu <roberto.sassu@huawei.com> > > + * > > + * DIGLIM functions available for use by kernel subsystems. > > + */ > > + > > +#ifndef __DIGLIM_H > > +#define __DIGLIM_H > > + > > +#include <crypto/hash_info.h> > > +#include <uapi/linux/diglim.h> > > + > > +#ifdef CONFIG_DIGLIM > > +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > > + enum compact_types type, u16 *modifiers, > > + u8 *actions); > > +#else > > +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > > + enum compact_types type, > > + u16 *modifiers, u8 *actions) > > +{ > > + return -ENOENT; > > +} > > +#endif /*CONFIG_DIGLIM*/ > > +#endif /*__DIGLIM_H*/ > > diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig > > index 71f0177e8716..8f94f4dcc052 100644 > > --- a/security/integrity/Kconfig > > +++ b/security/integrity/Kconfig > > @@ -98,5 +98,6 @@ config INTEGRITY_AUDIT > > > > source "security/integrity/ima/Kconfig" > > source "security/integrity/evm/Kconfig" > > +source "security/integrity/diglim/Kconfig" > > > > endif # if INTEGRITY > > diff --git a/security/integrity/Makefile b/security/integrity/Makefile > > index 7ee39d66cf16..d6166550a6b8 100644 > > --- a/security/integrity/Makefile > > +++ b/security/integrity/Makefile > > @@ -19,3 +19,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += > platform_certs/efi_parser.o \ > > platform_certs/keyring_handler.o > > obj-$(CONFIG_IMA) += ima/ > > obj-$(CONFIG_EVM) += evm/ > > +obj-$(CONFIG_DIGLIM) += diglim/ > > diff --git a/security/integrity/diglim/Kconfig > b/security/integrity/diglim/Kconfig > > new file mode 100644 > > index 000000000000..436a76a14337 > > --- /dev/null > > +++ b/security/integrity/diglim/Kconfig > > @@ -0,0 +1,11 @@ > > +# SPDX-License-Identifier: GPL-2.0-only > > +# Digest Lists Integrity Module (DIGLIM) > > +# > > +config DIGLIM > > + bool "Digest Lists Integrity Module (DIGLIM)" > > + select SECURITYFS > > + select CRYPTO > > + select CRYPTO_HASH_INFO > > + help > > + DIGLIM provides reference values for file content and metadata, > > + that can be used for measurement and appraisal with IMA. > > diff --git a/security/integrity/diglim/Makefile > b/security/integrity/diglim/Makefile > > new file mode 100644 > > index 000000000000..b761ed8cfb3e > > --- /dev/null > > +++ b/security/integrity/diglim/Makefile > > @@ -0,0 +1,8 @@ > > +# SPDX-License-Identifier: GPL-2.0 > > +# > > +# Makefile for building Digest Lists Integrity Module (DIGLIM). > > +# > > + > > +obj-$(CONFIG_DIGLIM) += diglim.o > > + > > +diglim-y := methods.o > > diff --git a/security/integrity/diglim/diglim.h > b/security/integrity/diglim/diglim.h > > index 578253d7e1d1..25851e7d4906 100644 > > --- a/security/integrity/diglim/diglim.h > > +++ b/security/integrity/diglim/diglim.h > > @@ -20,7 +20,7 @@ > > #include <linux/audit.h> > > #include <crypto/hash_info.h> > > #include <linux/hash_info.h> > > -#include <uapi/linux/diglim.h> > > +#include <linux/diglim.h> > > > > #define MAX_DIGEST_SIZE 64 > > #define HASH_BITS 10 > > @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) > > return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; > > } > > > > +extern struct h_table htable[COMPACT__LAST]; > > + > > it sounds somewhat risky to use just "htable" for a var declared > as external. Ok, adding diglim_ as prefix should be enough. > > static inline struct compact_list_hdr *get_hdr( > > struct digest_list_item *digest_list, > > loff_t hdr_offset) > > @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct > digest_list_item_ref *ref) > > > > return ref->digest_list->buf + ref->digest_offset; > > } > > + > > +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, > > + enum compact_types type, u16 *modifiers, > > + u8 *actions); > > +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, > > + enum compact_types type, > > + struct digest_list_item *digest_list, > > + loff_t digest_offset, loff_t hdr_offset); > > +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types > type, > > + struct digest_list_item *digest_list, loff_t digest_offset, > > + loff_t hdr_offset); > > +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, > > + loff_t size, u8 *buf, u8 actions, > > + const char *label); > > +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, > > + struct digest_list_item *digest_list); > > #endif /*__DIGLIM_INTERNAL_H*/ > > diff --git a/security/integrity/diglim/methods.c > b/security/integrity/diglim/methods.c > > new file mode 100644 > > index 000000000000..7ed61399cfe8 > > --- /dev/null > > +++ b/security/integrity/diglim/methods.c > > @@ -0,0 +1,499 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Copyright (C) 2005,2006,2007,2008 IBM Corporation > > + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH > > + * > > + * Author: Roberto Sassu <roberto.sassu@huawei.com> > > + * > > + * Functions to manage digest lists. > > + */ > > + > > +#include <linux/vmalloc.h> > > +#include <linux/module.h> > > +#include <linux/fault-inject.h> > > + > > +#include "diglim.h" > > +#include "../integrity.h" > > + > > +/* Define a cache for each object type. */ > > +static struct kmem_cache *digest_list_item_cache __read_mostly; > > +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; > > +static struct kmem_cache *digest_item_cache __read_mostly; > > + > > +/* Define a hash table for each digest type. */ > > +struct h_table htable[COMPACT__LAST] = {{ > > + .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT > > +}}; > > > > + > > +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS > > +static DECLARE_FAULT_ATTR(fail_diglim); > > + > > +static int __init fail_diglim_debugfs(void) > > +{ > > + struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL, > > + &fail_diglim); > > + > > + return PTR_ERR_OR_ZERO(dir); > > +} > > + > > +static inline bool should_fail_diglim(void) > > +{ > > + return should_fail(&fail_diglim, 1); > > +} > > + > > +late_initcall(fail_diglim_debugfs); > > +#else > > +static inline bool should_fail_diglim(void) > > +{ > > + return false; > > +} > > +#endif > > > I guess this is a matter of personal preference, but, IMO, it is a lot better > to place the debugfs stuff on a separate source file, avoiding ugly #ifdefs > in the middle of the code. > > Ok, the current code is too small to deserve a separate file, but > if later patches would add more stuff, then I would opt to have this on > a separate file. Ok. Thanks Roberto HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063 Managing Director: Li Peng, Li Jian, Shi Yanli > > + > > +/** > > + * __digest_lookup - lookup digest and return associated modifiers and > actions > > + * @digest: digest to lookup > > + * @algo: digest algorithm > > + * @type: type of digest to lookup (e.g. file, metadata) > > + * @modifiers: modifiers (attributes) associated to the found digest > > + * @actions: actions performed by IMA on the digest list containing the > digest > > + * > > + * This function searches the given digest in the hash table depending on > the > > + * passed type and sets the modifiers and actions associated to the digest, if > > + * the pointers are not NULL. > > + * > > + * This function is not intended for external use, as the returned digest item > > + * could be freed at any time after it has been returned. > > + * diglim_digest_get_info() should be used instead by external callers, as it > > + * only returns the modifiers and the actions associated to the digest at the > > + * time the digest is searched. > > + * > > + * RCU protects both the hash table and the linked list of references to the > > + * digest lists containing the found digest. > > + * > > + * Return: a digest_item structure if the digest is found, NULL otherwise. > > + */ > > +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, > > + enum compact_types type, u16 *modifiers, > > + u8 *actions) > > +{ > > + struct digest_item *d = NULL; > > + struct digest_list_item_ref *ref; > > + int digest_len = hash_digest_size[algo]; > > + unsigned int key = hash_key(digest); > > + bool found = false; > > + > > + rcu_read_lock(); > > + hlist_for_each_entry_rcu(d, &htable[type].queue[key], hnext) { > > + list_for_each_entry_rcu(ref, &d->refs, list) { > > + if (get_algo_ref(ref) != algo || > > + memcmp(get_digest_ref(ref), digest, digest_len)) > > + break; > > + > > + found = true; > > + > > + /* There is no need to scan all digest list refs. */ > > + if (!modifiers || !actions) > > + break; > > + > > + /* > > + * The resulting modifiers and actions are the OR of > the > > + * modifiers and actions for each digest list. > > + */ > > + *modifiers |= get_hdr_ref(ref)->modifiers; > > + *actions |= ref->digest_list->actions; > > + } > > + > > + if (found) > > + break; > > + } > > + > > + rcu_read_unlock(); > > + return d; > > +} > > + > > +/** > > + * diglim_digest_get_info - lookup digest and return modifiers and actions > > + * @digest: digest to lookup > > + * @algo: digest algorithm > > + * @type: type of digest to lookup (e.g. file, metadata) > > + * @modifiers: modifiers (attributes) associated to the found digest > > + * @actions: actions performed by IMA on the digest lists containing the > digest > > + * > > + * This function searches the given digest in the hash table depending on > the > > + * passed type and sets the modifiers and actions associated to the digest, if > > + * the pointers are not NULL. > > + * > > + * This function is safe for external use, as it does not return pointers of > > + * objects that can be freed without the caller notices it. > > + * > > + * Return: 0 if the digest is found, -ENOENT otherwise. > > + */ > > +int diglim_digest_get_info(u8 *digest, enum hash_algo algo, > > + enum compact_types type, u16 *modifiers, u8 > *actions) > > +{ > > + struct digest_item *d; > > + > > + d = __digest_lookup(digest, algo, type, modifiers, actions); > > + if (!d) > > + return -ENOENT; > > + > > + return 0; > > +} > > + > > +/** > > + * digest_list_ref_add - add reference to a digest list > > + * @d: digest a new reference is added to > > + * @digest_list: digest list whose reference is being added > > + * @digest_offset: offset of the digest in the buffer of the digest list > > + * @hdr_offset: offset of the header within the digest list the digest refers > to > > + * > > + * This function adds a new reference to an existing digest list for a given > > + * digest. The reference is described by the digest_list_item_ref structure > and > > + * consists of a pointer of the digest list, the offset of the digest to the > > + * beginning of the digest list buffer and the offset of the header the digest > > + * refers to (each digest list might be composed of several digest blocks, > each > > + * prefixed by a header describing the attributes of those digests). > > + * > > + * Return: 0 if a new digest list reference was successfully added, a negative > > + * value otherwise. > > + */ > > +static int digest_list_ref_add(struct digest_item *d, > > + struct digest_list_item *digest_list, > > + loff_t digest_offset, loff_t hdr_offset) > > +{ > > + struct digest_list_item_ref *new_ref = NULL; > > + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); > > + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); > > + int digest_len = hash_digest_size[algo]; > > + > > + /* Allocate a new reference. */ > > + if (!should_fail_diglim()) > > + new_ref = kmem_cache_alloc(digest_list_item_ref_cache, > > + GFP_KERNEL); > > + if (!new_ref) { > > + print_hex_dump(KERN_ERR, "digest list ref allocation failed: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + return -ENOMEM; > > + } > > + > > + /* Set the new reference. */ > > + new_ref->digest_list = digest_list; > > + /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */ > > + new_ref->digest_offset = digest_offset; > > + new_ref->hdr_offset = hdr_offset; > > + > > + list_add_tail_rcu(&new_ref->list, &d->refs); > > + > > + print_hex_dump_debug("add digest list ref: ", DUMP_PREFIX_NONE, > > + digest_len, 1, digest, digest_len, true); > > + return 0; > > +} > > + > > +/** > > + * digest_list_ref_del - del reference to a digest list > > + * @d: digest a reference is deleted from > > + * @digest_list: digest list whose reference is being deleted > > + * @digest_offset: offset of the digest in the buffer of the digest list > > + * @hdr_offset: offset of the header within the digest list the digest refers > to > > + * > > + * This function searches the reference to an already loaded digest list in > the > > + * linked list of references stored for each digest item. If the reference is > > + * found (if not, it is a bug), the function deletes it from the linked list. > > + */ > > +static void digest_list_ref_del(struct digest_item *d, > > + struct digest_list_item *digest_list, > > + loff_t digest_offset, loff_t hdr_offset) > > +{ > > + struct digest_list_item_ref *ref; > > + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); > > + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); > > + int digest_len = hash_digest_size[algo]; > > + > > + /* Search for a digest list reference. */ > > + list_for_each_entry(ref, &d->refs, list) > > + if (ref->digest_list == digest_list) > > + break; > > + > > + if (!ref) { > > + print_hex_dump(KERN_ERR, "digest list ref not found: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + return; > > + } > > + > > + list_del_rcu(&ref->list); > > + kmem_cache_free(digest_list_item_ref_cache, ref); > > + > > + print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE, > > + digest_len, 1, digest, digest_len, true); > > +} > > + > > +/** > > + * digest_add - add a new digest > > + * @digest: digest in binary form > > + * @algo: digest algorithm > > + * @type: digest type > > + * @digest_list: digest list the new digest belongs to > > + * @digest_offset: offset of the digest in the buffer of the digest list > > + * @hdr_offset: offset of the header within the digest list the digest refers > to > > + * > > + * This function first searches if the digest is already in the hash table for > > + * the given type. The digest is searched by comparing the passed digest > and > > + * algorithm with the digest obtained from the first digest list reference > > + * (buffer + digest_offset), or from the digest field of a digest list item, > > + * for a digest list. > > + * > > + * If the digest exists, only a new reference is added (there might be > multiple > > + * references to the same digest list). > > + * > > + * If the digest is not found, a new digest item is allocated and a reference > to > > + * the passed digest list is added to that item. The digest item is finally > > + * added to the hash table for the given type. > > + * > > + * Proper locking must be provided by the caller. > > + * > > + * Return: a new or the found digest item on success, an error pointer > > + * otherwise. > > + */ > > +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, > > + enum compact_types type, > > + struct digest_list_item *digest_list, > > + loff_t digest_offset, loff_t hdr_offset) > > +{ > > + int digest_len = hash_digest_size[algo]; > > + struct digest_item *d; > > + int ret; > > + > > + /* Search the digest. */ > > + d = __digest_lookup(digest, algo, type, NULL, NULL); > > + if (d) { > > + /* > > + * Add a new digest list reference to the existing digest item. > > + */ > > + ret = digest_list_ref_add(d, digest_list, digest_offset, > > + hdr_offset); > > + if (ret < 0) > > + return ERR_PTR(ret); > > + > > + print_hex_dump_debug("digest add duplicate: ", > DUMP_PREFIX_NONE, > > + digest_len, 1, digest, digest_len, true); > > + return d; > > + } > > + > > + /* Allocate a new digest item. */ > > + if (!should_fail_diglim()) > > + d = kmem_cache_alloc(digest_item_cache, GFP_KERNEL); > > + if (!d) { > > + print_hex_dump_debug("digest allocation failed: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + return ERR_PTR(-ENOMEM); > > + } > > + > > + INIT_LIST_HEAD(&d->refs); > > + > > + /* Add a new digest list reference to the new digest item. */ > > + ret = digest_list_ref_add(d, digest_list, digest_offset, hdr_offset); > > + if (ret < 0) { > > + kmem_cache_free(digest_item_cache, d); > > + return ERR_PTR(ret); > > + } > > + > > + /* Add the new digest item to the hash table for the given type. */ > > + hlist_add_head_rcu(&d->hnext, > &htable[type].queue[hash_key(digest)]); > > + htable[type].len++; > > + > > + print_hex_dump_debug("digest add: ", DUMP_PREFIX_NONE, > digest_len, 1, > > + digest, digest_len, true); > > + return d; > > +} > > + > > +/** > > + * digest_del - delete a digest with one reference, or just a reference > > + * @digest: digest in binary form > > + * @algo: digest algorithm > > + * @type: digest type > > + * @digest_list: digest list the digest belongs to > > + * @digest_offset: offset of the digest in the buffer of the digest list > > + * @hdr_offset: offset of the header within the digest list the digest refers > to > > + * > > + * This function is called when a digest list is being removed. The digest is > > + * first searched in the hash table for the given type. If it is found (if not, > > + * it is a bug, because digest lists can be deleted only if they were added > > + * previously), a reference of the passed digest list is deleted from the > linked > > + * list of references of the digest item. > > + * > > + * If the last reference was deleted, the digest item is also deleted and > > + * removed from the hash table. > > + * > > + * Proper locking must be provided by the caller. > > + */ > > +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types > type, > > + struct digest_list_item *digest_list, loff_t digest_offset, > > + loff_t hdr_offset) > > +{ > > + struct digest_item *d; > > + int digest_len = hash_digest_size[algo]; > > + > > + /* Search the digest. */ > > + d = __digest_lookup(digest, algo, type, NULL, NULL); > > + if (!d) { > > + print_hex_dump(KERN_ERR, "digest not found: ", > DUMP_PREFIX_NONE, > > + digest_len, 1, digest, digest_len, true); > > + return; > > + } > > + > > + /* Delete a reference of the passed digest list. */ > > + digest_list_ref_del(d, digest_list, digest_offset, hdr_offset); > > + > > + print_hex_dump_debug(!list_empty(&d->refs) ? > > + "digest del duplicate: " : "digest del: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + > > + /* Return if there are still references. */ > > + if (!list_empty(&d->refs)) > > + return; > > + > > + /* > > + * Remove the digest item from the hash table and free it if there are > > + * no more references left. > > + */ > > + hlist_del_rcu(&d->hnext); > > + htable[type].len--; > > + kmem_cache_free(digest_item_cache, d); > > +} > > + > > +/** > > + * digest_list_add - add a new digest list > > + * @digest: digest of the digest list in binary form > > + * @algo: digest algorithm > > + * @size: digest list size > > + * @buf: digest list buffer > > + * @actions: actions (measure/appraise) performed by IMA on the digest > list > > + * @label: label to be used to identify the digest list > > + * > > + * This function allocates a new digest list item, which contains the buffer, > > + * size, actions performed by IMA and a label. Each digest list item is > > + * associated to a digest item representing the digest of the digest list. > > + * > > + * This function prevents the same digest list to be added multiple times by > > + * searching its digest in the hash table for the COMPACT_DIGEST_LIST > type. > > + * > > + * The passed buffer is copied in a new memory area, to avoid to reference > > + * memory that could be freed by the caller. > > + * > > + * If allocation of a new digest list and the associated buffer was successful, > > + * its digest is added to the hash table for the COMPACT_DIGEST_LIST type. > > + * > > + * Proper locking must be provided by the caller. > > + * > > + * Return: the digest item associated to the digest list item on success, an > > + * error pointer otherwise. > > + */ > > +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, > > + loff_t size, u8 *buf, u8 actions, > > + const char *label) > > +{ > > + struct digest_item *d; > > + struct digest_list_item *digest_list = NULL; > > + int digest_len = hash_digest_size[algo]; > > + > > + /* Search the digest of the digest list. */ > > + d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, > NULL); > > + if (d) { > > + print_hex_dump(KERN_ERR, "digest list already uploaded: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + return ERR_PTR(-EEXIST); > > + } > > + > > + /* Allocate a new digest list. */ > > + if (!should_fail_diglim()) > > + digest_list = kmem_cache_alloc(digest_list_item_cache, > > + GFP_KERNEL); > > + if (!digest_list) { > > + print_hex_dump(KERN_ERR, "digest list allocation failed: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + return ERR_PTR(-ENOMEM); > > + } > > + > > + digest_list->buf = NULL; > > + digest_list->size = size; > > + > > + if (!should_fail_diglim()) > > + digest_list->buf = kmemdup(buf, size, GFP_KERNEL); > > + if (!digest_list->buf) { > > + print_hex_dump(KERN_ERR, "digest list allocation failed: ", > > + DUMP_PREFIX_NONE, digest_len, 1, digest, > > + digest_len, true); > > + kmem_cache_free(digest_list_item_cache, digest_list); > > + return ERR_PTR(-ENOMEM); > > + } > > + > > + digest_list->actions = actions; > > + memcpy(digest_list->digest, digest, hash_digest_size[algo]); > > + digest_list->algo = algo; > > + digest_list->label = label; > > + > > + /* Add the digest of the digest list to the hash table. */ > > + d = digest_add(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); > > + if (IS_ERR(d)) { > > + kfree(digest_list->buf); > > + kmem_cache_free(digest_list_item_cache, digest_list); > > + } > > + > > + return d; > > +} > > + > > +/** > > + * digest_list_del - delete an existing digest list > > + * @digest: digest of the digest list in binary form > > + * @algo: digest algorithm > > + * @actions: actions (measure/appraise) performed by IMA on the digest > list > > + * @digest_list: digest list to delete > > + * > > + * This function searches the digest of the digest list in the hash table for > > + * the COMPACT_DIGEST_LIST type. If it is found, this function frees the > buffer > > + * and the digest list item allocated in digest_list_add(). > > + * > > + * This function will be executed only for digest lists that were previously > > + * added. > > + * > > + * Proper locking must be provided by the caller. > > + */ > > +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, > > + struct digest_list_item *digest_list) > > +{ > > + /* Delete the digest item associated to the digest list. */ > > + digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); > > + > > + /* > > + * Free the buffer and the digest list item allocated when the digest > > + * list was added. > > + */ > > + kfree(digest_list->buf); > > + kmem_cache_free(digest_list_item_cache, digest_list); > > +} > > + > > +static int __init digest_list_cache_init(void) > > +{ > > + digest_list_item_cache = > kmem_cache_create("digest_list_item_cache", > > + sizeof(struct digest_list_item), > > + 0, SLAB_PANIC, NULL); > > + > > + digest_list_item_ref_cache = kmem_cache_create( > > + "digest_list_item_ref_cache", > > + sizeof(struct digest_list_item_ref), 0, > > + SLAB_PANIC, NULL); > > + > > + digest_item_cache = kmem_cache_create("digest_item_cache", > > + sizeof(struct digest_item), 0, > > + SLAB_PANIC, NULL); > > + > > + return 0; > > +} > > + > > +late_initcall(digest_list_cache_init) > > > > Thanks, > Mauro
diff --git a/Documentation/security/diglim/implementation.rst b/Documentation/security/diglim/implementation.rst index 6002049612a1..54af23b2f5f1 100644 --- a/Documentation/security/diglim/implementation.rst +++ b/Documentation/security/diglim/implementation.rst @@ -200,3 +200,12 @@ Similarly: the digest can be obtained by summing the address of the digest list buffer with ``digest_offset`` (except for the digest lists, where the digest is stored in the ``digest`` field of the ``digest_list_item`` structure). + + +Methods +------- + +This section introduces the methods requires to manage the three objects +defined. + +.. kernel-doc:: security/integrity/diglim/methods.c diff --git a/MAINTAINERS b/MAINTAINERS index f7592d41367d..9e085a36654a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5461,8 +5461,10 @@ F: Documentation/security/diglim/architecture.rst F: Documentation/security/diglim/implementation.rst F: Documentation/security/diglim/index.rst F: Documentation/security/diglim/introduction.rst +F: include/linux/diglim.h F: include/uapi/linux/diglim.h F: security/integrity/diglim/diglim.h +F: security/integrity/diglim/methods.c DIOLAN U2C-12 I2C DRIVER M: Guenter Roeck <linux@roeck-us.net> diff --git a/include/linux/diglim.h b/include/linux/diglim.h new file mode 100644 index 000000000000..d4b4548a288b --- /dev/null +++ b/include/linux/diglim.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@huawei.com> + * + * DIGLIM functions available for use by kernel subsystems. + */ + +#ifndef __DIGLIM_H +#define __DIGLIM_H + +#include <crypto/hash_info.h> +#include <uapi/linux/diglim.h> + +#ifdef CONFIG_DIGLIM +extern int diglim_digest_get_info(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions); +#else +static inline int diglim_digest_get_info(u8 *digest, enum hash_algo algo, + enum compact_types type, + u16 *modifiers, u8 *actions) +{ + return -ENOENT; +} +#endif /*CONFIG_DIGLIM*/ +#endif /*__DIGLIM_H*/ diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig index 71f0177e8716..8f94f4dcc052 100644 --- a/security/integrity/Kconfig +++ b/security/integrity/Kconfig @@ -98,5 +98,6 @@ config INTEGRITY_AUDIT source "security/integrity/ima/Kconfig" source "security/integrity/evm/Kconfig" +source "security/integrity/diglim/Kconfig" endif # if INTEGRITY diff --git a/security/integrity/Makefile b/security/integrity/Makefile index 7ee39d66cf16..d6166550a6b8 100644 --- a/security/integrity/Makefile +++ b/security/integrity/Makefile @@ -19,3 +19,4 @@ integrity-$(CONFIG_LOAD_PPC_KEYS) += platform_certs/efi_parser.o \ platform_certs/keyring_handler.o obj-$(CONFIG_IMA) += ima/ obj-$(CONFIG_EVM) += evm/ +obj-$(CONFIG_DIGLIM) += diglim/ diff --git a/security/integrity/diglim/Kconfig b/security/integrity/diglim/Kconfig new file mode 100644 index 000000000000..436a76a14337 --- /dev/null +++ b/security/integrity/diglim/Kconfig @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Digest Lists Integrity Module (DIGLIM) +# +config DIGLIM + bool "Digest Lists Integrity Module (DIGLIM)" + select SECURITYFS + select CRYPTO + select CRYPTO_HASH_INFO + help + DIGLIM provides reference values for file content and metadata, + that can be used for measurement and appraisal with IMA. diff --git a/security/integrity/diglim/Makefile b/security/integrity/diglim/Makefile new file mode 100644 index 000000000000..b761ed8cfb3e --- /dev/null +++ b/security/integrity/diglim/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for building Digest Lists Integrity Module (DIGLIM). +# + +obj-$(CONFIG_DIGLIM) += diglim.o + +diglim-y := methods.o diff --git a/security/integrity/diglim/diglim.h b/security/integrity/diglim/diglim.h index 578253d7e1d1..25851e7d4906 100644 --- a/security/integrity/diglim/diglim.h +++ b/security/integrity/diglim/diglim.h @@ -20,7 +20,7 @@ #include <linux/audit.h> #include <crypto/hash_info.h> #include <linux/hash_info.h> -#include <uapi/linux/diglim.h> +#include <linux/diglim.h> #define MAX_DIGEST_SIZE 64 #define HASH_BITS 10 @@ -81,6 +81,8 @@ static inline unsigned int hash_key(u8 *digest) return (digest[0] | digest[1] << 8) % DIGLIM_HTABLE_SIZE; } +extern struct h_table htable[COMPACT__LAST]; + static inline struct compact_list_hdr *get_hdr( struct digest_list_item *digest_list, loff_t hdr_offset) @@ -131,4 +133,20 @@ static inline u8 *get_digest_ref(struct digest_list_item_ref *ref) return ref->digest_list->buf + ref->digest_offset; } + +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions); +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset); +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types type, + struct digest_list_item *digest_list, loff_t digest_offset, + loff_t hdr_offset); +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, + loff_t size, u8 *buf, u8 actions, + const char *label); +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list); #endif /*__DIGLIM_INTERNAL_H*/ diff --git a/security/integrity/diglim/methods.c b/security/integrity/diglim/methods.c new file mode 100644 index 000000000000..7ed61399cfe8 --- /dev/null +++ b/security/integrity/diglim/methods.c @@ -0,0 +1,499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * Copyright (C) 2017-2021 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@huawei.com> + * + * Functions to manage digest lists. + */ + +#include <linux/vmalloc.h> +#include <linux/module.h> +#include <linux/fault-inject.h> + +#include "diglim.h" +#include "../integrity.h" + +/* Define a cache for each object type. */ +static struct kmem_cache *digest_list_item_cache __read_mostly; +static struct kmem_cache *digest_list_item_ref_cache __read_mostly; +static struct kmem_cache *digest_item_cache __read_mostly; + +/* Define a hash table for each digest type. */ +struct h_table htable[COMPACT__LAST] = {{ + .queue[0 ... DIGLIM_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}}; + +#ifdef CONFIG_FAULT_INJECTION_DEBUG_FS +static DECLARE_FAULT_ATTR(fail_diglim); + +static int __init fail_diglim_debugfs(void) +{ + struct dentry *dir = fault_create_debugfs_attr("fail_diglim", NULL, + &fail_diglim); + + return PTR_ERR_OR_ZERO(dir); +} + +static inline bool should_fail_diglim(void) +{ + return should_fail(&fail_diglim, 1); +} + +late_initcall(fail_diglim_debugfs); +#else +static inline bool should_fail_diglim(void) +{ + return false; +} +#endif + +/** + * __digest_lookup - lookup digest and return associated modifiers and actions + * @digest: digest to lookup + * @algo: digest algorithm + * @type: type of digest to lookup (e.g. file, metadata) + * @modifiers: modifiers (attributes) associated to the found digest + * @actions: actions performed by IMA on the digest list containing the digest + * + * This function searches the given digest in the hash table depending on the + * passed type and sets the modifiers and actions associated to the digest, if + * the pointers are not NULL. + * + * This function is not intended for external use, as the returned digest item + * could be freed at any time after it has been returned. + * diglim_digest_get_info() should be used instead by external callers, as it + * only returns the modifiers and the actions associated to the digest at the + * time the digest is searched. + * + * RCU protects both the hash table and the linked list of references to the + * digest lists containing the found digest. + * + * Return: a digest_item structure if the digest is found, NULL otherwise. + */ +struct digest_item *__digest_lookup(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, + u8 *actions) +{ + struct digest_item *d = NULL; + struct digest_list_item_ref *ref; + int digest_len = hash_digest_size[algo]; + unsigned int key = hash_key(digest); + bool found = false; + + rcu_read_lock(); + hlist_for_each_entry_rcu(d, &htable[type].queue[key], hnext) { + list_for_each_entry_rcu(ref, &d->refs, list) { + if (get_algo_ref(ref) != algo || + memcmp(get_digest_ref(ref), digest, digest_len)) + break; + + found = true; + + /* There is no need to scan all digest list refs. */ + if (!modifiers || !actions) + break; + + /* + * The resulting modifiers and actions are the OR of the + * modifiers and actions for each digest list. + */ + *modifiers |= get_hdr_ref(ref)->modifiers; + *actions |= ref->digest_list->actions; + } + + if (found) + break; + } + + rcu_read_unlock(); + return d; +} + +/** + * diglim_digest_get_info - lookup digest and return modifiers and actions + * @digest: digest to lookup + * @algo: digest algorithm + * @type: type of digest to lookup (e.g. file, metadata) + * @modifiers: modifiers (attributes) associated to the found digest + * @actions: actions performed by IMA on the digest lists containing the digest + * + * This function searches the given digest in the hash table depending on the + * passed type and sets the modifiers and actions associated to the digest, if + * the pointers are not NULL. + * + * This function is safe for external use, as it does not return pointers of + * objects that can be freed without the caller notices it. + * + * Return: 0 if the digest is found, -ENOENT otherwise. + */ +int diglim_digest_get_info(u8 *digest, enum hash_algo algo, + enum compact_types type, u16 *modifiers, u8 *actions) +{ + struct digest_item *d; + + d = __digest_lookup(digest, algo, type, modifiers, actions); + if (!d) + return -ENOENT; + + return 0; +} + +/** + * digest_list_ref_add - add reference to a digest list + * @d: digest a new reference is added to + * @digest_list: digest list whose reference is being added + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function adds a new reference to an existing digest list for a given + * digest. The reference is described by the digest_list_item_ref structure and + * consists of a pointer of the digest list, the offset of the digest to the + * beginning of the digest list buffer and the offset of the header the digest + * refers to (each digest list might be composed of several digest blocks, each + * prefixed by a header describing the attributes of those digests). + * + * Return: 0 if a new digest list reference was successfully added, a negative + * value otherwise. + */ +static int digest_list_ref_add(struct digest_item *d, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + struct digest_list_item_ref *new_ref = NULL; + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); + int digest_len = hash_digest_size[algo]; + + /* Allocate a new reference. */ + if (!should_fail_diglim()) + new_ref = kmem_cache_alloc(digest_list_item_ref_cache, + GFP_KERNEL); + if (!new_ref) { + print_hex_dump(KERN_ERR, "digest list ref allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return -ENOMEM; + } + + /* Set the new reference. */ + new_ref->digest_list = digest_list; + /* Converting loff_t -> u32 is fine as long as the digest list < 4G. */ + new_ref->digest_offset = digest_offset; + new_ref->hdr_offset = hdr_offset; + + list_add_tail_rcu(&new_ref->list, &d->refs); + + print_hex_dump_debug("add digest list ref: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return 0; +} + +/** + * digest_list_ref_del - del reference to a digest list + * @d: digest a reference is deleted from + * @digest_list: digest list whose reference is being deleted + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function searches the reference to an already loaded digest list in the + * linked list of references stored for each digest item. If the reference is + * found (if not, it is a bug), the function deletes it from the linked list. + */ +static void digest_list_ref_del(struct digest_item *d, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + struct digest_list_item_ref *ref; + u8 *digest = get_digest(digest_list, digest_offset, hdr_offset); + enum hash_algo algo = get_algo(digest_list, digest_offset, hdr_offset); + int digest_len = hash_digest_size[algo]; + + /* Search for a digest list reference. */ + list_for_each_entry(ref, &d->refs, list) + if (ref->digest_list == digest_list) + break; + + if (!ref) { + print_hex_dump(KERN_ERR, "digest list ref not found: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return; + } + + list_del_rcu(&ref->list); + kmem_cache_free(digest_list_item_ref_cache, ref); + + print_hex_dump_debug("del digest list ref: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); +} + +/** + * digest_add - add a new digest + * @digest: digest in binary form + * @algo: digest algorithm + * @type: digest type + * @digest_list: digest list the new digest belongs to + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function first searches if the digest is already in the hash table for + * the given type. The digest is searched by comparing the passed digest and + * algorithm with the digest obtained from the first digest list reference + * (buffer + digest_offset), or from the digest field of a digest list item, + * for a digest list. + * + * If the digest exists, only a new reference is added (there might be multiple + * references to the same digest list). + * + * If the digest is not found, a new digest item is allocated and a reference to + * the passed digest list is added to that item. The digest item is finally + * added to the hash table for the given type. + * + * Proper locking must be provided by the caller. + * + * Return: a new or the found digest item on success, an error pointer + * otherwise. + */ +struct digest_item *digest_add(u8 *digest, enum hash_algo algo, + enum compact_types type, + struct digest_list_item *digest_list, + loff_t digest_offset, loff_t hdr_offset) +{ + int digest_len = hash_digest_size[algo]; + struct digest_item *d; + int ret; + + /* Search the digest. */ + d = __digest_lookup(digest, algo, type, NULL, NULL); + if (d) { + /* + * Add a new digest list reference to the existing digest item. + */ + ret = digest_list_ref_add(d, digest_list, digest_offset, + hdr_offset); + if (ret < 0) + return ERR_PTR(ret); + + print_hex_dump_debug("digest add duplicate: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return d; + } + + /* Allocate a new digest item. */ + if (!should_fail_diglim()) + d = kmem_cache_alloc(digest_item_cache, GFP_KERNEL); + if (!d) { + print_hex_dump_debug("digest allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&d->refs); + + /* Add a new digest list reference to the new digest item. */ + ret = digest_list_ref_add(d, digest_list, digest_offset, hdr_offset); + if (ret < 0) { + kmem_cache_free(digest_item_cache, d); + return ERR_PTR(ret); + } + + /* Add the new digest item to the hash table for the given type. */ + hlist_add_head_rcu(&d->hnext, &htable[type].queue[hash_key(digest)]); + htable[type].len++; + + print_hex_dump_debug("digest add: ", DUMP_PREFIX_NONE, digest_len, 1, + digest, digest_len, true); + return d; +} + +/** + * digest_del - delete a digest with one reference, or just a reference + * @digest: digest in binary form + * @algo: digest algorithm + * @type: digest type + * @digest_list: digest list the digest belongs to + * @digest_offset: offset of the digest in the buffer of the digest list + * @hdr_offset: offset of the header within the digest list the digest refers to + * + * This function is called when a digest list is being removed. The digest is + * first searched in the hash table for the given type. If it is found (if not, + * it is a bug, because digest lists can be deleted only if they were added + * previously), a reference of the passed digest list is deleted from the linked + * list of references of the digest item. + * + * If the last reference was deleted, the digest item is also deleted and + * removed from the hash table. + * + * Proper locking must be provided by the caller. + */ +void digest_del(u8 *digest, enum hash_algo algo, enum compact_types type, + struct digest_list_item *digest_list, loff_t digest_offset, + loff_t hdr_offset) +{ + struct digest_item *d; + int digest_len = hash_digest_size[algo]; + + /* Search the digest. */ + d = __digest_lookup(digest, algo, type, NULL, NULL); + if (!d) { + print_hex_dump(KERN_ERR, "digest not found: ", DUMP_PREFIX_NONE, + digest_len, 1, digest, digest_len, true); + return; + } + + /* Delete a reference of the passed digest list. */ + digest_list_ref_del(d, digest_list, digest_offset, hdr_offset); + + print_hex_dump_debug(!list_empty(&d->refs) ? + "digest del duplicate: " : "digest del: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + + /* Return if there are still references. */ + if (!list_empty(&d->refs)) + return; + + /* + * Remove the digest item from the hash table and free it if there are + * no more references left. + */ + hlist_del_rcu(&d->hnext); + htable[type].len--; + kmem_cache_free(digest_item_cache, d); +} + +/** + * digest_list_add - add a new digest list + * @digest: digest of the digest list in binary form + * @algo: digest algorithm + * @size: digest list size + * @buf: digest list buffer + * @actions: actions (measure/appraise) performed by IMA on the digest list + * @label: label to be used to identify the digest list + * + * This function allocates a new digest list item, which contains the buffer, + * size, actions performed by IMA and a label. Each digest list item is + * associated to a digest item representing the digest of the digest list. + * + * This function prevents the same digest list to be added multiple times by + * searching its digest in the hash table for the COMPACT_DIGEST_LIST type. + * + * The passed buffer is copied in a new memory area, to avoid to reference + * memory that could be freed by the caller. + * + * If allocation of a new digest list and the associated buffer was successful, + * its digest is added to the hash table for the COMPACT_DIGEST_LIST type. + * + * Proper locking must be provided by the caller. + * + * Return: the digest item associated to the digest list item on success, an + * error pointer otherwise. + */ +struct digest_item *digest_list_add(u8 *digest, enum hash_algo algo, + loff_t size, u8 *buf, u8 actions, + const char *label) +{ + struct digest_item *d; + struct digest_list_item *digest_list = NULL; + int digest_len = hash_digest_size[algo]; + + /* Search the digest of the digest list. */ + d = __digest_lookup(digest, algo, COMPACT_DIGEST_LIST, NULL, NULL); + if (d) { + print_hex_dump(KERN_ERR, "digest list already uploaded: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-EEXIST); + } + + /* Allocate a new digest list. */ + if (!should_fail_diglim()) + digest_list = kmem_cache_alloc(digest_list_item_cache, + GFP_KERNEL); + if (!digest_list) { + print_hex_dump(KERN_ERR, "digest list allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + return ERR_PTR(-ENOMEM); + } + + digest_list->buf = NULL; + digest_list->size = size; + + if (!should_fail_diglim()) + digest_list->buf = kmemdup(buf, size, GFP_KERNEL); + if (!digest_list->buf) { + print_hex_dump(KERN_ERR, "digest list allocation failed: ", + DUMP_PREFIX_NONE, digest_len, 1, digest, + digest_len, true); + kmem_cache_free(digest_list_item_cache, digest_list); + return ERR_PTR(-ENOMEM); + } + + digest_list->actions = actions; + memcpy(digest_list->digest, digest, hash_digest_size[algo]); + digest_list->algo = algo; + digest_list->label = label; + + /* Add the digest of the digest list to the hash table. */ + d = digest_add(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); + if (IS_ERR(d)) { + kfree(digest_list->buf); + kmem_cache_free(digest_list_item_cache, digest_list); + } + + return d; +} + +/** + * digest_list_del - delete an existing digest list + * @digest: digest of the digest list in binary form + * @algo: digest algorithm + * @actions: actions (measure/appraise) performed by IMA on the digest list + * @digest_list: digest list to delete + * + * This function searches the digest of the digest list in the hash table for + * the COMPACT_DIGEST_LIST type. If it is found, this function frees the buffer + * and the digest list item allocated in digest_list_add(). + * + * This function will be executed only for digest lists that were previously + * added. + * + * Proper locking must be provided by the caller. + */ +void digest_list_del(u8 *digest, enum hash_algo algo, u8 actions, + struct digest_list_item *digest_list) +{ + /* Delete the digest item associated to the digest list. */ + digest_del(digest, algo, COMPACT_DIGEST_LIST, digest_list, 0, 0); + + /* + * Free the buffer and the digest list item allocated when the digest + * list was added. + */ + kfree(digest_list->buf); + kmem_cache_free(digest_list_item_cache, digest_list); +} + +static int __init digest_list_cache_init(void) +{ + digest_list_item_cache = kmem_cache_create("digest_list_item_cache", + sizeof(struct digest_list_item), + 0, SLAB_PANIC, NULL); + + digest_list_item_ref_cache = kmem_cache_create( + "digest_list_item_ref_cache", + sizeof(struct digest_list_item_ref), 0, + SLAB_PANIC, NULL); + + digest_item_cache = kmem_cache_create("digest_item_cache", + sizeof(struct digest_item), 0, + SLAB_PANIC, NULL); + + return 0; +} + +late_initcall(digest_list_cache_init)
Introduce the methods requires to manage the three objects defined. - digest_item methods: - digest_add() - digest_del() - __digest_lookup() - diglim_digest_get_info() - digest_list_item_ref methods: - digest_list_ref_add() - digest_list_ref_del() - digest_list_item methods: - digest_list_add() - digest_list_del() More information about these functions can be found in Documentation/security/diglim/implementation.rst. Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com> --- .../security/diglim/implementation.rst | 9 + MAINTAINERS | 2 + include/linux/diglim.h | 28 + security/integrity/Kconfig | 1 + security/integrity/Makefile | 1 + security/integrity/diglim/Kconfig | 11 + security/integrity/diglim/Makefile | 8 + security/integrity/diglim/diglim.h | 20 +- security/integrity/diglim/methods.c | 499 ++++++++++++++++++ 9 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 include/linux/diglim.h create mode 100644 security/integrity/diglim/Kconfig create mode 100644 security/integrity/diglim/Makefile create mode 100644 security/integrity/diglim/methods.c