| /** |
| * sdp_cache.c |
| * |
| * Store inode number of all the sensitive files that have been opened since booting |
| */ |
| #include <linux/kthread.h> |
| #include <linux/delay.h> |
| #include <linux/list.h> |
| #include <linux/pagemap.h> |
| #include <linux/slab.h> |
| |
| #include "../ext4.h" |
| #include "../ext4_crypto.h" |
| |
| #if defined(CONFIG_EXT4CRYPT_SDP) || defined(CONFIG_DDAR) |
| #include "../fscrypt_knox_private.h" |
| #endif |
| |
| #ifdef CONFIG_EXT4CRYPT_SDP |
| #include "fscrypto_sdp_private.h" |
| #include "fscrypto_sdp_dek_private.h" |
| #endif |
| |
| extern int dek_is_locked(int engine_id); |
| |
| static struct kmem_cache *cachep; |
| |
| LIST_HEAD(list_head); |
| static spinlock_t list_lock; |
| |
| struct _entry { |
| u32 engine_id; |
| struct super_block *sb; |
| unsigned long ino; // inode number |
| struct list_head list; |
| }; |
| |
| static void *dump_entry_list_locked(const char *msg) |
| { |
| struct list_head *e; |
| int i = 0; |
| |
| DEK_LOGD("%s(%s) : ", __func__, msg); |
| spin_lock(&list_lock); |
| list_for_each(e, &list_head) { |
| struct _entry *entry = list_entry(e, struct _entry, list); |
| |
| if (entry) |
| DEK_LOGD("[%d: %p %lu] ", i, entry->sb, entry->ino); |
| i++; |
| } |
| spin_unlock(&list_lock); |
| DEK_LOGD("\n"); |
| DEK_LOGE("%s(%s) : entry-num(%d)", __func__, msg, i); |
| |
| return NULL; |
| } |
| |
| static struct _entry *find_entry_locked(struct super_block *sb, unsigned long ino) |
| { |
| struct list_head *e; |
| |
| spin_lock(&list_lock); |
| list_for_each(e, &list_head) { |
| struct _entry *entry = list_entry(e, struct _entry, list); |
| |
| if (entry->sb == sb) |
| if (entry->ino == ino) { |
| spin_unlock(&list_lock); |
| return entry; |
| } |
| } |
| spin_unlock(&list_lock); |
| |
| return NULL; |
| } |
| |
| static int add_entry_locked(u32 engine_id, struct super_block *sb, unsigned long ino) |
| { |
| struct _entry *entry = NULL; |
| |
| DEK_LOGD("%s(sb:%p, ino:%lu) entered\n", __func__, sb, ino); |
| if (find_entry_locked(sb, ino)) |
| return -EEXIST; |
| entry = kmem_cache_alloc(cachep, GFP_KERNEL); |
| if (!entry) |
| return -ENOMEM; |
| |
| memset(entry, 0, sizeof(struct _entry)); |
| INIT_LIST_HEAD(&entry->list); |
| entry->engine_id = engine_id; |
| entry->sb = sb; |
| entry->ino = ino; |
| spin_lock(&list_lock); |
| list_add_tail(&entry->list, &list_head); |
| spin_unlock(&list_lock); |
| |
| dump_entry_list_locked("entry-added"); |
| |
| return 0; |
| } |
| |
| |
| static void _init(void *foo) |
| { |
| } |
| |
| int fscrypt_sdp_cache_init(void) |
| { |
| spin_lock_init(&list_lock); |
| INIT_LIST_HEAD(&list_head); |
| |
| cachep = kmem_cache_create("sdp_sensitive_ino_entry_cache", |
| sizeof(struct _entry), |
| 0, |
| (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC| |
| SLAB_MEM_SPREAD), |
| _init); |
| |
| if (cachep == NULL) { |
| DEK_LOGE("%s failed\n", __func__); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| void fscrypt_sdp_cache_add_inode_num(struct inode *inode) |
| { |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode);//This pointer must be loaded by get_encryption_info completely |
| if (ci && ci->ci_sdp_info) { |
| int res = add_entry_locked(ci->ci_sdp_info->engine_id, inode->i_sb, inode->i_ino); |
| if (!res) { |
| ci->ci_sdp_info->sdp_flags |= SDP_IS_INO_CACHED; |
| DEK_LOGD("%s(ino:%lu) sb:%p res:%d\n", __func__, inode->i_ino, inode->i_sb, res); |
| } |
| } |
| } |
| |
| void fscrypt_sdp_cache_remove_inode_num(struct inode *inode) |
| { |
| if (inode) { |
| struct list_head *e; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| |
| spin_lock(&list_lock); |
| list_for_each(e, &list_head) { |
| struct _entry *entry = list_entry(e, struct _entry, list); |
| |
| if (entry->sb == inode->i_sb) { |
| if (entry->ino == inode->i_ino) { |
| list_del(&entry->list); |
| if (ci && ci->ci_sdp_info) { |
| ci->ci_sdp_info->sdp_flags &= ~(SDP_IS_INO_CACHED); |
| } |
| spin_unlock(&list_lock); |
| kmem_cache_free(cachep, entry); |
| DEK_LOGD("%s(ino:%lu) sb:%p\n", __func__, inode->i_ino, inode->i_sb); |
| return; |
| } |
| } |
| } |
| spin_unlock(&list_lock); |
| } |
| } |
| |
| struct inode_drop_task_param { |
| int engine_id; |
| }; |
| |
| static unsigned long invalidate_mapping_pages_retry(struct address_space *mapping, |
| pgoff_t start, pgoff_t end, int retries) |
| { |
| DEK_LOGD("freeing [%s] sensitive inode[mapped pagenum = %lu]\n", |
| mapping->host->i_sb->s_type->name, |
| mapping->nrpages); |
| retry: |
| invalidate_mapping_pages(mapping, start, end); |
| DEK_LOGD("invalidate_mapping_pages [%lu] remained\n", |
| mapping->nrpages); |
| |
| if (mapping->nrpages != 0) { |
| if (retries > 0) { |
| DEK_LOGD("[%lu] mapped pages remained in sensitive inode, retry..\n", |
| mapping->nrpages); |
| retries--; |
| msleep(100); |
| goto retry; |
| } |
| } |
| |
| return mapping->nrpages; |
| } |
| |
| static void wait_file_io_retry(struct inode *inode) |
| { |
| bool ret; |
| |
| retry: |
| ret = fscrypt_sdp_is_cache_releasable(inode); |
| |
| if (!ret) { |
| msleep(100); |
| goto retry; |
| } |
| } |
| |
| static int inode_drop_task(void *arg) |
| { |
| struct inode_drop_task_param *param = arg; |
| int engine_id = param->engine_id; |
| |
| struct _entry *entry, *entry_safe; |
| struct inode *inode; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci; |
| LIST_HEAD(drop_list); |
| |
| DEK_LOGD("%s(engine_id:%d) entered\n", __func__, engine_id); |
| dump_entry_list_locked("inode_drop"); |
| |
| spin_lock(&list_lock); |
| list_for_each_entry_safe(entry, entry_safe, &list_head, list) { |
| if (entry && entry->engine_id == engine_id) { |
| list_del_init(&entry->list); |
| list_add_tail(&entry->list, &drop_list); |
| } |
| } |
| spin_unlock(&list_lock); |
| |
| if (!list_empty(&drop_list)) { |
| //Traverse local list and operate safely |
| list_for_each_entry_safe(entry, entry_safe, &drop_list, list) { |
| inode = ilookup(entry->sb, entry->ino); |
| if (!inode) { |
| DEK_LOGD("%s inode(%lu) not found\n", __func__, entry->ino); |
| goto err; |
| } |
| |
| DEK_LOGD("%s found ino:%lu sb:%p\n", __func__, entry->ino, entry->sb); |
| /* EXT4CRYPT-dedicated */ |
| ci = ext4_encryption_info(inode); |
| /* |
| * Instead of occuring BUG, skip the clearing only |
| * TODO: Must research later whether we can skip the logic in this case |
| BUG_ON(!ci); |
| BUG_ON(!ci->ci_sdp_info); |
| */ |
| if (!ci || !ci->ci_sdp_info) { |
| DEK_LOGD("%s May be already cleared\n", __func__); |
| goto free_icnt; |
| } |
| |
| DEK_LOGD("%s found ino:%lu engine_id:%d sdp_flags:%x\n", |
| __func__, entry->ino, ci->ci_sdp_info->engine_id, ci->ci_sdp_info->sdp_flags); |
| |
| if ((ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE) == 0) { |
| DEK_LOGE("%s not sensitive file\n", __func__); |
| goto free_icnt; |
| } |
| |
| wait_file_io_retry(inode); |
| if (filemap_write_and_wait(inode->i_mapping)) |
| DEK_LOGD("May failed to writeback"); |
| |
| DEK_LOGD("%s invalidating...\n", __func__); |
| if (invalidate_mapping_pages_retry(inode->i_mapping, 0, -1, 3) > 0) { |
| DEK_LOGE("Failed to invalidate entire pages.."); |
| } |
| fscrypt_sdp_unset_clearing_ongoing(inode); |
| free_icnt: |
| iput(inode); |
| err: |
| list_del(&entry->list); |
| kmem_cache_free(cachep, entry); |
| } |
| } |
| |
| DEK_LOGD("%s complete...\n", __func__); |
| dump_entry_list_locked("inode_drop(complete)"); |
| |
| kzfree(param); |
| return 0; |
| } |
| |
| void fscrypt_sdp_cache_drop_inode_mappings(int engine_id) |
| { |
| struct task_struct *task; |
| struct inode_drop_task_param *param = |
| kzalloc(sizeof(*param), GFP_KERNEL); |
| |
| param->engine_id = engine_id; |
| task = kthread_run(inode_drop_task, param, "fscrypt_sdp_drop_cached"); |
| if (IS_ERR(task)) { |
| DEK_LOGE("unable to create kernel thread fscrypt_sdp_drop_cached res:%ld\n", |
| PTR_ERR(task)); |
| } |
| } |
| |
| int fscrypt_sdp_file_not_readable(struct file *file) |
| { |
| struct address_space *mapping = file->f_mapping; |
| struct inode *inode = mapping->host; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| int retval = 0; |
| |
| if (ci && ci->ci_sdp_info) { |
| if (!S_ISDIR(inode->i_mode) && (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| int is_locked = dek_is_locked(ci->ci_sdp_info->engine_id); |
| if (is_locked) { |
| wait_file_io_retry(inode); |
| if (filemap_write_and_wait(inode->i_mapping)) |
| DEK_LOGD("May failed to writeback"); |
| |
| invalidate_mapping_pages_retry(inode->i_mapping, 0, -1, 3); |
| fscrypt_sdp_cache_remove_inode_num(inode); |
| fscrypt_sdp_unset_clearing_ongoing(inode); |
| } else { |
| int is_ino_cached = 0; |
| |
| spin_lock(&ci->ci_sdp_info->sdp_flag_lock); |
| if (ci->ci_sdp_info->sdp_flags & SDP_IS_CLEARING_ONGOING) { |
| is_locked = 1; |
| } else { |
| ci->ci_sdp_info->sdp_flags |= SDP_IS_FILE_IO_ONGOING; |
| } |
| if (ci->ci_sdp_info->sdp_flags & SDP_IS_INO_CACHED) |
| is_ino_cached = 1; |
| spin_unlock(&ci->ci_sdp_info->sdp_flag_lock); |
| |
| if (!is_ino_cached) |
| fscrypt_sdp_cache_add_inode_num(inode); |
| } |
| retval = is_locked; |
| } |
| } |
| |
| return retval; |
| } |
| |
| int fscrypt_sdp_file_not_writable(struct file *file) |
| { |
| struct address_space *mapping = file->f_mapping; |
| struct inode *inode = mapping->host; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| int retval = 0; |
| |
| if (!S_ISDIR(inode->i_mode) && ci && ci->ci_sdp_info && |
| (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| spin_lock(&ci->ci_sdp_info->sdp_flag_lock); |
| if (ci->ci_sdp_info->sdp_flags & SDP_IS_CLEARING_ONGOING) { |
| retval = -EINVAL; |
| } else { |
| ci->ci_sdp_info->sdp_flags |= SDP_IS_FILE_IO_ONGOING; |
| } |
| spin_unlock(&ci->ci_sdp_info->sdp_flag_lock); |
| } |
| |
| return retval; |
| } |
| |
| void fscrypt_sdp_unset_file_io_ongoing(struct file *file) |
| { |
| struct address_space *mapping = file->f_mapping; |
| struct inode *inode = mapping->host; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| |
| if (ci && ci->ci_sdp_info && (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| ci->ci_sdp_info->sdp_flags &= ~(SDP_IS_FILE_IO_ONGOING); |
| } |
| } |
| |
| void fscrypt_sdp_unset_clearing_ongoing(struct inode *inode) |
| { |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| |
| if (ci && ci->ci_sdp_info && (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| ci->ci_sdp_info->sdp_flags &= ~(SDP_IS_CLEARING_ONGOING); |
| } |
| } |
| |
| bool fscrypt_sdp_is_cache_releasable(struct inode *inode) |
| { |
| bool retval = false; |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| |
| if (ci && ci->ci_sdp_info && (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| spin_lock(&ci->ci_sdp_info->sdp_flag_lock); |
| if (ci->ci_sdp_info->sdp_flags & SDP_IS_FILE_IO_ONGOING || |
| ci->ci_sdp_info->sdp_flags & SDP_IS_CLEARING_ONGOING) { |
| retval = false; |
| } else { |
| ci->ci_sdp_info->sdp_flags |= SDP_IS_CLEARING_ONGOING; |
| retval = true; |
| } |
| spin_unlock(&ci->ci_sdp_info->sdp_flag_lock); |
| } |
| |
| return retval; |
| } |
| |
| bool fscrypt_sdp_is_locked_sensitive_inode(struct inode *inode) |
| { |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = ext4_encryption_info(inode); |
| |
| if (ci && ci->ci_sdp_info && |
| (ci->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE)) { |
| DEK_LOGD("Time to drop the inode..\n"); |
| return true; |
| } else |
| return false; |
| } |
| |
| int __fscrypt_sdp_d_delete(const struct dentry *dentry, int dek_is_locked) { |
| struct inode *inode = d_inode(dentry); |
| |
| if (inode && dek_is_locked) { |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *crypt_info = ext4_encryption_info(inode); |
| |
| if (crypt_info && crypt_info->ci_sdp_info && |
| crypt_info->ci_sdp_info->sdp_flags & SDP_DEK_IS_SENSITIVE) { |
| DEK_LOGD("Time to delete dcache....\n"); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(__fscrypt_sdp_d_delete); |
| |
| /* EXT4CRYPT-dedicated */ |
| int ext4_sdp_d_delete_wrapper(const struct dentry *dentry) { |
| struct inode *inode = d_inode(dentry); |
| /* EXT4CRYPT-dedicated */ |
| struct ext4_crypt_info *ci = inode ? ext4_encryption_info(inode) : NULL; |
| #if 1 |
| int is_locked = 1; /* TODO: */ |
| #else |
| int is_locked = (ci && ci->ci_sdp_info) ? dek_is_locked(ci->ci_sdp_info->engine_id) : 0; |
| #endif |
| |
| if (ci && __fscrypt_sdp_d_delete(dentry, is_locked)) |
| return 1; |
| |
| return 0; |
| } |
| |
| void fscrypt_sdp_drop_inode(struct inode *inode) { |
| inode->i_state |= I_WILL_FREE; |
| spin_unlock(&inode->i_lock); |
| write_inode_now(inode, 1); |
| spin_lock(&inode->i_lock); |
| WARN_ON(inode->i_state & I_NEW); |
| inode->i_state &= ~I_WILL_FREE; |
| } |