lineage_kernel_xcoverpro/crypto/diskcipher.c

434 lines
11 KiB
C
Executable File

/*
* 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 <linux/mm_types.h>
#include <linux/fs.h>
#include <linux/fscrypt.h>
#include "internal.h"
#ifdef CONFIG_CRYPTO_DISKCIPHER_DEBUG
#include <crypto/fmp.h>
#define DUMP_MAX 5
enum diskcipher_dbg { /* for DISKCIPHER_DEBUG */
DISKC_API_ALLOC, DISKC_API_FREE, DISKC_API_FREEREQ,
DISKC_API_SETKEY, DISKC_API_CLEARKEY,
DISKC_API_SET, DISKC_API_GET, DISKC_API_GET_DISK,
DISKC_API_CRYPT, DISKC_API_CLEAR,
DISKC_INODE_SYNC_ERR, DISKC_NO_SYNC_ERR,
DISKC_NO_DISKC_ERR, DISKC_NO_KEY_ERR,
DISKC_MERGE_NO, DISKC_MERGE_DM, DISKC_MERGE, DISKC_MERGE_DUN,
DISKC_USER_MAX
};
struct diskc_debug_info {
int cnt[DISKC_USER_MAX][2];
};
static struct diskc_debug_info diskc_dbg;
static void crypto_diskcipher_debug(enum diskcipher_dbg api, bool err)
{
struct diskc_debug_info *dbg = &diskc_dbg;
dbg->cnt[api][err]++;
}
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", "CLEARKEY",
"SET", "GET", "GET-dm",
"CRYPT", "CLEAR",
"err_inode", "err_sync", "err_diskc", "err_no_key",
"no-merge", "merge-dm", "merge", "merge-dun"};
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]);
}
#else
#define disckipher_log_show(a) do { } while (0)
#define crypto_diskcipher_debug(a, b) do { } while (0)
#endif
static int crypto_diskcipher_check(struct bio *bio)
{
struct crypto_diskcipher *ci = NULL;
struct inode *inode = NULL;
struct page *page = NULL;
if (!bio) {
pr_err("%s: doesn't exist bio\n", __func__);
return 0;
}
/* enc without fscrypt */
ci = bio->bi_cryptd;
if (!ci->inode)
return 0;
if (ci->algo == 0)
return 0;
page = bio->bi_io_vec[0].bv_page;
if (!page || PageAnon(page) || !page->mapping || !page->mapping->host || atomic_read(&ci->inode->i_dio_count))
return 0;
if (!page->mapping->host->i_crypt_info)
return 0;
inode = page->mapping->host;
if (ci->inode != inode) {
pr_err("%s: fails to invalid inode\n", __func__);
return -EINVAL;
}
if (!fscrypt_has_encryption_key(inode)) {
pr_err("%s: fails to invalid key\n", __func__);
return -EINVAL;
}
ci = fscrypt_get_bio_cryptd(inode);
if (!ci) {
pr_err("%s: fails to invalid crypto info\n", __func__);
return -EINVAL;
} else if ((bio->bi_cryptd != ci) &&
!(bio->bi_flags & REQ_OP_DISCARD)) {
pr_err("%s: fails to async crypto info\n", __func__);
return -EINVAL;
}
return 0;
}
struct crypto_diskcipher *crypto_diskcipher_get(struct bio *bio)
{
struct crypto_diskcipher *diskc = NULL;
if (!bio || !virt_addr_valid(bio)) {
pr_err("%s: Invalid bio:%pK\n", __func__, bio);
return NULL;
}
if (bio->bi_opf & REQ_CRYPT) {
if (bio->bi_cryptd) {
if (!crypto_diskcipher_check(bio)) {
diskc = bio->bi_cryptd;
} else {
pr_err("%s: fail to check diskcipher bio:%pK\n",
__func__, bio);
diskc = ERR_PTR(-EINVAL);
}
} else {
pr_err("%s: no diskcipher on bio:%pK\n",
__func__, bio);
diskc = ERR_PTR(-EINVAL);
}
}
return diskc;
}
static inline void *bio_has_crypt(struct bio *bio)
{
if (bio && (bio->bi_opf & REQ_CRYPT))
return bio->bi_cryptd;
return NULL;
}
bool crypto_diskcipher_blk_mergeble(struct bio *bio1, struct bio *bio2)
{
if (!bio_has_crypt(bio1) && !bio_has_crypt(bio2))
return true;
if (bio_has_crypt(bio1) == bio_has_crypt(bio2)) {
struct crypto_diskcipher *tfm1 = bio1->bi_cryptd;
struct crypto_diskcipher *tfm2 = bio2->bi_cryptd;
#ifdef CONFIG_CRYPTO_DISKCIPHER_DEBUG
struct inode *inode1 = tfm1->inode;
struct inode *inode2 = tfm2->inode;
if (inode1 != inode2)
panic("%s: no same inode:%pK, %pK, tfm:%pK,%pK, bi_opf:%x,%x\n",
__func__, inode1, inode2, tfm1, tfm2, bio1->bi_opf, bio2->bi_opf);
if ((!inode1 && bio_dun(bio1)) || (!inode2 && bio_dun(bio2)))
panic("%s: inval inode:%pK,%pK, tfm:%pK,%pK, bio:%pK,%pK, bi_opf:%x,%x\n",
__func__, inode1, inode2, tfm1, tfm2, bio1, bio2, bio1->bi_opf, bio2->bi_opf);
#endif
/* no inode for DM-crypt and DM-default-key */
if (!tfm1->inode) {
crypto_diskcipher_debug(DISKC_MERGE_DM, false);
return true;
}
if ((tfm1->ivmode == IV_MODE_DUN) &&
(tfm2->ivmode == IV_MODE_DUN)) {
if (bio_dun(bio1) && bio_dun(bio2) &&
(bio_end_dun(bio1) == bio_dun(bio2))) {
crypto_diskcipher_debug(DISKC_MERGE_DUN, false);
return true;
}
} else if ((tfm1->ivmode == IV_MODE_LBA) &&
(tfm2->ivmode == IV_MODE_LBA)) {
crypto_diskcipher_debug(DISKC_MERGE, false);
return true;
}
crypto_diskcipher_debug(DISKC_MERGE_NO, false);
}
return false;
}
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;
if (dun)
bio->bi_iter.bi_dun = dun;
}
crypto_diskcipher_debug(DISKC_API_SET, false);
}
int crypto_diskcipher_setkey(struct crypto_diskcipher *tfm, const char *in_key,
unsigned int key_len, bool persistent,
const struct inode *inode)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = __crypto_diskcipher_alg(base->__crt_alg);
int ret = -EINVAL;
if (cra)
ret = cra->setkey(tfm, in_key, key_len, persistent);
else
pr_err("%s: doesn't exist cra. base:%pK", __func__, base);
tfm->inode = (struct inode *)inode;
tfm->ivmode = IV_MODE_LBA;
if (inode) {
/* check the filesystem for fscrypt */
if (inode->i_sb) {
if (inode->i_sb->s_type) {
if (!strcmp(inode->i_sb->s_type->name, "f2fs"))
tfm->ivmode = IV_MODE_DUN;
}
}
}
crypto_diskcipher_debug(DISKC_API_SETKEY, ret ? true : false);
return ret;
}
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);
int ret = -EINVAL;
if (cra)
ret = cra->clearkey(tfm);
else
pr_err("%s: doesn't exist cra. base:%pK", __func__, base);
crypto_diskcipher_debug(DISKC_API_CLEARKEY, ret ? true : false);
return ret;
}
int crypto_diskcipher_set_crypt(struct crypto_diskcipher *tfm, void *req)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = NULL;
int ret = -EINVAL;
if (!base) {
pr_err("%s: doesn't exist base. tfm:%pK", __func__, tfm);
goto out;
}
cra = __crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%pK\n", __func__, base);
goto out;
}
ret = cra->crypt(tfm, req);
if (ret)
pr_err("%s fails ret:%d, cra:%pK\n", __func__, ret, cra);
out:
crypto_diskcipher_debug(DISKC_API_CRYPT, ret ? true : false);
return ret;
}
int crypto_diskcipher_clear_crypt(struct crypto_diskcipher *tfm, void *req)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = NULL;
int ret = -EINVAL;
if (!base) {
pr_err("%s: doesn't exist base, tfm:%pK\n", __func__, tfm);
goto out;
}
cra = __crypto_diskcipher_alg(base->__crt_alg);
if (!cra) {
pr_err("%s: doesn't exist cra. base:%pK\n", __func__, base);
goto out;
}
ret = cra->clear(tfm, req);
if (ret)
pr_err("%s fails ret:%d, cra:%pK\n", __func__, ret, cra);
out:
crypto_diskcipher_debug(DISKC_API_CLEAR, ret ? true : false);
return ret;
}
int diskcipher_do_crypt(struct crypto_diskcipher *tfm,
struct diskcipher_test_request *req)
{
struct crypto_tfm *base = crypto_diskcipher_tfm(tfm);
struct diskcipher_alg *cra = __crypto_diskcipher_alg(base->__crt_alg);
int ret = -EINVAL;
if (!cra) {
pr_err("%s: doesn't exist cra. base:%pK\n", __func__, base);
return ret;
}
if (cra->do_crypt)
ret = cra->do_crypt(tfm, req);
if (ret)
pr_err("%s fails ret:%d", __func__, ret);
return ret;
}
static int crypto_diskcipher_init_tfm(struct crypto_tfm *base)
{
struct crypto_diskcipher *tfm = __crypto_diskcipher_cast(base);
struct diskcipher_alg *alg = crypto_diskcipher_alg(tfm);
if (alg->init)
alg->init(tfm);
return 0;
}
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)
{
int alg_name_len;
if (!force) {
crypto_diskcipher_debug(DISKC_API_ALLOC, false);
return crypto_alloc_tfm(alg_name,
&crypto_diskcipher_type, type, mask);
}
alg_name_len = strlen(alg_name);
if (alg_name_len + DISKC_NAME_SIZE < CRYPTO_MAX_ALG_NAME) {
char diskc_name[CRYPTO_MAX_ALG_NAME];
strcpy(diskc_name, alg_name);
strcat(diskc_name, DISKC_NAME);
crypto_diskcipher_debug(DISKC_API_ALLOC, false);
return crypto_alloc_tfm(diskc_name,
&crypto_diskcipher_type, type, mask);
}
crypto_diskcipher_debug(DISKC_API_ALLOC, true);
return NULL;
}
void crypto_free_diskcipher(struct crypto_diskcipher *tfm)
{
crypto_diskcipher_debug(DISKC_API_FREE, false);
crypto_destroy_tfm(tfm, crypto_diskcipher_tfm(tfm));
}
int crypto_register_diskcipher(struct diskcipher_alg *alg)
{
struct crypto_alg *base = &alg->base;
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);
}