diff options
author | Andrey Markovytch <andreym@codeaurora.org> | 2015-06-08 11:29:36 +0300 |
---|---|---|
committer | David Keitel <dkeitel@codeaurora.org> | 2016-03-23 21:24:03 -0700 |
commit | ecc052ba4efd2ee81fac2a35047bbd34a0344aca (patch) | |
tree | 8bf695f7e58b2bf44d03e82600a2b7bf31cb49d5 | |
parent | a56866b4eb9939e5e82d56eef4f172c069768712 (diff) |
platform: msm: add Per-File-Tagger (PFT) driver
Integrated from msm-3.14. Additional fixes were made to compile with the
new kernel and various new warnings and checkpatch issues were fixed
Change-Id: I073db1041e41eac9066e37ee099f1da9e4eed6c0
Signed-off-by: Andrey Markovytch <andreym@codeaurora.org>
[gbroner@codeaurora.org: fixed merge conflict and adapted the LSM
security hooks]
Signed-off-by: Gilad Broner <gbroner@codeaurora.org>
-rw-r--r-- | block/bio.c | 1 | ||||
-rw-r--r-- | block/blk-merge.c | 6 | ||||
-rw-r--r-- | drivers/platform/msm/Kconfig | 10 | ||||
-rw-r--r-- | drivers/platform/msm/Makefile | 2 | ||||
-rw-r--r-- | drivers/platform/msm/pft.c | 1869 | ||||
-rw-r--r-- | fs/direct-io.c | 14 | ||||
-rw-r--r-- | fs/namei.c | 14 | ||||
-rw-r--r-- | include/linux/blk_types.h | 7 | ||||
-rw-r--r-- | include/linux/fs.h | 2 | ||||
-rw-r--r-- | include/linux/lsm_hooks.h | 7 | ||||
-rw-r--r-- | include/linux/pft.h | 96 | ||||
-rw-r--r-- | include/linux/security.h | 21 | ||||
-rw-r--r-- | include/uapi/linux/Kbuild | 1 | ||||
-rw-r--r-- | include/uapi/linux/msm_pft.h | 134 | ||||
-rw-r--r-- | security/security.c | 21 | ||||
-rw-r--r-- | security/selinux/hooks.c | 59 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 1 |
17 files changed, 2264 insertions, 1 deletions
diff --git a/block/bio.c b/block/bio.c index 4f184d938942..fd3159b2d4ac 100644 --- a/block/bio.c +++ b/block/bio.c @@ -584,6 +584,7 @@ void __bio_clone_fast(struct bio *bio, struct bio *bio_src) bio->bi_rw = bio_src->bi_rw; bio->bi_iter = bio_src->bi_iter; bio->bi_io_vec = bio_src->bi_io_vec; + bio->bi_dio_inode = bio_src->bi_dio_inode; } EXPORT_SYMBOL(__bio_clone_fast); diff --git a/block/blk-merge.c b/block/blk-merge.c index b966db8f3556..5346f2d79652 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -6,6 +6,7 @@ #include <linux/bio.h> #include <linux/blkdev.h> #include <linux/scatterlist.h> +#include <linux/security.h> #include "blk.h" @@ -772,6 +773,11 @@ bool blk_rq_merge_ok(struct request *rq, struct bio *bio) !blk_write_same_mergeable(rq->bio, bio)) return false; + /* Don't merge bios of files with different encryption */ + if (!security_allow_merge_bio(rq->bio, bio)) + return false; + + return true; } diff --git a/drivers/platform/msm/Kconfig b/drivers/platform/msm/Kconfig index 19510e5c2279..e5b95378a336 100644 --- a/drivers/platform/msm/Kconfig +++ b/drivers/platform/msm/Kconfig @@ -134,6 +134,16 @@ config SSM This driver uses Secure Channel Manager interface for trustzone communication and communicates with modem over SMD channel. +config PFT + bool "Per-File-Tagger driver" + 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 GPIO_USB_DETECT tristate "GPIO-based USB VBUS Detection" depends on POWER_SUPPLY diff --git a/drivers/platform/msm/Makefile b/drivers/platform/msm/Makefile index c33f5e53c1b3..866338c39760 100644 --- a/drivers/platform/msm/Makefile +++ b/drivers/platform/msm/Makefile @@ -1,6 +1,8 @@ # # Makefile for the MSM specific device drivers. # +ccflags-y += -Isecurity/selinux -Isecurity/selinux/include +obj-$(CONFIG_PFT) += pft.o obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o obj-$(CONFIG_MSM_MHI) += mhi/ diff --git a/drivers/platform/msm/pft.c b/drivers/platform/msm/pft.c new file mode 100644 index 000000000000..9884fd3366ac --- /dev/null +++ b/drivers/platform/msm/pft.c @@ -0,0 +1,1869 @@ +/* + * Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* + * Per-File-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(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), + + LSM_HOOK_INIT(allow_merge_bio, pft_allow_merge_bio), +}; + +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_debug("pft is not the chosen lsm.\n"); + } + + 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_bio_get_inode() - get the inode from a bio. + * @bio: Pointer to BIO structure. + * + * Walk the bio struct links to get the inode. + * + * Return: pointer to the inode struct if successful, or NULL otherwise. + */ +static struct inode *pft_bio_get_inode(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(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_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(struct bio *bio1, 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; + + /* + * 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_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/fs/direct-io.c b/fs/direct-io.c index 602e8441bc0f..cfcf7db05812 100644 --- a/fs/direct-io.c +++ b/fs/direct-io.c @@ -399,6 +399,7 @@ static inline void dio_bio_submit(struct dio *dio, struct dio_submit *sdio) if (dio->is_async && dio->rw == READ && dio->should_dirty) bio_set_pages_dirty(bio); + bio->bi_dio_inode = dio->inode; dio->bio_bdev = bio->bi_bdev; if (sdio->submit_io) { @@ -413,6 +414,19 @@ static inline void dio_bio_submit(struct dio *dio, struct dio_submit *sdio) sdio->logical_offset_in_bio = 0; } +struct inode *dio_bio_get_inode(struct bio *bio) +{ + struct inode *inode = NULL; + + if (bio == NULL) + return NULL; + + inode = bio->bi_dio_inode; + + return inode; +} +EXPORT_SYMBOL(dio_bio_get_inode); + /* * Release any resources in case of a failure */ diff --git a/fs/namei.c b/fs/namei.c index 0c3974cd3ecd..5fd8a57d132b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2657,8 +2657,14 @@ int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, if (error) return error; error = dir->i_op->create(dir, dentry, mode, want_excl); + if (error) + return error; + error = security_inode_post_create(dir, dentry, mode); + if (error) + return error; if (!error) fsnotify_create(dir, dentry); + return error; } EXPORT_SYMBOL(vfs_create); @@ -3510,8 +3516,16 @@ int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) return error; error = dir->i_op->mknod(dir, dentry, mode, dev); + if (error) + return error; + + error = security_inode_post_create(dir, dentry, mode); + if (error) + return error; + if (!error) fsnotify_create(dir, dentry); + return error; } EXPORT_SYMBOL(vfs_mknod); diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h index d32400f5402b..e897f4d3810c 100644 --- a/include/linux/blk_types.h +++ b/include/linux/blk_types.h @@ -88,6 +88,13 @@ struct bio { unsigned short bi_vcnt; /* how many bio_vec's */ /* + * When using dircet-io (O_DIRECT), we can't get the inode from a bio + * by walking bio->bi_io_vec->bv_page->mapping->host + * since the page is anon. + */ + struct inode *bi_dio_inode; + + /* * Everything starting with bi_max_vecs will be preserved by bio_reset() */ diff --git a/include/linux/fs.h b/include/linux/fs.h index 605454ccaa0e..522b582e61c7 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2734,6 +2734,8 @@ static inline void inode_dio_end(struct inode *inode) wake_up_bit(&inode->i_state, __I_DIO_WAKEUP); } +struct inode *dio_bio_get_inode(struct bio *bio); + extern void inode_set_flags(struct inode *inode, unsigned int flags, unsigned int mask); diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index ec3a6bab29de..1919b06f28f4 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -1384,6 +1384,8 @@ union security_list_options { size_t *len); int (*inode_create)(struct inode *dir, struct dentry *dentry, umode_t mode); + int (*inode_post_create)(struct inode *dir, struct dentry *dentry, + umode_t mode); int (*inode_link)(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry); int (*inode_unlink)(struct inode *dir, struct dentry *dentry); @@ -1440,6 +1442,8 @@ union security_list_options { struct fown_struct *fown, int sig); int (*file_receive)(struct file *file); int (*file_open)(struct file *file, const struct cred *cred); + int (*file_close)(struct file *file); + bool (*allow_merge_bio)(struct bio *bio1, struct bio *bio2); int (*task_create)(unsigned long clone_flags); void (*task_free)(struct task_struct *task); @@ -1666,6 +1670,7 @@ struct security_hook_heads { struct list_head inode_free_security; struct list_head inode_init_security; struct list_head inode_create; + struct list_head inode_post_create; struct list_head inode_link; struct list_head inode_unlink; struct list_head inode_symlink; @@ -1702,6 +1707,8 @@ struct security_hook_heads { struct list_head file_send_sigiotask; struct list_head file_receive; struct list_head file_open; + struct list_head file_close; + struct list_head allow_merge_bio; struct list_head task_create; struct list_head task_free; struct list_head cred_alloc_blank; diff --git a/include/linux/pft.h b/include/linux/pft.h new file mode 100644 index 000000000000..f2173b89a2a0 --- /dev/null +++ b/include/linux/pft.h @@ -0,0 +1,96 @@ +/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef PFT_H_ +#define PFT_H_ + +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/bio.h> + +#ifdef CONFIG_PFT + +/* dm-req-crypt API */ +int pft_get_key_index(struct bio *bio, u32 *key_index, + bool *is_encrypted, bool *is_inplace); + +/* block layer API */ +bool pft_allow_merge_bio(struct bio *bio1, struct bio *bio2); + +/* --- security hooks , called from selinux --- */ +int pft_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode); + +int pft_inode_post_create(struct inode *dir, struct dentry *dentry, + umode_t mode); + +int pft_file_open(struct file *filp, const struct cred *cred); + +int pft_file_permission(struct file *file, int mask); + +int pft_file_close(struct file *filp); + +int pft_inode_unlink(struct inode *dir, struct dentry *dentry); + +int pft_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, + dev_t dev); + +int pft_inode_rename(struct inode *inode, struct dentry *dentry, + struct inode *new_inode, struct dentry *new_dentry); + +int pft_inode_set_xattr(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags); + + +#else +static inline int pft_get_key_index(struct bio *bio, u32 *key_index, + bool *is_encrypted, bool *is_inplace) +{ return -ENODEV; } + +static inline bool pft_allow_merge_bio(struct bio *bio1, struct bio *bio2) +{ return true; } + +static inline int pft_file_permission(struct file *file, int mask) +{ return 0; } + +static inline int pft_inode_create( + struct inode *dir, struct dentry *dentry, umode_t mode) +{ return 0; } + +static inline int pft_inode_post_create( + struct inode *dir, struct dentry *dentry, umode_t mode) +{ return 0; } + +static inline int pft_file_open(struct file *filp, const struct cred *cred) +{ return 0; } + +static inline int pft_file_close(struct file *filp) +{ return 0; } + +static inline int pft_inode_unlink(struct inode *dir, struct dentry *dentry) +{ return 0; } + +static inline int pft_inode_mknod(struct inode *dir, struct dentry *dentry, + umode_t mode, dev_t dev) +{ return 0; } + +static inline int pft_inode_rename(struct inode *inode, struct dentry *dentry, + struct inode *new_inode, struct dentry *new_dentry) +{ return 0; } + +static inline int pft_inode_set_xattr(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags) +{ return 0; } + +#endif /* CONFIG_PFT */ + +#endif /* PFT_H */ diff --git a/include/linux/security.h b/include/linux/security.h index 2f4c1f7aa7db..3de0302aecf2 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -28,6 +28,7 @@ #include <linux/err.h> #include <linux/string.h> #include <linux/mm.h> +#include <linux/bio.h> struct linux_binprm; struct cred; @@ -244,6 +245,7 @@ int security_old_inode_init_security(struct inode *inode, struct inode *dir, const struct qstr *qstr, const char **name, void **value, size_t *len); int security_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode); +int security_inode_post_create(struct inode *dir, struct dentry *dentry, umode_t mode); int security_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry); int security_inode_unlink(struct inode *dir, struct dentry *dentry); @@ -290,6 +292,8 @@ int security_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int sig); int security_file_receive(struct file *file); int security_file_open(struct file *file, const struct cred *cred); +int security_file_close(struct file *file); +bool security_allow_merge_bio(struct bio *bio1, struct bio *bio2); int security_task_create(unsigned long clone_flags); void security_task_free(struct task_struct *task); int security_cred_alloc_blank(struct cred *cred, gfp_t gfp); @@ -605,6 +609,13 @@ static inline int security_inode_create(struct inode *dir, return 0; } +static inline int security_inode_post_create(struct inode *dir, + struct dentry *dentry, + umode_t mode) +{ + return 0; +} + static inline int security_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) @@ -810,6 +821,16 @@ static inline int security_file_open(struct file *file, return 0; } +static inline int security_file_close(struct file *file) +{ + return 0; +} + +static inline int security_allow_merge_bio(struct bio *bio1, struct bio *bio2) +{ + return true; +} + static inline int security_task_create(unsigned long clone_flags) { return 0; diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild index 4c0fd6369a76..16b7e327818d 100644 --- a/include/uapi/linux/Kbuild +++ b/include/uapi/linux/Kbuild @@ -302,6 +302,7 @@ header-y += msm_audio_alac.h header-y += msm_audio_ape.h header-y += msm_ion.h header-y += msm_kgsl.h +header-y += msm_pft.h header-y += msm_mdp.h header-y += msm_mdp_ext.h header-y += msm_rmnet.h diff --git a/include/uapi/linux/msm_pft.h b/include/uapi/linux/msm_pft.h new file mode 100644 index 000000000000..1127337eb5de --- /dev/null +++ b/include/uapi/linux/msm_pft.h @@ -0,0 +1,134 @@ +/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef MSM_PFT_H_ +#define MSM_PFT_H_ + +#include <linux/types.h> + +/** + * enum pft_command_opcode - PFT driver command ID + * + * @PFT_CMD_OPCODE_SET_STATE - + * command ID to set PFT driver state + * @PFT_CMD_OPCODE_UPDATE_REG_APP_UID - + * command ID to update the list of registered application + * UID + * @PFT_CMD_OPCODE_PERFORM_IN_PLACE_FILE_ENC - + * command ID to perfrom in-place file encryption + */ +enum pft_command_opcode { + PFT_CMD_OPCODE_SET_STATE, + PFT_CMD_OPCODE_UPDATE_REG_APP_UID, + PFT_CMD_OPCODE_PERFORM_IN_PLACE_FILE_ENC, + /* */ + PFT_CMD_OPCODE_MAX_COMMAND_INDEX +}; + +/** + * enum pft_state - PFT driver operational states + * + * @PFT_STATE_DEACTIVATED - driver is deativated. + * @PFT_STATE_DEACTIVATING - driver is in the process of being deativated. + * @PFT_STATE_KEY_REMOVED - driver is active but no encryption key is loaded. + * @PFT_STATE_REMOVING_KEY - driver is active, but the encryption key is being + * removed. + * @PFT_STATE_KEY_LOADED - driver is active, and the encryption key is loaded + * to encryption block, hence registered apps can perform file operations + * on encrypted files. + */ +enum pft_state { + PFT_STATE_DEACTIVATED, + PFT_STATE_DEACTIVATING, + PFT_STATE_KEY_REMOVED, + PFT_STATE_REMOVING_KEY, + PFT_STATE_KEY_LOADED, + /* Internal */ + PFT_STATE_MAX_INDEX +}; + +/** + * enum pft_command_response_code - PFT response on the previous + * command + * + * @PFT_CMD_RESP_SUCCESS - The command was properly processed + * without an error. + * @PFT_CMD_RESP_GENERAL_ERROR - + * Indicates an error that cannot be better described by a + * more specific errors below. + * @PFT_CMD_RESP_INVALID_COMMAND - Invalid or unsupported + * command id. + * @PFT_CMD_RESP_INVALID_CMD_PARAMS - Invalid command + * parameters. + * @PFT_CMD_RESP_INVALID_STATE - Invalid state + * @PFT_CMD_RESP_ALREADY_IN_STATE - Used to indicates that + * the new state is equal to the existing one. + * @PFT_CMD_RESP_INPLACE_FILE_IS_OPEN - Used to indicates + * that the file that should be encrypted is already open + * and can be encrypted. + * @PFT_CMD_RESP_ENT_FILES_CLOSING_FAILURE + * Indicates about failure of the PFT to close Enterprise files + * @PFT_CMD_RESP_MAX_INDEX + */ +enum pft_command_response_code { + PFT_CMD_RESP_SUCCESS, + PFT_CMD_RESP_GENERAL_ERROR, + PFT_CMD_RESP_INVALID_COMMAND, + PFT_CMD_RESP_INVALID_CMD_PARAMS, + PFT_CMD_RESP_INVALID_STATE, + PFT_CMD_RESP_ALREADY_IN_STATE, + PFT_CMD_RESP_INPLACE_FILE_IS_OPEN, + PFT_CMD_RESP_ENT_FILES_CLOSING_FAILURE, + /* Internal */ + PFT_CMD_RESP_MAX_INDEX +}; + +/** + * struct pft_command_response - response structure + * + * @command_id - see enum pft_command_response_code + * @error_codee - see enum pft_command_response_code + */ +struct pft_command_response { + __u32 command_id; + __u32 error_code; +}; + +/** + * struct pft_command - pft command + * + * @opcode - see enum pft_command_opcode. + * @set_state.state - see enum pft_state. + * @update_app_list.count - number of items in the + * registered applications list. + * @update_app_list.table - registered applications array + * @preform_in_place_file_enc.file_descriptor - file descriptor + * of the opened file to be in-placed encrypted. + */ +struct pft_command { + __u32 opcode; + union { + struct { + /* @see pft_state */ + __u32 state; + } set_state; + struct { + __u32 items_count; /* number of items */ + uid_t table[0]; /* array of UIDs */ + } update_app_list; + struct { + __u32 file_descriptor; + } preform_in_place_file_enc; + }; +}; + +#endif /* MSM_PFT_H_ */ diff --git a/security/security.c b/security/security.c index 46f405ce6b0f..81a555c14a35 100644 --- a/security/security.c +++ b/security/security.c @@ -513,6 +513,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) { @@ -844,6 +852,16 @@ int security_file_open(struct file *file, const struct cred *cred) return fsnotify_perm(file, MAY_OPEN); } +int security_file_close(struct file *file) +{ + return call_int_hook(file_close, 0, file); +} + +bool security_allow_merge_bio(struct bio *bio1, struct bio *bio2) +{ + return call_int_hook(allow_merge_bio, 1, bio1, bio2); +} + int security_task_create(unsigned long clone_flags) { return call_int_hook(task_create, 0, clone_flags); @@ -1614,6 +1632,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 = @@ -1673,6 +1692,8 @@ 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), + .allow_merge_bio = LIST_HEAD_INIT(security_hook_heads.allow_merge_bio), .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 7c22a15c7e4b..a00bb5f9fcde 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -83,6 +83,7 @@ #include <linux/export.h> #include <linux/msg.h> #include <linux/shm.h> +#include <linux/pft.h> #include "avc.h" #include "objsec.h" @@ -1777,9 +1778,15 @@ static int may_create(struct inode *dir, if (rc) return rc; - return avc_has_perm(newsid, sbsec->sid, + rc = avc_has_perm(newsid, sbsec->sid, SECCLASS_FILESYSTEM, FILESYSTEM__ASSOCIATE, &ad); + if (rc) + return rc; + + rc = pft_inode_mknod(dir, dentry, 0, 0); + + return rc; } /* Check whether a task can create a key. */ @@ -1836,6 +1843,12 @@ static int may_link(struct inode *dir, } rc = avc_has_perm(sid, isec->sid, isec->sclass, av, &ad); + if (rc) + return rc; + + if (kind == MAY_UNLINK) + rc = pft_inode_unlink(dir, dentry); + return rc; } @@ -2806,9 +2819,21 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, static int selinux_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) { + int ret; + + ret = pft_inode_create(dir, dentry, mode); + if (ret < 0) + return ret; + return may_create(dir, dentry, SECCLASS_FILE); } +static int selinux_inode_post_create(struct inode *dir, struct dentry *dentry, + umode_t mode) +{ + return pft_inode_post_create(dir, dentry, mode); +} + static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { return may_link(dir, old_dentry, MAY_LINK); @@ -2842,6 +2867,12 @@ static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t static int selinux_inode_rename(struct inode *old_inode, struct dentry *old_dentry, struct inode *new_inode, struct dentry *new_dentry) { + int rc; + + rc = pft_inode_rename(old_inode, old_dentry, new_inode, new_dentry); + if (rc) + return rc; + return may_rename(old_inode, old_dentry, new_inode, new_dentry); } @@ -2966,6 +2997,9 @@ static int selinux_inode_setotherxattr(struct dentry *dentry, const char *name) { const struct cred *cred = current_cred(); + if (pft_inode_set_xattr(dentry, name, NULL, 0, 0) < 0) + return -EACCES; + if (!strncmp(name, XATTR_SECURITY_PREFIX, sizeof XATTR_SECURITY_PREFIX - 1)) { if (!strcmp(name, XATTR_NAME_CAPS)) { @@ -3216,11 +3250,16 @@ static int selinux_file_permission(struct file *file, int mask) struct file_security_struct *fsec = file->f_security; struct inode_security_struct *isec = inode->i_security; u32 sid = current_sid(); + int ret; if (!mask) /* No permission to check. Existence test. */ return 0; + ret = pft_file_permission(file, mask); + if (ret < 0) + return ret; + if (sid == fsec->sid && fsec->isid == isec->sid && fsec->pseqno == avc_policy_seqno()) /* No change since file_open check. */ @@ -3511,6 +3550,11 @@ static int selinux_file_open(struct file *file, const struct cred *cred) { struct file_security_struct *fsec; struct inode_security_struct *isec; + int ret; + + ret = pft_file_open(file, cred); + if (ret < 0) + return ret; fsec = file->f_security; isec = file_inode(file)->i_security; @@ -3534,6 +3578,16 @@ static int selinux_file_open(struct file *file, const struct cred *cred) return file_path_has_perm(cred, file, open_file_to_av(file)); } +static int selinux_file_close(struct file *file) +{ + return pft_file_close(file); +} + +static bool selinux_allow_merge_bio(struct bio *bio1, struct bio *bio2) +{ + return pft_allow_merge_bio(bio1, bio2); +} + /* task security operations */ static int selinux_task_create(unsigned long clone_flags) @@ -5905,6 +5959,7 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(inode_free_security, selinux_inode_free_security), LSM_HOOK_INIT(inode_init_security, selinux_inode_init_security), LSM_HOOK_INIT(inode_create, selinux_inode_create), + LSM_HOOK_INIT(inode_post_create, selinux_inode_post_create), LSM_HOOK_INIT(inode_link, selinux_inode_link), LSM_HOOK_INIT(inode_unlink, selinux_inode_unlink), LSM_HOOK_INIT(inode_symlink, selinux_inode_symlink), @@ -5941,6 +5996,8 @@ static struct security_hook_list selinux_hooks[] = { LSM_HOOK_INIT(file_receive, selinux_file_receive), LSM_HOOK_INIT(file_open, selinux_file_open), + LSM_HOOK_INIT(file_close, selinux_file_close), + LSM_HOOK_INIT(allow_merge_bio, selinux_allow_merge_bio), LSM_HOOK_INIT(task_create, selinux_task_create), LSM_HOOK_INIT(cred_alloc_blank, selinux_cred_alloc_blank), diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 81fa718d5cb3..a6204e701224 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -47,6 +47,7 @@ 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 */ struct mutex lock; }; |