diff options
-rw-r--r-- | include/linux/pfk.h | 50 | ||||
-rw-r--r-- | security/pfe/Kconfig | 13 | ||||
-rw-r--r-- | security/pfe/Makefile | 1 | ||||
-rw-r--r-- | security/pfe/pfk.c | 700 | ||||
-rw-r--r-- | security/pfe/pfk_ice.c | 125 | ||||
-rw-r--r-- | security/pfe/pfk_ice.h | 33 | ||||
-rw-r--r-- | security/pfe/pfk_kc.c | 376 | ||||
-rw-r--r-- | security/pfe/pfk_kc.h | 26 | ||||
-rw-r--r-- | security/selinux/hooks.c | 4 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 1 |
10 files changed, 1328 insertions, 1 deletions
diff --git a/include/linux/pfk.h b/include/linux/pfk.h new file mode 100644 index 000000000000..f492d758291e --- /dev/null +++ b/include/linux/pfk.h @@ -0,0 +1,50 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PFK_H_ +#define PFK_H_ + +#include <linux/bio.h> + +struct ice_crypto_setting; + +#ifdef CONFIG_PFK + +int pfk_load_key(const struct bio *bio, struct ice_crypto_setting *ice_setting); +int pfk_remove_key(const unsigned char *key, size_t key_size); +bool pfk_allow_merge_bio(struct bio *bio1, struct bio *bio2); + +#else +static inline int pfk_load_key(const struct bio *bio, + struct ice_crypto_setting *ice_setting) +{ + return -ENODEV; +} + +static inline int pfk_remove_key(const unsigned char *key, size_t key_size) +{ + return -ENODEV; +} + +static inline bool pfk_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2) +{ + return true; +} + +static inline void pfk_remove_all_keys(void) +{ +} + +#endif /* CONFIG_PFK */ + +#endif /* PFK_H */ diff --git a/security/pfe/Kconfig b/security/pfe/Kconfig index 12d0339835e3..b951861bfc84 100644 --- a/security/pfe/Kconfig +++ b/security/pfe/Kconfig @@ -12,4 +12,17 @@ config PFT registered application. Tagged files are encrypted using the dm-req-crypt driver. +config PFK + bool "Per-File-Key driver" + depends on SECURITY + depends on ECRYPT_FS + default n + help + This driver is used for storing eCryptfs information + in file node. + This is part of eCryptfs hardware enhanced solution + provided by Qualcomm Technologies, Inc. + Information is used when file is encrypted later using + ICE or dm crypto engine + endmenu diff --git a/security/pfe/Makefile b/security/pfe/Makefile index da43f37d10cf..983eedb86170 100644 --- a/security/pfe/Makefile +++ b/security/pfe/Makefile @@ -5,3 +5,4 @@ ccflags-y += -Isecurity/selinux -Isecurity/selinux/include -Ifs/ecryptfs obj-$(CONFIG_PFT) += pft.o +obj-$(CONFIG_PFK) += pfk.o pfk_kc.o pfk_ice.o diff --git a/security/pfe/pfk.c b/security/pfe/pfk.c new file mode 100644 index 000000000000..e8d7cb678aed --- /dev/null +++ b/security/pfe/pfk.c @@ -0,0 +1,700 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Per-File-Key (PFK). + * + * This driver is used for storing eCryptfs information (mainly file + * encryption key) in file node as part of eCryptfs hardware enhanced solution + * provided by Qualcomm Technologies, Inc. + * + * The information is stored in node when file is first opened (eCryptfs + * will fire a callback notifying PFK about this event) and will be later + * accessed by Block Device Driver to actually load the key to encryption hw. + * + * PFK exposes API's for loading and removing keys from encryption hw + * and also API to determine whether 2 adjacent blocks can be agregated by + * Block Layer in one request to encryption hw. + * PFK is only supposed to be used by eCryptfs, except the below. + * + * Please note, the only API that uses EXPORT_SYMBOL() is pfk_remove_key, + * this is intentionally, as it is the only API that is intended to be used + * by any kernel module, including dynamically loaded ones. All other API's, + * as mentioned above are only supposed to be used by eCryptfs which is + * a static module. + */ + + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ +#define pr_fmt(fmt) "pfk [%s]: " fmt, __func__ + + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/printk.h> +#include <linux/bio.h> +#include <linux/security.h> +#include <linux/lsm_hooks.h> +#include <crypto/ice.h> + +#include <linux/pfk.h> +#include <linux/ecryptfs.h> + +#include "pfk_kc.h" +#include "objsec.h" +#include "ecryptfs_kernel.h" +#include "pfk_ice.h" + +static DEFINE_MUTEX(pfk_lock); +static bool pfk_ready; +static int g_events_handle; + + +/* might be replaced by a table when more than one cipher is supported */ +#define PFK_SUPPORTED_CIPHER "aes_xts" +#define PFK_SUPPORTED_KEY_SIZE 32 + +static int pfk_inode_alloc_security(struct inode *inode) +{ + struct inode_security_struct *i_sec = NULL; + + if (inode == NULL) + return -EINVAL; + + i_sec = kzalloc(sizeof(*i_sec), GFP_KERNEL); + + if (i_sec == NULL) + return -ENOMEM; + + inode->i_security = i_sec; + + return 0; +} + +static void pfk_inode_free_security(struct inode *inode) +{ + if (inode == NULL) + return; + + kzfree(inode->i_security); +} + +static struct security_hook_list pfk_hooks[] = { + LSM_HOOK_INIT(inode_alloc_security, pfk_inode_alloc_security), + LSM_HOOK_INIT(inode_free_security, pfk_inode_free_security), + LSM_HOOK_INIT(allow_merge_bio, pfk_allow_merge_bio), +}; + +static int __init pfk_lsm_init(void) +{ + /* Check if PFK is the chosen lsm via security_module_enable() */ + if (security_module_enable("pfk")) { + security_add_hooks(pfk_hooks, ARRAY_SIZE(pfk_hooks)); + pr_debug("pfk is the chosen lsm, registered successfully !\n"); + } else { + pr_debug("pfk is not the chosen lsm.\n"); + if (!selinux_is_enabled()) { + pr_err("se linux is not enabled.\n"); + return -ENODEV; + } + + } + + return 0; +} + +/** + * pfk_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static inline bool pfk_is_ready(void) +{ + return pfk_ready; +} + +/** + * pfk_get_page_index() - get the inode from a bio. + * @bio: Pointer to BIO structure. + * + * Walk the bio struct links to get the inode. + * Please note, that in general bio may consist of several pages from + * several files, but in our case we always assume that all pages come + * from the same file, since our logic ensures it. That is why we only + * walk through the first page to look for inode. + * + * Return: pointer to the inode struct if successful, or NULL otherwise. + * + */ +static int pfk_get_page_index(const struct bio *bio, pgoff_t *page_index) +{ + if (!bio || !page_index) + return -EPERM; + + /* check bio vec count > 0 before using the bio->bi_io_vec[] array */ + if (!bio->bi_vcnt) + return -EINVAL; + if (!bio->bi_io_vec) + return -EINVAL; + if (!bio->bi_io_vec->bv_page) + return -EINVAL; + + *page_index = bio->bi_io_vec->bv_page->index; + + return 0; +} + +/** + * pfk_bio_get_inode() - get the inode from a bio. + * @bio: Pointer to BIO structure. + * + * Walk the bio struct links to get the inode. + * Please note, that in general bio may consist of several pages from + * several files, but in our case we always assume that all pages come + * from the same file, since our logic ensures it. That is why we only + * walk through the first page to look for inode. + * + * Return: pointer to the inode struct if successful, or NULL otherwise. + * + */ +static struct inode *pfk_bio_get_inode(const struct bio *bio) +{ + if (!bio) + return NULL; + /* check bio vec count > 0 before using the bio->bi_io_vec[] array */ + if (!bio->bi_vcnt) + return NULL; + if (!bio->bi_io_vec) + return NULL; + if (!bio->bi_io_vec->bv_page) + return NULL; + + if (PageAnon(bio->bi_io_vec->bv_page)) { + struct inode *inode; + + /* Using direct-io (O_DIRECT) without page cache */ + inode = dio_bio_get_inode((struct bio *)bio); + pr_debug("inode on direct-io, inode = 0x%p.\n", inode); + + return inode; + } + + if (!bio->bi_io_vec->bv_page->mapping) + return NULL; + + if (!bio->bi_io_vec->bv_page->mapping->host) + return NULL; + + return bio->bi_io_vec->bv_page->mapping->host; +} + +/** + * pfk_get_ecryptfs_data() - retrieves ecryptfs data stored inside node + * @inode: inode + * + * Return the data or NULL if there isn't any or in case of error + * Should be invoked under lock + */ +static void *pfk_get_ecryptfs_data(const struct inode *inode) +{ + struct inode_security_struct *isec = NULL; + + if (!inode) + return NULL; + + isec = inode->i_security; + + if (!isec) { + pr_debug("i_security is NULL, could be irrelevant file\n"); + return NULL; + } + + return isec->pfk_data; +} + +/** + * pfk_set_ecryptfs_data() - stores ecryptfs data inside node + * @inode: inode to update + * @data: data to put inside the node + * + * Returns 0 in case of success, error otherwise + * Should be invoked under lock + */ +static int pfk_set_ecryptfs_data(struct inode *inode, void *ecryptfs_data) +{ + struct inode_security_struct *isec = NULL; + + if (!inode) + return -EPERM; + + isec = inode->i_security; + + if (!isec) { + pr_err("i_security is NULL, not ready yet\n"); + return -EINVAL; + } + + isec->pfk_data = ecryptfs_data; + + return 0; +} + + +/** + * pfk_parse_cipher() - translate string cipher to enum + * @cipher: cipher in string as received from ecryptfs + * @algo: pointer to store the output enum (can be null) + * + * return 0 in case of success, error otherwise (i.e not supported cipher) + */ +static int pfk_parse_cipher(const unsigned char *cipher, + enum ice_cryto_algo_mode *algo) +{ + /* + * currently only AES XTS algo is supported + * in the future, table with supported ciphers might + * be introduced + */ + + if (!cipher) + return -EPERM; + + if (!strcmp(cipher, PFK_SUPPORTED_CIPHER) == 0) { + pr_err("not supported alghoritm\n"); + return -EINVAL; + } + + if (algo) + *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; + + return 0; +} + +/** + * pfk_key_size_to_key_type() - translate key size to key size enum + * @key_size: key size in bytes + * @key_size_type: pointer to store the output enum (can be null) + * + * return 0 in case of success, error otherwise (i.e not supported key size) + */ +static int pfk_key_size_to_key_type(size_t key_size, + enum ice_crpto_key_size *key_size_type) +{ + /* + * currently only 32 bit key size is supported + * in the future, table with supported key sizes might + * be introduced + */ + + if (key_size != PFK_SUPPORTED_KEY_SIZE) { + pr_err("not supported key size %lu\n", key_size); + return -EINVAL; + } + + if (key_size_type) + *key_size_type = ICE_CRYPTO_KEY_SIZE_256; + + return 0; +} + +/** + * pfk_load_key() - loads the encryption key to the ICE + * @bio: Pointer to the BIO structure + * @ice_setting: Pointer to ice setting structure that will be filled with + * ice configuration values, including the index to which the key was loaded + * + * Via bio gets access to ecryptfs key stored in auxiliary structure inside + * inode and loads it to encryption hw. + * Returns the index where the key is stored in encryption hw and additional + * information that will be used later for configuration of the encryption hw. + * + */ +int pfk_load_key(const struct bio *bio, struct ice_crypto_setting *ice_setting) +{ + struct inode *inode = NULL; + int ret = 0; + const unsigned char *key = NULL; + const unsigned char *cipher = NULL; + void *ecryptfs_data = NULL; + u32 key_index = 0; + enum ice_cryto_algo_mode algo_mode = 0; + enum ice_crpto_key_size key_size_type = 0; + size_t key_size = 0; + pgoff_t offset; + bool is_metadata = false; + + if (!pfk_is_ready()) + return -ENODEV; + + if (!bio) + return -EPERM; + + if (!ice_setting) { + pr_err("ice setting is NULL\n"); + return -EPERM; + } + + inode = pfk_bio_get_inode(bio); + if (!inode) + return -EINVAL; + + ecryptfs_data = pfk_get_ecryptfs_data(inode); + + if (!ecryptfs_data) { + ret = -EINVAL; + goto end; + } + + ret = pfk_get_page_index(bio, &offset); + if (ret != 0) { + pr_err("could not get page index from bio, probably bug %d\n", + ret); + ret = -EINVAL; + goto end; + } + + is_metadata = ecryptfs_is_page_in_metadata(ecryptfs_data, offset); + if (is_metadata == true) { + pr_debug("ecryptfs metadata, bypassing ICE\n"); + ret = -ESPIPE; + goto end; + } + + key = ecryptfs_get_key(ecryptfs_data); + if (!key) { + pr_err("could not parse key from ecryptfs\n"); + ret = -EINVAL; + goto end; + } + + key_size = ecryptfs_get_key_size(ecryptfs_data); + if (!key_size) { + pr_err("could not parse key size from ecryptfs\n"); + ret = -EINVAL; + goto end; + } + + cipher = ecryptfs_get_cipher(ecryptfs_data); + if (!key) { + pr_err("could not parse key from ecryptfs\n"); + ret = -EINVAL; + goto end; + } + + ret = pfk_parse_cipher(cipher, &algo_mode); + if (ret != 0) + return ret; + + ret = pfk_key_size_to_key_type(key_size, &key_size_type); + if (ret != 0) + return ret; + + ret = pfk_kc_load_key(key, key_size, &key_index); + if (ret != 0) { + pr_err("could not load key into pfk key cache, error %d\n", + ret); + return -EINVAL; + } + + ice_setting->key_size = key_size_type; + ice_setting->algo_mode = algo_mode; + /* hardcoded for now */ + ice_setting->key_mode = ICE_CRYPTO_USE_LUT_SW_KEY; + ice_setting->key_index = key_index; + + return 0; + +end: + + return ret; +} + +/** + * pfk_remove_key() - removes key from hw + * @key: pointer to the key + * @key_size: key size + * + * Will be used by external clients to remove a particular key for security + * reasons. + * The only API that can be used by dynamically loaded modules, + * see explanations above at the beginning of this file. + * The key is removed securely (by memsetting the previous value) + */ +int pfk_remove_key(const unsigned char *key, size_t key_size) +{ + int ret = 0; + + if (!pfk_is_ready()) + return -ENODEV; + + if (!key) + return -EPERM; + + ret = pfk_kc_remove_key(key, key_size); + + return ret; +} +EXPORT_SYMBOL(pfk_remove_key); + +/** + * pfk_allow_merge_bio() - Check if 2 BIOs can be merged. + * @bio1: Pointer to first BIO structure. + * @bio2: Pointer to second BIO structure. + * + * Prevent merging of BIOs from encrypted and non-encrypted + * files, or files encrypted with different key. + * Also prevent non encrypted and encrypted data from the same file + * to be merged (ecryptfs header if stored inside file should be non + * encrypted) + * This API is called by the file system block layer. + * + * Return: true if the BIOs allowed to be merged, false + * otherwise. + */ +bool pfk_allow_merge_bio(struct bio *bio1, struct bio *bio2) +{ + int ret; + void *ecryptfs_data1 = NULL; + void *ecryptfs_data2 = NULL; + pgoff_t offset1, offset2; + bool res = false; + struct inode *inode1 = NULL; + struct inode *inode2 = NULL; + + if (!pfk_is_ready()) + return -ENODEV; + + if (!bio1 || !bio2) + return -EPERM; + + inode1 = pfk_bio_get_inode(bio1); + inode2 = pfk_bio_get_inode(bio2); + + ecryptfs_data1 = pfk_get_ecryptfs_data(pfk_bio_get_inode(bio1)); + ecryptfs_data2 = pfk_get_ecryptfs_data(pfk_bio_get_inode(bio2)); + + /* + * if we have 2 different encrypted files or 1 encrypted and 1 regular, + * merge is forbidden + */ + if (!ecryptfs_is_data_equal(ecryptfs_data1, ecryptfs_data2)) { + res = false; + goto end; + } + + /* + * if both are equall in their NULLINNESS, we have 2 unencrypted files, + * allow merge + */ + if (!ecryptfs_data1) { + res = true; + goto end; + } + + + /* + * at this point both bio's are in the same file which is probably + * encrypted, last thing to check is header vs data + * We are assuming that we are not working in O_DIRECT mode, + * since it is not currently supported by eCryptfs + */ + ret = pfk_get_page_index(bio1, &offset1); + if (ret != 0) { + pr_err("could not get page index from bio1, probably bug %d\n", + ret); + res = false; + goto end; + } + + ret = pfk_get_page_index(bio2, &offset2); + if (ret != 0) { + pr_err("could not get page index from bio2, bug %d\n", ret); + res = false; + goto end; + } + + res = (ecryptfs_is_page_in_metadata(ecryptfs_data1, offset1) == + ecryptfs_is_page_in_metadata(ecryptfs_data2, offset2)); + + /* fall through */ + +end: + + return res; +} + +/** + * pfk_open_cb() - callback function for file open event + * @inode: file inode + * @data: data provided by eCryptfs + * + * Will be invoked from eCryptfs in case of file open event + */ +static void pfk_open_cb(struct inode *inode, void *ecryptfs_data) +{ + size_t key_size; + const unsigned char *cipher = NULL; + + if (!pfk_is_ready()) + return; + + if (!inode) { + pr_err("inode is null\n"); + return; + } + + key_size = ecryptfs_get_key_size(ecryptfs_data); + if (!(key_size)) { + pr_err("could not parse key size from ecryptfs\n"); + return; + } + + cipher = ecryptfs_get_cipher(ecryptfs_data); + if (!cipher) { + pr_err("could not parse key from ecryptfs\n"); + return; + } + + if (0 != pfk_parse_cipher(cipher, NULL)) + return; + + if (0 != pfk_key_size_to_key_type(key_size, NULL)) + return; + + mutex_lock(&pfk_lock); + pfk_set_ecryptfs_data(inode, ecryptfs_data); + mutex_unlock(&pfk_lock); +} + +/** + * pfk_release_cb() - callback function for file release event + * @inode: file inode + * + * Will be invoked from eCryptfs in case of file release event + */ +static void pfk_release_cb(struct inode *inode) +{ + const unsigned char *key = NULL; + size_t key_size; + void *data = NULL; + + if (!pfk_is_ready()) + return; + + if (!inode) { + pr_err("inode is null\n"); + return; + } + + data = pfk_get_ecryptfs_data(inode); + if (!data) { + pr_err("could not get ecryptfs data from inode\n"); + return; + } + + key = ecryptfs_get_key(data); + if (!key) { + pr_err("could not parse key from ecryptfs\n"); + return; + } + + key_size = ecryptfs_get_key_size(data); + if (!(key_size)) { + pr_err("could not parse key size from ecryptfs\n"); + return; + } + + pfk_kc_remove_key(key, key_size); + + mutex_lock(&pfk_lock); + pfk_set_ecryptfs_data(inode, NULL); + mutex_unlock(&pfk_lock); +} + +static bool pfk_is_cipher_supported_cb(char *cipher) +{ + if (!pfk_is_ready()) + return false; + + if (!cipher) + return false; + + return (pfk_parse_cipher(cipher, NULL)) == 0; +} + +static bool pfk_is_hw_crypt_cb(void) +{ + if (!pfk_is_ready()) + return false; + + return true; +} + + +static void __exit pfk_exit(void) +{ + pfk_ready = false; + ecryptfs_unregister_from_events(g_events_handle); + pfk_kc_deinit(); +} + +static int __init pfk_init(void) +{ + + int ret = 0; + struct ecryptfs_events events = {0}; + + events.open_cb = pfk_open_cb; + events.release_cb = pfk_release_cb; + events.is_cipher_supported_cb = pfk_is_cipher_supported_cb; + events.is_hw_crypt_cb = pfk_is_hw_crypt_cb; + + g_events_handle = ecryptfs_register_to_events(&events); + if (0 == g_events_handle) { + pr_err("could not register with eCryptfs, error %d\n", ret); + goto fail; + } + + ret = pfk_kc_init(); + if (ret != 0) { + pr_err("could init pfk key cache, error %d\n", ret); + ecryptfs_unregister_from_events(g_events_handle); + goto fail; + } + + ret = pfk_lsm_init(); + if (ret != 0) { + pr_debug("neither pfk nor se-linux sec modules are enabled\n"); + pr_debug("not an error, just don't enable pfk\n"); + pfk_kc_deinit(); + ecryptfs_unregister_from_events(g_events_handle); + return 0; + } + + pfk_ready = true; + pr_info("Driver initialized successfully\n"); + + return 0; + +fail: + pr_err("Failed to init driver\n"); + return -ENODEV; +} + +module_init(pfk_init); +module_exit(pfk_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Per-File-Key driver"); diff --git a/security/pfe/pfk_ice.c b/security/pfe/pfk_ice.c new file mode 100644 index 000000000000..d26dee245cc5 --- /dev/null +++ b/security/pfe/pfk_ice.c @@ -0,0 +1,125 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/async.h> +#include <linux/mm.h> +#include <linux/of.h> +#include <soc/qcom/scm.h> +#include <linux/device-mapper.h> +#include <soc/qcom/qseecomi.h> +#include "pfk_ice.h" + + +/**********************************/ +/** global definitions **/ +/**********************************/ + +#define TZ_ES_SET_ICE_KEY 0x2 +#define TZ_ES_INVALIDATE_ICE_KEY 0x3 + +/* index 0 and 1 is reserved for FDE */ +#define MIN_ICE_KEY_INDEX 2 + +#define MAX_ICE_KEY_INDEX 31 + + +#define TZ_ES_SET_ICE_KEY_ID \ + TZ_SYSCALL_CREATE_SMC_ID(TZ_OWNER_SIP, TZ_SVC_ES, TZ_ES_SET_ICE_KEY) + + +#define TZ_ES_INVALIDATE_ICE_KEY_ID \ + TZ_SYSCALL_CREATE_SMC_ID(TZ_OWNER_SIP, \ + TZ_SVC_ES, TZ_ES_INVALIDATE_ICE_KEY) + + +#define TZ_ES_SET_ICE_KEY_PARAM_ID \ + TZ_SYSCALL_CREATE_PARAM_ID_3( \ + TZ_SYSCALL_PARAM_TYPE_VAL, \ + TZ_SYSCALL_PARAM_TYPE_BUF_RW, TZ_SYSCALL_PARAM_TYPE_VAL) + +#define TZ_ES_INVALIDATE_ICE_KEY_PARAM_ID \ + TZ_SYSCALL_CREATE_PARAM_ID_1( \ + TZ_SYSCALL_PARAM_TYPE_VAL) + +#define ICE_KEY_SIZE 32 + + +uint8_t ice_key[ICE_KEY_SIZE]; + +int qti_pfk_ice_set_key(uint32_t index, uint8_t *key) +{ + struct scm_desc desc = {0}; + int ret; + char *tzbuf = (char *)ice_key; + + uint32_t smc_id = 0; + u32 tzbuflen = sizeof(ice_key); + + if (index < MIN_ICE_KEY_INDEX || index > MAX_ICE_KEY_INDEX) + return -EINVAL; + + if (!tzbuf) + return -ENOMEM; + + memset(tzbuf, 0, tzbuflen); + memcpy(ice_key, key, ICE_KEY_SIZE); + + dmac_flush_range(tzbuf, tzbuf + tzbuflen); + + smc_id = TZ_ES_SET_ICE_KEY_ID; + pr_debug(" %s , smc_id = 0x%x\n", __func__, smc_id); + + desc.arginfo = TZ_ES_SET_ICE_KEY_PARAM_ID; + desc.args[0] = index; + desc.args[1] = virt_to_phys(tzbuf); + desc.args[2] = tzbuflen; + + ret = scm_call2_atomic(smc_id, &desc); + pr_debug(" %s , ret = %d\n", __func__, ret); + if (ret) + pr_err("%s: Error: 0x%x\n", __func__, ret); + + return ret; +} + + +int qti_pfk_ice_invalidate_key(uint32_t index) +{ + struct scm_desc desc = {0}; + int ret; + + uint32_t smc_id = 0; + + if (index < MIN_ICE_KEY_INDEX || index > MAX_ICE_KEY_INDEX) + return -EINVAL; + + smc_id = TZ_ES_INVALIDATE_ICE_KEY_ID; + pr_debug(" %s , smc_id = 0x%x\n", __func__, smc_id); + + desc.arginfo = TZ_ES_INVALIDATE_ICE_KEY_PARAM_ID; + desc.args[0] = index; + + ret = scm_call2_atomic(smc_id, &desc); + + pr_debug(" %s , ret = %d\n", __func__, ret); + if (ret) + pr_err("%s: Error: 0x%x\n", __func__, ret); + + return ret; + +} diff --git a/security/pfe/pfk_ice.h b/security/pfe/pfk_ice.h new file mode 100644 index 000000000000..b1a5c4c807a3 --- /dev/null +++ b/security/pfe/pfk_ice.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PFK_ICE_H_ +#define PFK_ICE_H_ + +/* + * PFK ICE + * + * ICE keys configuration through scm calls. + * + */ + +#include <linux/types.h> + + +int pfk_ice_init(void); +int pfk_ice_deinit(void); + +int qti_pfk_ice_set_key(uint32_t index, uint8_t *key); +int qti_pfk_ice_invalidate_key(uint32_t index); + + +#endif /* PFK_ICE_H_ */ diff --git a/security/pfe/pfk_kc.c b/security/pfe/pfk_kc.c new file mode 100644 index 000000000000..687663f5a7ac --- /dev/null +++ b/security/pfe/pfk_kc.c @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * PFK Key Cache + * + * Key Cache used internally in PFK. + * The purpose of the cache is to save access time to QSEE + * when loading the keys. + * Currently the cache is the same size as the total number of keys that can + * be loaded to ICE. Since this number is relatively small, the alghoritms for + * cache eviction are simple, linear and based on last usage timestamp, i.e + * the node that will be evicted is the one with the oldest timestamp. + * Empty entries always have the oldest timestamp. + * + */ + +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <crypto/ice.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/printk.h> + +#include "pfk_kc.h" +#include "pfk_ice.h" + + +/** the first available index in ice engine */ +#define PFK_KC_STARTING_INDEX 2 + +/** currently the only supported key size */ +#define PFK_KC_KEY_SIZE 32 + +/** Table size */ +/* TODO replace by some constant from ice.h */ +#define PFK_KC_TABLE_SIZE ((32) - (PFK_KC_STARTING_INDEX)) + +/** The maximum key size */ +#define PFK_MAX_KEY_SIZE PFK_KC_KEY_SIZE + +static DEFINE_SPINLOCK(kc_lock); +static bool kc_ready; + +struct kc_entry { + unsigned char key[PFK_MAX_KEY_SIZE]; + size_t key_size; + u64 time_stamp; + u32 key_index; +}; + +static struct kc_entry kc_table[PFK_KC_TABLE_SIZE] = {{{0} , 0, 0, 0} }; + +/** + * pfk_min_time_entry() - update min time and update min entry + * @min_time: pointer to current min_time, might be updated with new value + * @time: time to compare minimum with + * @min_entry: ptr to ptr to current min_entry, might be updated with + * ptr to new entry + * @entry: will be the new min_entry if the time was updated + * + * + * Calculates the minimum between min_time and time. Replaces the min_time + * if time is less and replaces min_entry with entry + * + */ +static inline void pfk_min_time_entry(u64 *min_time, u64 time, + struct kc_entry **min_entry, struct kc_entry *entry) +{ + if (time_before64(time, *min_time)) { + *min_time = time; + *min_entry = entry; + } +} + +/** + * kc_is_ready() - driver is initialized and ready. + * + * Return: true if the key cache is ready. + */ +static inline bool kc_is_ready(void) +{ + return kc_ready == true; +} + +/** + * kc_find_key() - find kc entry + * @key: key to look for + * @key_size: the key size + * + * Return entry or NULL in case of error + * Should be invoked under lock + */ +static struct kc_entry *kc_find_key(const unsigned char *key, size_t key_size) +{ + struct kc_entry *entry = NULL; + int i = 0; + + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = &(kc_table[i]); + if (entry->key_size != key_size) + continue; + + if (0 == memcmp(entry->key, key, key_size)) + return entry; + } + + return NULL; +} + +/** + * kc_find_oldest_entry() - finds the entry with minimal timestamp + * + * Returns entry with minimal timestamp. Empty entries have timestamp + * of 0, therefore they are returned first. + * Should always succeed, the returned entry should never be NULL + * Should be invoked under lock + */ +static struct kc_entry *kc_find_oldest_entry(void) +{ + struct kc_entry *curr_min_entry = NULL; + struct kc_entry *entry = NULL; + u64 min_time = 0; + int i = 0; + + min_time = kc_table[0].time_stamp; + curr_min_entry = &(kc_table[0]); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = &(kc_table[i]); + if (!entry->time_stamp) + return entry; + + pfk_min_time_entry(&min_time, entry->time_stamp, + &curr_min_entry, entry); + } + + return curr_min_entry; +} + +/** + * kc_update_timestamp() - updates timestamp of entry to current + * + * @entry: entry to update + * + * If system time can't be retrieved, timestamp will not be updated + * Should be invoked under lock + */ +static void kc_update_timestamp(struct kc_entry *entry) +{ + if (!entry) + return; + + entry->time_stamp = get_jiffies_64(); +} + +/** + * kc_clear_entry() - clear the key from entry and remove the key from ICE + * + * @entry: pointer to entry + * @clear_qscee: if true, also clear the key from qscee + * + * Securely wipe and release the key memory, remove the key from ICE + * Should be invoked under lock + */ +static void kc_clear_entry(struct kc_entry *entry, bool clear_qscee) +{ + if (!entry) + return; + + if (clear_qscee) + qti_pfk_ice_invalidate_key(entry->key_index); + + memset(entry->key, 0, entry->key_size); + + entry->time_stamp = 0; +} + +/** + * kc_replace_entry() - replaces the key in given entry and + * loads the new key to ICE + * + * @entry: entry to replace key in + * @key: key + * @key_size: key_size + * + * The previous key is securely released and wiped, the new one is loaded + * to ICE. + * Should be invoked under lock + */ +static int kc_replace_entry(struct kc_entry *entry, const unsigned char *key, + size_t key_size) +{ + int ret = 0; + + kc_clear_entry(entry, false); + + memcpy(entry->key, key, key_size); + entry->key_size = key_size; + + ret = qti_pfk_ice_set_key(entry->key_index, (uint8_t *) key); + if (ret != 0) { + ret = -EINVAL; + goto err; + } + + kc_update_timestamp(entry); + + return 0; + +err: + + kc_clear_entry(entry, true); + + return ret; + +} + +/** + * pfk_kc_init() - init function + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_init(void) +{ + int i = 0; + + spin_lock(&kc_lock); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) + kc_table[i].key_index = PFK_KC_STARTING_INDEX + i; + + spin_unlock(&kc_lock); + + kc_ready = true; + + return 0; +} + + +/** + * pfk_kc_denit() - deinit function + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_deinit(void) +{ + pfk_kc_clear(); + kc_ready = false; + + return 0; +} + +/** + * pfk_kc_load_key() - retrieve the key from cache or add it if it's not there + * return the ICE hw key index + * @key: pointer to the key + * @key_size: the size of the key, assumed to be not bigger than + * @key_index: the pointer to key_index where the output will be stored + * + * If key is present in cache, than the key_index will be retrieved from cache. + * If it is not present, the oldest entry from kc table will be evicted, + * the key will be loaded to ICE via QSEE to the index that is the evicted + * entry number and stored in cache + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_load_key(const unsigned char *key, size_t key_size, u32 *key_index) +{ + int ret = 0; + struct kc_entry *entry = NULL; + + if (!kc_is_ready()) + return -ENODEV; + + if (!key || !key_index) + return -EPERM; + + if (key_size != PFK_KC_KEY_SIZE) + return -EPERM; + + spin_lock(&kc_lock); + entry = kc_find_key(key, key_size); + if (!entry) { + entry = kc_find_oldest_entry(); + if (!entry) { + pr_err("internal error, there should always be an oldest entry\n"); + spin_unlock(&kc_lock); + return -EINVAL; + } + + pr_debug("didn't found key in cache, replacing entry with index %d\n", + entry->key_index); + + ret = kc_replace_entry(entry, key, key_size); + if (ret) { + spin_unlock(&kc_lock); + return -EINVAL; + } + + } else { + pr_debug("found key in cache, index %d\n", entry->key_index); + kc_update_timestamp(entry); + } + + *key_index = entry->key_index; + spin_unlock(&kc_lock); + + return 0; +} + +/** + * pfk_kc_remove_key() - remove the key from cache and from ICE engine + * @key: pointer to the key + * @key_size: the size of the key, assumed to be not bigger than + * + * Return 0 in case of success, error otherwise (also in case of non + * (existing key) + */ +int pfk_kc_remove_key(const unsigned char *key, size_t key_size) +{ + struct kc_entry *entry = NULL; + + if (!kc_is_ready()) + return -ENODEV; + + if (!key) + return -EPERM; + + if (key_size != PFK_KC_KEY_SIZE) + return -EPERM; + + spin_lock(&kc_lock); + entry = kc_find_key(key, key_size); + if (!entry) { + pr_err("key does not exist\n"); + spin_unlock(&kc_lock); + return -EINVAL; + } + + kc_clear_entry(entry, true); + spin_unlock(&kc_lock); + + return 0; +} + +/** + * pfk_kc_clear() - clear the table and remove all keys from ICE + * + */ +void pfk_kc_clear(void) +{ + struct kc_entry *entry = NULL; + int i = 0; + + if (!kc_is_ready()) + return; + + spin_lock(&kc_lock); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = &(kc_table[i]); + kc_clear_entry(entry, true); + } + spin_unlock(&kc_lock); +} + diff --git a/security/pfe/pfk_kc.h b/security/pfe/pfk_kc.h new file mode 100644 index 000000000000..86cc1b43e4f3 --- /dev/null +++ b/security/pfe/pfk_kc.h @@ -0,0 +1,26 @@ +/* Copyright (c) 2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PFK_KC_H_ +#define PFK_KC_H_ + +#include <linux/types.h> + +int pfk_kc_init(void); +int pfk_kc_deinit(void); +int pfk_kc_load_key(const unsigned char *key, size_t key_size, u32 *key_index); +int pfk_kc_remove_key(const unsigned char *key, size_t key_size); +void pfk_kc_clear(void); + + + +#endif /* PFK_KC_H_ */ diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index a00bb5f9fcde..e17c4a7c62d5 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -84,6 +84,7 @@ #include <linux/msg.h> #include <linux/shm.h> #include <linux/pft.h> +#include <linux/pfk.h> #include "avc.h" #include "objsec.h" @@ -3585,7 +3586,8 @@ static int selinux_file_close(struct file *file) static bool selinux_allow_merge_bio(struct bio *bio1, struct bio *bio2) { - return pft_allow_merge_bio(bio1, bio2); + return pft_allow_merge_bio(bio1, bio2) && + pfk_allow_merge_bio(bio1, bio2); } /* task security operations */ diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index a6204e701224..b4f6589593a9 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -48,6 +48,7 @@ struct inode_security_struct { u16 sclass; /* security class of this object */ unsigned char initialized; /* initialization flag */ u32 tag; /* Per-File-Encryption tag */ + void *pfk_data; /* Per-File-Key data from ecryptfs */ struct mutex lock; }; |