diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/Kconfig | 5 | ||||
-rw-r--r-- | security/Makefile | 2 | ||||
-rw-r--r-- | security/pfe/Kconfig | 28 | ||||
-rw-r--r-- | security/pfe/Makefile | 9 | ||||
-rw-r--r-- | security/pfe/pfk.c | 496 | ||||
-rw-r--r-- | security/pfe/pfk_ecryptfs.c | 592 | ||||
-rw-r--r-- | security/pfe/pfk_ecryptfs.h | 39 | ||||
-rw-r--r-- | security/pfe/pfk_ext4.c | 212 | ||||
-rw-r--r-- | security/pfe/pfk_ext4.h | 37 | ||||
-rw-r--r-- | security/pfe/pfk_ice.c | 192 | ||||
-rw-r--r-- | security/pfe/pfk_ice.h | 33 | ||||
-rw-r--r-- | security/pfe/pfk_internal.h | 34 | ||||
-rw-r--r-- | security/pfe/pfk_kc.c | 899 | ||||
-rw-r--r-- | security/pfe/pfk_kc.h | 33 | ||||
-rw-r--r-- | security/pfe/pft.c | 1877 | ||||
-rw-r--r-- | security/security.c | 10 | ||||
-rw-r--r-- | security/selinux/hooks.c | 5 | ||||
-rw-r--r-- | security/selinux/include/classmap.h | 2 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 5 | ||||
-rw-r--r-- | security/selinux/include/security.h | 1 |
20 files changed, 4506 insertions, 5 deletions
diff --git a/security/Kconfig b/security/Kconfig index 7bcb805f36a4..a98f77799e2c 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -6,6 +6,11 @@ menu "Security options" source security/keys/Kconfig +if ARCH_QCOM +source security/pfe/Kconfig +endif + + config SECURITY_DMESG_RESTRICT bool "Restrict unprivileged access to the kernel syslog" default n diff --git a/security/Makefile b/security/Makefile index c9bfbc84ff50..5256fe3fca5f 100644 --- a/security/Makefile +++ b/security/Makefile @@ -8,6 +8,7 @@ subdir-$(CONFIG_SECURITY_SMACK) += smack subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama +subdir-$(CONFIG_ARCH_QCOM) += pfe # always enable default capabilities obj-y += commoncap.o @@ -22,6 +23,7 @@ obj-$(CONFIG_AUDIT) += lsm_audit.o obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ obj-$(CONFIG_SECURITY_YAMA) += yama/ +obj-$(CONFIG_ARCH_QCOM) += pfe/ obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o # Object integrity file lists diff --git a/security/pfe/Kconfig b/security/pfe/Kconfig new file mode 100644 index 000000000000..0cd9e81a4952 --- /dev/null +++ b/security/pfe/Kconfig @@ -0,0 +1,28 @@ +menu "Qualcomm Technologies, Inc Per File Encryption security device drivers" + depends on ARCH_QCOM + +config PFT + bool "Per-File-Tagger driver" + depends on SECURITY + default n + help + This driver is used for tagging enterprise files. + It is part of the Per-File-Encryption (PFE) feature. + The driver is tagging files when created by + registered application. + Tagged files are encrypted using the dm-req-crypt driver. + +config PFK + bool "Per-File-Key driver" + depends on SECURITY + depends on SECURITY_SELINUX + 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 new file mode 100644 index 000000000000..f7badf74c73f --- /dev/null +++ b/security/pfe/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the MSM specific security device drivers. +# + +ccflags-y += -Isecurity/selinux -Isecurity/selinux/include -Ifs/ecryptfs +ccflags-y += -Ifs/ext4 + +obj-$(CONFIG_PFT) += pft.o +obj-$(CONFIG_PFK) += pfk.o pfk_kc.o pfk_ice.o pfk_ext4.o pfk_ecryptfs.o diff --git a/security/pfe/pfk.c b/security/pfe/pfk.c new file mode 100644 index 000000000000..2e5aa2fb6688 --- /dev/null +++ b/security/pfe/pfk.c @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2015-2017, 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 responsible for overall management of various + * Per File Encryption variants that work on top of or as part of different + * file systems. + * + * The driver has the following purpose : + * 1) Define priorities between PFE's if more than one is enabled + * 2) Extract key information from inode + * 3) Load and manage various keys in ICE HW engine + * 4) It should be invoked from various layers in FS/BLOCK/STORAGE DRIVER + * that need to take decision on HW encryption management of the data + * Some examples: + * BLOCK LAYER: when it takes decision on whether 2 chunks can be united + * to one encryption / decryption request sent to the HW + * + * UFS DRIVER: when it need to configure ICE HW with a particular key slot + * to be used for encryption / decryption + * + * PFE variants can differ on particular way of storing the cryptographic info + * inside inode, actions to be taken upon file operations, etc., but the common + * properties are described above + * + */ + + +/* 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 <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" +#include "pfk_ext4.h" +#include "pfk_ecryptfs.h" +#include "pfk_internal.h" +#include "ext4.h" + +static bool pfk_ready; + + +/* might be replaced by a table when more than one cipher is supported */ +#define PFK_SUPPORTED_KEY_SIZE 32 +#define PFK_SUPPORTED_SALT_SIZE 32 + +/* Various PFE types and function tables to support each one of them */ +enum pfe_type {ECRYPTFS_PFE, EXT4_CRYPT_PFE, INVALID_PFE}; + +typedef int (*pfk_parse_inode_type)(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); + +typedef bool (*pfk_allow_merge_bio_type)(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); + +static const pfk_parse_inode_type pfk_parse_inode_ftable[] = { + /* ECRYPTFS_PFE */ &pfk_ecryptfs_parse_inode, + /* EXT4_CRYPT_PFE */ &pfk_ext4_parse_inode, +}; + +static const pfk_allow_merge_bio_type pfk_allow_merge_bio_ftable[] = { + /* ECRYPTFS_PFE */ &pfk_ecryptfs_allow_merge_bio, + /* EXT4_CRYPT_PFE */ &pfk_ext4_allow_merge_bio, +}; + +static void __exit pfk_exit(void) +{ + pfk_ready = false; + pfk_ext4_deinit(); + pfk_ecryptfs_deinit(); + pfk_kc_deinit(); +} + +static int __init pfk_init(void) +{ + + int ret = 0; + + ret = pfk_ecryptfs_init(); + if (ret != 0) + goto fail; + + ret = pfk_ext4_init(); + if (ret != 0) { + pfk_ecryptfs_deinit(); + goto fail; + } + + ret = pfk_kc_init(); + if (ret != 0) { + pr_err("could init pfk key cache, error %d\n", ret); + pfk_ext4_deinit(); + pfk_ecryptfs_deinit(); + goto fail; + } + + pfk_ready = true; + pr_info("Driver initialized successfully\n"); + + return 0; + +fail: + pr_err("Failed to init driver\n"); + return -ENODEV; +} + +/* + * If more than one type is supported simultaneously, this function will also + * set the priority between them + */ +static enum pfe_type pfk_get_pfe_type(const struct inode *inode) +{ + if (!inode) + return INVALID_PFE; + + if (pfk_is_ecryptfs_type(inode)) + return ECRYPTFS_PFE; + + if (pfk_is_ext4_type(inode)) + return EXT4_CRYPT_PFE; + + return INVALID_PFE; +} + +/** + * inode_to_filename() - get the filename from inode pointer. + * @inode: inode pointer + * + * it is used for debug prints. + * + * Return: filename string or "unknown". + */ +char *inode_to_filename(const struct inode *inode) +{ + struct dentry *dentry = NULL; + char *filename = NULL; + + if (hlist_empty(&inode->i_dentry)) + return "unknown"; + + dentry = hlist_entry(inode->i_dentry.first, struct dentry, d_u.d_alias); + filename = dentry->d_iname; + + return filename; +} + +/** + * 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_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; + if (!bio_has_data((struct bio *)bio)) + 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_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) + */ +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 %zu\n", key_size); + return -EINVAL; + } + + if (key_size_type) + *key_size_type = ICE_CRYPTO_KEY_SIZE_256; + + return 0; +} + +/* + * Retrieves filesystem type from inode's superblock + */ +bool pfe_is_inode_filesystem_type(const struct inode *inode, + const char *fs_type) +{ + if (!inode || !fs_type) + return false; + + if (!inode->i_sb) + return false; + + if (!inode->i_sb->s_type) + return false; + + return (strcmp(inode->i_sb->s_type->name, fs_type) == 0); +} + + +/** + * pfk_load_key_start() - loads PFE encryption key to the ICE + * Can also be invoked from non + * PFE context, in this case it + * is not relevant and is_pfe + * flag is set to false + * + * @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 + * @is_pfe: will be false if inode is not relevant to PFE, in such a case + * it should be treated as non PFE by the block layer + * + * 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. + * + * Must be followed by pfk_load_key_end when key is no longer used by ice + * + */ +int pfk_load_key_start(const struct bio *bio, + struct ice_crypto_setting *ice_setting, bool *is_pfe, + bool async) +{ + int ret = 0; + struct pfk_key_info key_info = {0}; + enum ice_cryto_algo_mode algo_mode = ICE_CRYPTO_ALGO_MODE_AES_XTS; + enum ice_crpto_key_size key_size_type = 0; + u32 key_index = 0; + struct inode *inode = NULL; + enum pfe_type which_pfe = INVALID_PFE; + + if (!is_pfe) { + pr_err("is_pfe is NULL\n"); + return -EINVAL; + } + + /* + * only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_is_ready()) + return -ENODEV; + + if (!ice_setting) { + pr_err("ice setting is NULL\n"); + return -EINVAL; + } + + inode = pfk_bio_get_inode(bio); + if (!inode) { + *is_pfe = false; + return -EINVAL; + } + + which_pfe = pfk_get_pfe_type(inode); + if (which_pfe == INVALID_PFE) { + *is_pfe = false; + return -EPERM; + } + + pr_debug("parsing file %s with PFE %d\n", + inode_to_filename(inode), which_pfe); + + ret = (*(pfk_parse_inode_ftable[which_pfe])) + (bio, inode, &key_info, &algo_mode, is_pfe); + if (ret != 0) + return ret; + + ret = pfk_key_size_to_key_type(key_info.key_size, &key_size_type); + if (ret != 0) + return ret; + + ret = pfk_kc_load_key_start(key_info.key, key_info.key_size, + key_info.salt, key_info.salt_size, &key_index, async); + if (ret) { + if (ret != -EBUSY && ret != -EAGAIN) + pr_err("start: could not load key into pfk key cache, error %d\n", + ret); + + return ret; + } + + 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; + + pr_debug("loaded key for file %s key_index %d\n", + inode_to_filename(inode), key_index); + + return 0; +} + +/** + * pfk_load_key_end() - marks the PFE key as no longer used by ICE + * Can also be invoked from non + * PFE context, in this case it is not + * relevant and is_pfe flag is + * set to false + * + * @bio: Pointer to the BIO structure + * @is_pfe: Pointer to is_pfe flag, which will be true if function was invoked + * from PFE context + */ +int pfk_load_key_end(const struct bio *bio, bool *is_pfe) +{ + int ret = 0; + struct pfk_key_info key_info = {0}; + enum pfe_type which_pfe = INVALID_PFE; + struct inode *inode = NULL; + + if (!is_pfe) { + pr_err("is_pfe is NULL\n"); + return -EINVAL; + } + + /* only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_is_ready()) + return -ENODEV; + + inode = pfk_bio_get_inode(bio); + if (!inode) { + *is_pfe = false; + return -EINVAL; + } + + which_pfe = pfk_get_pfe_type(inode); + if (which_pfe == INVALID_PFE) { + *is_pfe = false; + return -EPERM; + } + + ret = (*(pfk_parse_inode_ftable[which_pfe])) + (bio, inode, &key_info, NULL, is_pfe); + if (ret != 0) + return ret; + + pfk_kc_load_key_end(key_info.key, key_info.key_size, + key_info.salt, key_info.salt_size); + + pr_debug("finished using key for file %s\n", + inode_to_filename(inode)); + + return 0; +} + +/** + * 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(const struct bio *bio1, const struct bio *bio2) +{ + struct inode *inode1 = NULL; + struct inode *inode2 = NULL; + enum pfe_type which_pfe1 = INVALID_PFE; + enum pfe_type which_pfe2 = INVALID_PFE; + + if (!pfk_is_ready()) + return false; + + if (!bio1 || !bio2) + return false; + + if (bio1 == bio2) + return true; + + inode1 = pfk_bio_get_inode(bio1); + inode2 = pfk_bio_get_inode(bio2); + + + which_pfe1 = pfk_get_pfe_type(inode1); + which_pfe2 = pfk_get_pfe_type(inode2); + + /* nodes with different encryption, do not merge */ + if (which_pfe1 != which_pfe2) + return false; + + /* both nodes do not have encryption, allow merge */ + if (which_pfe1 == INVALID_PFE) + return true; + + return (*(pfk_allow_merge_bio_ftable[which_pfe1]))(bio1, bio2, + inode1, inode2); +} +/** + * Flush key table on storage core reset. During core reset key configuration + * is lost in ICE. We need to flash the cache, so that the keys will be + * reconfigured again for every subsequent transaction + */ +void pfk_clear_on_reset(void) +{ + if (!pfk_is_ready()) + return; + + pfk_kc_clear_on_reset(); +} + +module_init(pfk_init); +module_exit(pfk_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Per-File-Key driver"); diff --git a/security/pfe/pfk_ecryptfs.c b/security/pfe/pfk_ecryptfs.c new file mode 100644 index 000000000000..f55ee83c30ed --- /dev/null +++ b/security/pfe/pfk_ecryptfs.c @@ -0,0 +1,592 @@ +/* + * Copyright (c) 2015-2017, 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) - eCryptfs. + * + * 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. + * + */ + + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ +#define pr_fmt(fmt) "pfk_ecryptfs [%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_ecryptfs.h" +#include "pfk_kc.h" +#include "objsec.h" +#include "ecryptfs_kernel.h" +#include "pfk_ice.h" + +static DEFINE_MUTEX(pfk_ecryptfs_lock); +static bool pfk_ecryptfs_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_SALT_SIZE 32 + +static void *pfk_ecryptfs_get_data(const struct inode *inode); +static void pfk_ecryptfs_open_cb(struct inode *inode, void *ecryptfs_data); +static void pfk_ecryptfs_release_cb(struct inode *inode); +static bool pfk_ecryptfs_is_cipher_supported_cb(const void *ecryptfs_data); +static size_t pfk_ecryptfs_get_salt_key_size_cb(const void *ecryptfs_data); +static bool pfk_ecryptfs_is_hw_crypt_cb(void); + + +/** + * pfk_is_ecryptfs_type() - return true if inode belongs to ICE ecryptfs PFE + * @inode: inode pointer + */ +bool pfk_is_ecryptfs_type(const struct inode *inode) +{ + void *ecryptfs_data = NULL; + + /* + * the actual filesystem of an inode is still ext4, eCryptfs never + * reaches bio + */ + if (!pfe_is_inode_filesystem_type(inode, "ext4")) + return false; + + ecryptfs_data = pfk_ecryptfs_get_data(inode); + + if (!ecryptfs_data) + return false; + + return true; +} + +/* + * pfk_ecryptfs_lsm_init() - makes sure either se-linux is + * registered as security module as it is required by pfk_ecryptfs. + * + * This is required because ecryptfs uses a field inside security struct in + * inode to store its info + */ +static int __init pfk_ecryptfs_lsm_init(void) +{ + if (!selinux_is_enabled()) { + pr_err("PFE eCryptfs requires se linux to be enabled\n"); + return -ENODEV; + } + + return 0; +} + +/* + * pfk_ecryptfs_deinit() - Deinit function, should be invoked by upper PFK layer + */ +void pfk_ecryptfs_deinit(void) +{ + pfk_ecryptfs_ready = false; + ecryptfs_unregister_from_events(g_events_handle); +} + +/* + * pfk_ecryptfs_init() - Init function, should be invoked by upper PFK layer + */ +int __init pfk_ecryptfs_init(void) +{ + int ret = 0; + struct ecryptfs_events events = {0}; + + events.open_cb = pfk_ecryptfs_open_cb; + events.release_cb = pfk_ecryptfs_release_cb; + events.is_cipher_supported_cb = pfk_ecryptfs_is_cipher_supported_cb; + events.is_hw_crypt_cb = pfk_ecryptfs_is_hw_crypt_cb; + events.get_salt_key_size_cb = pfk_ecryptfs_get_salt_key_size_cb; + + g_events_handle = ecryptfs_register_to_events(&events); + if (g_events_handle == 0) { + pr_err("could not register with eCryptfs, error %d\n", ret); + goto fail; + } + + ret = pfk_ecryptfs_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 ecryptfs\n"); + ecryptfs_unregister_from_events(g_events_handle); + return 0; + } + + pfk_ecryptfs_ready = true; + pr_info("PFK ecryptfs inited successfully\n"); + + return 0; + +fail: + pr_err("Failed to init PFK ecryptfs\n"); + return -ENODEV; +} + +/** + * pfk_ecryptfs_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static inline bool pfk_ecryptfs_is_ready(void) +{ + return pfk_ecryptfs_ready; +} + +/** + * pfk_ecryptfs_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_ecryptfs_get_page_index(const struct bio *bio, + pgoff_t *page_index) +{ + if (!bio || !page_index) + return -EINVAL; + if (!bio_has_data((struct bio *)bio)) + 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_ecryptfs_get_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_ecryptfs_get_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_ecryptfs_set_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_ecryptfs_set_data(struct inode *inode, void *ecryptfs_data) +{ + struct inode_security_struct *isec = NULL; + + if (!inode) + return -EINVAL; + + 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_ecryptfs_parse_cipher() - parse cipher from ecryptfs to enum + * @ecryptfs_data: ecrypfs data + * @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_ecryptfs_parse_cipher(const void *ecryptfs_data, + enum ice_cryto_algo_mode *algo) +{ + /* + * currently only AES XTS algo is supported + * in the future, table with supported ciphers might + * be introduced + */ + + if (!ecryptfs_data) + return -EINVAL; + + if (!ecryptfs_cipher_match(ecryptfs_data, + PFK_SUPPORTED_CIPHER, sizeof(PFK_SUPPORTED_CIPHER))) { + pr_debug("ecryptfs alghoritm is not supported by pfk\n"); + return -EINVAL; + } + + if (algo) + *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; + + return 0; +} + +/* + * pfk_ecryptfs_parse_inode() - parses key and algo information from inode + * + * Should be invoked by upper pfk layer + * @bio: bio + * @inode: inode to be parsed + * @key_info: out, key and salt information to be stored + * @algo: out, algorithm to be stored (can be null) + * @is_pfe: out, will be false if inode is not relevant to PFE, in such a case + * it should be treated as non PFE by the block layer + */ +int pfk_ecryptfs_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe) +{ + int ret = 0; + void *ecryptfs_data = NULL; + pgoff_t offset; + bool is_metadata = false; + + if (!is_pfe) + return -EINVAL; + + /* + * only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_ecryptfs_is_ready()) + return -ENODEV; + + if (!inode) + return -EINVAL; + + if (!key_info) + return -EINVAL; + + ecryptfs_data = pfk_ecryptfs_get_data(inode); + if (!ecryptfs_data) { + pr_err("internal error, no ecryptfs data\n"); + return -EINVAL; + } + + ret = pfk_ecryptfs_get_page_index(bio, &offset); + if (ret != 0) { + pr_err("could not get page index from bio, probably bug %d\n", + ret); + return -EINVAL; + } + + is_metadata = ecryptfs_is_page_in_metadata(ecryptfs_data, offset); + if (is_metadata == true) { + pr_debug("ecryptfs metadata, bypassing ICE\n"); + *is_pfe = false; + return -EPERM; + } + + key_info->key = ecryptfs_get_key(ecryptfs_data); + if (!key_info->key) { + pr_err("could not parse key from ecryptfs\n"); + return -EINVAL; + } + + key_info->key_size = ecryptfs_get_key_size(ecryptfs_data); + if (!key_info->key_size) { + pr_err("could not parse key size from ecryptfs\n"); + return -EINVAL; + } + + key_info->salt = ecryptfs_get_salt(ecryptfs_data); + if (!key_info->salt) { + pr_err("could not parse salt from ecryptfs\n"); + return -EINVAL; + } + + key_info->salt_size = ecryptfs_get_salt_size(ecryptfs_data); + if (!key_info->salt_size) { + pr_err("could not parse salt size from ecryptfs\n"); + return -EINVAL; + } + + ret = pfk_ecryptfs_parse_cipher(ecryptfs_data, algo); + if (ret != 0) { + pr_err("not supported cipher\n"); + return ret; + } + + return 0; +} + +/** + * pfk_ecryptfs_allow_merge_bio() - Check if 2 bios can be merged. + * + * Should be invoked by upper pfk layer + * + * @bio1: Pointer to first BIO structure. + * @bio2: Pointer to second BIO structure. + * @inode1: Pointer to inode from first bio + * @inode2: Pointer to inode from second bio + * + * 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) + * + * Return: true if the BIOs allowed to be merged, false + * otherwise. + */ +bool pfk_ecryptfs_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2) +{ + int ret; + void *ecryptfs_data1 = NULL; + void *ecryptfs_data2 = NULL; + pgoff_t offset1, offset2; + + /* if there is no ecryptfs pfk, don't disallow merging blocks */ + if (!pfk_ecryptfs_is_ready()) + return true; + + if (!inode1 || !inode2) + return false; + + ecryptfs_data1 = pfk_ecryptfs_get_data(inode1); + ecryptfs_data2 = pfk_ecryptfs_get_data(inode2); + + if (!ecryptfs_data1 || !ecryptfs_data2) { + pr_err("internal error, ecryptfs data should not be null"); + return false; + } + + /* + * if we have 2 different encrypted files merge is not allowed + */ + if (!ecryptfs_is_data_equal(ecryptfs_data1, ecryptfs_data2)) + return false; + + /* + * 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_ecryptfs_get_page_index(bio1, &offset1); + if (ret != 0) { + pr_err("could not get page index from bio1, probably bug %d\n", + ret); + return false; + } + + ret = pfk_ecryptfs_get_page_index(bio2, &offset2); + if (ret != 0) { + pr_err("could not get page index from bio2, bug %d\n", ret); + return false; + } + + return (ecryptfs_is_page_in_metadata(ecryptfs_data1, offset1) == + ecryptfs_is_page_in_metadata(ecryptfs_data2, offset2)); +} + +/** + * pfk_ecryptfs_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_ecryptfs_open_cb(struct inode *inode, void *ecryptfs_data) +{ + size_t key_size; + + if (!pfk_ecryptfs_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; + } + + if (pfk_ecryptfs_parse_cipher(ecryptfs_data, NULL) != 0) { + pr_debug("open_cb: not supported cipher\n"); + return; + } + + if (pfk_key_size_to_key_type(key_size, NULL) != 0) + return; + + mutex_lock(&pfk_ecryptfs_lock); + pfk_ecryptfs_set_data(inode, ecryptfs_data); + mutex_unlock(&pfk_ecryptfs_lock); +} + +/** + * pfk_ecryptfs_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_ecryptfs_release_cb(struct inode *inode) +{ + const unsigned char *key = NULL; + const unsigned char *salt = NULL; + size_t key_size = 0; + size_t salt_size = 0; + void *data = NULL; + + if (!pfk_ecryptfs_is_ready()) + return; + + if (!inode) { + pr_err("inode is null\n"); + return; + } + + data = pfk_ecryptfs_get_data(inode); + if (!data) { + pr_debug("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; + } + + salt = ecryptfs_get_salt(data); + if (!salt) { + pr_err("could not parse salt from ecryptfs\n"); + return; + } + + salt_size = ecryptfs_get_salt_size(data); + if (!salt_size) { + pr_err("could not parse salt size from ecryptfs\n"); + return; + } + + pfk_kc_remove_key_with_salt(key, key_size, salt, salt_size); + + mutex_lock(&pfk_ecryptfs_lock); + pfk_ecryptfs_set_data(inode, NULL); + mutex_unlock(&pfk_ecryptfs_lock); +} + +/* + * pfk_ecryptfs_is_cipher_supported_cb() - callback function to determine + * whether a particular cipher (stored in ecryptfs_data) is cupported by pfk + * + * Ecryptfs should invoke this callback whenever it needs to determine whether + * pfk supports the particular cipher mode + * + * @ecryptfs_data: ecryptfs data + */ +static bool pfk_ecryptfs_is_cipher_supported_cb(const void *ecryptfs_data) +{ + if (!pfk_ecryptfs_is_ready()) + return false; + + if (!ecryptfs_data) + return false; + + return (pfk_ecryptfs_parse_cipher(ecryptfs_data, NULL)) == 0; +} + +/* + * pfk_ecryptfs_is_hw_crypt_cb() - callback function that implements a query + * by ecryptfs whether PFK supports HW encryption + */ +static bool pfk_ecryptfs_is_hw_crypt_cb(void) +{ + if (!pfk_ecryptfs_is_ready()) + return false; + + return true; +} + +/* + * pfk_ecryptfs_get_salt_key_size_cb() - callback function to determine + * what is the salt size supported by PFK + * + * @ecryptfs_data: ecryptfs data + */ +static size_t pfk_ecryptfs_get_salt_key_size_cb(const void *ecryptfs_data) +{ + if (!pfk_ecryptfs_is_ready()) + return 0; + + if (!pfk_ecryptfs_is_cipher_supported_cb(ecryptfs_data)) + return 0; + + return PFK_SUPPORTED_SALT_SIZE; +} diff --git a/security/pfe/pfk_ecryptfs.h b/security/pfe/pfk_ecryptfs.h new file mode 100644 index 000000000000..bd3a8f2800b8 --- /dev/null +++ b/security/pfe/pfk_ecryptfs.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2015-2017, 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_ECRYPTFS_H_ +#define _PFK_ECRYPTFS_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <crypto/ice.h> +#include "pfk_internal.h" + + +bool pfk_is_ecryptfs_type(const struct inode *inode); + +int pfk_ecryptfs_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); + +bool pfk_ecryptfs_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); + +int __init pfk_ecryptfs_init(void); + +void pfk_ecryptfs_deinit(void); + +#endif /* _PFK_ECRYPTFS_H_ */ diff --git a/security/pfe/pfk_ext4.c b/security/pfe/pfk_ext4.c new file mode 100644 index 000000000000..b9df18f9fb01 --- /dev/null +++ b/security/pfe/pfk_ext4.c @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2015-2017, 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) - EXT4 + * + * This driver is used for working with EXT4 crypt extension + * + * The key information is stored in node by EXT4 when file is first opened + * 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. + * + */ + + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ +#define pr_fmt(fmt) "pfk_ext4 [%s]: " fmt, __func__ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/printk.h> + +#include "ext4_ice.h" +#include "pfk_ext4.h" + +static bool pfk_ext4_ready; + +/* + * pfk_ext4_deinit() - Deinit function, should be invoked by upper PFK layer + */ +void pfk_ext4_deinit(void) +{ + pfk_ext4_ready = false; +} + +/* + * pfk_ecryptfs_init() - Init function, should be invoked by upper PFK layer + */ +int __init pfk_ext4_init(void) +{ + pfk_ext4_ready = true; + pr_info("PFK EXT4 inited successfully\n"); + + return 0; +} + +/** + * pfk_ecryptfs_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static inline bool pfk_ext4_is_ready(void) +{ + return pfk_ext4_ready; +} + +/** + * pfk_ext4_dump_inode() - dumps all interesting info about inode to the screen + * + * + */ +/* + * static void pfk_ext4_dump_inode(const struct inode* inode) + * { + * struct ext4_crypt_info *ci = ext4_encryption_info((struct inode*)inode); + * + * pr_debug("dumping inode with address 0x%p\n", inode); + * pr_debug("S_ISREG is %d\n", S_ISREG(inode->i_mode)); + * pr_debug("EXT4_INODE_ENCRYPT flag is %d\n", + * ext4_test_inode_flag((struct inode*)inode, EXT4_INODE_ENCRYPT)); + * if (ci) { + * pr_debug("crypt_info address 0x%p\n", ci); + * pr_debug("ci->ci_data_mode %d\n", ci->ci_data_mode); + * } else { + * pr_debug("crypt_info is NULL\n"); + * } + * } +*/ + +/** + * pfk_is_ext4_type() - return true if inode belongs to ICE EXT4 PFE + * @inode: inode pointer + */ +bool pfk_is_ext4_type(const struct inode *inode) +{ + if (!pfe_is_inode_filesystem_type(inode, "ext4")) + return false; + + return ext4_should_be_processed_by_ice(inode); +} + +/** + * pfk_ext4_parse_cipher() - parse cipher from inode to enum + * @inode: inode + * @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_ext4_parse_cipher(const struct inode *inode, + enum ice_cryto_algo_mode *algo) +{ + /* + * currently only AES XTS algo is supported + * in the future, table with supported ciphers might + * be introduced + */ + + if (!inode) + return -EINVAL; + + if (!ext4_is_aes_xts_cipher(inode)) { + pr_err("ext4 alghoritm is not supported by pfk\n"); + return -EINVAL; + } + + if (algo) + *algo = ICE_CRYPTO_ALGO_MODE_AES_XTS; + + return 0; +} + + +int pfk_ext4_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe) +{ + int ret = 0; + + if (!is_pfe) + return -EINVAL; + + /* + * only a few errors below can indicate that + * this function was not invoked within PFE context, + * otherwise we will consider it PFE + */ + *is_pfe = true; + + if (!pfk_ext4_is_ready()) + return -ENODEV; + + if (!inode) + return -EINVAL; + + if (!key_info) + return -EINVAL; + + key_info->key = ext4_get_ice_encryption_key(inode); + if (!key_info->key) { + pr_err("could not parse key from ext4\n"); + return -EINVAL; + } + + key_info->key_size = ext4_get_ice_encryption_key_size(inode); + if (!key_info->key_size) { + pr_err("could not parse key size from ext4\n"); + return -EINVAL; + } + + key_info->salt = ext4_get_ice_encryption_salt(inode); + if (!key_info->salt) { + pr_err("could not parse salt from ext4\n"); + return -EINVAL; + } + + key_info->salt_size = ext4_get_ice_encryption_salt_size(inode); + if (!key_info->salt_size) { + pr_err("could not parse salt size from ext4\n"); + return -EINVAL; + } + + ret = pfk_ext4_parse_cipher(inode, algo); + if (ret != 0) { + pr_err("not supported cipher\n"); + return ret; + } + + return 0; +} + +bool pfk_ext4_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2) +{ + /* if there is no ext4 pfk, don't disallow merging blocks */ + if (!pfk_ext4_is_ready()) + return true; + + if (!inode1 || !inode2) + return false; + + return ext4_is_ice_encryption_info_equal(inode1, inode2); +} + diff --git a/security/pfe/pfk_ext4.h b/security/pfe/pfk_ext4.h new file mode 100644 index 000000000000..1f336325f1db --- /dev/null +++ b/security/pfe/pfk_ext4.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2015-2017, 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_EXT4_H_ +#define _PFK_EXT4_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <crypto/ice.h> +#include "pfk_internal.h" + +bool pfk_is_ext4_type(const struct inode *inode); + +int pfk_ext4_parse_inode(const struct bio *bio, + const struct inode *inode, + struct pfk_key_info *key_info, + enum ice_cryto_algo_mode *algo, + bool *is_pfe); + +bool pfk_ext4_allow_merge_bio(const struct bio *bio1, + const struct bio *bio2, const struct inode *inode1, + const struct inode *inode2); + +int __init pfk_ext4_init(void); + +void pfk_ext4_deinit(void); + +#endif /* _PFK_EXT4_H_ */ diff --git a/security/pfe/pfk_ice.c b/security/pfe/pfk_ice.c new file mode 100644 index 000000000000..2bf18b74bfd5 --- /dev/null +++ b/security/pfe/pfk_ice.c @@ -0,0 +1,192 @@ +/* Copyright (c) 2015-2017, 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 <crypto/ice.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_5( \ + TZ_SYSCALL_PARAM_TYPE_VAL, \ + TZ_SYSCALL_PARAM_TYPE_BUF_RW, 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 +#define ICE_SALT_SIZE 32 + +uint8_t ice_key[ICE_KEY_SIZE]; +uint8_t ice_salt[ICE_KEY_SIZE]; + +int qti_pfk_ice_set_key(uint32_t index, uint8_t *key, uint8_t *salt, + char *storage_type) +{ + struct scm_desc desc = {0}; + int ret, ret1; + char *tzbuf_key = (char *)ice_key; + char *tzbuf_salt = (char *)ice_salt; + char *s_type = storage_type; + + uint32_t smc_id = 0; + u32 tzbuflen_key = sizeof(ice_key); + u32 tzbuflen_salt = sizeof(ice_salt); + + if (index < MIN_ICE_KEY_INDEX || index > MAX_ICE_KEY_INDEX) { + pr_err("%s Invalid index %d\n", __func__, index); + return -EINVAL; + } + + if (!key || !salt) { + pr_err("%s Invalid key/salt\n", __func__); + return -EINVAL; + } + + if (!tzbuf_key || !tzbuf_salt) { + pr_err("%s No Memory\n", __func__); + return -ENOMEM; + } + + if (s_type == NULL) { + pr_err("%s Invalid storage\n", __func__); + return -EINVAL; + } + + memset(tzbuf_key, 0, tzbuflen_key); + memset(tzbuf_salt, 0, tzbuflen_salt); + + memcpy(ice_key, key, tzbuflen_key); + memcpy(ice_salt, salt, tzbuflen_salt); + + dmac_flush_range(tzbuf_key, tzbuf_key + tzbuflen_key); + dmac_flush_range(tzbuf_salt, tzbuf_salt + tzbuflen_salt); + + smc_id = TZ_ES_SET_ICE_KEY_ID; + + desc.arginfo = TZ_ES_SET_ICE_KEY_PARAM_ID; + desc.args[0] = index; + desc.args[1] = virt_to_phys(tzbuf_key); + desc.args[2] = tzbuflen_key; + desc.args[3] = virt_to_phys(tzbuf_salt); + desc.args[4] = tzbuflen_salt; + + ret = qcom_ice_setup_ice_hw((const char *)s_type, true); + + if (ret) { + pr_err("%s: could not enable clocks: %d\n", __func__, ret); + goto out; + } + + ret = scm_call2(smc_id, &desc); + + pr_debug(" %s , ret = %d\n", __func__, ret); + + if (ret) { + pr_err("%s: Set key Error: %d\n", __func__, ret); + if (ret == -EBUSY) { + if (qcom_ice_setup_ice_hw((const char *)s_type, false)) + pr_err("%s: disable clock failed\n", __func__); + goto out; + } + /*Try to invalidate the key to keep ICE in proper state*/ + smc_id = TZ_ES_INVALIDATE_ICE_KEY_ID; + desc.arginfo = TZ_ES_INVALIDATE_ICE_KEY_PARAM_ID; + desc.args[0] = index; + ret1 = scm_call2(smc_id, &desc); + if (ret1) + pr_err("%s:Invalidate key Error: %d\n", __func__, + ret1); + } + + ret = qcom_ice_setup_ice_hw((const char *)s_type, false); +out: + return ret; +} + + +int qti_pfk_ice_invalidate_key(uint32_t index, char *storage_type) +{ + struct scm_desc desc = {0}; + int ret; + + uint32_t smc_id = 0; + + if (index < MIN_ICE_KEY_INDEX || index > MAX_ICE_KEY_INDEX) { + pr_err("%s Invalid index %d\n", __func__, index); + return -EINVAL; + } + + if (storage_type == NULL) { + pr_err("%s Invalid storage\n", __func__); + return -EINVAL; + } + + smc_id = TZ_ES_INVALIDATE_ICE_KEY_ID; + + desc.arginfo = TZ_ES_INVALIDATE_ICE_KEY_PARAM_ID; + desc.args[0] = index; + + ret = qcom_ice_setup_ice_hw((const char *)storage_type, true); + + if (ret) { + pr_err("%s: could not enable clocks: 0x%x\n", __func__, ret); + return ret; + } + + ret = scm_call2(smc_id, &desc); + + if (ret) { + pr_err("%s: Error: 0x%x\n", __func__, ret); + if (qcom_ice_setup_ice_hw((const char *)storage_type, false)) + pr_err("%s: could not disable clocks\n", __func__); + } else { + ret = qcom_ice_setup_ice_hw((const char *)storage_type, false); + } + + return ret; +} diff --git a/security/pfe/pfk_ice.h b/security/pfe/pfk_ice.h new file mode 100644 index 000000000000..fb7c0d142953 --- /dev/null +++ b/security/pfe/pfk_ice.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2017, 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, uint8_t *salt, + char *storage_type); +int qti_pfk_ice_invalidate_key(uint32_t index, char *storage_type); + + +#endif /* PFK_ICE_H_ */ diff --git a/security/pfe/pfk_internal.h b/security/pfe/pfk_internal.h new file mode 100644 index 000000000000..abdd0b325b39 --- /dev/null +++ b/security/pfe/pfk_internal.h @@ -0,0 +1,34 @@ +/* Copyright (c) 2015-2016, 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_INTERNAL_H_ +#define _PFK_INTERNAL_H_ + +#include <linux/types.h> +#include <crypto/ice.h> + +struct pfk_key_info { + const unsigned char *key; + const unsigned char *salt; + size_t key_size; + size_t salt_size; +}; + +int pfk_key_size_to_key_type(size_t key_size, + enum ice_crpto_key_size *key_size_type); + +bool pfe_is_inode_filesystem_type(const struct inode *inode, + const char *fs_type); + +char *inode_to_filename(const struct inode *inode); + +#endif /* _PFK_INTERNAL_H_ */ diff --git a/security/pfe/pfk_kc.c b/security/pfe/pfk_kc.c new file mode 100644 index 000000000000..b428e1b8b11c --- /dev/null +++ b/security/pfe/pfk_kc.c @@ -0,0 +1,899 @@ +/* + * Copyright (c) 2015-2017, 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 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 algorithms 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/module.h> +#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 <linux/sched.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 and salt sizes */ +#define PFK_KC_KEY_SIZE 32 +#define PFK_KC_SALT_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 and salt size */ +#define PFK_MAX_KEY_SIZE PFK_KC_KEY_SIZE +#define PFK_MAX_SALT_SIZE PFK_KC_SALT_SIZE +#define PFK_UFS "ufs" + +static DEFINE_SPINLOCK(kc_lock); +static unsigned long flags; +static bool kc_ready; +static char *s_type = "sdcc"; + +/** + * enum pfk_kc_entry_state - state of the entry inside kc table + * + * @FREE: entry is free + * @ACTIVE_ICE_PRELOAD: entry is actively used by ICE engine + and cannot be used by others. SCM call + to load key to ICE is pending to be performed + * @ACTIVE_ICE_LOADED: entry is actively used by ICE engine and + cannot be used by others. SCM call to load the + key to ICE was successfully executed and key is + now loaded + * @INACTIVE_INVALIDATING: entry is being invalidated during file close + and cannot be used by others until invalidation + is complete + * @INACTIVE: entry's key is already loaded, but is not + currently being used. It can be re-used for + optimization and to avoid SCM call cost or + it can be taken by another key if there are + no FREE entries + * @SCM_ERROR: error occurred while scm call was performed to + load the key to ICE + */ +enum pfk_kc_entry_state { + FREE, + ACTIVE_ICE_PRELOAD, + ACTIVE_ICE_LOADED, + INACTIVE_INVALIDATING, + INACTIVE, + SCM_ERROR +}; + +struct kc_entry { + unsigned char key[PFK_MAX_KEY_SIZE]; + size_t key_size; + + unsigned char salt[PFK_MAX_SALT_SIZE]; + size_t salt_size; + + u64 time_stamp; + u32 key_index; + + struct task_struct *thread_pending; + + enum pfk_kc_entry_state state; + + /* ref count for the number of requests in the HW queue for this key */ + int loaded_ref_cnt; + int scm_error; +}; + +static struct kc_entry kc_table[PFK_KC_TABLE_SIZE]; + +/** + * 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; +} + +static inline void kc_spin_lock(void) +{ + spin_lock_irqsave(&kc_lock, flags); +} + +static inline void kc_spin_unlock(void) +{ + spin_unlock_irqrestore(&kc_lock, flags); +} + +/** + * kc_entry_is_available() - checks whether the entry is available + * + * Return true if it is , false otherwise or if invalid + * Should be invoked under spinlock + */ +static bool kc_entry_is_available(const struct kc_entry *entry) +{ + if (!entry) + return false; + + return (entry->state == FREE || entry->state == INACTIVE); +} + +/** + * kc_entry_wait_till_available() - waits till entry is available + * + * Returns 0 in case of success or -ERESTARTSYS if the wait was interrupted + * by signal + * + * Should be invoked under spinlock + */ +static int kc_entry_wait_till_available(struct kc_entry *entry) +{ + int res = 0; + + while (!kc_entry_is_available(entry)) { + set_current_state(TASK_INTERRUPTIBLE); + if (signal_pending(current)) { + res = -ERESTARTSYS; + break; + } + /* assuming only one thread can try to invalidate + * the same entry + */ + entry->thread_pending = current; + kc_spin_unlock(); + schedule(); + kc_spin_lock(); + } + set_current_state(TASK_RUNNING); + + return res; +} + +/** + * kc_entry_start_invalidating() - moves entry to state + * INACTIVE_INVALIDATING + * If entry is in use, waits till + * it gets available + * @entry: pointer to entry + * + * Return 0 in case of success, otherwise error + * Should be invoked under spinlock + */ +static int kc_entry_start_invalidating(struct kc_entry *entry) +{ + int res; + + res = kc_entry_wait_till_available(entry); + if (res) + return res; + + entry->state = INACTIVE_INVALIDATING; + + return 0; +} + +/** + * kc_entry_finish_invalidating() - moves entry to state FREE + * wakes up all the tasks waiting + * on it + * + * @entry: pointer to entry + * + * Return 0 in case of success, otherwise error + * Should be invoked under spinlock + */ +static void kc_entry_finish_invalidating(struct kc_entry *entry) +{ + if (!entry) + return; + + if (entry->state != INACTIVE_INVALIDATING) + return; + + entry->state = FREE; +} + +/** + * kc_min_entry() - compare two entries to find one with minimal time + * @a: ptr to the first entry. If NULL the other entry will be returned + * @b: pointer to the second entry + * + * Return the entry which timestamp is the minimal, or b if a is NULL + */ +static inline struct kc_entry *kc_min_entry(struct kc_entry *a, + struct kc_entry *b) +{ + if (!a) + return b; + + if (time_before64(b->time_stamp, a->time_stamp)) + return b; + + return a; +} + +/** + * kc_entry_at_index() - return entry at specific index + * @index: index of entry to be accessed + * + * Return entry + * Should be invoked under spinlock + */ +static struct kc_entry *kc_entry_at_index(int index) +{ + return &(kc_table[index]); +} + +/** + * kc_find_key_at_index() - find kc entry starting at specific index + * @key: key to look for + * @key_size: the key size + * @salt: salt to look for + * @salt_size: the salt size + * @sarting_index: index to start search with, if entry found, updated with + * index of that entry + * + * Return entry or NULL in case of error + * Should be invoked under spinlock + */ +static struct kc_entry *kc_find_key_at_index(const unsigned char *key, + size_t key_size, const unsigned char *salt, size_t salt_size, + int *starting_index) +{ + struct kc_entry *entry = NULL; + int i = 0; + + for (i = *starting_index; i < PFK_KC_TABLE_SIZE; i++) { + entry = kc_entry_at_index(i); + + if (NULL != salt) { + if (entry->salt_size != salt_size) + continue; + + if (0 != memcmp(entry->salt, salt, salt_size)) + continue; + } + + if (entry->key_size != key_size) + continue; + + if (0 == memcmp(entry->key, key, key_size)) { + *starting_index = i; + return entry; + } + } + + return NULL; +} + +/** + * kc_find_key() - find kc entry + * @key: key to look for + * @key_size: the key size + * @salt: salt to look for + * @salt_size: the salt size + * + * Return entry or NULL in case of error + * Should be invoked under spinlock + */ +static struct kc_entry *kc_find_key(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size) +{ + int index = 0; + + return kc_find_key_at_index(key, key_size, salt, salt_size, &index); +} + +/** + * kc_find_oldest_entry_non_locked() - finds the entry with minimal timestamp + * that is not locked + * + * Returns entry with minimal timestamp. Empty entries have timestamp + * of 0, therefore they are returned first. + * If all the entries are locked, will return NULL + * Should be invoked under spin lock + */ +static struct kc_entry *kc_find_oldest_entry_non_locked(void) +{ + struct kc_entry *curr_min_entry = NULL; + struct kc_entry *entry = NULL; + int i = 0; + + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = kc_entry_at_index(i); + + if (entry->state == FREE) + return entry; + + if (entry->state == INACTIVE) + curr_min_entry = kc_min_entry(curr_min_entry, entry); + } + + return curr_min_entry; +} + +/** + * kc_update_timestamp() - updates timestamp of entry to current + * + * @entry: entry to update + * + */ +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 mark entry not in use + * + * @entry: pointer to entry + * + * Should be invoked under spinlock + */ +static void kc_clear_entry(struct kc_entry *entry) +{ + if (!entry) + return; + + memset(entry->key, 0, entry->key_size); + memset(entry->salt, 0, entry->salt_size); + + entry->key_size = 0; + entry->salt_size = 0; + + entry->time_stamp = 0; + entry->scm_error = 0; +} + +/** + * kc_update_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 + * @salt: salt + * @salt_size: salt_size + * + * The previous key is securely released and wiped, the new one is loaded + * to ICE. + * Should be invoked under spinlock + */ +static int kc_update_entry(struct kc_entry *entry, const unsigned char *key, + size_t key_size, const unsigned char *salt, size_t salt_size) +{ + int ret; + + kc_clear_entry(entry); + + memcpy(entry->key, key, key_size); + entry->key_size = key_size; + + memcpy(entry->salt, salt, salt_size); + entry->salt_size = salt_size; + + /* Mark entry as no longer free before releasing the lock */ + entry->state = ACTIVE_ICE_PRELOAD; + kc_spin_unlock(); + + ret = qti_pfk_ice_set_key(entry->key_index, entry->key, + entry->salt, s_type); + + kc_spin_lock(); + return ret; +} + +/** + * pfk_kc_init() - init function + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_init(void) +{ + int i = 0; + struct kc_entry *entry = NULL; + + kc_spin_lock(); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = kc_entry_at_index(i); + entry->key_index = PFK_KC_STARTING_INDEX + i; + } + kc_ready = true; + kc_spin_unlock(); + + return 0; +} + +/** + * pfk_kc_denit() - deinit function + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_deinit(void) +{ + int res = pfk_kc_clear(); + kc_ready = false; + + return res; +} + +/** + * pfk_kc_load_key_start() - retrieve the key from cache or add it if + * it's not there and return the ICE hw key index in @key_index. + * @key: pointer to the key + * @key_size: the size of the key + * @salt: pointer to the salt + * @salt_size: the size of the salt + * @key_index: the pointer to key_index where the output will be stored + * @async: whether scm calls are allowed in the caller context + * + * 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. + * Entry that is going to be used is marked as being used, it will mark + * as not being used when ICE finishes using it and pfk_kc_load_key_end + * will be invoked. + * As QSEE calls can only be done from a non-atomic context, when @async flag + * is set to 'false', it specifies that it is ok to make the calls in the + * current context. Otherwise, when @async is set, the caller should retry the + * call again from a different context, and -EAGAIN error will be returned. + * + * Return 0 in case of success, error otherwise + */ +int pfk_kc_load_key_start(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size, u32 *key_index, + bool async) +{ + int ret = 0; + struct kc_entry *entry = NULL; + bool entry_exists = false; + + if (!kc_is_ready()) + return -ENODEV; + + if (!key || !salt || !key_index) + return -EINVAL; + + if (key_size != PFK_KC_KEY_SIZE) { + pr_err("unsupported key size %zu\n", key_size); + return -EINVAL; + } + + if (salt_size != PFK_KC_SALT_SIZE) { + pr_err("unsupported salt size %zu\n", salt_size); + return -EINVAL; + } + + kc_spin_lock(); + + entry = kc_find_key(key, key_size, salt, salt_size); + if (!entry) { + if (async) { + pr_debug("found empty entry, a separate task will populate it\n"); + kc_spin_unlock(); + return -EAGAIN; + } + + entry = kc_find_oldest_entry_non_locked(); + if (!entry) { + /* could not find a single non locked entry, + * return EBUSY to upper layers so that the + * request will be rescheduled + */ + kc_spin_unlock(); + return -EBUSY; + } + } else { + entry_exists = true; + } + + pr_debug("entry with index %d is in state %d\n", + entry->key_index, entry->state); + + switch (entry->state) { + case (INACTIVE): + if (entry_exists) { + kc_update_timestamp(entry); + entry->state = ACTIVE_ICE_LOADED; + + if (!strcmp(s_type, (char *)PFK_UFS)) { + if (async) + entry->loaded_ref_cnt++; + } else { + entry->loaded_ref_cnt++; + } + break; + } + case (FREE): + ret = kc_update_entry(entry, key, key_size, salt, salt_size); + if (ret) { + entry->state = SCM_ERROR; + entry->scm_error = ret; + pr_err("%s: key load error (%d)\n", __func__, ret); + } else { + kc_update_timestamp(entry); + entry->state = ACTIVE_ICE_LOADED; + + /* + * In case of UFS only increase ref cnt for async calls, + * sync calls from within work thread do not pass + * requests further to HW + */ + if (!strcmp(s_type, (char *)PFK_UFS)) { + if (async) + entry->loaded_ref_cnt++; + } else { + entry->loaded_ref_cnt++; + } + } + break; + case (ACTIVE_ICE_PRELOAD): + case (INACTIVE_INVALIDATING): + ret = -EAGAIN; + break; + case (ACTIVE_ICE_LOADED): + kc_update_timestamp(entry); + + if (!strcmp(s_type, (char *)PFK_UFS)) { + if (async) + entry->loaded_ref_cnt++; + } else { + entry->loaded_ref_cnt++; + } + break; + case(SCM_ERROR): + ret = entry->scm_error; + kc_clear_entry(entry); + entry->state = FREE; + break; + default: + pr_err("invalid state %d for entry with key index %d\n", + entry->state, entry->key_index); + ret = -EINVAL; + } + + *key_index = entry->key_index; + kc_spin_unlock(); + + return ret; +} + +/** + * pfk_kc_load_key_end() - finish the process of key loading that was started + * by pfk_kc_load_key_start + * by marking the entry as not + * being in use + * @key: pointer to the key + * @key_size: the size of the key + * @salt: pointer to the salt + * @salt_size: the size of the salt + * + */ +void pfk_kc_load_key_end(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size) +{ + struct kc_entry *entry = NULL; + struct task_struct *tmp_pending = NULL; + int ref_cnt = 0; + + if (!kc_is_ready()) + return; + + if (!key || !salt) + return; + + if (key_size != PFK_KC_KEY_SIZE) + return; + + if (salt_size != PFK_KC_SALT_SIZE) + return; + + kc_spin_lock(); + + entry = kc_find_key(key, key_size, salt, salt_size); + if (!entry) { + kc_spin_unlock(); + pr_err("internal error, there should an entry to unlock\n"); + + return; + } + ref_cnt = --entry->loaded_ref_cnt; + + if (ref_cnt < 0) + pr_err("internal error, ref count should never be negative\n"); + + if (!ref_cnt) { + entry->state = INACTIVE; + /* + * wake-up invalidation if it's waiting + * for the entry to be released + */ + if (entry->thread_pending) { + tmp_pending = entry->thread_pending; + entry->thread_pending = NULL; + + kc_spin_unlock(); + wake_up_process(tmp_pending); + return; + } + } + + kc_spin_unlock(); +} + +/** + * 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 + * @salt: pointer to the key + * @salt_size: the size of the key + * + * Return 0 in case of success, error otherwise (also in case of non + * (existing key) + */ +int pfk_kc_remove_key_with_salt(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size) +{ + struct kc_entry *entry = NULL; + int res = 0; + + if (!kc_is_ready()) + return -ENODEV; + + if (!key) + return -EINVAL; + + if (!salt) + return -EINVAL; + + if (key_size != PFK_KC_KEY_SIZE) + return -EINVAL; + + if (salt_size != PFK_KC_SALT_SIZE) + return -EINVAL; + + kc_spin_lock(); + + entry = kc_find_key(key, key_size, salt, salt_size); + if (!entry) { + pr_debug("%s: key does not exist\n", __func__); + kc_spin_unlock(); + return -EINVAL; + } + + res = kc_entry_start_invalidating(entry); + if (res != 0) { + kc_spin_unlock(); + return res; + } + kc_clear_entry(entry); + + kc_spin_unlock(); + + qti_pfk_ice_invalidate_key(entry->key_index, s_type); + + kc_spin_lock(); + kc_entry_finish_invalidating(entry); + kc_spin_unlock(); + + return 0; +} + +/** + * pfk_kc_remove_key() - remove the key from cache and from ICE engine + * when no salt is available. Will only search key part, if there are several, + * all will be removed + * + * @key: pointer to the key + * @key_size: the size of the key + * + * Return 0 in case of success, error otherwise (also for non-existing key) + */ +int pfk_kc_remove_key(const unsigned char *key, size_t key_size) +{ + struct kc_entry *entry = NULL; + int index = 0; + int temp_indexes[PFK_KC_TABLE_SIZE] = {0}; + int temp_indexes_size = 0; + int i = 0; + int res = 0; + + if (!kc_is_ready()) + return -ENODEV; + + if (!key) + return -EINVAL; + + if (key_size != PFK_KC_KEY_SIZE) + return -EINVAL; + + memset(temp_indexes, -1, sizeof(temp_indexes)); + + kc_spin_lock(); + + entry = kc_find_key_at_index(key, key_size, NULL, 0, &index); + if (!entry) { + pr_err("%s: key does not exist\n", __func__); + kc_spin_unlock(); + return -EINVAL; + } + + res = kc_entry_start_invalidating(entry); + if (res != 0) { + kc_spin_unlock(); + return res; + } + + temp_indexes[temp_indexes_size++] = index; + kc_clear_entry(entry); + + /* let's clean additional entries with the same key if there are any */ + do { + index++; + entry = kc_find_key_at_index(key, key_size, NULL, 0, &index); + if (!entry) + break; + + res = kc_entry_start_invalidating(entry); + if (res != 0) { + kc_spin_unlock(); + goto out; + } + + temp_indexes[temp_indexes_size++] = index; + + kc_clear_entry(entry); + + + } while (true); + + kc_spin_unlock(); + + temp_indexes_size--; + for (i = temp_indexes_size; i >= 0 ; i--) + qti_pfk_ice_invalidate_key( + kc_entry_at_index(temp_indexes[i])->key_index, + s_type); + + /* fall through */ + res = 0; + +out: + kc_spin_lock(); + for (i = temp_indexes_size; i >= 0 ; i--) + kc_entry_finish_invalidating( + kc_entry_at_index(temp_indexes[i])); + kc_spin_unlock(); + + return res; +} + +/** + * pfk_kc_clear() - clear the table and remove all keys from ICE + * + * Return 0 on success, error otherwise + * + */ +int pfk_kc_clear(void) +{ + struct kc_entry *entry = NULL; + int i = 0; + int res = 0; + + if (!kc_is_ready()) + return -ENODEV; + + kc_spin_lock(); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = kc_entry_at_index(i); + res = kc_entry_start_invalidating(entry); + if (res != 0) { + kc_spin_unlock(); + goto out; + } + kc_clear_entry(entry); + } + kc_spin_unlock(); + + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) + qti_pfk_ice_invalidate_key(kc_entry_at_index(i)->key_index, + s_type); + + /* fall through */ + res = 0; +out: + kc_spin_lock(); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) + kc_entry_finish_invalidating(kc_entry_at_index(i)); + kc_spin_unlock(); + + return res; +} + +/** + * pfk_kc_clear_on_reset() - clear the table and remove all keys from ICE + * The assumption is that at this point we don't have any pending transactions + * Also, there is no need to clear keys from ICE + * + * Return 0 on success, error otherwise + * + */ +void pfk_kc_clear_on_reset(void) +{ + struct kc_entry *entry = NULL; + int i = 0; + + if (!kc_is_ready()) + return; + + kc_spin_lock(); + for (i = 0; i < PFK_KC_TABLE_SIZE; i++) { + entry = kc_entry_at_index(i); + kc_clear_entry(entry); + } + kc_spin_unlock(); +} + +static int pfk_kc_find_storage_type(char **device) +{ + char boot[20] = {'\0'}; + char *match = (char *)strnstr(saved_command_line, + "androidboot.bootdevice=", + strlen(saved_command_line)); + if (match) { + memcpy(boot, (match + strlen("androidboot.bootdevice=")), + sizeof(boot) - 1); + if (strnstr(boot, PFK_UFS, strlen(boot))) + *device = PFK_UFS; + + return 0; + } + return -EINVAL; +} + +static int __init pfk_kc_pre_init(void) +{ + return pfk_kc_find_storage_type(&s_type); +} + +static void __exit pfk_kc_exit(void) +{ + s_type = NULL; +} + +module_init(pfk_kc_pre_init); +module_exit(pfk_kc_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Per-File-Key-KC driver"); diff --git a/security/pfe/pfk_kc.h b/security/pfe/pfk_kc.h new file mode 100644 index 000000000000..dc4ad15b359d --- /dev/null +++ b/security/pfe/pfk_kc.h @@ -0,0 +1,33 @@ +/* Copyright (c) 2015-2017, 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_start(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size, u32 *key_index, + bool async); +void pfk_kc_load_key_end(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size); +int pfk_kc_remove_key_with_salt(const unsigned char *key, size_t key_size, + const unsigned char *salt, size_t salt_size); +int pfk_kc_remove_key(const unsigned char *key, size_t key_size); +int pfk_kc_clear(void); +void pfk_kc_clear_on_reset(void); +extern char *saved_command_line; + + +#endif /* PFK_KC_H_ */ diff --git a/security/pfe/pft.c b/security/pfe/pft.c new file mode 100644 index 000000000000..0c49fbdd4f28 --- /dev/null +++ b/security/pfe/pft.c @@ -0,0 +1,1877 @@ +/* + * Copyright (c) 2016-2017, 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-Tagger (PFT). + * + * This driver tags enterprise file for encryption/decryption, + * as part of the Per-File-Encryption (PFE) feature. + * + * Enterprise registered applications are identified by their UID. + * + * The PFT exposes character-device interface to the user-space application, + * to handle the following commands: + * 1. Update registered applications list + * 2. Encryption (in-place) of a file that was created before. + * 3. Set State - update the state. + * + * The PFT exposes kernel API hooks that are intercepting file operations + * like create/open/read/write for tagging files and also for access control. + * It utilizes the existing security framework hooks + * that calls the selinux hooks. + * + * The PFT exposes kernel API to the dm-req-crypt driver to provide the info + * if a file is tagged or not. The dm-req-crypt driver is doing the + * actual encryption/decryptiom. + * + * Tagging the file: + * 1. Non-volatile tagging on storage using file extra-attribute (xattr). + * 2. Volatile tagging on the file's inode, for fast access. + * + */ + +/* Uncomment the line below to enable debug messages */ +/* #define DEBUG 1 */ + +#define pr_fmt(fmt) "pft [%s]: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/sched.h> +#include <linux/uaccess.h> +#include <linux/cred.h> +#include <linux/list.h> +#include <linux/errno.h> +#include <linux/printk.h> +#include <linux/blkdev.h> +#include <linux/elevator.h> +#include <linux/bio.h> +#include <linux/bitops.h> +#include <linux/fdtable.h> +#include <linux/selinux.h> +#include <linux/security.h> +#include <linux/lsm_hooks.h> + +#include <linux/pft.h> +#include <uapi/linux/msm_pft.h> + +#include "objsec.h" + +/* File tagging as encrypted/non-encrypted is valid */ +#define PFT_TAG_MAGIC ((u32)(0xABC00000)) + +/* File tagged as encrypted */ +#define PFT_TAG_ENCRYPTED BIT(16) + +#define PFT_TAG_MAGIC_MASK 0xFFF00000 +#define PFT_TAG_FLAGS_MASK 0x000F0000 +#define PFT_TAG_KEY_MASK 0x0000FFFF + +/* The default encryption key index */ +#define PFT_DEFAULT_KEY_INDEX 1 + +/* The default key index for non-encrypted files */ +#define PFT_NO_KEY 0 + +/* PFT extended attribute name */ +#define XATTR_NAME_PFE "security.pfe" + +/* PFT driver requested major number */ +#define PFT_REQUESTED_MAJOR 213 + +/* PFT driver name */ +#define DEVICE_NAME "pft" + +/* Maximum registered applications */ +#define PFT_MAX_APPS 1000 + +/* Maximum command size */ +#define PFT_MAX_COMMAND_SIZE (PAGE_SIZE) + +/* Current Process ID */ +#define current_pid() ((u32)(current->pid)) + +static const char *pft_state_name[PFT_STATE_MAX_INDEX] = { + "deactivated", + "deactivating", + "key_removed", + "removing_key", + "key_loaded", +}; + +/** + * struct pft_file_info - pft file node info. + * @file: pointer to file stucture. + * @pid: process ID. + * @list: next list item. + * + * A node in the list of the current open encrypted files. + */ +struct pft_file_info { + struct file *file; + pid_t pid; + struct list_head list; +}; + +/** + * struct pft_device - device state structure. + * + * @open_count: device open count. + * @major: device major number. + * @state: Per-File-Encryption state. + * @response: command response. + * @pfm_pid: PFM process id. + * @inplace_file: file for in-place encryption. + * @uid_table: registered application array (UID). + * @uid_count: number of registered applications. + * @open_file_list: open encrypted file list. + * @lock: lock protect list access. + * + * The open_count purpose is to ensure that only one user space + * application uses this driver. + * The open_file_list is used to close open encrypted files + * after the key is removed from the encryption hardware. + */ +struct pft_device { + struct cdev cdev; + dev_t device_no; + struct class *driver_class; + int open_count; + int major; + enum pft_state state; + struct pft_command_response response; + u32 pfm_pid; + struct file *inplace_file; + kuid_t *uid_table; + u32 uid_count; + struct list_head open_file_list; + struct mutex lock; + bool is_chosen_lsm; +}; + +/* Device Driver State */ +static struct pft_device *pft_dev; + +static struct inode *pft_bio_get_inode(const struct bio *bio); + +static int pft_inode_alloc_security(struct inode *inode) +{ + struct inode_security_struct *i_sec = NULL; + + i_sec = kzalloc(sizeof(*i_sec), GFP_KERNEL); + + if (i_sec == NULL) + return -ENOMEM; + + inode->i_security = i_sec; + + return 0; +} + +static void pft_inode_free_security(struct inode *inode) +{ + kzfree(inode->i_security); +} + +static struct security_hook_list pft_hooks[] = { + LSM_HOOK_INIT(inode_create, pft_inode_create), + LSM_HOOK_INIT(inode_post_create, pft_inode_post_create), + LSM_HOOK_INIT(inode_unlink, pft_inode_unlink), + LSM_HOOK_INIT(inode_mknod, pft_inode_mknod), + LSM_HOOK_INIT(inode_rename, pft_inode_rename), + LSM_HOOK_INIT(inode_setxattr, pft_inode_set_xattr), + LSM_HOOK_INIT(inode_alloc_security, pft_inode_alloc_security), + LSM_HOOK_INIT(inode_free_security, pft_inode_free_security), + + LSM_HOOK_INIT(file_open, pft_file_open), + LSM_HOOK_INIT(file_permission, pft_file_permission), + LSM_HOOK_INIT(file_close, pft_file_close), +}; + +static int __init pft_lsm_init(struct pft_device *dev) +{ + /* Check if PFT is the chosen lsm via security_module_enable() */ + if (security_module_enable("pft")) { + security_add_hooks(pft_hooks, ARRAY_SIZE(pft_hooks)); + dev->is_chosen_lsm = true; + pr_debug("pft is the chosen lsm, registered successfully !\n"); + } else { + pr_err("pft is not the chosen lsm\n"); + return -ENODEV; + } + + return 0; +} + +/** + * pft_is_ready() - driver is initialized and ready. + * + * Return: true if the driver is ready. + */ +static bool pft_is_ready(void) +{ + return pft_dev != NULL; +} + +/** + * file_to_filename() - get the filename from file pointer. + * @filp: file pointer + * + * it is used for debug prints. + * + * Return: filename string or "unknown". + */ +static char *file_to_filename(struct file *filp) +{ + struct dentry *dentry = NULL; + char *filename = NULL; + + if (!filp || !filp->f_path.dentry) + return "unknown"; + + dentry = filp->f_path.dentry; + filename = dentry->d_iname; + + return filename; +} + +/** + * inode_to_filename() - get the filename from inode pointer. + * @inode: inode pointer + * + * it is used for debug prints. + * + * Return: filename string or "unknown". + */ +static char *inode_to_filename(struct inode *inode) +{ + struct dentry *dentry = NULL; + char *filename = NULL; + + if (hlist_empty(&inode->i_dentry)) + return "unknown"; + + dentry = hlist_entry(inode->i_dentry.first, struct dentry, d_u.d_alias); + filename = dentry->d_iname; + + return filename; +} + +/** + * pft_set_response() - set response error code. + * + * @error_code: The error code to return on response. + */ +static inline void pft_set_response(u32 error_code) +{ + pft_dev->response.error_code = error_code; +} + +/** + * pft_add_file()- Add the file to the list of opened encrypted + * files. + * @filp: file to add. + * + * Return: 0 of successful operation, negative value otherwise. + */ +static int pft_add_file(struct file *filp) +{ + struct pft_file_info *node = NULL; + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return -ENOMEM; + + node->file = filp; + INIT_LIST_HEAD(&node->list); + + mutex_lock(&pft_dev->lock); + list_add(&node->list, &pft_dev->open_file_list); + pr_debug("adding file %s to open list.\n", file_to_filename(filp)); + mutex_unlock(&pft_dev->lock); + + return 0; +} + +/** + * pft_remove_file()- Remove the given file from the list of + * open encrypted files. + * @filp: file to remove. + * + * Return: 0 on success, negative value on failure. + */ +static int pft_remove_file(struct file *filp) +{ + int ret = -ENOENT; + struct pft_file_info *tmp = NULL; + struct list_head *pos = NULL; + struct list_head *next = NULL; + bool found = false; + + mutex_lock(&pft_dev->lock); + list_for_each_safe(pos, next, &pft_dev->open_file_list) { + tmp = list_entry(pos, struct pft_file_info, list); + if (filp == tmp->file) { + found = true; + break; + } + } + + if (found) { + pr_debug("remove file %s. from open list.\n ", + file_to_filename(filp)); + list_del(&tmp->list); + kfree(tmp); + ret = 0; + } + mutex_unlock(&pft_dev->lock); + + return ret; +} + +/** + * pft_is_current_process_registered()- Check if current process + * is registered. + * + * Return: true if current process is registered. + */ +static bool pft_is_current_process_registered(void) +{ + int is_registered = false; + int i; + kuid_t uid = current_uid(); + + mutex_lock(&pft_dev->lock); + for (i = 0; i < pft_dev->uid_count; i++) { + if (uid_eq(pft_dev->uid_table[i], uid)) { + pr_debug("current UID [%u] is registered.\n", + __kuid_val(uid)); + is_registered = true; + break; + } + } + mutex_unlock(&pft_dev->lock); + + return is_registered; +} + +/** + * pft_is_xattr_supported() - Check if the filesystem supports + * extended attributes. + * @indoe: pointer to the file inode + * + * Return: true if supported, false if not. + */ +static bool pft_is_xattr_supported(struct inode *inode) +{ + if (inode == NULL) { + pr_err("invalid argument inode passed as NULL"); + return false; + } + + if (inode->i_security == NULL) { + pr_debug("i_security is NULL, not ready yet\n"); + return false; + } + + if (inode->i_op == NULL) { + pr_debug("i_op is NULL\n"); + return false; + } + + if (inode->i_op->getxattr == NULL) { + pr_debug_once("getxattr() not supported , filename=%s\n", + inode_to_filename(inode)); + return false; + } + + if (inode->i_op->setxattr == NULL) { + pr_debug("setxattr() not supported\n"); + return false; + } + + return true; +} + +/** + * pft_get_inode_tag() - get the file tag. + * @indoe: pointer to the file inode + * + * Return: tag + */ +static u32 pft_get_inode_tag(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + + if (isec == NULL) + return 0; + + return isec->tag; +} + +/** + * pft_get_inode_key_index() - get the file key. + * @indoe: pointer to the file inode + * + * Return: key index + */ +static inline u32 pft_get_inode_key_index(struct inode *inode) +{ + return pft_get_inode_tag(inode) & PFT_TAG_KEY_MASK; +} + +/** + * pft_is_tag_valid() - is the tag valid + * @indoe: pointer to the file inode + * + * The tagging is set to valid when an enterprise file is created + * or when an file is opened first time after power up and the + * xattr was checked to see if the file is encrypted or not. + * + * Return: true if the tag is valid. + */ +static inline bool pft_is_tag_valid(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + + if (isec == NULL) + return false; + + return ((isec->tag & PFT_TAG_MAGIC_MASK) == PFT_TAG_MAGIC) ? + true : false; +} + +/** + * pft_is_file_encrypted() - is inode tagged as encrypted. + * + * @tag: holds the key index and tagging flags. + * + * Return: true if the file is encrypted. + */ +static inline bool pft_is_file_encrypted(u32 tag) +{ + return (tag & PFT_TAG_ENCRYPTED) ? true : false; +} + +/** + * pft_tag_inode_non_encrypted() - Tag the inode as + * non-encrypted. + * @indoe: pointer to the file inode + * + * Tag file as non-encrypted, only the valid bit is set, + * the encrypted bit is not set. + */ +static inline void pft_tag_inode_non_encrypted(struct inode *inode) +{ + struct inode_security_struct *isec = inode->i_security; + + isec->tag = (u32)(PFT_TAG_MAGIC); +} + +/** + * pft_tag_inode_encrypted() - Tag the inode as encrypted. + * @indoe: pointer to the file inode + * + * Set the valid bit, the encrypted bit, and the key index. + */ +static void pft_tag_inode_encrypted(struct inode *inode, u32 key_index) +{ + struct inode_security_struct *isec = inode->i_security; + + isec->tag = key_index | PFT_TAG_ENCRYPTED | PFT_TAG_MAGIC; +} + +/** + * pft_get_file_tag()- get the file tag. + * @dentry: pointer to file dentry. + * @tag_ptr: pointer to tag. + * + * This is the major function for detecting tag files. + * Get the tag from the inode if tag is valid, + * or from the xattr if this is the 1st time after power up. + * + * Return: 0 on successe, negative value on failure. + */ +static int pft_get_file_tag(struct dentry *dentry, u32 *tag_ptr) +{ + ssize_t size = 0; + struct inode *inode; + const char *xattr_name = XATTR_NAME_PFE; + u32 key; + + if (!dentry || !dentry->d_inode || !tag_ptr) { + pr_err("invalid param"); + return -EINVAL; + } + + inode = dentry->d_inode; + if (pft_is_tag_valid(inode)) { + *tag_ptr = pft_get_inode_tag(inode); + return 0; + } + + /* + * For the first time reading the tag, the tag is not valid, hence + * get xattr. + */ + size = inode->i_op->getxattr(dentry, xattr_name, &key, sizeof(key)); + + if (size == -ENODATA || size == -EOPNOTSUPP) { + pft_tag_inode_non_encrypted(inode); + *tag_ptr = pft_get_inode_tag(inode); + } else if (size > 0) { + pr_debug("First time file %s opened, found xattr = %u.\n", + inode_to_filename(inode), key); + pft_tag_inode_encrypted(inode, key); + *tag_ptr = pft_get_inode_tag(inode); + } else { + pr_err("getxattr() failure, ret=%zu.\n", size); + return -EINVAL; + } + + return 0; +} + +/** + * pft_tag_file() - Tag the file saving the key_index. + * @dentry: file dentry. + * @key_index: encryption key index. + * + * This is the major function for tagging a file. + * Tag the file on both the xattr and the inode. + * + * Return: 0 on successe, negative value on failure. + */ +static int pft_tag_file(struct dentry *dentry, u32 key_index) +{ + int size = 0; + const char *xattr_name = XATTR_NAME_PFE; + + if (!dentry || !dentry->d_inode) { + pr_err("invalid NULL param"); + return -EINVAL; + } + + if (!pft_is_xattr_supported(dentry->d_inode)) { + pr_err("set xattr for file %s is not support.\n", + dentry->d_iname); + return -EINVAL; + } + + size = dentry->d_inode->i_op->setxattr(dentry, xattr_name, &key_index, + sizeof(key_index), 0); + if (size < 0) { + pr_err("failed to set xattr for file %s, ret =%d.\n", + dentry->d_iname, size); + return -EFAULT; + } + + pft_tag_inode_encrypted(dentry->d_inode, key_index); + pr_debug("file %s tagged encrypted\n", dentry->d_iname); + + return 0; +} + +/** + * pft_get_app_key_index() - get the application key index. + * @uid: registered application UID + * + * Get key index based on the given registered application UID. + * Currently only one key is supported. + * + * Return: encryption key index. + */ +static inline u32 pft_get_app_key_index(kuid_t uid) +{ + return PFT_DEFAULT_KEY_INDEX; +} + +/** + * pft_is_encrypted_file() - is the file encrypted. + * @dentry: file pointer. + * + * Return: true if the file is encrypted, false otherwise. + */ +static bool pft_is_encrypted_file(struct dentry *dentry) +{ + int rc; + u32 tag; + + if (!pft_is_ready()) + return false; + + if (!pft_is_xattr_supported(dentry->d_inode)) + return false; + + rc = pft_get_file_tag(dentry, &tag); + if (rc < 0) + return false; + + return pft_is_file_encrypted(tag); +} + +/** + * pft_is_inplace_inode() - is this the inode of file for + * in-place encryption. + * @inode: inode of file to check. + * + * Return: true if this file is being encrypted, false + * otherwise. + */ +static bool pft_is_inplace_inode(struct inode *inode) +{ + if (!pft_dev->inplace_file || !pft_dev->inplace_file->f_path.dentry) + return false; + + return pft_dev->inplace_file->f_path.dentry->d_inode == inode; +} + +/** + * pft_is_inplace_file() - is this the file for in-place + * encryption. + * @filp: file to check. + * + * A file struct might be allocated per process, inode should be + * only one. + * + * Return: true if this file is being encrypted, false + * otherwise. + */ +static inline bool pft_is_inplace_file(struct file *filp) +{ + if (!filp || !filp->f_path.dentry || !filp->f_path.dentry->d_inode) + return false; + + return pft_is_inplace_inode(filp->f_path.dentry->d_inode); +} + +/** + * pft_get_key_index() - get the key index and other indications + * @inode: Pointer to inode struct + * @key_index: Pointer to the return value of key index + * @is_encrypted: Pointer to the return value. + * @is_inplace: Pointer to the return value. + * + * Provides the given inode's encryption key index, and well as + * indications whether the file is encrypted or is it currently + * being in-placed encrypted. + * This API is called by the dm-req-crypt to decide if to + * encrypt/decrypt the file. + * File tagging depends on the hooks to be called from selinux, + * so if selinux is disabled then tagging is also not + * valid. + * + * Return: 0 on successe, negative value on failure. + */ +int pft_get_key_index(struct bio *bio, u32 *key_index, + bool *is_encrypted, bool *is_inplace) +{ + u32 tag = 0; + struct inode *inode = NULL; + + if (!pft_is_ready()) + return -ENODEV; + + if (!selinux_is_enabled() && !pft_dev->is_chosen_lsm) + return -ENODEV; + + if (!bio) + return -EPERM; + + if (!is_encrypted) { + pr_err("is_encrypted is NULL\n"); + return -EPERM; + } + if (!is_inplace) { + pr_err("is_inplace is NULL\n"); + return -EPERM; + } + if (!key_index) { + pr_err("key_index is NULL\n"); + return -EPERM; + } + + inode = pft_bio_get_inode(bio); + if (!inode) + return -EINVAL; + + if (!pft_is_tag_valid(inode)) { + pr_debug("file %s, Tag not valid\n", inode_to_filename(inode)); + return -EINVAL; + } + + if (!pft_is_xattr_supported(inode)) { + *is_encrypted = false; + *is_inplace = false; + *key_index = 0; + return 0; + } + + tag = pft_get_inode_tag(inode); + + *is_encrypted = pft_is_file_encrypted(tag); + *key_index = pft_get_inode_key_index(inode); + *is_inplace = pft_is_inplace_inode(inode); + + if (*is_encrypted) + pr_debug("file %s is encrypted\n", inode_to_filename(inode)); + + return 0; +} +EXPORT_SYMBOL(pft_get_key_index); + +/** + * pft_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. + * This API is called by the file system block layer. + * + * Return: true if the BIOs allowed to be merged, false + * otherwise. + */ +bool pft_allow_merge_bio(const struct bio *bio1, const struct bio *bio2) +{ + u32 key_index1 = 0, key_index2 = 0; + bool is_encrypted1 = false, is_encrypted2 = false; + bool allow = false; + bool is_inplace = false; /* N.A. */ + int ret; + + if (!pft_is_ready()) + return true; + + if (!bio1 || !bio2) + return -EPERM; + + /* + * Encrypted BIOs are created only when file encryption is enabled, + * which happens only when key is loaded. + */ + if (pft_dev->state != PFT_STATE_KEY_LOADED) + return true; + + ret = pft_get_key_index(bio1, &key_index1, + &is_encrypted1, &is_inplace); + if (ret) + is_encrypted1 = false; + + ret = pft_get_key_index(bio2, &key_index2, + &is_encrypted2, &is_inplace); + if (ret) + is_encrypted2 = false; + + allow = ((is_encrypted1 == is_encrypted2) && + (key_index1 == key_index2)); + + return allow; +} +EXPORT_SYMBOL(pft_allow_merge_bio); + +/** + * pft_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 *pft_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; +} + + +/** + * pft_inode_create() - file creation callback. + * @dir: directory inode pointer + * @dentry: file dentry pointer + * @mode: flags + * + * This hook is called when file is created by VFS. + * This hook is called from the selinux driver. + * This hooks check file creation permission for enterprise + * applications. + * Call path: + * vfs_create()->security_inode_create()->selinux_inode_create() + * + * Return: 0 on successe, negative value on failure. + */ +int pft_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) +{ + if (!dir || !dentry) + return 0; + + if (!pft_is_ready()) + return 0; + + switch (pft_dev->state) { + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_LOADED: + break; + case PFT_STATE_KEY_REMOVED: + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + /* At this state no new encrypted files can be created */ + if (pft_is_current_process_registered()) { + pr_debug("key removed, registered uid %u is denied from creating new file %s\n", + __kuid_val(current_uid()), dentry->d_iname); + return -EACCES; + } + break; + default: + BUG(); /* State is set by "set state" command */ + break; + } + + return 0; + +} +EXPORT_SYMBOL(pft_inode_create); + +/** + * pft_inode_post_create() - file creation callback. + * @dir: directory inode pointer + * @dentry: file dentry pointer + * @mode: flags + * + * This hook is called when file is created by VFS. + * This hook is called from the selinux driver. + * This hooks tags new files as encrypted when created by + * enterprise applications. + * Call path: + * vfs_create()->security_inode_post_create()->selinux_inode_post_create() + * + * Return: 0 on successe, negative value on failure. + */ +int pft_inode_post_create(struct inode *dir, struct dentry *dentry, + umode_t mode) +{ + int ret; + + if (!dir || !dentry) + return 0; + + if (!pft_is_ready()) + return 0; + + switch (pft_dev->state) { + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_REMOVED: + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + break; + case PFT_STATE_KEY_LOADED: + /* Check whether the new file should be encrypted */ + if (pft_is_current_process_registered()) { + u32 key_index = pft_get_app_key_index(current_uid()); + + ret = pft_tag_file(dentry, key_index); + + if (ret == 0) + pr_debug("pid[%u] uid[%d] creating file %s\n", + current_pid(), + __kuid_val(current_uid()), + dentry->d_iname); + else { + pr_err("Failed to tag file %s by pid %d\n", + dentry->d_iname, current_pid()); + return -EFAULT; + } + } + break; + default: + BUG(); /* State is set by "set state" command */ + break; + } + + return 0; +} +EXPORT_SYMBOL(pft_inode_post_create); + +/** + * pft_inode_mknod() - mknode file hook (callback) + * @dir: directory inode pointer + * @dentry: file dentry pointer + * @mode: flags + * @dev: + * + * This hook checks encrypted file access permission by + * enterprise application. + * Call path: + * vfs_mknod()->security_inode_mknod()->selinux_inode_mknod()->pft_inode_mknod() + * + * Return: 0 on successful operation, negative value otherwise. + */ +int pft_inode_mknod(struct inode *dir, struct dentry *dentry, + umode_t mode, dev_t dev) +{ + int rc; + + /* Check if allowed to create new encrypted files */ + rc = pft_inode_create(dir, dentry, mode); + + return rc; +} +EXPORT_SYMBOL(pft_inode_mknod); + +/** + * pft_inode_rename() - file rename hook. + * @inode: directory inode + * @dentry: file dentry + * @new_inode + * @new_dentry + * + * Block attempt to rename enterprise file. + * + * Return: 0 on allowed operation, negative value otherwise. + */ +int pft_inode_rename(struct inode *inode, struct dentry *dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ + if (!inode || !dentry || !new_inode || !new_dentry || !dentry->d_inode) + return 0; + + if (!pft_is_ready()) + return 0; + + /* do nothing for non-encrypted files */ + if (!pft_is_encrypted_file(dentry)) + return 0; + + pr_debug("attempt to rename encrypted file [%s]\n", dentry->d_iname); + + if (pft_is_inplace_inode(dentry->d_inode)) { + pr_err("access to file %s by uid [%d] pid [%d] is blocked.\n", + inode_to_filename(inode), __kuid_val(current_uid()), + current_pid()); + return -EACCES; + } + + if (!pft_is_current_process_registered()) { + pr_err("app (uid %u pid %u) can't access file %s\n", + __kuid_val(current_uid()), current_pid(), + dentry->d_iname); + return -EACCES; + } + + pr_debug("rename file %s\n", dentry->d_iname); + + return 0; +} +EXPORT_SYMBOL(pft_inode_rename); + +/** + * pft_file_open() - file open hook (callback). + * @filp: file pointer + * @cred: credentials pointer + * + * This hook is called when file is opened by VFS. + * It is called from the selinux driver. + * It checks enterprise file xattr when first opened. + * It adds encrypted file to the list of open files. + * Call path: + * do_filp_open()->security_dentry_open()->selinux_dentry_open() + * + * Return: 0 on successe, negative value on failure. + */ +int pft_file_open(struct file *filp, const struct cred *cred) +{ + int ret; + + if (!filp || !filp->f_path.dentry) + return 0; + + if (!pft_is_ready()) + return 0; + + if (filp->f_flags & O_DIRECT) + pr_debug("file %s using O_DIRECT.\n", file_to_filename(filp)); + + /* do nothing for non-encrypted files */ + if (!pft_is_encrypted_file(filp->f_path.dentry)) + return 0; + + /* + * Only PFM allowed to access in-place-encryption-file + * during in-place-encryption process + */ + if (pft_is_inplace_file(filp) && current_pid() != pft_dev->pfm_pid) { + pr_err("Access to file %s by uid %d pid %d is blocked.\n", + file_to_filename(filp), + __kuid_val(current_uid()), + current_pid()); + return -EACCES; + } + + switch (pft_dev->state) { + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_REMOVED: + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + /* Block any access for encrypted files when key not loaded */ + pr_debug("key not loaded. uid (%u) can not access file %s\n", + __kuid_val(current_uid()), file_to_filename(filp)); + return -EACCES; + case PFT_STATE_KEY_LOADED: + /* Only registered apps may access encrypted files. */ + if (!pft_is_current_process_registered()) { + pr_err("unregistered app (uid %u pid %u) is trying to access encrypted file %s\n", + __kuid_val(current_uid()), current_pid(), + file_to_filename(filp)); + return -EACCES; + } + + ret = pft_add_file(filp); + if (ret) { + pr_err("failed to add file %s to the list.\n", + file_to_filename(filp)); + return -EFAULT; + } + break; + default: + BUG(); /* State is set by "set state" command */ + break; + } + + return 0; +} +EXPORT_SYMBOL(pft_file_open); + +/** + * pft_file_permission() - check file access permission. + * @filp: file pointer + * @mask: flags + * + * This hook is called when file is read/write by VFS. + * This hook is called from the selinux driver. + * This hook checks encrypted file access permission by + * enterprise application. + * Call path: + * vfs_read()->security_file_permission()->selinux_file_permission() + * + * Return: 0 on success, negative value on failure. + */ +int pft_file_permission(struct file *filp, int mask) +{ + if (!filp) + return 0; + + if (!pft_is_ready()) + return 0; + + /* do nothing for non-encrypted files */ + if (!pft_is_encrypted_file(filp->f_path.dentry)) + return 0; + + /* + * Only PFM allowed to access in-place-encryption-file + * during in-place encryption process + */ + if (pft_is_inplace_file(filp)) { + if (current_pid() == pft_dev->pfm_pid) { + /* mask MAY_WRITE=2 / MAY_READ=4 */ + pr_debug("r/w [mask 0x%x] file %s (UID %d, PID %d).\n", + mask, file_to_filename(filp), + __kuid_val(current_uid()), current_pid()); + return 0; + } + pr_err("Access to file %s by (UID %d, PID %d) is blocked.\n", + file_to_filename(filp), + __kuid_val(current_uid()), current_pid()); + return -EACCES; + + } + + switch (pft_dev->state) { + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_REMOVED: + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + /* Block any access for encrypted files when key not loaded */ + pr_debug("key not loaded. uid (%u) can not access file %s\n", + __kuid_val(current_uid()), file_to_filename(filp)); + return -EACCES; + case PFT_STATE_KEY_LOADED: + /* Only registered apps can access encrypted files. */ + if (!pft_is_current_process_registered()) { + pr_err("unregistered app (uid %u pid %u) is trying to access encrypted file %s\n", + __kuid_val(current_uid()), current_pid(), + file_to_filename(filp)); + return -EACCES; + } + break; + default: + BUG(); /* State is set by "set state" command */ + break; + } + + return 0; +} +EXPORT_SYMBOL(pft_file_permission); + +/** + * pft_sync_file() - sync the file. + * @filp: file pointer + * + * Complete writing any pending write request of encrypted data + * before key is removed, to avoid writing garbage to + * enterprise files. + */ +static void pft_sync_file(struct file *filp) +{ + int ret; + + ret = vfs_fsync(filp, false); + + if (ret) + pr_debug("failed to sync file %s, ret = %d.\n", + file_to_filename(filp), ret); + else + pr_debug("Sync file %s ok.\n", file_to_filename(filp)); + +} + +/** + * pft_file_close()- handle file close event + * @filp: file pointer + * + * This hook is called when file is closed by VFS. + * This hook is called from the selinux driver. + * + * Return: 0 on successful operation, negative value otherwise. + */ +int pft_file_close(struct file *filp) +{ + if (!filp) + return 0; + + if (!pft_is_ready()) + return 0; + + /* do nothing for non-encrypted files */ + if (!pft_is_encrypted_file(filp->f_path.dentry)) + return 0; + + if (pft_is_inplace_file(filp)) { + pr_debug("pid [%u] uid [%u] is closing in-place-encryption file %s\n", + current_pid(), __kuid_val(current_uid()), + file_to_filename(filp)); + pft_dev->inplace_file = NULL; + } + + switch (pft_dev->state) { + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + /* + * Do not allow apps to close file when + * pft_close_opened_enc_files() is closing files. + * Normally, all enterprise apps are closed by PFM + * before getting to this state, so the apps files are + * norammly closed by now. + * pft_close_opened_enc_files() is running in PFM context. + */ + if (current_pid() != pft_dev->pfm_pid) + return -EACCES; + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_LOADED: + case PFT_STATE_KEY_REMOVED: + break; + default: + BUG(); /* State is set by "set state" command */ + break; + } + + pft_sync_file(filp); + pft_remove_file(filp); + + return 0; +} +EXPORT_SYMBOL(pft_file_close); + +/** + * pft_inode_unlink() - Delete file hook. + * @dir: directory inode pointer + * @dentry: file dentry pointer + * + * call path: vfs_unlink()->security_inode_unlink(). + * + * Return: 0 on successful operation, negative value otherwise. + */ +int pft_inode_unlink(struct inode *dir, struct dentry *dentry) +{ + struct inode *inode = NULL; + + if (!dir || !dentry || !dentry->d_inode) + return 0; + + if (!pft_is_ready()) + return 0; + + inode = dentry->d_inode; + + /* do nothing for non-encrypted files */ + if (!pft_is_encrypted_file(dentry)) + return 0; + + if (pft_is_inplace_inode(inode)) { + pr_err("can't delete file %s by uid [%d] pid [%d]\n", + inode_to_filename(inode), + __kuid_val(current_uid()), + current_pid()); + return -EBUSY; + } + + if (!pft_is_current_process_registered()) { + pr_err("app (uid %u pid %u) is trying to access file %s\n", + __kuid_val(current_uid()), + current_pid(), + inode_to_filename(inode)); + return -EACCES; + } + pr_debug("delete file %s\n", inode_to_filename(inode)); + + return 0; +} +EXPORT_SYMBOL(pft_inode_unlink); + +/** + * pft_inode_set_xattr() - set/remove xattr callback. + * @dentry: file dentry pointer + * @name: xattr name. + * + * This hook checks attempt to set/remove PFE xattr. + * Only this kernel driver allows to set the PFE xattr, so block + * any attempt to do it from user space. Allow access for other + * xattr. + * + * Return: 0 on successful operation, negative value otherwise. + */ + +int pft_inode_set_xattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags) +{ + struct inode *inode = NULL; + + if (!dentry || !dentry->d_inode) + return 0; + + inode = dentry->d_inode; + + if (strcmp(name, XATTR_NAME_PFE) != 0) { + pr_debug("xattr name=%s file %s\n", name, + inode_to_filename(inode)); + return 0; /* Not PFE xattr so it is ok */ + } + + pr_err("Attemp to set/remove PFE xattr for file %s\n", + inode_to_filename(inode)); + + /* Only PFT kernel driver allows to set the PFE xattr */ + return -EACCES; +} +EXPORT_SYMBOL(pft_inode_set_xattr); + +/** + * pft_close_opened_enc_files() - Close all the currently open + * encrypted files + * + * Close all open encrypted file when removing key or + * deactivating. + */ +static void pft_close_opened_enc_files(void) +{ + struct pft_file_info *tmp = NULL; + struct list_head *pos = NULL; + struct list_head *next = NULL; + + list_for_each_safe(pos, next, &pft_dev->open_file_list) { + struct file *filp; + + tmp = list_entry(pos, struct pft_file_info, list); + filp = tmp->file; + pr_debug("closing file %s.\n", file_to_filename(filp)); + /* filp_close() eventually calls pft_file_close() */ + filp_close(filp, NULL); + } +} + +/** + * pft_set_state() - Handle "Set State" command + * @command: command buffer. + * @size: size of command buffer. + * + * The command execution status is reported by the response. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int pft_set_state(struct pft_command *command, int size) +{ + u32 state = command->set_state.state; + int expected_size = sizeof(command->opcode) + + sizeof(command->set_state); + + if (size != expected_size) { + pr_err("Invalid buffer size\n"); + pft_set_response(PFT_CMD_RESP_INVALID_CMD_PARAMS); + return -EINVAL; + } + + if (state >= PFT_STATE_MAX_INDEX) { + pr_err("Invalid state %d\n", command->set_state.state); + pft_set_response(PFT_CMD_RESP_INVALID_STATE); + return 0; + } + + pr_debug("Set State %d [%s].\n", state, pft_state_name[state]); + + switch (command->set_state.state) { + case PFT_STATE_DEACTIVATING: + case PFT_STATE_REMOVING_KEY: + pft_close_opened_enc_files(); + /* Fall through */ + case PFT_STATE_DEACTIVATED: + case PFT_STATE_KEY_LOADED: + case PFT_STATE_KEY_REMOVED: + pft_dev->state = command->set_state.state; + pft_set_response(PFT_CMD_RESP_SUCCESS); + break; + default: + pr_err("Invalid state %d\n", command->set_state.state); + pft_set_response(PFT_CMD_RESP_INVALID_STATE); + break; + } + + return 0; +} + +/** + * pft_get_process_open_file() - get file pointer using file + * descriptor index. + * @index: file descriptor index. + * + * Return: file pointer on success, NULL on failure. + */ +static struct file *pft_get_process_open_file(int index) +{ + struct fdtable *files_table; + + files_table = files_fdtable(current->files); + if (files_table == NULL) + return NULL; + + if (index >= files_table->max_fds) + return NULL; + else + return files_table->fd[index]; +} + +/** + * pft_set_inplace_file() - handle "inplace file encryption" + * command. + * @command: command buffer. + * @size: size of command buffer. + * + * The command execution status is reported by the response. + * + * Return: 0 if command is valid, negative value otherwise. + */ +static int pft_set_inplace_file(struct pft_command *command, int size) +{ + int expected_size; + u32 fd; + int rc; + struct file *filp = NULL; + struct inode *inode = NULL; + int writecount; + + expected_size = sizeof(command->opcode) + + sizeof(command->preform_in_place_file_enc.file_descriptor); + + if (size != expected_size) { + pr_err("invalid command size %d expected %d.\n", + size, expected_size); + pft_set_response(PFT_CMD_RESP_INVALID_CMD_PARAMS); + return -EINVAL; + } + + if (pft_dev->state != (u32) PFT_STATE_KEY_LOADED) { + pr_err("Key not loaded, state [%d], In-place-encryption is not allowed.\n", + pft_dev->state); + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + return 0; + } + + /* allow only one in-place file encryption at a time */ + if (pft_dev->inplace_file != NULL) { + pr_err("file %s in-place-encryption in progress.\n", + file_to_filename(pft_dev->inplace_file)); + /* @todo - use new error code */ + pft_set_response(PFT_CMD_RESP_INPLACE_FILE_IS_OPEN); + return 0; + } + + fd = command->preform_in_place_file_enc.file_descriptor; + filp = pft_get_process_open_file(fd); + + if (filp == NULL) { + pr_err("failed to find file by fd %d.\n", fd); + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + return 0; + } + + /* Verify the file is not already open by other than PFM */ + if (!filp->f_path.dentry || !filp->f_path.dentry->d_inode) { + pr_err("failed to get inode of inplace-file.\n"); + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + return 0; + } + + inode = filp->f_path.dentry->d_inode; + writecount = atomic_read(&inode->i_writecount); + if (writecount > 1) { + pr_err("file %s is opened %d times for write.\n", + file_to_filename(filp), writecount); + pft_set_response(PFT_CMD_RESP_INPLACE_FILE_IS_OPEN); + return 0; + } + + /* + * Check if the file was already encryprted. + * In practice, it is unlikely to happen, + * because PFM is not an enterprise application + * it won't be able to open encrypted file. + */ + if (pft_is_encrypted_file(filp->f_path.dentry)) { + pr_err("file %s is already encrypted.\n", + file_to_filename(filp)); + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + return 0; + } + + + /* Update the current in-place-encryption file */ + pft_dev->inplace_file = filp; + + /* + * Now, any new access to this file is allowed only to PFM. + * Lets make sure that all pending writes are completed + * before encrypting the file. + */ + pft_sync_file(filp); + + rc = pft_tag_file(pft_dev->inplace_file->f_path.dentry, + pft_get_app_key_index(current_uid())); + + if (!rc) { + pr_debug("tagged file %s to be encrypted.\n", + file_to_filename(pft_dev->inplace_file)); + pft_set_response(PFT_CMD_RESP_SUCCESS); + } else { + pr_err("failed to tag file %s for encryption.\n", + file_to_filename(pft_dev->inplace_file)); + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + } + + return 0; +} + +/** + * pft_update_reg_apps() - Update the registered application + * list. + * @command: command buffer. + * @size: size of command buffer. + * + * The command execution status is reported by the response. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int pft_update_reg_apps(struct pft_command *command, int size) +{ + int i; + int expected_size; + void *buf; + int buf_size; + u32 items_count = command->update_app_list.items_count; + + if (items_count > PFT_MAX_APPS) { + pr_err("Number of apps [%d] > max apps [%d]\n", + items_count , PFT_MAX_APPS); + pft_set_response(PFT_CMD_RESP_INVALID_CMD_PARAMS); + return -EINVAL; + } + + expected_size = + sizeof(command->opcode) + + sizeof(command->update_app_list.items_count) + + (command->update_app_list.items_count * sizeof(u32)); + + if (size != expected_size) { + pr_err("invalid command size %d expected %d.\n", + size, expected_size); + pft_set_response(PFT_CMD_RESP_INVALID_CMD_PARAMS); + return -EINVAL; + } + + mutex_lock(&pft_dev->lock); + + /* Free old table */ + kfree(pft_dev->uid_table); + pft_dev->uid_table = NULL; + pft_dev->uid_count = 0; + + if (items_count == 0) { + pr_info("empty app list - clear list.\n"); + mutex_unlock(&pft_dev->lock); + return 0; + } + + buf_size = command->update_app_list.items_count * sizeof(u32); + buf = kzalloc(buf_size, GFP_KERNEL); + + if (!buf) { + pft_set_response(PFT_CMD_RESP_GENERAL_ERROR); + mutex_unlock(&pft_dev->lock); + return 0; + } + + pft_dev->uid_table = buf; + pft_dev->uid_count = command->update_app_list.items_count; + pr_debug("uid_count = %d\n", pft_dev->uid_count); + for (i = 0; i < pft_dev->uid_count; i++) + pft_dev->uid_table[i] = + KUIDT_INIT(command->update_app_list.table[i]); + pft_set_response(PFT_CMD_RESP_SUCCESS); + mutex_unlock(&pft_dev->lock); + + return 0; +} + +/** + * pft_handle_command() - Handle user space app commands. + * @buf: command buffer. + * @buf_size: command buffer size. + * + * Return: 0 on successful operation, negative value otherwise. + */ +static int pft_handle_command(void *buf, int buf_size) +{ + int ret = 0; + struct pft_command *command = NULL; + + /* opcode field is the minimum length of command */ + if (buf_size < sizeof(command->opcode)) { + pr_err("Invalid argument used buffer size\n"); + return -EINVAL; + } + + command = (struct pft_command *)buf; + + pft_dev->response.command_id = command->opcode; + + switch (command->opcode) { + case PFT_CMD_OPCODE_SET_STATE: + ret = pft_set_state(command, buf_size); + break; + case PFT_CMD_OPCODE_UPDATE_REG_APP_UID: + ret = pft_update_reg_apps(command, buf_size); + break; + case PFT_CMD_OPCODE_PERFORM_IN_PLACE_FILE_ENC: + ret = pft_set_inplace_file(command, buf_size); + break; + default: + pr_err("Invalid command_op_code %u\n", command->opcode); + pft_set_response(PFT_CMD_RESP_INVALID_COMMAND); + return 0; + } + + return ret; +} + +static int pft_device_open(struct inode *inode, struct file *file) +{ + int ret; + + mutex_lock(&pft_dev->lock); + if (pft_dev->open_count > 0) { + pr_err("PFT device is already opened (%d)\n", + pft_dev->open_count); + ret = -EBUSY; + } else { + pft_dev->open_count++; + pft_dev->pfm_pid = current_pid(); + pr_debug("PFT device opened by %d (%d)\n", + pft_dev->pfm_pid, pft_dev->open_count); + ret = 0; + } + mutex_unlock(&pft_dev->lock); + + pr_debug("device opened, count %d\n", pft_dev->open_count); + + return ret; +} + +static int pft_device_release(struct inode *inode, struct file *file) +{ + mutex_lock(&pft_dev->lock); + if (pft_dev->open_count > 0) + pft_dev->open_count--; + pft_dev->pfm_pid = UINT_MAX; + mutex_unlock(&pft_dev->lock); + + pr_debug("device released, count %d\n", pft_dev->open_count); + + return 0; +} + +/** + * pft_device_write() - Get commands from user sapce. + * + * Return: number of bytes to write on success to get the + * command buffer, negative value on failure. + * The error code for handling the command should be retrieve by + * reading the response. + * Note: any reurn value of 0..size-1 will cause retry by the + * OS, so avoid it. + */ +static ssize_t pft_device_write(struct file *filp, const char __user *user_buff, + size_t size, loff_t *f_pos) +{ + int ret; + char *cmd_buf; + + if (size > PFT_MAX_COMMAND_SIZE || !user_buff || !f_pos) { + pr_err("inavlid parameters.\n"); + return -EINVAL; + } + + cmd_buf = kzalloc(size, GFP_KERNEL); + if (cmd_buf == NULL) + return -ENOMEM; + + ret = copy_from_user(cmd_buf, user_buff, size); + if (ret) { + pr_err("Unable to copy from user (err %d)\n", ret); + kfree(cmd_buf); + return -EFAULT; + } + + ret = pft_handle_command(cmd_buf, size); + if (ret) { + kfree(cmd_buf); + return -EFAULT; + } + + kfree(cmd_buf); + + return size; +} + +/** + * pft_device_read() - return response of last command. + * + * Return: number of bytes to read on success, negative value on + * failure. + */ +static ssize_t pft_device_read(struct file *filp, char __user *buffer, + size_t length, loff_t *f_pos) +{ + int ret = 0; + + if (!buffer || !f_pos || length < sizeof(pft_dev->response)) { + pr_err("inavlid parameters.\n"); + return -EFAULT; + } + + ret = copy_to_user(buffer, &(pft_dev->response), + sizeof(pft_dev->response)); + if (ret) { + pr_err("Unable to copy to user, err = %d.\n", ret); + return -EINVAL; + } + + return sizeof(pft_dev->response); +} + + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = pft_device_read, + .write = pft_device_write, + .open = pft_device_open, + .release = pft_device_release, +}; + +static int __init pft_register_chardev(void) +{ + int rc; + unsigned baseminor = 0; + unsigned count = 1; + struct device *class_dev; + + rc = alloc_chrdev_region(&pft_dev->device_no, baseminor, count, + DEVICE_NAME); + if (rc < 0) { + pr_err("alloc_chrdev_region failed %d\n", rc); + return rc; + } + + pft_dev->driver_class = class_create(THIS_MODULE, DEVICE_NAME); + if (IS_ERR(pft_dev->driver_class)) { + rc = -ENOMEM; + pr_err("class_create failed %d\n", rc); + goto exit_unreg_chrdev_region; + } + + class_dev = device_create(pft_dev->driver_class, NULL, + pft_dev->device_no, NULL, + DEVICE_NAME); + if (!class_dev) { + pr_err("class_device_create failed %d\n", rc); + rc = -ENOMEM; + goto exit_destroy_class; + } + + cdev_init(&pft_dev->cdev, &fops); + pft_dev->cdev.owner = THIS_MODULE; + + rc = cdev_add(&pft_dev->cdev, MKDEV(MAJOR(pft_dev->device_no), 0), 1); + if (rc < 0) { + pr_err("cdev_add failed %d\n", rc); + goto exit_destroy_device; + } + + return 0; + +exit_destroy_device: + device_destroy(pft_dev->driver_class, pft_dev->device_no); +exit_destroy_class: + class_destroy(pft_dev->driver_class); +exit_unreg_chrdev_region: + unregister_chrdev_region(pft_dev->device_no, 1); + return rc; +} + +static void __exit pft_unregister_chrdev(void) +{ + cdev_del(&pft_dev->cdev); + device_destroy(pft_dev->driver_class, pft_dev->device_no); + class_destroy(pft_dev->driver_class); + unregister_chrdev_region(pft_dev->device_no, 1); + +} + +static void __exit pft_free_open_files_list(void) +{ + struct pft_file_info *tmp = NULL; + struct list_head *pos = NULL; + struct list_head *next = NULL; + + mutex_lock(&pft_dev->lock); + list_for_each_safe(pos, next, &pft_dev->open_file_list) { + tmp = list_entry(pos, struct pft_file_info, list); + list_del(&tmp->list); + kfree(tmp); + } + mutex_unlock(&pft_dev->lock); +} + +static void __exit pft_exit(void) +{ + if (pft_dev == NULL) + return; + + pft_unregister_chrdev(); + pft_free_open_files_list(); + + kfree(pft_dev->uid_table); + kfree(pft_dev); + pft_dev = NULL; +} + +static int __init pft_init(void) +{ + int ret; + struct pft_device *dev = NULL; + + dev = kzalloc(sizeof(struct pft_device), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + pft_dev = dev; + + dev->state = PFT_STATE_DEACTIVATED; + dev->pfm_pid = UINT_MAX; + + INIT_LIST_HEAD(&dev->open_file_list); + mutex_init(&dev->lock); + + ret = pft_register_chardev(); + if (ret) { + pr_err("create character device failed.\n"); + goto fail; + } + + pft_lsm_init(dev); + + return 0; + +fail: + pr_err("Failed to init driver.\n"); + kfree(dev); + pft_dev = NULL; + + return -ENODEV; +} + +module_init(pft_init); +module_exit(pft_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Per-File-Tagger driver"); diff --git a/security/security.c b/security/security.c index 42c4cb0cb122..e60d560e45f8 100644 --- a/security/security.c +++ b/security/security.c @@ -514,6 +514,14 @@ int security_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode } EXPORT_SYMBOL_GPL(security_inode_create); +int security_inode_post_create(struct inode *dir, struct dentry *dentry, + umode_t mode) +{ + if (unlikely(IS_PRIVATE(dir))) + return 0; + return call_int_hook(inode_post_create, 0, dir, dentry, mode); +} + int security_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { @@ -1622,6 +1630,7 @@ struct security_hook_heads security_hook_heads = { .inode_init_security = LIST_HEAD_INIT(security_hook_heads.inode_init_security), .inode_create = LIST_HEAD_INIT(security_hook_heads.inode_create), + .inode_post_create = LIST_HEAD_INIT(security_hook_heads.inode_post_create), .inode_link = LIST_HEAD_INIT(security_hook_heads.inode_link), .inode_unlink = LIST_HEAD_INIT(security_hook_heads.inode_unlink), .inode_symlink = @@ -1681,6 +1690,7 @@ struct security_hook_heads security_hook_heads = { LIST_HEAD_INIT(security_hook_heads.file_send_sigiotask), .file_receive = LIST_HEAD_INIT(security_hook_heads.file_receive), .file_open = LIST_HEAD_INIT(security_hook_heads.file_open), + .file_close = LIST_HEAD_INIT(security_hook_heads.file_close), .task_create = LIST_HEAD_INIT(security_hook_heads.task_create), .task_free = LIST_HEAD_INIT(security_hook_heads.task_free), .cred_alloc_blank = diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d6278891c160..f7e2cd8c3e53 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -1252,6 +1252,8 @@ static inline u16 socket_type_to_security_class(int family, int type, int protoc return SECCLASS_KEY_SOCKET; case PF_APPLETALK: return SECCLASS_APPLETALK_SOCKET; + case PF_CAN: + return SECCLASS_CAN_SOCKET; } return SECCLASS_SOCKET; @@ -1836,8 +1838,7 @@ static int may_link(struct inode *dir, return 0; } - rc = avc_has_perm(sid, isec->sid, isec->sclass, av, &ad); - return rc; + return avc_has_perm(sid, isec->sid, isec->sclass, av, &ad); } static inline int may_rename(struct inode *old_dir, diff --git a/security/selinux/include/classmap.h b/security/selinux/include/classmap.h index 31dc821a6be0..8a764f40730b 100644 --- a/security/selinux/include/classmap.h +++ b/security/selinux/include/classmap.h @@ -157,5 +157,7 @@ struct security_class_mapping secclass_map[] = { { COMMON_SOCK_PERMS, "attach_queue", NULL } }, { "binder", { "impersonate", "call", "set_context_mgr", "transfer", NULL } }, + { "can_socket", + { COMMON_SOCK_PERMS, NULL } }, { NULL } }; diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 81fa718d5cb3..f6027d67a0e6 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -25,8 +25,7 @@ #include <linux/in.h> #include <linux/spinlock.h> #include <net/net_namespace.h> -#include "flask.h" -#include "avc.h" +#include "security.h" struct task_security_struct { u32 osid; /* SID prior to last execve */ @@ -47,6 +46,8 @@ struct inode_security_struct { u32 sid; /* SID of this object */ 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; }; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index dff1d1a1367a..b45a3a72c161 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -12,7 +12,6 @@ #include <linux/dcache.h> #include <linux/magic.h> #include <linux/types.h> -#include "flask.h" #define SECSID_NULL 0x00000000 /* unspecified SID */ #define SECSID_WILD 0xffffffff /* wildcard SID */ |