kernel_samsung_a53x/security/sdp/dd_core.c

2427 lines
62 KiB
C
Raw Permalink Normal View History

2024-06-15 21:02:09 +02:00
/*
* File: security/sdp/dd.c
*
* Samsung Dual-DAR(Data At Rest) cache I/O relay driver
*
* Author: Olic Moon <olic.moon@samsung.com>
*/
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/aio.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>
#include <linux/wait.h>
#include <linux/jiffies.h>
#include <linux/atomic.h>
#include <linux/sched/signal.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/blk-crypto.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/freezer.h>
#define __FS_HAS_ENCRYPTION IS_ENABLED(CONFIG_FSCRYPT_SDP)
#include <linux/fscrypt.h>
#ifdef CONFIG_FSCRYPT_SDP
#include <sdp/fs_request.h>
#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 <init>", 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 <free>", 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 <free>", 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 <queued>", 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 <processing>", 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 <submitted>", 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);
}
}
}