/* * File: security/sdp/dd.c * * Samsung Dual-DAR(Data At Rest) cache I/O relay driver * * Author: Olic Moon */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __FS_HAS_ENCRYPTION IS_ENABLED(CONFIG_FSCRYPT_SDP) #include #ifdef CONFIG_FSCRYPT_SDP #include #endif #include "dd_common.h" #define TIMEOUT_PENDING_REQ (5*1000) #ifdef CONFIG_FSCRYPT_SDP extern int fscrypt_sdp_get_storage_type(struct dentry *target_dentry); #endif static struct kmem_cache *dd_req_cachep; static struct workqueue_struct *dd_free_req_workqueue; static struct workqueue_struct *dd_ioctl_workqueue; static struct workqueue_struct *dd_read_workqueue; //static struct task_struct *abort_thread; static struct dd_context *dd_context_global; struct dd_context *dd_get_global_context(void) { return dd_context_global; } static inline void __benchmark_init(struct dd_benchmark_result *bm_result) __acquires(&bm_result->lock) __releases(&bm_result->lock) { int i; spin_lock(&bm_result->lock); bm_result->count = 0; for (i = 0; i < DD_REQ_COUNT; i++) bm_result->data[i] = 0; spin_unlock(&bm_result->lock); } static LIST_HEAD(dd_free_reqs); static DEFINE_SPINLOCK(dd_req_list_lock); static inline long req_time_spent(struct dd_req *req) { return jiffies_to_msecs(jiffies) - req->timestamp; } #if CONFIG_DD_REQ_DEBUG static LIST_HEAD(dd_debug_req_list); static void __dd_debug_req(const char *msg, int mask, const char *func, struct dd_req *req) { struct dd_proc *proc = (struct dd_proc *) req->info->proc; __dd_debug(mask, func, "req {id:%ld ino:%ld (%s) code:%d abort:%d pid:%d(tgid:%d), proc:%p state:%d alive:%ld list:%p(prev:%p, next:%p)}: %s\n", req->unique, req->ino, (req->dir == WRITE) ? "write":"read", req->code, req->abort, req->pid, req->tgid, proc, req->state, req_time_spent(req), &req->list, req->list.prev, req->list.next, msg); } #if 1 #define dd_debug_req(msg, mask, req) \ __dd_debug_req(msg, mask, __func__, req) #else #define dd_debug_req(msg, mask, req) \ __dd_debug_req(msg, mask, __func__, req) #endif static int dd_dump_debug_req_list(int mask) { struct dd_context *ctx = dd_context_global; struct dd_proc *proc = NULL; struct dd_req *req = NULL; int num = 0; spin_lock(&ctx->lock); list_for_each_entry(proc, &ctx->procs, proc_node) { num++; dd_debug(mask, "proc { pid:%d(%p) tgid:%d abort:%d req_count:%d pending:%p processing:%p submitted:%p}\n", proc->pid, proc, proc->tgid, proc->abort, atomic_read(&proc->reqcount), &proc->pending, &proc->processing, &proc->submitted); } spin_unlock(&ctx->lock); dd_debug(mask, "available process:%d\n", num); num = 0; spin_lock(&dd_req_list_lock); list_for_each_entry(req, &dd_debug_req_list, debug_list) { num++; dd_debug_req("dump", mask, req); } spin_unlock(&dd_req_list_lock); dd_debug(mask, "allocated requests: %d\n", num); return num; } #endif static void dd_info_get(struct dd_info *info); static struct dd_req *get_req(const char *msg, struct dd_info *info, dd_request_code_t code, int dir) { struct dd_context *ctx = dd_context_global; struct dd_req *req; BUG_ON(!info); spin_lock(&dd_req_list_lock); req = list_first_entry_or_null(&dd_free_reqs, struct dd_req, list); if (req) { if (!list_empty(&req->list)) list_del_init(&req->list); else dd_error("get req: debug list is already empty\n"); } spin_unlock(&dd_req_list_lock); if (!req) { req = kmem_cache_alloc(dd_req_cachep, GFP_NOFS); if (!req) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&req->list); init_waitqueue_head(&req->waitq); } #if CONFIG_DD_REQ_DEBUG INIT_LIST_HEAD(&req->debug_list); spin_lock(&dd_req_list_lock); list_add(&req->debug_list, &dd_debug_req_list); spin_unlock(&dd_req_list_lock); #endif req->context = ctx; req->info = info; dd_info_get(req->info); req->code = code; req->dir = dir; req->ino = info->ino; req->pid = 0; req->tgid = 0; req->timestamp = jiffies_to_msecs(jiffies); req->abort = 0; req->need_xattr_flush = 0; req->user_space_crypto = dd_policy_user_space_crypto(info->policy.flags); memset(&req->bm, 0, sizeof(struct dd_benchmark)); spin_lock(&ctx->ctr_lock); ctx->req_ctr++; if (ctx->req_ctr > 4096) ctx->req_ctr = 0; req->unique = ctx->req_ctr; spin_unlock(&ctx->ctr_lock); dd_debug_req("req ", DD_DEBUG_PROCESS, req); dd_req_state(req, DD_REQ_INIT); return req; } static inline void dd_proc_try_free(struct dd_proc *proc) { int reqcnt = atomic_read(&proc->reqcount); BUG_ON(!proc->abort); if (reqcnt > 0) { dd_info("dd_proc_try_free: delayed freeing proc:%p (reqcnt:%d)\n", proc, reqcnt); } else { int i; dd_info("dd_proc_try_free: freeing proc:%p\n", proc); for (i = 0; i < MAX_NUM_CONTROL_PAGE; i++) if (proc->control_page[i]) mempool_free(proc->control_page[i], proc->context->page_pool); kfree(proc); } } static void dd_free_req_work(struct work_struct *work); static inline void put_req(const char *msg, struct dd_req *req) { if (unlikely(!req)) return; /** * Avoid reentrance. Since request objects are shared * by crypto thread and kernel services and they can * be aborted at any moment. This is to avoid fatal error * even if abort_req and put_req are called multiple times */ if (unlikely(req->state == DD_REQ_UNALLOCATED)) return; if (!req->abort) { if (req->state != DD_REQ_SUBMITTED) { dd_error("req unique:%d state is not submitted (%d)\n", req->unique, req->state); } BUG_ON(req->state != DD_REQ_SUBMITTED); } // delayed freeing req object dd_req_state(req, DD_REQ_FINISHING); INIT_WORK(&req->delayed_free_work, dd_free_req_work); queue_work(dd_free_req_workqueue, &req->delayed_free_work); } /** * delayed freeing req object. put_req() can be called by an irq handler where we * don't want to hold spin lock. put_req() only mark state as finished than this function * free those finished requests */ static void dd_free_req_work(struct work_struct *work) { struct dd_req *req = container_of(work, struct dd_req, delayed_free_work); if (req->state == DD_REQ_FINISHING) { struct dd_info *info = req->info; struct dd_proc *proc = (struct dd_proc *) info->proc; dd_debug_req("req ", DD_DEBUG_PROCESS, req); /** * proc must not null in majority of cases * except req was aborted before assigned to a crypto task. */ if (proc && req->user_space_crypto) { /** * In case the request was aborted, requested is * dequeued immediately */ spin_lock(&proc->lock); if (!list_empty(&req->list)) list_del_init(&req->list); spin_unlock(&proc->lock); if (atomic_dec_and_test(&proc->reqcount)) { dd_process("req:%p unbound from proc:%p\n", req, proc); if (proc->abort) dd_proc_try_free(proc); } } spin_lock(&dd_req_list_lock); list_add_tail(&req->list, &dd_free_reqs); #if CONFIG_DD_REQ_DEBUG if (!list_empty(&req->debug_list)) list_del(&req->debug_list); else dd_error("free req: debug list is already empty\n"); #endif spin_unlock(&dd_req_list_lock); dd_req_state(req, DD_REQ_UNALLOCATED); // Run benchmark only for read operation if (!req->abort && req->dir == READ) { dd_submit_benchmark(&req->bm, req->context); dd_dump_benchmark(&req->context->bm_result); } spin_lock(&info->lock); if (atomic_dec_and_test(&info->reqcount)) { dd_process("req:%p unbound from info:%p\n", req, info); info->proc = NULL; } spin_unlock(&info->lock); dd_info_try_free(info); } else { dd_debug_req("req ", DD_DEBUG_PROCESS, req); } } static void dd_free_pages(struct dd_info *info, struct bio *clone); static inline void abort_req(const char *msg, struct dd_req *req, int err) { struct dd_info *info = req->info; struct dd_proc *proc = (struct dd_proc *) info->proc; #ifdef CONFIG_FSCRYPT_SDP sdp_fs_command_t *cmd = NULL; #endif if (unlikely(!req)) return; if (unlikely(req->state == DD_REQ_UNALLOCATED)) return; if (req->abort) return; #ifdef CONFIG_FSCRYPT_SDP printk("Record audit log in case of a failure during en/decryption of dualdar protected file\n"); if (req->dir == READ) { cmd = sdp_fs_command_alloc(FSOP_AUDIT_FAIL_DECRYPT, current->tgid, info->policy.userid, -1, info->ino, err, GFP_KERNEL); } else { cmd = sdp_fs_command_alloc(FSOP_AUDIT_FAIL_ENCRYPT, current->tgid, info->policy.userid, -1, info->ino, err, GFP_KERNEL); } if (cmd) { sdp_fs_request(cmd, NULL); sdp_fs_command_free(cmd); } #endif req->abort = 1; if (proc) { spin_lock(&proc->lock); // skip in case req is not assigned to any process if (!list_empty(&req->list)) list_del_init(&req->list); spin_unlock(&proc->lock); } dd_debug_req(msg, DD_DEBUG_ERROR, req); switch (req->state) { case DD_REQ_INIT: case DD_REQ_PENDING: case DD_REQ_PROCESSING: case DD_REQ_SUBMITTED: { if (req->code == DD_REQ_CRYPTO_BIO) { unsigned dir = bio_data_dir(req->u.bio.orig); struct bio *orig = req->u.bio.orig; struct bio *clone = req->u.bio.clone; if (clone) { if (dir == WRITE) dd_free_pages(req->info, clone); bio_put(clone); } orig->bi_status = err; bio_endio(orig); } break; } case DD_REQ_FINISHING: // nothing to do default: break; } if (req->user_space_crypto) wake_up_interruptible(&req->waitq); put_req(__func__, req); } static inline void proc_abort_req_all(const char *msg, struct dd_proc *proc) __acquires(&proc->lock) __releases(&proc->lock) { struct dd_req *pos, *temp; BUG_ON(!proc); spin_lock(&proc->lock); list_for_each_entry_safe(pos, temp, &proc->pending, list) { spin_unlock(&proc->lock); abort_req(__func__, pos, -EIO); spin_lock(&proc->lock); } list_for_each_entry_safe(pos, temp, &proc->processing, list) { spin_unlock(&proc->lock); abort_req(__func__, pos, -EIO); spin_lock(&proc->lock); } proc->abort = 1; wake_up(&proc->waitq); spin_unlock(&proc->lock); } #if CONFIG_DD_REQ_DEBUG static int dd_abort_pending_req_timeout(int mask) { struct dd_req *req = NULL; int num = 0; spin_lock(&dd_req_list_lock); list_for_each_entry(req, &dd_debug_req_list, debug_list) { if ((req_time_spent(req) > TIMEOUT_PENDING_REQ) && (req->state != DD_REQ_FINISHING)) { num++; abort_req(__func__, req, -EIO); } } spin_unlock(&dd_req_list_lock); if (num > 0) dd_debug(mask, "aborted requests: %d\n", num); return num; } #endif static struct dd_req *find_req(struct dd_proc *proc, dd_req_state_t state, unsigned long ino) __acquires(proc->lock) __releases(proc->lock) { struct dd_req *req = NULL, *p = NULL; struct list_head *head = NULL; switch (state) { case DD_REQ_PENDING: head = &proc->pending; break; case DD_REQ_PROCESSING: head = &proc->processing; break; case DD_REQ_SUBMITTED: head = &proc->submitted; break; default: dd_error("can't find process queue for state %d\n", state); break; } if (!head) return NULL; spin_lock(&proc->lock); list_for_each_entry(p, head, list) { if (p->info->ino == ino) { req = p; break; } } spin_unlock(&proc->lock); return req; } #define DD_CRYPTO_REQ_TIMEOUT 3000 // caller required to hold proc->lock static void queue_pending_req_locked(struct dd_proc *proc, struct dd_req *req) { list_move_tail(&req->list, &proc->pending); dd_req_state(req, DD_REQ_PENDING); req->pid = proc->pid; req->tgid = proc->tgid; } static void dd_free_pages(struct dd_info *info, struct bio *clone) { struct dd_context *ctx = (struct dd_context *) info->context; struct address_space *mapping; struct bvec_iter iter; struct bio_vec bv; bio_for_each_bvec(bv, clone, iter) { BUG_ON(!bv.bv_page); mapping = page_mapping(bv.bv_page); if (mapping) { bv.bv_page->mapping = NULL; } mempool_free(bv.bv_page, ctx->page_pool); bv.bv_page = NULL; } } extern int dd_oem_page_crypto_inplace(struct dd_info *info, struct page *page, int dir); /** * Run CE based OEM encryption * * This is a security requirement to hide cryptographic artifacts of vendor data-at-rest * layer behind device level encryption */ int dd_oem_crypto_bio_pages(struct dd_req *req, int dir, struct bio *bio) { struct bio_vec *bv; struct bvec_iter_all i; int err = 0; bio_for_each_segment_all(bv, bio, i) { struct page *page = bv->bv_page; int ret; dd_verbose("unique:%d dir:%d\n", req->unique, dir); ret = dd_oem_page_crypto_inplace(req->info, page, dir); if (ret) { err = ret; dd_error("failed to decrypt page:[ino:%ld] [index:%ld] \n", page->mapping->host->i_ino, page->index); SetPageError(page); } } return err; } static void dd_end_io(struct bio *clone); static struct bio *dd_clone_bio(struct dd_req *req, struct bio *orig, unsigned size) { struct dd_info *info = req->info; struct dd_context *ctx = (struct dd_context *) info->context; struct bio *clone; struct bio_vec bv; struct bvec_iter iter; unsigned int nr_iovecs = 0; gfp_t gfp_mask = GFP_NOWAIT | __GFP_HIGHMEM; unsigned i, len, remaining_size; struct page *page; struct bio_vec *bvec; struct bio_vec *orig_bvec; retry: if (unlikely(gfp_mask & __GFP_DIRECT_RECLAIM)) mutex_lock(&ctx->bio_alloc_lock); dd_debug_req("cloning bio", DD_DEBUG_VERBOSE, req); bio_for_each_bvec(bv, orig, iter) { nr_iovecs++; } clone = bio_alloc_bioset(GFP_NOIO, nr_iovecs, ctx->bio_set); if (!clone) { dd_error("failed to alloc bioset\n"); goto return_clone; } clone->bi_private = req; bio_copy_dev(clone, orig); clone->bi_opf = orig->bi_opf; bio_crypt_clone(clone, orig, GFP_NOIO); remaining_size = size; for (i = 0; i < nr_iovecs; i++) { page = mempool_alloc(ctx->page_pool, gfp_mask); if (!page) { dd_free_pages(info, clone); bio_put(clone); gfp_mask |= __GFP_DIRECT_RECLAIM; nr_iovecs = 0; goto retry; } len = (unsigned int)((remaining_size > PAGE_SIZE) ? PAGE_SIZE : remaining_size); bvec = &clone->bi_io_vec[clone->bi_vcnt]; orig_bvec = &orig->bi_io_vec[clone->bi_vcnt++]; bvec->bv_page = page; bvec->bv_len = len; bvec->bv_offset = 0; if (i == 0) { bvec->bv_page->mapping = page_mapping(orig_bvec->bv_page); } clone->bi_iter.bi_size += len; remaining_size -= len; } return_clone: if (unlikely(gfp_mask & __GFP_DIRECT_RECLAIM)) mutex_unlock(&ctx->bio_alloc_lock); return clone; } static int __request_user(struct dd_req *req) { struct dd_context *context = req->context; struct dd_info *info = req->info; BUG_ON(!req->user_space_crypto); BUG_ON(!info || !context); spin_lock(&info->lock); if (!info->proc) { spin_lock(&context->lock); info->proc = list_first_entry_or_null(&context->procs, struct dd_proc, proc_node); spin_unlock(&context->lock); } if (unlikely(!info->proc)) { dd_error("no available process"); spin_unlock(&info->lock); return -EIO; // IO error (5) } else { struct dd_proc *proc = (struct dd_proc *) info->proc; atomic_inc(&info->reqcount); atomic_inc(&proc->reqcount); spin_lock_irq(&proc->lock); queue_pending_req_locked(proc, req); spin_unlock_irq(&proc->lock); spin_lock(&context->lock); list_move_tail(&proc->proc_node, &context->procs); spin_unlock(&context->lock); spin_unlock(&info->lock); wake_up(&proc->waitq); dd_debug_req("req ", DD_DEBUG_PROCESS, req); return 0; } } static void dd_decrypt_work(struct work_struct *work) { struct dd_req *req = container_of(work, struct dd_req, decryption_work); struct dd_info *info = req->info; struct bio *orig = req->u.bio.orig; dd_dump_bio_pages("from disk", req->u.bio.orig); #ifdef CONFIG_SDP_KEY_DUMP if (!dd_policy_skip_decryption_inner_and_outer(req->info->policy.flags)) { #endif if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { dd_verbose("skip oem s/w crypt. hw encryption enabled\n"); } else { if (dd_oem_crypto_bio_pages(req, READ, orig)) { dd_error("failed oem crypto\n"); goto abort_out; } dd_dump_bio_pages("outer (s/w) decryption done", req->u.bio.orig); } #ifdef CONFIG_SDP_KEY_DUMP } else { dd_info("skip decryption for outer layer - ino : %ld, flag : 0x%04x\n", req->info->inode->i_ino, req->info->policy.flags); } #endif if (req->user_space_crypto) { #ifdef CONFIG_SDP_KEY_DUMP if (!dd_policy_skip_decryption_inner(req->info->policy.flags)) { #endif if (__request_user(req)) { dd_error("failed vendor crypto\n"); goto abort_out; } #ifdef CONFIG_SDP_KEY_DUMP } else { dd_info("skip decryption for inner layer - ino : %ld, flag : 0x%04x\n", req->info->inode->i_ino, req->info->policy.flags); dd_req_state(req, DD_REQ_SUBMITTED); orig->bi_status = 0; bio_endio(orig); put_req(__func__, req); } #endif } else { #ifdef CONFIG_SDP_KEY_DUMP if (!dd_policy_skip_decryption_inner(req->info->policy.flags)) { #endif if (dd_sec_crypt_bio_pages(info, req->u.bio.orig, NULL, DD_DECRYPT)) { dd_error("failed dd crypto\n"); goto abort_out; } #ifdef CONFIG_SDP_KEY_DUMP } else { dd_info("skip decryption for inner layer - ino : %ld, flag : 0x%04x\n", req->info->inode->i_ino, req->info->policy.flags); } #endif dd_req_state(req, DD_REQ_SUBMITTED); orig->bi_status = 0; bio_endio(orig); put_req(__func__, req); } return; abort_out: abort_req(__func__, req, -EIO); } static void dd_end_io(struct bio *clone) { struct dd_req *req = (struct dd_req *) clone->bi_private; struct dd_info *info = req->info; struct bio *orig = req->u.bio.orig; unsigned dir = bio_data_dir(clone); int err; err = clone->bi_status; if (dir == WRITE) dd_free_pages(info, clone); // free clone bio in both success or error case dd_verbose("ino:%ld info->context:%p\n", info->ino, info->context); bio_put(clone); req->u.bio.clone = NULL; if (!err) if (dir == READ) { INIT_WORK(&req->decryption_work, dd_decrypt_work); queue_work(dd_read_workqueue, &req->decryption_work); return; } // report success or error to caller orig->bi_status = err; bio_endio(orig); put_req(__func__, req); } int dd_submit_bio(struct dd_info *info, struct bio *bio) { struct dd_context *context = (struct dd_context *) info->context; struct dd_req *req = get_req(__func__, info, DD_REQ_CRYPTO_BIO, bio_data_dir(bio)); int err = 0; dd_verbose("ino:%ld info->context:%p\n", info->ino, info->context); req->info = info; req->u.bio.orig = bio; if (bio_data_dir(bio) == WRITE) { req->u.bio.clone = dd_clone_bio(req, bio, bio->bi_iter.bi_size); BUG_ON(!req->u.bio.clone); if (req->user_space_crypto) { if (__request_user(req)) { dd_error("failed to request daemon\n"); goto err_out; } } else { if (dd_sec_crypt_bio_pages(info, req->u.bio.orig, req->u.bio.clone, DD_ENCRYPT)) { dd_error("failed dd crypto\n"); goto err_out; } if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { dd_verbose("skip oem s/w crypt. hw encryption enabled\n"); // fscrypt_set_ice_dun(req->info->inode, req->u.bio.clone, 0/* temporary */); } else { if (dd_oem_crypto_bio_pages(req, WRITE, req->u.bio.clone)) { dd_error("failed oem crypto\n"); goto err_out; } } dd_req_state(req, DD_REQ_SUBMITTED); req->u.bio.clone->bi_private = req; req->u.bio.clone->bi_end_io = dd_end_io; submit_bio_noacct(req->u.bio.clone); } } else { // clone a bio that shares original bio's biovec req->u.bio.clone = bio_clone_fast(bio, GFP_NOIO, context->bio_set); if (!req->u.bio.clone) { dd_error("failed in bio clone\n"); goto err_out; } req->u.bio.clone->bi_private = req; req->u.bio.clone->bi_end_io = dd_end_io; #ifdef CONFIG_SDP_KEY_DUMP if (!dd_policy_skip_decryption_inner_and_outer(req->info->policy.flags)) { #endif if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { dd_verbose("skip oem s/w crypt. hw encryption enabled\n"); // fscrypt_set_ice_dun(req->info->inode, req->u.bio.clone, 0/* temporary */); } #ifdef CONFIG_SDP_KEY_DUMP } else { if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { req->u.bio.clone->bi_crypt_context = NULL; dd_info("skip h/w decryption for ino(%ld)\n", req->info->inode->i_ino); } } #endif submit_bio_noacct(req->u.bio.clone); } return err; err_out: abort_req(__func__, req, -EIO); return -EIO; } EXPORT_SYMBOL(dd_submit_bio); #define DD_PAGE_CRYPTO_REQ_TIMEOUT 10000 static int __wait_user_request(struct dd_req *req) { int intr; dd_debug_req("wait for user response", DD_DEBUG_PROCESS, req); while (req->state != DD_REQ_SUBMITTED) { intr = wait_event_interruptible_timeout(req->waitq, req->state == DD_REQ_SUBMITTED, msecs_to_jiffies(DD_PAGE_CRYPTO_REQ_TIMEOUT)); dd_verbose("wake up unique:%d\n", req->unique); if (intr == 0 || intr == -ERESTARTSYS) { dd_error("timeout or interrupted (%d) [ID:%d] ino:%ld\n", intr, req->unique, req->ino); abort_req(__func__, req, -EIO); break; } } dd_verbose("user request completed (unique:%d)\n", req->unique); if (req->abort) return -ECONNABORTED; put_req(__func__, req); return 0; // return 0 if success } int dd_page_crypto(struct dd_info *info, dd_crypto_direction_t dir, struct page *src_page, struct page *dst_page) { struct dd_req *req = NULL; int err = 0; BUG_ON(!src_page || !dst_page); dd_verbose("dd_page_crypto ino:%ld\n", info->ino); req = get_req(__func__, info, DD_REQ_CRYPTO_PAGE, READ); if (IS_ERR(req)) return -ENOMEM; req->u.page.dir = dir; req->u.page.src_page = src_page; req->u.page.dst_page = dst_page; if (dir == DD_DECRYPT) { if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { dd_verbose("skip oem s/w crypt. hw encryption enabled\n"); } else { err = dd_oem_page_crypto_inplace(info, src_page, READ); if (err) { dd_error("failed oem crypto\n"); return err; } } if (req->user_space_crypto) { err = __request_user(req); if (err) { dd_error("failed user request err:%d\n", err); return err; } return __wait_user_request(req); } else { err = dd_sec_crypt_page(info, DD_DECRYPT, src_page, src_page); if (err) { dd_error("failed page crypto:%d\n", err); } dd_req_state(req, DD_REQ_SUBMITTED); put_req(__func__, req); return err; } } else { dd_error("operation not supported\n"); BUG(); } } /** * Performance flag to describe lock status. We don't want to make a blocking * user space call for every time a file is opened * * This flag is set by user space daemon and is protected by SEAndroid MAC enforcement */ unsigned int dd_lock_state = 1; // 1: locked 0: unlocked module_param_named(lock_state, dd_lock_state, uint, 0600); MODULE_PARM_DESC(lock_state, "ddar lock status"); int dd_is_user_deamon_locked(void) { return dd_lock_state; } #ifdef CONFIG_DDAR_USER_PREPARE int dd_prepare(struct dd_info *info) { struct dd_req *req = NULL; int err = 0; if (dd_debug_bit_test(DD_DEBUG_CALL_STACK)) dump_stack(); req = get_req(__func__, info, DD_REQ_PREPARE); if (!req) return -ENOMEM; req->u.prepare.metadata = info->mdpage; err = __request_user(req); if (err) { dd_error("failed user request err:%d\n", err); return err; } return __wait_user_request(req); } #endif #define LIMIT_PAGE_NUM (MAX_NUM_REQUEST_PER_CONTROL * MAX_NUM_CONTROL_PAGE) struct dd_mmap_layout default_mmap_layout = { .page_size = PAGE_SIZE, .page_num_limit = LIMIT_PAGE_NUM, .control_area = { .start = 0x00000000, .size = PAGE_SIZE * MAX_NUM_CONTROL_PAGE }, // 32KB .metadata_area = { .start = 0x01000000, .size = PAGE_SIZE * MAX_NUM_INO_PER_USER_REQUEST }, // 128KB .plaintext_page_area = { .start = 0x02000000, .size = PAGE_SIZE * LIMIT_PAGE_NUM }, // 1 MB .ciphertext_page_area = { .start = 0x03000000, .size = PAGE_SIZE * LIMIT_PAGE_NUM } // MB }; static int dd_open(struct inode *nodp, struct file *filp) { filp->private_data = NULL; return 0; } static int dd_mmap_available(struct dd_proc *proc) { if (!proc->control_vma || !proc->metadata_vma || !proc->plaintext_vma || !proc->ciphertext_vma) return 0; return 1; } static int dd_mmap(struct file *filp, struct vm_area_struct *vma) { struct dd_proc *proc = filp->private_data; struct dd_context *context = proc->context; unsigned long addr = vma->vm_pgoff << PAGE_SHIFT; BUG_ON(!proc); dd_memory("offset:%p\n", (void *)addr); spin_lock(&proc->lock); if (addr == context->layout->control_area.start) { proc->control_vma = vma; proc->control_vma->vm_flags |= VM_LOCKED; } else if (addr == context->layout->metadata_area.start) { proc->metadata_vma = vma; proc->metadata_vma->vm_flags |= VM_LOCKED; } else if (addr == context->layout->plaintext_page_area.start) { proc->plaintext_vma = vma; proc->plaintext_vma->vm_flags |= VM_LOCKED; } else if (addr == context->layout->ciphertext_page_area.start) { proc->ciphertext_vma = vma; proc->ciphertext_vma->vm_flags |= VM_LOCKED; } else { dd_error("mmap failed. mmap addr:%p\n", (void *)addr); } spin_unlock(&proc->lock); return 0; } typedef enum { DD_TRANS_OK = 0, DD_TRANS_ERR_FULL, DD_TRANS_ERR_PROC_DIED, DD_TRANS_ERR_UNKNOWN } dd_transaction_result; // currently ongoing user-space crypto request. This is always on stack, so no locking needed struct dd_transaction { struct dd_proc *proc; struct dd_mmap_control *control; int num_ctr_pg; int num_md_pg; int num_mapped_pg; unsigned long mapped_ino[MAX_NUM_INO_PER_USER_REQUEST]; }; static inline int __is_ctr_page_full(struct dd_mmap_control *control) { if (control->num_requests == MAX_NUM_REQUEST_PER_CONTROL) return 1; return 0; } static inline struct dd_user_req *__get_next_user_req(struct dd_mmap_control *control) { if (control->num_requests >= MAX_NUM_REQUEST_PER_CONTROL) { return NULL; } return &control->requests[control->num_requests++]; } /** * Returns control page from current transaction */ static inline struct dd_mmap_control *__get_next_ctr_page(struct dd_transaction *t) { struct dd_proc *proc = t->proc; unsigned long control_area_addr = proc->control_vma->vm_start; struct dd_mmap_control *control; control = (struct dd_mmap_control *) page_address(proc->control_page[t->num_ctr_pg]); memset((void *)control, 0, sizeof(struct dd_mmap_control)); dd_memory("mapping text pages control:%p (struct page:%p num_ctr:%d)\n", (void *)control, (void *)proc->control_page[t->num_ctr_pg], t->num_ctr_pg); remap_pfn_range(proc->control_vma, control_area_addr + (t->num_ctr_pg << PAGE_SHIFT), page_to_pfn(proc->control_page[t->num_ctr_pg]), PAGE_SIZE, proc->control_vma->vm_page_prot); t->num_ctr_pg++; dd_memory("mapping control page proc:%p control:%p [mapped pages:%d] [total control pages:%d]\n", proc, (void *) (control_area_addr + (t->num_ctr_pg << PAGE_SHIFT)), t->num_mapped_pg, t->num_ctr_pg); BUG_ON(t->num_ctr_pg > MAX_NUM_CONTROL_PAGE); return control; } /** * Map if current transaction hasn't yet mapped meta-data page */ static inline void __get_md_page(struct dd_proc *proc, struct page *mdpage, unsigned long int ino, struct dd_transaction *t) { int i = 0; unsigned long metadata_area_addr = proc->metadata_vma->vm_start; while (i < t->num_md_pg) { if (t->mapped_ino[i] == ino) return; i++; } dd_memory("mapping metadata area proc:%p md:%p (num_md:%d, num_ctr:%d) [ino:%ld]\n", proc, (void *) (metadata_area_addr + (t->num_md_pg << PAGE_SHIFT)), t->num_md_pg, t->num_ctr_pg, ino); remap_pfn_range(proc->metadata_vma, metadata_area_addr + (t->num_md_pg << PAGE_SHIFT), page_to_pfn(mdpage), PAGE_SIZE, proc->metadata_vma->vm_page_prot); t->mapped_ino[i] = ino; t->num_md_pg++; } #ifdef CONFIG_DDAR_USER_PREPARE static dd_transaction_result __process_prepare_request_locked( struct dd_req *req, struct dd_transaction *t) { // create single user space request for prepare struct dd_user_req *p = __get_next_user_req(t->control); p->unique = req->unique; p->userid = req->info->crypt_context.policy.userid; p->version = req->info->crypt_context.policy.version; p->ino = req->info->inode->i_ino; p->code = DD_REQ_PREPARE; return DD_TRANS_OK; } #endif static dd_transaction_result __process_bio_request_locked( struct dd_req *req, struct dd_transaction *t) { struct dd_proc *proc = t->proc; struct bio *orig = req->u.bio.orig; struct bio *clone = req->u.bio.clone; int dir = bio_data_dir(req->u.bio.orig); struct bvec_iter iter_backup; unsigned require_ctr_pages; /** * NOTE: Do not assume the inode object is still alive at this point. * * It's possible bio was requested for a file's dirty page and the file gets * removed before it's scheduled for crypto operation. In such case the inode * object is freed; dd_info->inode points to NULL as well. * * At the very least, dd_info maintains refcount dd_info will be still alive until * the request completes */ BUG_ON(!req->info); /** * page count limit check. If current bio holds more pages than current user space * transaction can mmap, postpone this to next round. Skipped bio will stay in * pending queue until next dd_ioctl_wait_crypto_request() is called. * * We don't support splitting single bio into multiple user space request to avoid * extra memory copy and keep code straight-forward. */ require_ctr_pages = (orig->bi_vcnt / MAX_NUM_REQUEST_PER_CONTROL); if (orig->bi_vcnt % MAX_NUM_REQUEST_PER_CONTROL > 0) require_ctr_pages++; if (require_ctr_pages > (MAX_NUM_CONTROL_PAGE - t->num_ctr_pg)) { dd_verbose("cannot accommodate %d control pages (%d available), continue\n", require_ctr_pages, MAX_NUM_CONTROL_PAGE - t->num_ctr_pg); return DD_TRANS_ERR_FULL; } // dd_verbose("%s mapping bio(vcnt:%d, ino:%ld) control available:%d (required:%d)\n", // __func__, orig->bi_vcnt, req->ino, // MAX_NUM_CONTROL_PAGE - t->num_ctr_pg, require_ctr_pages); spin_unlock(&proc->lock); // map pages in bio to user space vma BUG_ON(dir == WRITE && !clone); // back up bio iterator. restore before submitting it to block layer memcpy(&iter_backup, &orig->bi_iter, sizeof(struct bvec_iter)); while (orig->bi_iter.bi_size) { int vm_offset = t->num_mapped_pg << PAGE_SHIFT; struct dd_user_req *p = __get_next_user_req(t->control); if (!p) { t->control = __get_next_ctr_page(t); continue; // try again } dd_verbose("bi_size:%d (control->num_requests:%d)\n", orig->bi_iter.bi_size, t->control->num_requests); p->unique = req->unique; p->userid = req->info->policy.userid; p->version = req->info->policy.version; p->ino = req->info->ino; p->code = DD_REQ_CRYPTO_BIO; if (dir == WRITE) { struct bio_vec orig_bv = bio_iter_iovec(orig, orig->bi_iter); struct bio_vec clone_bv = bio_iter_iovec(clone, clone->bi_iter); struct page *plaintext_page = orig_bv.bv_page; struct page *ciphertext_page = clone_bv.bv_page; BUG_ON(req->info->ino != orig_bv.bv_page->mapping->host->i_ino); p->u.bio.rw = DD_ENCRYPT; p->u.bio.index = plaintext_page->index; p->u.bio.plain_addr = proc->plaintext_vma->vm_start + vm_offset; p->u.bio.cipher_addr = proc->ciphertext_vma->vm_start + vm_offset; remap_pfn_range(proc->plaintext_vma, p->u.bio.plain_addr, page_to_pfn(plaintext_page), PAGE_SIZE, proc->plaintext_vma->vm_page_prot); remap_pfn_range(proc->ciphertext_vma, p->u.bio.cipher_addr, page_to_pfn(ciphertext_page), PAGE_SIZE, proc->ciphertext_vma->vm_page_prot); dd_dump("plain page", page_address(plaintext_page), PAGE_SIZE); bio_advance_iter(orig, &orig->bi_iter, 1 << PAGE_SHIFT); bio_advance_iter(clone, &clone->bi_iter, 1 << PAGE_SHIFT); } else { struct bio_vec orig_bv = bio_iter_iovec(orig, orig->bi_iter); struct page *ciphertext_page = orig_bv.bv_page; BUG_ON(req->info->ino != orig_bv.bv_page->mapping->host->i_ino); p->u.bio.rw = DD_DECRYPT; // crypto inplace decryption p->u.bio.index = ciphertext_page->index; p->u.bio.cipher_addr = proc->ciphertext_vma->vm_start + vm_offset; p->u.bio.plain_addr = proc->ciphertext_vma->vm_start + vm_offset; remap_pfn_range(proc->ciphertext_vma, p->u.bio.cipher_addr, page_to_pfn(ciphertext_page), PAGE_SIZE, proc->ciphertext_vma->vm_page_prot); dd_dump("cipher page", page_address(ciphertext_page), PAGE_SIZE); bio_advance_iter(orig, &orig->bi_iter, 1 << PAGE_SHIFT); } t->num_mapped_pg++; dd_memory("remap_pfn_range(plain:%p cipher:%p) dir:%d t->num_mapped_pg:%d\n", (void *)p->u.bio.plain_addr, (void *)p->u.bio.cipher_addr, dir, t->num_mapped_pg); } dd_memory("done mapping req:[%d] restore bi_iter\n", req->unique); memcpy(&orig->bi_iter, &iter_backup, sizeof(struct bvec_iter)); if (dir == WRITE) memcpy(&clone->bi_iter, &iter_backup, sizeof(struct bvec_iter)); spin_lock(&proc->lock); return DD_TRANS_OK; } static dd_transaction_result __process_page_request_locked( struct dd_req *req, struct dd_transaction *t) { struct dd_proc *proc = t->proc; int vm_offset = t->num_mapped_pg << PAGE_SHIFT; struct dd_user_req *p = NULL; dd_crypto_direction_t dir = req->u.page.dir; unsigned require_ctr_pages = 1; BUG_ON(!req->info); if (require_ctr_pages > (MAX_NUM_CONTROL_PAGE - t->num_ctr_pg)) { dd_verbose("cannot accommodate %d control pages (%d available), continue\n", require_ctr_pages, MAX_NUM_CONTROL_PAGE - t->num_ctr_pg); return DD_TRANS_ERR_FULL; } p = __get_next_user_req(t->control); if (!p) { spin_unlock(&proc->lock); t->control = __get_next_ctr_page(t); spin_lock(&proc->lock); p = __get_next_user_req(t->control); } p->unique = req->unique; p->userid = req->info->policy.userid; p->version = req->info->policy.version; p->ino = req->info->ino; p->code = DD_REQ_CRYPTO_PAGE; p->u.bio.rw = dir; spin_unlock(&proc->lock); if (dir == DD_DECRYPT) { struct page *ciphertext_page = req->u.page.src_page; p->u.bio.index = ciphertext_page->index; p->u.bio.cipher_addr = proc->ciphertext_vma->vm_start + vm_offset; p->u.bio.plain_addr = proc->ciphertext_vma->vm_start + vm_offset; remap_pfn_range(proc->ciphertext_vma, p->u.bio.cipher_addr, page_to_pfn(ciphertext_page), PAGE_SIZE, proc->ciphertext_vma->vm_page_prot); dd_dump("cipher page", page_address(ciphertext_page), PAGE_SIZE); t->num_mapped_pg++; } else { BUG(); } spin_lock(&proc->lock); return DD_TRANS_OK; } /** * process_request - mmap requested bio to user address space * as defined in dd_proc's vma fields */ int process_request(struct dd_proc *proc, struct dd_transaction *t) __releases(proc->lock) __acquires(proc->lock) { struct dd_req *req, *temp; memset(t, 0, sizeof(struct dd_transaction)); t->proc = proc; t->control = NULL; spin_lock(&proc->lock); list_for_each_entry_safe(req, temp, &proc->pending, list) { dd_transaction_result result = DD_TRANS_ERR_UNKNOWN; if (t->num_md_pg >= MAX_NUM_INO_PER_USER_REQUEST-1) { dd_verbose("can't assign more metadata page (num-ctlpage:%d)\n", t->num_ctr_pg); break; } spin_unlock(&proc->lock); if (!t->control || __is_ctr_page_full(t->control)) t->control = __get_next_ctr_page(t); spin_lock(&proc->lock); dd_debug_req("req ", DD_DEBUG_PROCESS, req); dd_verbose("retrieve req [unique:%d] [code:%d] [ino:%ld] [num_pg:%d] [num_md:%d] [num_ctr:%d]\n", req->unique, req->code, req->ino, t->num_mapped_pg, t->num_md_pg, t->num_ctr_pg); switch (req->code) { case DD_REQ_PREPARE: { #ifdef CONFIG_DDAR_USER_PREPARE result = __process_prepare_request_locked(req, t); #endif break; } case DD_REQ_CRYPTO_BIO: result = __process_bio_request_locked(req, t); break; case DD_REQ_CRYPTO_PAGE: result = __process_page_request_locked(req, t); break; default: dd_error("unknown req code:%d\n", req->code); } BUG_ON(!req->info); BUG_ON(t->num_ctr_pg > MAX_NUM_CONTROL_PAGE); if (result == DD_TRANS_ERR_FULL) { break; } // todo: handle error when daemon process is not running at this moment if (result != DD_TRANS_OK) { continue; } // map meta-data page if not mapped in current transaction spin_unlock(&proc->lock); __get_md_page(proc, req->info->mdpage, req->ino, t); spin_lock(&proc->lock); list_move(&req->list, &proc->processing); dd_req_state(req, DD_REQ_PROCESSING); } spin_unlock(&proc->lock); return 0; } long dd_ioctl_wait_crypto_request(struct dd_proc *proc, struct dd_ioc *ioc) { int rc = 0; int err = 0; struct dd_transaction t; memset(&t, 0, sizeof(struct dd_transaction)); if (!dd_mmap_available(proc)) { dd_error("dd_ioctl_wait_crypto_request: user space memory not registered\n"); return -ENOMEM; } spin_lock(&proc->lock); dd_verbose("waiting req (current:%p, pid:%p)\n", current, proc); if (list_empty(&proc->pending)) { DECLARE_WAITQUEUE(wait, current); add_wait_queue_exclusive(&proc->waitq, &wait); while (!proc->abort && list_empty(&proc->pending)) { set_current_state(TASK_INTERRUPTIBLE); if (signal_pending(current)) { dd_error("dd_ioctl_wait_crypto_request() received signal in interruptible process state\n"); rc = -EINTR; break; } spin_unlock(&proc->lock); freezable_schedule_timeout(msecs_to_jiffies(2000)); spin_lock(&proc->lock); } set_current_state(TASK_RUNNING); remove_wait_queue(&proc->waitq, &wait); } spin_unlock(&proc->lock); if (proc->abort) { dd_error("process abort\n"); return -EPIPE; } if (rc) { dd_error("dd_ioctl_wait_crypto_request() failed, try again :%d\n", rc); return rc; } dd_verbose("dd_ioctl_wait_crypto_request: retrieving req pid:%p\n", proc); err = process_request(proc, &t); if (!err) { dd_verbose("process_request: control:%d metadata:%d\n", t.num_ctr_pg, t.num_md_pg); ioc->u.crypto_request.num_control_page = t.num_ctr_pg; ioc->u.crypto_request.num_metadata_page = t.num_md_pg; } return err; } long dd_ioctl_submit_crypto_result(struct dd_proc *proc, struct dd_user_resp *errs, int num_err) { struct dd_req *req, *temp; blk_qc_t ret; // should never happen. proc object is freed only when user thread dies or close() the fd BUG_ON(!proc); spin_lock(&proc->lock); list_for_each_entry_safe(req, temp, &proc->processing, list) { int err = get_user_resp_err(errs, num_err, req->ino); list_move(&req->list, &proc->submitted); dd_req_state(req, DD_REQ_SUBMITTED); spin_unlock(&proc->lock); dd_debug_req("req ", DD_DEBUG_PROCESS, req); if (err) { dd_error("req :%d (ino:%ld) failed err:%d\n", req->code, req->ino, err); abort_req(__func__, req, -EIO); } else { switch (req->code) { case DD_REQ_CRYPTO_BIO: { int dir = bio_data_dir(req->u.bio.orig); if (dir == WRITE) { dd_dump_bio_pages("inner encryption done", req->u.bio.clone); if (fscrypt_inode_uses_inline_crypto(req->info->inode)) { dd_verbose("skip oem s/w crypt. hw encryption enabled\n"); // fscrypt_set_ice_dun(req->info->inode, req->u.bio.clone, 0/* temporary */); } else { if (dd_oem_crypto_bio_pages(req, WRITE, req->u.bio.clone)) { abort_req(__func__, req, -EIO); goto err_continue; } dd_dump_bio_pages("outter (s/w) encryption done", req->u.bio.clone); } req->u.bio.clone->bi_status = err; req->u.bio.clone->bi_end_io = dd_end_io; ret = submit_bio(req->u.bio.clone); } else { dd_dump_bio_pages("inner decryption done", req->u.bio.orig); bio_endio(req->u.bio.orig); put_req(__func__, req); } } break; case DD_REQ_CRYPTO_PAGE: case DD_REQ_PREPARE: /** * do nothing: this will wake up process waiting for user * space request and it is kernel service's responsibility * to free req object */ break; case DD_REQ_INVALID: default: break; } } wake_up_interruptible(&req->waitq); err_continue: spin_lock(&proc->lock); } spin_unlock(&proc->lock); return 0; } struct ioc_set_xattr_work { struct work_struct work; struct inode *inode; char name[MAX_XATTR_NAME_LEN]; char value[MAX_XATTR_LEN]; int size; }; static void dd_write_metadata_work(struct work_struct *work) { struct ioc_set_xattr_work *ioc_work = container_of(work, struct ioc_set_xattr_work, work); dd_write_crypto_metadata(ioc_work->inode, ioc_work->name, ioc_work->value, ioc_work->size); dd_verbose("dd_write_metadata_work %ld complete (i_state:0x%x)\n", ioc_work->inode->i_ino, ioc_work->inode->i_state); iput(ioc_work->inode); kfree(ioc_work); } long dd_ioctl_add_crypto_proc(struct file *filp) { struct dd_proc *proc = kzalloc(sizeof(*proc), GFP_KERNEL); int i; if (proc == NULL) return -ENOMEM; spin_lock_init(&proc->lock); proc->context = dd_context_global; INIT_LIST_HEAD(&proc->proc_node); proc->abort = 0; init_waitqueue_head(&proc->waitq); INIT_LIST_HEAD(&proc->pending); INIT_LIST_HEAD(&proc->processing); INIT_LIST_HEAD(&proc->submitted); filp->private_data = (void *) proc; proc->pid = current->pid; proc->tgid = current->tgid; proc->control_vma = NULL; proc->metadata_vma = NULL; proc->plaintext_vma = NULL; proc->ciphertext_vma = NULL; proc->num_control_page = 0; for (i = 0; i < MAX_NUM_CONTROL_PAGE; i++) { proc->control_page[i] = mempool_alloc(proc->context->page_pool, GFP_NOWAIT); if (!proc->control_page[i]) goto nomem_out; } atomic_set(&proc->reqcount, 1); spin_lock(&dd_context_global->lock); list_add_tail(&proc->proc_node, &dd_context_global->procs); spin_unlock(&dd_context_global->lock); dd_info("process %p (pid:%d, tgid:%d) added\n", proc, proc->pid, proc->tgid); return 0; nomem_out: for (i = 0; i < MAX_NUM_CONTROL_PAGE; i++) if (proc->control_page[i]) mempool_free(proc->control_page[i], proc->context->page_pool); return -ENOMEM; } long dd_ioctl_remove_crypto_proc(int userid) { struct dd_context *ctx = dd_context_global; struct dd_proc *proc = NULL; spin_lock(&dd_context_global->lock); list_for_each_entry(proc, &ctx->procs, proc_node) { proc->abort = 1; wake_up(&proc->waitq); } spin_unlock(&dd_context_global->lock); return 0; } static long dd_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct dd_proc *proc = (struct dd_proc *) filp->private_data; struct dd_req *req = NULL; struct dd_ioc ioc; struct inode *inode; int err; if (copy_from_user(&ioc, (void __user *) arg, sizeof(struct dd_ioc))) return -EFAULT; switch (cmd) { case DD_IOCTL_REGISTER_CRYPTO_TASK: { dd_info("DD_IOCTL_REGISTER_CRYPTO_TASK"); return dd_ioctl_add_crypto_proc(filp); } case DD_IOCTL_UNREGISTER_CRYPTO_TASK: { dd_info("DD_IOCTL_UNREGISTER_CRYPTO_TASK"); return dd_ioctl_remove_crypto_proc(0); } case DD_IOCTL_GET_DEBUG_MASK: { dd_verbose("DD_IOCTL_GET_DEBUG_MASK"); ioc.u.debug_mask = dd_debug_mask; if (copy_to_user((void __user *)arg, &ioc, sizeof(struct dd_ioc))) { return -EFAULT; } break; } case DD_IOCTL_SEND_LOG: { __dd_debug(ioc.u.log_msg.mask, "dualdard", "%s", ioc.u.log_msg.buf); break; } case DD_IOCTL_UPDATE_LOCK_STATE: { int old_state = dd_lock_state; dd_lock_state = ioc.u.lock.state; dd_verbose("DD_IOCTL_UPDATE_LOCK_STATE: %d\n", dd_lock_state); if (old_state != dd_lock_state) { if (dd_lock_state) { dd_error("ddar locked. trigger cache invalidation"); } } return 0; } /* { KNOX_SUPPORT_DAR_DUAL_DO */ case DD_IOCTL_GET_LOCK_STATE: { dd_verbose("DD_IOCTL_GET_LOCK_STATE : %u", dd_lock_state); ioc.u.lock.state = dd_lock_state; if (copy_to_user((void __user *)arg, &ioc, sizeof(unsigned int))) { return -EFAULT; } return 0; } /* } KNOX_SUPPORT_DAR_DUAL_DO */ #if USE_KEYRING case DD_IOCTL_ADD_KEY: case DD_IOCTL_EVICT_KEY: dd_error("use keyring (no driver support)\n"); return -EOPNOTSUPP; #else case DD_IOCTL_ADD_KEY: dd_info("DD_IOCTL_ADD_KEY"); return dd_add_master_key(ioc.u.add_key.userid, ioc.u.add_key.key, ioc.u.add_key.len); case DD_IOCTL_EVICT_KEY: dd_info("DD_IOCTL_EVICT_KEY"); dd_evict_master_key(ioc.u.evict_key.userid); return 0; case DD_IOCTL_DUMP_KEY: dd_info("DD_IOCTL_DUMP_KEY"); #ifdef CONFIG_SDP_KEY_DUMP err = dd_dump_key(ioc.u.dump_key.userid, ioc.u.dump_key.fileDescriptor); return err; #else return 0; #endif #endif case DD_IOCTL_SKIP_DECRYPTION_BOTH: { struct fd f = {NULL, 0}; struct inode *inode; struct dd_crypt_context crypt_context; dd_info("DD_IOCTL_SKIP_DECRYPTION_BOTH"); f = fdget(ioc.u.dump_key.fileDescriptor); if (unlikely(f.file == NULL)) { dd_error("invalid fd : %d\n", ioc.u.dump_key.fileDescriptor); return -EINVAL; } inode = f.file->f_path.dentry->d_inode; if (!inode) { dd_error("invalid inode address\n"); return -EBADF; } if (dd_read_crypt_context(inode, &crypt_context) != sizeof(struct dd_crypt_context)) { dd_error("failed to read dd crypt context - ino:%ld\n", inode->i_ino); return -EINVAL; } crypt_context.policy.flags |= DD_POLICY_SKIP_DECRYPTION_INNER; crypt_context.policy.flags |= DD_POLICY_SKIP_DECRYPTION_OUTER; if (dd_write_crypt_context(inode, &crypt_context, NULL)) { dd_error("failed to write dd crypt context - ino:%ld\n", inode->i_ino); return -EINVAL; } dd_info("updated policy - ino:%ld\n", inode->i_ino); return 0; } case DD_IOCTL_SKIP_DECRYPTION_INNER: { struct fd f = { NULL, 0 }; struct inode *inode; struct dd_crypt_context crypt_context; dd_info("DD_IOCTL_SKIP_DECRYPTION_INNER"); f = fdget(ioc.u.dump_key.fileDescriptor); if (unlikely(f.file == NULL)) { dd_error("invalid fd : %d\n", ioc.u.dump_key.fileDescriptor); return -EINVAL; } inode = f.file->f_path.dentry->d_inode; if (!inode) { dd_error("invalid inode address\n"); return -EBADF; } if (dd_read_crypt_context(inode, &crypt_context) != sizeof(struct dd_crypt_context)) { dd_error("failed to read dd crypt context - ino:%ld\n", inode->i_ino); return -EINVAL; } crypt_context.policy.flags &= ~DD_POLICY_SKIP_DECRYPTION_OUTER; crypt_context.policy.flags |= DD_POLICY_SKIP_DECRYPTION_INNER; if (dd_write_crypt_context(inode, &crypt_context, NULL)) { dd_error("failed to write dd crypt context - ino:%ld\n", inode->i_ino); } dd_info("updated policy - ino:%ld\n", inode->i_ino); return 0; } case DD_IOCTL_NO_SKIP_DECRYPTION: { struct fd f = { NULL, 0 }; struct inode *inode; struct dd_crypt_context crypt_context; dd_info("DD_IOCTL_NO_SKIP_DECRYPTION"); f = fdget(ioc.u.dump_key.fileDescriptor); if (unlikely(f.file == NULL)) { dd_error("invalid fd : %d\n", ioc.u.dump_key.fileDescriptor); return -EINVAL; } inode = f.file->f_path.dentry->d_inode; if (!inode) { dd_error("invalid inode address\n"); return -EBADF; } if (dd_read_crypt_context(inode, &crypt_context) != sizeof(struct dd_crypt_context)) { dd_error("failed to read dd crypt context - ino:%ld\n", inode->i_ino); return -EINVAL; } crypt_context.policy.flags &= ~DD_POLICY_SKIP_DECRYPTION_OUTER; crypt_context.policy.flags &= ~DD_POLICY_SKIP_DECRYPTION_INNER; if (dd_write_crypt_context(inode, &crypt_context, NULL)) { dd_error("failed to write dd crypt context - ino:%ld\n", inode->i_ino); } dd_info("updated policy - ino:%ld\n", inode->i_ino); return 0; } case DD_IOCTL_TRACE_DDAR_FILE: { #ifdef CONFIG_SDP_KEY_DUMP struct fd f = { NULL, 0 }; struct inode *inode; struct dd_crypt_context crypt_context; dd_info("DD_IOCTL_TRACE_DDAR_FILE"); f = fdget(ioc.u.dump_key.fileDescriptor); if (unlikely(f.file == NULL)) { dd_error("invalid fd : %d\n", ioc.u.dump_key.fileDescriptor); return -EINVAL; } inode = f.file->f_path.dentry->d_inode; if (!inode) { dd_error("invalid inode address\n"); return -EBADF; } if (dd_read_crypt_context(inode, &crypt_context) != sizeof(struct dd_crypt_context)) { dd_error("failed to read dd crypt context - ino:%ld\n", inode->i_ino); return -EINVAL; } crypt_context.policy.flags |= DD_POLICY_TRACE_FILE; if (dd_write_crypt_context(inode, &crypt_context, NULL)) { dd_error("failed to write dd crypt context - ino:%ld\n", inode->i_ino); } fscrypt_dd_trace_inode(inode); dd_info("updated policy - ino:%ld\n", inode->i_ino); #endif return 0; } case DD_IOCTL_DUMP_REQ_LIST: { dd_info("DD_IOCTL_DUMP_REQ_LIST"); dd_dump_debug_req_list(DD_DEBUG_INFO); return 0; } case DD_IOCTL_ABORT_PENDING_REQ_TIMEOUT: { dd_info("DD_IOCTL_ABORT_LONG_PENDING_REQ"); dd_abort_pending_req_timeout(DD_DEBUG_INFO); return 0; } case DD_IOCTL_GET_MMAP_LAYOUT: { dd_verbose("DD_IOCTL_GET_MMAP_LAYOUT"); memcpy(&ioc.u.layout, dd_context_global->layout, sizeof(struct dd_mmap_layout)); if (copy_to_user((void __user *)arg, &ioc, sizeof(struct dd_ioc))) { return -EFAULT; } return 0; } case DD_IOCTL_WAIT_CRYPTO_REQUEST: { int rc; BUG_ON(!proc); // caller is not a crypto task rc = dd_ioctl_wait_crypto_request(proc, &ioc); if (rc < 0) { dd_error("DD_IOCTL_WAIT_CRYPTO_REQUEST failed :%d\n", rc); return rc; } if (copy_to_user((void __user *)arg, &ioc, sizeof(struct dd_ioc))) { return -EFAULT; } dd_verbose("DD_IOCTL_WAIT_CRYPTO_REQUEST finish rc:%d\n", rc); break; } case DD_IOCTL_SUBMIT_CRYPTO_RESULT: { BUG_ON(!proc); // caller is not a crypto task dd_verbose("DD_IOCTL_SUBMIT_CRYPTO_RESULT\n"); return dd_ioctl_submit_crypto_result(proc, ioc.u.crypto_response.err, ioc.u.crypto_response.num_err); } case DD_IOCTL_ABORT_CRYPTO_REQUEST: { dd_verbose("DD_IOCTL_ABORT_CRYPTO_REQUEST\n"); proc_abort_req_all("DD_IOCTL_ABORT_CRYPTO_REQUEST", proc); return 0; } case DD_IOCTL_GET_XATTR: { BUG_ON(!proc); // caller is not a crypto task req = find_req(proc, DD_REQ_PROCESSING, ioc.u.xattr.ino); if (!req) { dd_error("request not found for ino:%ld!\n", ioc.u.xattr.ino); return -EBADF; } inode = req->info->inode; if (!inode) { dd_error("DD_IOCTL_GET_XATTR: invalid info->inode address. Ignore\n"); return -EBADF; } err = dd_read_crypto_metadata(inode, ioc.u.xattr.name, ioc.u.xattr.value, MAX_XATTR_LEN); if (err < 0) { dd_verbose("failed to read crypto metadata name:%s err:%d\n", ioc.u.xattr.name, err); return err; } ioc.u.xattr.size = err; if (copy_to_user((void __user *)arg, &ioc, sizeof(struct dd_ioc))) return -EFAULT; return 0; } case DD_IOCTL_SET_XATTR: { struct ioc_set_xattr_work *ioc_work = kmalloc(sizeof(struct ioc_set_xattr_work), GFP_KERNEL); if (!ioc_work) { dd_error("DD_IOCTL_SET_XATTR - failed to allocate memory\n"); return -ENOMEM; } dd_verbose("DD_IOCTL_SET_XATTR %ld\n", ioc.u.xattr.ino); BUG_ON(!proc); // caller is not a crypto task if (ioc.u.xattr.size > MAX_XATTR_LEN) { dd_error("DD_IOCTL_SET_XATTR - invalid input\n"); kfree(ioc_work); return -EINVAL; } req = find_req(proc, DD_REQ_PROCESSING, ioc.u.xattr.ino); if (!req) { dd_error("request not found for ino:%ld!\n", ioc.u.xattr.ino); kfree(ioc_work); return -EBADF; } inode = req->info->inode; if (!inode) { dd_error("DD_IOCTL_SET_XATTR: invalid info->inode address. Ignore\n"); kfree(ioc_work); return -EBADF; } ioc_work->inode = req->info->inode; memcpy(ioc_work->name, ioc.u.xattr.name, MAX_XATTR_NAME_LEN); memcpy(ioc_work->value, ioc.u.xattr.value, ioc.u.xattr.size); ioc_work->size = ioc.u.xattr.size; if (!igrab(ioc_work->inode)) { dd_error("failed to grab inode refcount. this inode may be getting removed\n"); kfree(ioc_work); return 0; } INIT_WORK(&ioc_work->work, dd_write_metadata_work); queue_work(dd_ioctl_workqueue, &ioc_work->work); req->need_xattr_flush = 1; return 0; } default: return -ENOTTY; } return 0; } static int dd_release(struct inode *node, struct file *filp) { struct dd_proc *proc = filp->private_data; if (proc) { struct dd_context *ctx = proc->context; dd_info("process %p (pid:%d tgid:%d) released\n", proc, proc->pid, proc->tgid); spin_lock(&ctx->lock); list_del(&proc->proc_node); spin_unlock(&ctx->lock); proc_abort_req_all(__func__, proc); proc->abort = 1; dd_proc_try_free(proc); } return 0; } static ssize_t dd_read(struct file *filp, char __user *ubuf, size_t len, loff_t *offset) { return 0; } ssize_t dd_write(struct file *filp, const char __user *ubuf, size_t len, loff_t *offset) { return 0; } const struct file_operations dd_dev_operations = { .owner = THIS_MODULE, .unlocked_ioctl = dd_ioctl, .compat_ioctl = dd_ioctl, .mmap = dd_mmap, .open = dd_open, .release = dd_release, .read = dd_read, .write = dd_write, }; static struct miscdevice dd_miscdevice = { .minor = MISC_DYNAMIC_MINOR, .name = "dd", .fops = &dd_dev_operations, }; static int enforce_caller_gid(uid_t uid) { struct group_info *group_info; int i; if (!uid_is_app(uid)) { return 0; } group_info = get_current_groups(); for (i = 0; i < group_info->ngroups; i++) { kgid_t gid = group_info->gid[i]; if (gid.val == AID_VENDOR_DDAR_DE_ACCESS) { goto enforce; } } return 0; enforce: { char msg[256]; snprintf(msg, 128, "gid restricted uid:%d pid:%d gids:\n", uid, current->tgid); for (i = 0; i < group_info->ngroups; i++) { kgid_t gid = group_info->gid[i]; if (gid.val == AID_VENDOR_DDAR_DE_ACCESS) { snprintf(msg, 128, "%s %d", msg, gid.val); break; } } dd_info("%s\n", msg); } return -1; } static struct kmem_cache *ext4_dd_info_cachep; struct dd_info *alloc_dd_info(struct inode *inode, const struct dd_policy *dd_policy, struct dd_crypt_context *crypt_context) { struct dd_info *info; uid_t calling_uid = from_kuid(&init_user_ns, current_uid()); unsigned char master_key[256]; int rc = 0; #ifdef CONFIG_FSCRYPT_SDP sdp_fs_command_t *cmd = NULL; #endif if (!dd_policy) { dd_error("alloc_dd_info: dd_policy is null (ino:%ld)\n", inode->i_ino); return ERR_PTR(-EINVAL); } BUG_ON(!ext4_dd_info_cachep); if (S_ISREG(inode->i_mode)) { if (dd_policy_user_space_crypto(dd_policy->flags)) { if (dd_is_user_deamon_locked()) { dd_error("dd state locked (dd_lock_state:%d)\n", dd_lock_state); return ERR_PTR(-ENOKEY); } } else if (dd_policy_kernel_crypto(dd_policy->flags)) { rc = get_dd_master_key(dd_policy->userid, (void *)master_key); if (rc) { dd_error("dd state locked (can't find master key for user:%d)\n", dd_policy->userid); return ERR_PTR(-ENOKEY); } } } if (dd_policy_gid_restriction(dd_policy->flags)) { if (enforce_caller_gid(calling_uid)) { #ifdef CONFIG_FSCRYPT_SDP int partition_id = -1; struct dentry *de = NULL; de = d_find_alias(inode); if (de) { partition_id = fscrypt_sdp_get_storage_type(de); } cmd = sdp_fs_command_alloc(FSOP_AUDIT_FAIL_DE_ACCESS, current->tgid, dd_policy->userid, partition_id, inode->i_ino, -EACCES, GFP_KERNEL); if (cmd) { sdp_fs_request(cmd, NULL); sdp_fs_command_free(cmd); } #endif dd_error("gid restricted.. calling-uid:%d ino:%ld\n", calling_uid, inode->i_ino); return ERR_PTR(-EACCES); } } if (dd_policy_secure_erase(dd_policy->flags)) { mapping_set_sensitive(inode->i_mapping); } info = kmem_cache_alloc(ext4_dd_info_cachep, GFP_NOFS); if (!info) { dd_error("failed to allocate dd info\n"); return ERR_PTR(-ENOMEM); } memcpy(&info->policy, dd_policy, sizeof(struct dd_policy)); info->context = (void *) dd_context_global; info->inode = inode; info->ino = inode->i_ino; info->ctfm = NULL; info->proc = NULL; info->mdpage = NULL; spin_lock_init(&info->lock); atomic_set(&info->reqcount, 0); atomic_set(&info->refcount, 1); if (dd_policy_user_space_crypto(dd_policy->flags)) { struct metadata_hdr *hdr; info->mdpage = alloc_page(GFP_KERNEL); hdr = (struct metadata_hdr *) page_address(info->mdpage); memset(hdr, 0, sizeof(struct metadata_hdr)); hdr->ino = inode->i_ino; hdr->userid = dd_policy->userid; hdr->initialized = 0; if (crypt_context == NULL) { struct dd_crypt_context temp_crypt_context; memset(&temp_crypt_context, 0, sizeof(struct dd_crypt_context)); memcpy(&temp_crypt_context.policy, dd_policy, sizeof(struct dd_policy)); memcpy(&info->crypt_context, &temp_crypt_context, sizeof(struct dd_crypt_context)); } else { memcpy(&info->crypt_context, crypt_context, sizeof(struct dd_crypt_context)); } #ifdef CONFIG_DDAR_USER_PREPARE if (dd_prepare(info)) { dd_info_try_free(info); return NULL; } #endif } else if (dd_policy_kernel_crypto(dd_policy->flags) && S_ISREG(inode->i_mode)) { struct crypto_skcipher *ctfm; if (crypt_context == NULL) { struct dd_crypt_context temp_crypt_context; memset(&temp_crypt_context, 0, sizeof(struct dd_crypt_context)); memcpy(&temp_crypt_context.policy, dd_policy, sizeof(struct dd_policy)); rc = dd_generate_file_key(dd_policy->userid, &temp_crypt_context); if (rc) { dd_error("failed to generate file key (ino:%ld)\n", inode->i_ino); goto out; } memcpy(&info->crypt_context, &temp_crypt_context, sizeof(struct dd_crypt_context)); } else { memcpy(&info->crypt_context, crypt_context, sizeof(struct dd_crypt_context)); } ctfm = dd_alloc_ctfm(&info->crypt_context, (void *)master_key); if (!ctfm || IS_ERR(ctfm)) { rc = ctfm ? PTR_ERR(ctfm) : -ENOMEM; goto out; } info->ctfm = ctfm; out: memzero_explicit(master_key, 256); if (rc) { dd_info_try_free(info); return ERR_PTR(rc); } } else if (dd_policy_gid_restriction(dd_policy->flags) || dd_policy_kernel_crypto(dd_policy->flags)) { if (crypt_context == NULL) { struct dd_crypt_context temp_crypt_context; memset(&temp_crypt_context, 0, sizeof(struct dd_crypt_context)); memcpy(&temp_crypt_context.policy, dd_policy, sizeof(struct dd_policy)); memcpy(&info->crypt_context, &temp_crypt_context, sizeof(struct dd_crypt_context)); } else { memcpy(&info->crypt_context, crypt_context, sizeof(struct dd_crypt_context)); } } else { // nothing to do } dd_process("ino:%ld user:%d flags:0x%04x context:%p\n", inode->i_ino, dd_policy->userid, dd_policy->flags, info->context); return info; } static void dd_info_get(struct dd_info *info) { atomic_inc(&info->refcount); } // decrease info->refcount. if new count is 0, free the object void dd_info_try_free(struct dd_info *info) { if (!info) return; dd_verbose("info refcount:%d, ino:%ld\n", atomic_read(&info->refcount), info->ino); if (atomic_dec_and_test(&info->refcount)) { dd_verbose("freeing dd info ino:%ld\n", info->ino); if (info->mdpage) __free_page(info->mdpage); if (info->ctfm) crypto_free_skcipher(info->ctfm); kmem_cache_free(ext4_dd_info_cachep, info); } } void dd_init_context(const char *name) { struct dd_context *context = (struct dd_context *) kmalloc(sizeof(struct dd_context), GFP_KERNEL); if (context) { int ret = 0; dd_info("dd_init_context %s\n", name); strncpy(context->name, name, sizeof(context->name) - 1); context->name[sizeof(context->name) - 1] = 0; context->req_ctr = 0; spin_lock_init(&context->lock); spin_lock_init(&context->ctr_lock); INIT_LIST_HEAD(&context->procs); spin_lock_init(&context->bm_result.lock); /* Enable message ratelimiting. Default is 10 messages per 5 secs. */ ratelimit_state_init(&context->bm_result.ratelimit_state, 5 * HZ, 10); __benchmark_init(&context->bm_result); context->layout = &default_mmap_layout; mutex_init(&context->bio_alloc_lock); context->bio_set = kzalloc(sizeof(struct bio_set), GFP_KERNEL); if (!context->bio_set) { dd_error("Failed to allocate bioset\n"); return; } ret = bioset_init(context->bio_set, 64, 0, (BIOSET_NEED_BVECS | BIOSET_NEED_RESCUER)); if (ret) { dd_error("Failed to init bioset\n"); } context->page_pool = mempool_create_page_pool(256, 0); dd_context_global = context; // TODO: remove me: this is temporary defence code to enable QA testing // abort_thread = kthread_run(abort_req_timeout_thread, NULL, "ddar_abort_req_timeoutd"); // if (abort_thread) { // dd_info("abort req timeout thread created\n"); // } else { // dd_error("Failed to create abort req timeout thread\n"); // } } else { dd_error("dd_init_context %s failed\n", name); } } atomic64_t dd_count; void set_ddar_count(long count) { atomic64_set(&dd_count, count); } long get_ddar_count(void) { long count = atomic64_read(&dd_count); return count; } void inc_ddar_count(void) { atomic64_inc(&dd_count); } void dec_ddar_count(void) { atomic64_dec(&dd_count); } static int __init dd_init(void) { int err = -ENOMEM; BUG_ON(sizeof(struct metadata_hdr) != METADATA_HEADER_LEN); set_ddar_count(0); dd_free_req_workqueue = alloc_workqueue("dd_free_req_workqueue", WQ_HIGHPRI, 0); if (!dd_free_req_workqueue) goto out; dd_read_workqueue = alloc_workqueue("dd_read_workqueue", WQ_HIGHPRI, 0); if (!dd_read_workqueue) goto out; dd_ioctl_workqueue = alloc_workqueue("dd_ioctl_workqueue", WQ_HIGHPRI, 0); if (!dd_ioctl_workqueue) goto out; dd_req_cachep = KMEM_CACHE(dd_req, SLAB_RECLAIM_ACCOUNT); if (!dd_req_cachep) goto out_cache_clean; ext4_dd_info_cachep = KMEM_CACHE(dd_info, SLAB_RECLAIM_ACCOUNT); if (!ext4_dd_info_cachep) goto out_cache_clean; err = misc_register(&dd_miscdevice); if (err) goto out_cache_clean; dd_init_context("knox-ddar"); return 0; out_cache_clean: destroy_workqueue(dd_free_req_workqueue); destroy_workqueue(dd_read_workqueue); destroy_workqueue(dd_ioctl_workqueue); kmem_cache_destroy(ext4_dd_info_cachep); kmem_cache_destroy(dd_req_cachep); out: return err; } module_init(dd_init); unsigned int dd_debug_mask = 0x03; module_param_named(debug_mask, dd_debug_mask, uint, 0600); MODULE_PARM_DESC(debug_mask, "dd driver debug mask"); #define LOGBUF_MAX (512) void dd_dump(const char *msg, char *buf, int len) { unsigned char logbuf[LOGBUF_MAX]; int i, j; if (!(dd_debug_mask & DD_DEBUG_MEMDUMP)) return; if (len > LOGBUF_MAX) len = LOGBUF_MAX; memset(logbuf, 0, LOGBUF_MAX); i = 0; while (i < len) { int l = (len-i > 16) ? 16 : len-i; snprintf(logbuf, LOGBUF_MAX, "%s\t:", logbuf); for (j = 0; j < l; j++) { snprintf(logbuf, LOGBUF_MAX, "%s%02hhX", logbuf, buf[i+j]); if (j % 2) snprintf(logbuf, LOGBUF_MAX, "%s ", logbuf); } if (l < 16) for (j = 0; j < (16 - l); j++) { snprintf(logbuf, LOGBUF_MAX, "%s ", logbuf); if (j % 2) snprintf(logbuf, LOGBUF_MAX, "%s ", logbuf); } snprintf(logbuf, LOGBUF_MAX, "%s\t\t", logbuf); for (j = 0; j < l; j++) snprintf(logbuf, LOGBUF_MAX, "%s%c", logbuf, isalnum(buf[i+j]) ? buf[i+j]:'.'); snprintf(logbuf, LOGBUF_MAX, "%s\n", logbuf); i += l; } printk("knox-dd: %s (%p) %d bytes:\n%s\n", msg, buf, len, logbuf); } void dd_dump_bio_pages(const char *msg, struct bio *bio) { struct bio_vec *bv; struct bvec_iter_all i; if (!(dd_debug_mask & DD_DEBUG_MEMDUMP)) return; dd_verbose("%s: bio bi_flags:%x bi_opf:%x bi_status:%d\n", msg, bio->bi_flags, bio->bi_opf, bio->bi_status); bio_for_each_segment_all(bv, bio, i) { struct page *page = bv->bv_page; BUG_ON(!page); // empty bio dd_dump(msg, page_address(page), PAGE_SIZE); } } void __dd_debug(unsigned int mask, const char *func, const char *fmt, ...) { if (dd_debug_bit_test(mask)) { struct va_format vaf; va_list args; int buf_len = 256; char buf[256]; va_start(args, fmt); vaf.fmt = fmt; vaf.va = &args; snprintf(buf, buf_len, "[%.2x:%16.s] %pV", mask, func, &vaf); va_end(args); printk("knox-dd%s\n", buf); if (mask & (DD_DEBUG_ERROR | DD_DEBUG_INFO)) { dek_add_to_log(-1, buf); } } }