/* * Copyright (c) 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. */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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), 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_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");