blob: 74cc5d2ecfb17c7696275e3f3f5ca5619be1ac1a [file] [log] [blame]
/*
* Copyright (C) 2017 Samsung Electronics Co., Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/blkdev.h>
#include <linux/errno.h>
#include <linux/module.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/crypto.h>
#include <crypto/algapi.h>
#include <crypto/diskcipher.h>
#include <linux/delay.h>
#include "internal.h"
#ifdef CONFIG_CRYPTO_DISKCIPHER_DEBUG
#include <crypto/fmp.h>
#include <linux/mm_types.h>
#include <linux/fs.h>
#include <linux/fscrypt.h>
#define DUMP_MAX 20
struct dump_err {
struct page *page;
struct bio bio;
struct fmp_crypto_info ci;
enum diskcipher_dbg api;
};
struct diskc_debug_info {
struct dump_err dump[DUMP_MAX];
int err;
int cnt[DISKC_USER_MAX][2];
};
static struct diskc_debug_info diskc_dbg;
void crypto_diskcipher_debug(enum diskcipher_dbg api, int bi_opf)
{
int idx = 0;
struct diskc_debug_info *dbg = &diskc_dbg;
if (api <= DISKC_API_MAX)
dbg->cnt[api][bi_opf]++;
else {
if (bi_opf)
idx = 1;
dbg->cnt[api][idx]++;
}
}
static void print_err(void)
{
int i, j;
struct bio_vec *bv; /* bio page list */
struct bio *bio;
struct fmp_crypto_info *ci;
struct diskc_debug_info *dbg = &diskc_dbg;
for (j = 0; j < dbg->err; j++) {
bio = &dbg->dump[j].bio;
ci = &dbg->dump[j].ci;
if (bio) {
#ifdef CONFIG_BLK_DEV_CRYPT
pr_info
("%s(%d/%d): bio:%p ci:%p page:%p flag:%x, opf:%x, crypt:%p\n",
__func__, j, dbg->err, bio, ci, &dbg->dump[j].page,
bio->bi_flags, bio->bi_opf, bio->bi_cryptd);
#else
pr_info
("%s(%d/%d): bio:%p ci:%p page:%p flag:%x, opf:%x\n",
__func__, j, dbg->err, bio, ci, &dbg->dump[j].page,
bio->bi_flags, bio->bi_opf);
#endif
print_hex_dump(KERN_CONT, "bio:", DUMP_PREFIX_OFFSET,
16, 1, bio, sizeof(struct bio), false);
for (i = 0; i < bio->bi_max_vecs; i++) {
bv = &bio->bi_io_vec[i];
pr_info("bv[%d] page:%p len:%d offset:%d\n",
i, bv->bv_page, bv->bv_len, bv->bv_offset);
}
}
if (ci) {
pr_info("[ci] key_size:%d algo_mode:%d\n",
ci->key_size, ci->algo_mode);
print_hex_dump(KERN_CONT, "key:", DUMP_PREFIX_OFFSET,
16, 1, ci->key, sizeof(ci->key), false);
}
}
}
static void disckipher_log_show(struct seq_file *m)
{
int i;
struct diskc_debug_info *dbg = &diskc_dbg;
char name[DISKC_USER_MAX][32] = {
"ALLOC", "FREE", "FREEREQ", "SETKEY", "SET", "GET", "CRYPT", "CLEAR",
"DISKC_API_MAX", "FS_PAGEIO", "FS_READP", "FS_DIO", "FS_BLOCK_WRITE",
"FS_ZEROPAGE", "BLK_BH", "DMCRYPT", "DISKC_MERGE", "DISKC_MERGE_ERR_INODE", "DISKC_MERGE_ERR_DISK",
"FS_DEC_WARN", "FS_ENC_WARN", "DISKC_MERGE_DIO", "DISKC_FREE_REQ_WARN",
"DISKC_FREE_WQ_WARN", "DISKC_CRYPT_WARN",
"DM_CRYPT_NONENCRYPT", "DM_CRYPT_CTR", "DM_CRYPT_DTR", "DM_CRYPT_OVER",
"F2FS_gc", "F2FS_gc_data_page", "F2FS_gc_data_page_no_key", "F2FS_gc_data_page_no_key_FC",
"F2FS_gc_data_page_FC", "F2FS_gc_data_block", "F2FS_gc_data_block_key",
"F2FS_gc_data_block_err1", "F2FS_gc_data_block_err2", "F2FS_gc_data_block_err3", "F2FS_gc_skip",
"DISKC_ERR", "DISKC_NO_KEY_ERR", "DISKC_NO_SYNC_ERR", "DISKC_NO_CRYPT_ERR", "DISKC_NO_DISKC_ERR"};
for (i = 0; i < DISKC_USER_MAX; i++)
if (dbg->cnt[i][0] || dbg->cnt[i][1])
seq_printf(m, "%s\t: %6u(err:%u)\n",
name[i], dbg->cnt[i][0], dbg->cnt[i][1]);
if (dbg->err)
print_err();
}
/* check diskcipher for FBE */
#undef DISKC_FS_ENCRYPT_DEBUG /* it's not work on fips */
#ifdef DISKC_FS_ENCRYPT_DEBUG
static void dump_err(struct crypto_diskcipher *ci, enum diskcipher_dbg api,
struct bio *bio, struct page *page)
{
struct diskc_debug_info *dbg = &diskc_dbg;
if ((dbg->err < DUMP_MAX) && ci) {
struct crypto_tfm *tfm = crypto_diskcipher_tfm(ci);
dbg->dump[dbg->err].api = api;
memcpy(&dbg->dump[dbg->err].ci, crypto_tfm_ctx(tfm),
sizeof(struct fmp_crypto_info));
if (page)
dbg->dump[dbg->err].page = page;
if (bio)
memcpy(&dbg->dump[dbg->err].bio, bio,
sizeof(struct bio));
}
dbg->err++;
}
static bool crypto_diskcipher_check(struct bio *bio)
{
int ret = 0;
struct crypto_diskcipher *ci = NULL;
struct inode *inode = NULL;
struct page *page = NULL;
if (!bio) {
pr_err("%s: doesn't exist bio\n", __func__);
goto out;
}
page = bio->bi_io_vec[0].bv_page;
if (page && !PageAnon(page) && bio)
if (page->mapping)
if (page->mapping->host) {
if (page->mapping->host->i_crypt_info) {
inode = page->mapping->host;
ci = fscrypt_get_bio_cryptd(inode);
if (ci && (bio->bi_cryptd != ci)
&& (!(bio->bi_flags & REQ_OP_DISCARD))) {
pr_err("%s: no sync err\n", __func__);
dump_err(ci, DISKC_API_GET, bio, page);
crypto_diskcipher_debug(DISKC_NO_SYNC_ERR, 0);
ret = -EINVAL;
}
if (!ci) {
crypto_diskcipher_debug(DISKC_NO_DISKC_ERR, 1);
pr_err("%s: no crypt err\n", __func__);
ret = -EINVAL;
}
} else {
crypto_diskcipher_debug(DISKC_NO_KEY_ERR, 1);
ret = -EINVAL;
}
}
out:
crypto_diskcipher_debug(DISKC_API_GET, ret);
return ret;
}
#else
#define crypto_diskcipher_check(a) (0)
#endif
#else
#define crypto_diskcipher_check(a) (0)
#define disckipher_log_show(a) do { } while (0)
#endif
struct crypto_diskcipher *crypto_diskcipher_get(struct bio *bio)
{
if (!bio || !virt_addr_valid(bio)) {
pr_err("%s: Invalid bio:%p\n", __func__, bio);
return NULL;
}
if (bio->bi_opf & REQ_CRYPT) {
if (bio->bi_cryptd) {
if (!crypto_diskcipher_check(bio))
return bio->bi_cryptd;
else
return ERR_PTR(-EINVAL);
} else {
crypto_diskcipher_debug(DISKC_NO_CRYPT_ERR, 0);
return ERR_PTR(-EINVAL);
}
}
return NULL;
}
void crypto_diskcipher_set(struct bio *bio,
struct crypto_diskcipher *tfm, u64 dun)
{
if (bio && tfm) {
bio->bi_opf |= REQ_CRYPT;
bio->bi_cryptd = tfm;
#ifdef CONFIG_CRYPTO_DISKCIPHER_DUN
if (dun)
bio->bi_iter.bi_dun = dun;
#endif
}
crypto_diskcipher_debug(DISKC_API_SET, 0);
}
/* debug freerq */
enum diskc_status {
DISKC_ST_INIT,
DISKC_ST_FREE_REQ,
DISKC_ST_FREE,
};
int crypto_diskcipher_setkey(struct crypto_diskcipher *tfm, const char *in_key,
unsigned int key_len, bool persistent)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%p", __func__, base);
return -EINVAL;
}
crypto_diskcipher_debug(DISKC_API_SETKEY, 0);
return cra->setkey(base, in_key, key_len, persistent);
}
int crypto_diskcipher_clearkey(struct crypto_diskcipher *tfm)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%p", __func__, base);
return -EINVAL;
}
return cra->clearkey(base);
}
int crypto_diskcipher_set_crypt(struct crypto_diskcipher *tfm, void *req)
{
int ret = 0;
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = NULL;
if (!base) {
pr_err("%s: doesn't exist cra. base:%p", __func__, base);
ret = -EINVAL;
goto out;
}
cra = crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%p\n", __func__, base);
ret = -EINVAL;
goto out;
}
if (atomic_read(&tfm->status) == DISKC_ST_FREE) {
pr_err("%s: tfm is free\n", __func__);
crypto_diskcipher_debug(DISKC_CRYPT_WARN, 0);
return -EINVAL;
}
ret = cra->crypt(base, req);
#ifdef USE_FREE_REQ
if (!list_empty(&cra->freectrl.freelist)) {
if (!atomic_read(&cra->freectrl.freewq_active)) {
atomic_set(&cra->freectrl.freewq_active, 1);
schedule_delayed_work(&cra->freectrl.freewq, 0);
}
}
#endif
out:
if (ret)
pr_err("%s fails ret:%d, cra:%p\n", __func__, ret, cra);
crypto_diskcipher_debug(DISKC_API_CRYPT, ret);
return ret;
}
int crypto_diskcipher_clear_crypt(struct crypto_diskcipher *tfm, void *req)
{
int ret = 0;
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = NULL;
if (!base) {
pr_err("%s: doesn't exist base, tfm:%p\n", __func__, tfm);
ret = -EINVAL;
goto out;
}
cra = crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%p\n", __func__, base);
ret = -EINVAL;
goto out;
}
if (atomic_read(&tfm->status) == DISKC_ST_FREE) {
pr_warn("%s: tfm is free\n", __func__);
return -EINVAL;
}
ret = cra->clear(base, req);
if (ret)
pr_err("%s fails", __func__);
out:
crypto_diskcipher_debug(DISKC_API_CLEAR, ret);
return ret;
}
#ifndef CONFIG_CRYPTO_MANAGER_DISABLE_TESTS
int diskcipher_do_crypt(struct crypto_diskcipher *tfm,
struct diskcipher_test_request *req)
{
int ret;
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%p\n", __func__, base);
ret = -EINVAL;
goto out;
}
if (cra->do_crypt)
ret = cra->do_crypt(base, req);
else
ret = -EINVAL;
if (ret)
pr_err("%s fails ret:%d", __func__, ret);
out:
return ret;
}
#endif
static int crypto_diskcipher_init_tfm(struct crypto_tfm *base)
{
struct crypto_diskcipher *tfm = __crypto_diskcipher_cast(base);
atomic_set(&tfm->status, DISKC_ST_INIT);
return 0;
}
#ifdef USE_FREE_REQ
static void free_workq_func(struct work_struct *work)
{
struct diskcipher_alg *cra =
container_of(work, struct diskcipher_alg, freectrl.freewq.work);
struct diskcipher_freectrl *fctrl = &cra->freectrl;
struct crypto_diskcipher *_tfm, *tmp;
unsigned long cur_jiffies = jiffies;
struct list_head poss_free_list;
unsigned long flags;
INIT_LIST_HEAD(&poss_free_list);
/* pickup freelist */
spin_lock_irqsave(&fctrl->freelist_lock, flags);
list_for_each_entry_safe(_tfm, tmp, &fctrl->freelist, node) {
if (jiffies_to_msecs(cur_jiffies - _tfm->req_jiffies) > fctrl->max_io_ms)
list_move_tail(&_tfm->node, &poss_free_list);
}
spin_unlock_irqrestore(&fctrl->freelist_lock, flags);
list_for_each_entry_safe(_tfm, tmp, &poss_free_list, node) {
if (atomic_read (&_tfm->status) != DISKC_ST_FREE_REQ)
crypto_diskcipher_debug(DISKC_FREE_WQ_WARN, 0);
crypto_free_diskcipher(_tfm);
}
if (!list_empty(&fctrl->freelist))
schedule_delayed_work(&fctrl->freewq, msecs_to_jiffies(fctrl->max_io_ms));
else
atomic_set(&fctrl->freewq_active, 0);
}
#endif
void crypto_free_req_diskcipher(struct crypto_diskcipher *tfm)
{
#ifdef USE_FREE_REQ
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = crypto_diskcipher_alg(base->__crt_alg);
struct diskcipher_freectrl *fctrl = &cra->freectrl;
unsigned long flags;
if (atomic_read(&tfm->status) != DISKC_ST_INIT) {
crypto_diskcipher_debug(DISKC_FREE_REQ_WARN, 0);
pr_warn("%s: already submit status:%d\n", __func__, atomic_read(&tfm->status));
return;
}
atomic_set(&tfm->status, DISKC_ST_FREE_REQ);
INIT_LIST_HEAD(&tfm->node);
tfm->req_jiffies = jiffies;
spin_lock_irqsave(&fctrl->freelist_lock, flags);
list_move_tail(&tfm->node, &fctrl->freelist);
spin_unlock_irqrestore(&fctrl->freelist_lock, flags);
crypto_diskcipher_debug(DISKC_API_FREEREQ, 0);
#else
crypto_free_diskcipher(tfm);
#endif
}
unsigned int crypto_diskcipher_extsize(struct crypto_alg *alg)
{
return alg->cra_ctxsize +
(alg->cra_alignmask & ~(crypto_tfm_ctx_alignment() - 1));
}
static void crypto_diskcipher_show(struct seq_file *m, struct crypto_alg *alg)
{
seq_printf(m, "type : diskcipher\n");
disckipher_log_show(m);
}
static const struct crypto_type crypto_diskcipher_type = {
.extsize = crypto_diskcipher_extsize,
.init_tfm = crypto_diskcipher_init_tfm,
#ifdef CONFIG_PROC_FS
.show = crypto_diskcipher_show,
#endif
.maskclear = ~CRYPTO_ALG_TYPE_MASK,
.maskset = CRYPTO_ALG_TYPE_MASK,
.type = CRYPTO_ALG_TYPE_DISKCIPHER,
.tfmsize = offsetof(struct crypto_diskcipher, base),
};
#define DISKC_NAME "-disk"
#define DISKC_NAME_SIZE (5)
#define DISKCIPHER_MAX_IO_MS (1000)
struct crypto_diskcipher *crypto_alloc_diskcipher(const char *alg_name,
u32 type, u32 mask, bool force)
{
crypto_diskcipher_debug(DISKC_API_ALLOC, 0);
if (force) {
if (strlen(alg_name) + DISKC_NAME_SIZE < CRYPTO_MAX_ALG_NAME) {
char diskc_name[CRYPTO_MAX_ALG_NAME];
strcpy(diskc_name, alg_name);
strcat(diskc_name, DISKC_NAME);
return crypto_alloc_tfm(diskc_name,
&crypto_diskcipher_type, type, mask);
}
} else {
return crypto_alloc_tfm(alg_name, &crypto_diskcipher_type, type, mask);
}
return NULL;
}
void crypto_free_diskcipher(struct crypto_diskcipher *tfm)
{
crypto_diskcipher_debug(DISKC_API_FREE, 0);
atomic_set(&tfm->status, DISKC_ST_FREE);
crypto_destroy_tfm(tfm, crypto_diskcipher_tfm(tfm));
}
int crypto_register_diskcipher(struct diskcipher_alg *alg)
{
struct crypto_alg *base = &alg->base;
#ifdef USE_FREE_REQ
struct diskcipher_freectrl *fctrl = &alg->freectrl;
INIT_LIST_HEAD(&fctrl->freelist);
INIT_DELAYED_WORK(&fctrl->freewq, free_workq_func);
spin_lock_init(&fctrl->freelist_lock);
if (!fctrl->max_io_ms)
fctrl->max_io_ms = DISKCIPHER_MAX_IO_MS;
#endif
base->cra_type = &crypto_diskcipher_type;
base->cra_flags = CRYPTO_ALG_TYPE_DISKCIPHER;
return crypto_register_alg(base);
}
void crypto_unregister_diskcipher(struct diskcipher_alg *alg)
{
crypto_unregister_alg(&alg->base);
}
int crypto_register_diskciphers(struct diskcipher_alg *algs, int count)
{
int i, ret;
for (i = 0; i < count; i++) {
ret = crypto_register_diskcipher(algs + i);
if (ret)
goto err;
}
return 0;
err:
for (--i; i >= 0; --i)
crypto_unregister_diskcipher(algs + i);
return ret;
}
void crypto_unregister_diskciphers(struct diskcipher_alg *algs, int count)
{
int i;
for (i = count - 1; i >= 0; --i)
crypto_unregister_diskcipher(algs + i);
}