459 lines
12 KiB
C
459 lines
12 KiB
C
|
#include "dm-verity.h"
|
||
|
#include "dm-verity-fec.h"
|
||
|
#include "verity_handle.h"
|
||
|
#if defined (CONFIG_DM_VERITY_HW_RETRY)
|
||
|
#include <linux/mtd/hisi_nve_interface.h>
|
||
|
#include <linux/mtd/hisi_nve_number.h>
|
||
|
#endif
|
||
|
#include <linux/delay.h>
|
||
|
|
||
|
#define DM_MSG_PREFIX "oem_verity"
|
||
|
|
||
|
#if defined (CONFIG_HUAWEI_DSM)
|
||
|
#include <linux/jiffies.h>
|
||
|
#include <dsm/dsm_pub.h>
|
||
|
#include <linux/ctype.h>
|
||
|
|
||
|
static struct dsm_dev dm_dsm_dev = {
|
||
|
.name = "dsm_dm_verity",
|
||
|
.device_name = NULL,
|
||
|
.ic_name = NULL,
|
||
|
.module_name = NULL,
|
||
|
.fops = NULL,
|
||
|
.buff_size = 1024,
|
||
|
};
|
||
|
|
||
|
static unsigned long timeout;
|
||
|
static struct dsm_client *dm_dsm_dclient = NULL;
|
||
|
static unsigned long err_count;
|
||
|
#define DSM_REPORT_INTERVAL (1)
|
||
|
#define DM_VERITY_MAX_PRINT_ERRS 20
|
||
|
#define HASH_ERR_VALUE 1
|
||
|
#define ROW_DATA_LENGTH 16
|
||
|
#define ROW_DATA_PER_HEX_LENGTH 3
|
||
|
|
||
|
enum info_type {
|
||
|
CE_INFO = 0,
|
||
|
FEC_INFO,
|
||
|
REREAD_INFO,
|
||
|
REREAD_ERR,
|
||
|
};
|
||
|
|
||
|
void verity_dsm_init(void){
|
||
|
|
||
|
if (!dm_dsm_dclient) {
|
||
|
dm_dsm_dclient = dsm_register_client(&dm_dsm_dev);
|
||
|
if (NULL == dm_dsm_dclient) {
|
||
|
pr_err("[%s]dsm_register_client register fail.\n", __func__);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
timeout = jiffies;
|
||
|
}
|
||
|
|
||
|
static void verity_dsm(struct dm_verity *v, enum verity_block_type type,
|
||
|
unsigned long long block, int error_no, enum info_type sub_err)
|
||
|
{
|
||
|
const char *type_str = "";
|
||
|
const char *last_ops = "";
|
||
|
const char *current_ops = "";
|
||
|
char devname[BDEVNAME_SIZE+1];
|
||
|
memset(devname, 0x00, BDEVNAME_SIZE+1);
|
||
|
bdevname(v->data_dev->bdev, devname);
|
||
|
switch (type) {
|
||
|
case DM_VERITY_BLOCK_TYPE_DATA:
|
||
|
type_str = "data";
|
||
|
break;
|
||
|
case DM_VERITY_BLOCK_TYPE_METADATA:
|
||
|
type_str = "metadata";
|
||
|
break;
|
||
|
default:
|
||
|
/*go on, and we must make sure no execute this case*/
|
||
|
pr_err("unknown type\n");
|
||
|
}
|
||
|
|
||
|
if (time_after(jiffies, timeout)) {
|
||
|
if (!dsm_client_ocuppy(dm_dsm_dclient)) {
|
||
|
dsm_client_record(dm_dsm_dclient, "%s: %s block %d is corrupted, dmd error num %d sub %d;version:1.0\n",
|
||
|
devname, type_str, block, error_no, (int)sub_err);
|
||
|
dsm_client_notify(dm_dsm_dclient, error_no);
|
||
|
}
|
||
|
|
||
|
timeout = jiffies + DSM_REPORT_INTERVAL*HZ;
|
||
|
}
|
||
|
|
||
|
switch (sub_err) {
|
||
|
case CE_INFO:
|
||
|
last_ops = "CE hash fail,";
|
||
|
current_ops = "soft hash ok,";
|
||
|
break;
|
||
|
case FEC_INFO:
|
||
|
last_ops = "hash verify fail,";
|
||
|
current_ops = "fec correct success,";
|
||
|
break;
|
||
|
case REREAD_INFO:
|
||
|
last_ops = "hash verify fail before reread,";
|
||
|
current_ops = "hash verify ok after reread,";
|
||
|
break;
|
||
|
case REREAD_ERR:
|
||
|
last_ops = "hash verify fail before reread,";
|
||
|
current_ops = "hash verify fail after reread,";
|
||
|
break;
|
||
|
default:
|
||
|
/*go on, and we must make sure no execute this case*/
|
||
|
pr_err("unknown sub_err\n");
|
||
|
}
|
||
|
pr_err("[check image]:%s %s %s block= %llu,block name = %s\n", last_ops, current_ops,
|
||
|
type_str, block, devname);
|
||
|
}
|
||
|
|
||
|
#if defined (CONFIG_DM_VERITY_HW_RETRY)
|
||
|
extern int get_dsm_notify_flag(void);
|
||
|
|
||
|
#define DM_MAX_ERR_COUNT 4
|
||
|
static int verity_read_write_nv(char* name, int nv_number, int opcode, char value)
|
||
|
{
|
||
|
struct hisi_nve_info_user nve;
|
||
|
int ret;
|
||
|
|
||
|
if (!name || (opcode != NV_READ && opcode != NV_WRITE)) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
memset(&nve, 0, sizeof(nve));
|
||
|
strncpy(nve.nv_name, name, strlen(name)+1);
|
||
|
nve.nv_number = nv_number;
|
||
|
nve.valid_size = 1;
|
||
|
nve.nv_operation = opcode;
|
||
|
|
||
|
if (opcode == NV_WRITE) {
|
||
|
nve.nv_data[0] = value;
|
||
|
}
|
||
|
ret = hisi_nve_direct_access(&nve);
|
||
|
if (ret) {
|
||
|
DMERR("nve ops fail! nv_name=%s,nv_number=%d,opcode=%d",name,nv_number,opcode);
|
||
|
return -1;
|
||
|
}
|
||
|
if (opcode == NV_READ) {
|
||
|
return (int)nve.nv_data[0];
|
||
|
}
|
||
|
return ret;
|
||
|
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
/* just make static checker happy */
|
||
|
static void hard_hash_fail_verity_dsm(struct dm_verity *v, enum verity_block_type type,
|
||
|
unsigned long long block, int error_no, enum info_type sub_err)
|
||
|
{
|
||
|
#if defined (CONFIG_DM_VERITY_HW_RETRY)
|
||
|
if (get_dsm_notify_flag()) {
|
||
|
verity_dsm(v, type, block, error_no, sub_err);
|
||
|
}
|
||
|
verity_read_write_nv("HWHASH", NVE_HW_HASH_ERR_NUM, NV_WRITE, HASH_ERR_VALUE);
|
||
|
#else
|
||
|
verity_dsm(v, type, block, error_no, sub_err);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void print_block_data(unsigned long long blocknr, unsigned char *data_to_dump,
|
||
|
int start, int len)
|
||
|
{
|
||
|
int i, j;
|
||
|
int bh_offset = (start / ROW_DATA_LENGTH) * ROW_DATA_LENGTH;
|
||
|
char row_data[ROW_DATA_LENGTH+1];
|
||
|
char row_hex[ROW_DATA_LENGTH * ROW_DATA_LENGTH + 1];
|
||
|
char ch;
|
||
|
memset(row_data, 0x00, ROW_DATA_LENGTH+1);
|
||
|
memset(row_hex, 0x00, ROW_DATA_LENGTH * ROW_DATA_LENGTH + 1);
|
||
|
|
||
|
if (err_count >= DM_VERITY_MAX_PRINT_ERRS)
|
||
|
return;
|
||
|
|
||
|
err_count++;
|
||
|
|
||
|
pr_err(" block error# : %llu, start offset(byte) : %d\n", blocknr, start);
|
||
|
pr_err("printing Hash dump %dbyte\n", len);
|
||
|
pr_err("-------------------------------------------------\n");
|
||
|
|
||
|
for (i = 0; i < (len + ROW_DATA_LENGTH - 1) / ROW_DATA_LENGTH; i++) {
|
||
|
for (j = 0; j < ROW_DATA_LENGTH; j++) {
|
||
|
ch = *(data_to_dump + bh_offset + j);
|
||
|
if (start <= bh_offset + j
|
||
|
&& start + len > bh_offset + j) {
|
||
|
|
||
|
if (isascii(ch) && isprint(ch))
|
||
|
sprintf(row_data + j, "%c", ch);
|
||
|
else
|
||
|
sprintf(row_data + j, ".");
|
||
|
|
||
|
sprintf(row_hex + (j * ROW_DATA_PER_HEX_LENGTH), "%2.2x ", ch);
|
||
|
} else {
|
||
|
sprintf(row_data + j, " ");
|
||
|
sprintf(row_hex + (j * ROW_DATA_PER_HEX_LENGTH), "-- ");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pr_err("0x%4.4x : %s | %s\n"
|
||
|
, bh_offset, row_hex, row_data);
|
||
|
bh_offset += ROW_DATA_LENGTH;
|
||
|
}
|
||
|
pr_err("---------------------------------------------------\n");
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* OEM define Handle verification errors.
|
||
|
*/
|
||
|
#if defined (CONFIG_DM_VERITY_HW_RETRY)
|
||
|
int oem_verity_handle_err(struct dm_verity *v)
|
||
|
{
|
||
|
int value = verity_read_write_nv("VMODE", NVE_VERIFY_MODE_NUM,NV_READ, (char)0);
|
||
|
if (0 > value) {
|
||
|
pr_err("read verify mode nve fail!\n");
|
||
|
/* we need pay attention on this case */
|
||
|
return 0;
|
||
|
} else if (DM_MAX_ERR_COUNT == value) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (0 == v->verify_failed_flag) {
|
||
|
if (DM_MAX_ERR_COUNT <= (value ++)) {
|
||
|
value = DM_MAX_ERR_COUNT;
|
||
|
}
|
||
|
if (verity_read_write_nv("VMODE", NVE_VERIFY_MODE_NUM, NV_WRITE, (char)value)) {
|
||
|
pr_err("wirte verify mode nve fail!\n");
|
||
|
}
|
||
|
v->verify_failed_flag = 1;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
int verity_hash_sel_sha(struct dm_verity *v, struct shash_desc *desc,
|
||
|
const u8 *data, size_t len, u8 *digest, u32 count)
|
||
|
{
|
||
|
int r;
|
||
|
|
||
|
r = verity_hash_init(v, desc, count);
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
|
||
|
r = verity_hash_update(v, desc, data, len);
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
|
||
|
return verity_hash_final(v, desc, digest);
|
||
|
}
|
||
|
/*
|
||
|
*compute digest for the DATA TYPE and the METADATA TYPE,
|
||
|
*and verify
|
||
|
*/
|
||
|
static int verity_soft_hash(struct dm_verity *v, struct dm_verity_io *io,
|
||
|
enum verity_block_type type, u8 *data,
|
||
|
struct bvec_iter *iter, u8 *want_digest) {
|
||
|
int r;
|
||
|
if(DM_VERITY_BLOCK_TYPE_DATA == type) {
|
||
|
/*we get data from iter for 'DATA TYPE'*/
|
||
|
r = verity_hash_init(v, verity_io_hash_desc(v, io), NO_FIRST_TIME_HASH_VERIFY);
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
|
||
|
r = verity_for_bv_block(v, io, iter, verity_bv_hash_update);
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
|
||
|
r = verity_hash_final(v, verity_io_hash_desc(v, io), verity_io_real_digest(v, io));
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
}else {
|
||
|
/*we get data from vmalloc for 'METADATA TYPE'. And the default type is 'METADATA TYPE'*/
|
||
|
r = verity_hash_sel_sha(v, verity_io_hash_desc(v, io),
|
||
|
data, 1 << v->hash_dev_block_bits,
|
||
|
verity_io_real_digest(v, io), NO_FIRST_TIME_HASH_VERIFY);
|
||
|
if (unlikely(r < 0))
|
||
|
return r;
|
||
|
}
|
||
|
if (likely(memcmp(verity_io_real_digest(v, io), want_digest,
|
||
|
v->digest_size) == 0)) {
|
||
|
return 0;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
for copy data to iter,
|
||
|
we must pay attention to that the iter will be changed.
|
||
|
*/
|
||
|
static int dataops_for_bv_block(struct dm_verity *v, struct dm_verity_io *io,
|
||
|
struct bvec_iter *iter,u8 *data,
|
||
|
int (*process)(u8 *dest, u8 *src,size_t len))
|
||
|
{
|
||
|
unsigned todo = 1 << v->data_dev_block_bits;
|
||
|
unsigned offset = 0;
|
||
|
struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size);
|
||
|
|
||
|
do {
|
||
|
int r;
|
||
|
u8 *page;
|
||
|
unsigned len;
|
||
|
struct bio_vec bv = bio_iter_iovec(bio, *iter);
|
||
|
|
||
|
page = kmap_atomic(bv.bv_page);
|
||
|
len = bv.bv_len;
|
||
|
|
||
|
if (likely(len >= todo))
|
||
|
len = todo;
|
||
|
|
||
|
r = process(page + bv.bv_offset, data + offset, len);
|
||
|
kunmap_atomic(page);
|
||
|
|
||
|
if (r != 0)
|
||
|
return r;
|
||
|
|
||
|
bio_advance_iter(bio, iter, len);
|
||
|
|
||
|
todo -= len;
|
||
|
offset +=len;
|
||
|
} while (todo);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int verity_memcpy(u8* dest,u8* src,size_t len) {
|
||
|
memcpy(dest, src, len);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct dm_bufio_client *
|
||
|
read_data_from_ufs_emmc(struct dm_verity *v, struct dm_verity_io *io, struct dm_buffer **bp,
|
||
|
u8 **data, sector_t block, enum verity_block_type type) {
|
||
|
struct dm_bufio_client *bufio;
|
||
|
unsigned verity_block_size;
|
||
|
struct block_device *bdev;
|
||
|
/*we set param for different type.And the default type is "METADATA TYPE"*/
|
||
|
if (DM_VERITY_BLOCK_TYPE_DATA == type) {
|
||
|
verity_block_size = 1 << v->data_dev_block_bits;
|
||
|
bdev = v->data_dev->bdev;
|
||
|
} else {
|
||
|
verity_block_size = 1 << v->hash_dev_block_bits;
|
||
|
bdev = v->hash_dev->bdev;
|
||
|
}
|
||
|
|
||
|
bufio = dm_bufio_client_create(bdev,
|
||
|
verity_block_size, 1, 0,
|
||
|
NULL, NULL);
|
||
|
if (IS_ERR(bufio)) {
|
||
|
return NULL;
|
||
|
}
|
||
|
*data = dm_bufio_read(bufio, block, bp);
|
||
|
if (unlikely(IS_ERR(*data))) {
|
||
|
pr_err("read a null\n");
|
||
|
}
|
||
|
return bufio;
|
||
|
}
|
||
|
/*
|
||
|
*compute digest for the DATA TYPE and the METADATA TYPE,
|
||
|
*and verify,
|
||
|
*when the data verify seccussfuly,copy data to the memory.
|
||
|
*/
|
||
|
static int verify_hash(struct dm_verity *v, struct dm_verity_io *io,
|
||
|
u8* data, u8* metadata,struct bvec_iter *iter, u8* want_digest, enum verity_block_type type) {
|
||
|
int r;
|
||
|
unsigned verity_block_size;
|
||
|
/*we set param for different type.And the default type is "METADATA TYPE"*/
|
||
|
if (DM_VERITY_BLOCK_TYPE_DATA == type) {
|
||
|
verity_block_size = 1 << v->data_dev_block_bits;
|
||
|
} else {
|
||
|
verity_block_size = 1 << v->hash_dev_block_bits;
|
||
|
}
|
||
|
|
||
|
r = verity_hash_sel_sha(v, verity_io_hash_desc(v, io),
|
||
|
data, verity_block_size,
|
||
|
verity_io_real_digest(v, io),NO_FIRST_TIME_HASH_VERIFY);
|
||
|
if (unlikely(r < 0)) {
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
r = memcmp(verity_io_real_digest(v, io), want_digest, v->digest_size);
|
||
|
|
||
|
/*if the data is valid, copy the data*/
|
||
|
if(0 == r && (DM_VERITY_BLOCK_TYPE_METADATA == type)) {
|
||
|
memcpy(metadata, data, verity_block_size);
|
||
|
} else if(0 == r && (DM_VERITY_BLOCK_TYPE_DATA == type)) {
|
||
|
dataops_for_bv_block(v, io, iter, data, verity_memcpy);
|
||
|
}
|
||
|
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
static int reread_data_for_verify(struct dm_verity *v, struct dm_verity_io *io,
|
||
|
sector_t block, u8* metadata,struct bvec_iter *iter, u8* want_digest, enum verity_block_type type) {
|
||
|
struct dm_bufio_client *bufio;
|
||
|
struct dm_buffer *buf;
|
||
|
u8 *real_data;
|
||
|
int r;
|
||
|
bufio = read_data_from_ufs_emmc(v, io, &buf, &real_data, block, type);
|
||
|
if (NULL == bufio) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (unlikely(IS_ERR(real_data))) {
|
||
|
r = 1;
|
||
|
goto release_ret;
|
||
|
}
|
||
|
|
||
|
r = verify_hash(v, io, real_data, metadata, iter, want_digest, type);
|
||
|
|
||
|
#if defined (CONFIG_HUAWEI_DSM)
|
||
|
if (0 == r) {
|
||
|
verity_dsm(v, type, block, DSM_DM_VERITY_ERROR_NO, REREAD_INFO);
|
||
|
} else {
|
||
|
verity_dsm(v, type, block, DSM_DM_VERITY_ERROR_NO, REREAD_ERR);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
release_ret:
|
||
|
if (buf) {
|
||
|
dm_bufio_release(buf);
|
||
|
}
|
||
|
if(bufio) {
|
||
|
dm_bufio_client_destroy(bufio);
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
int oem_verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io, sector_t block, u8* metadata,
|
||
|
struct bvec_iter* iter, struct bvec_iter* start, struct bvec_iter* start1,
|
||
|
u8* want_digest, enum verity_block_type type) {
|
||
|
int r = 0;
|
||
|
|
||
|
if(verity_soft_hash(v, io, type, metadata, iter, want_digest) == 0) {
|
||
|
#if defined (CONFIG_HUAWEI_DSM)
|
||
|
hard_hash_fail_verity_dsm(v, type, block, DSM_DM_VERITY_CE_ERROR_NO, CE_INFO);
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (0 == verity_fec_decode(v, io, type,
|
||
|
block, metadata, start)) {
|
||
|
#if defined (CONFIG_HUAWEI_DSM)
|
||
|
verity_dsm(v, type, block, DSM_DM_VERITY_FEC_INFO_NO, FEC_INFO);
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
r = reread_data_for_verify(v, io, block, metadata, start1, want_digest, type);
|
||
|
#if defined (CONFIG_HUAWEI_DSM)
|
||
|
if (0 != r) {
|
||
|
print_block_data((unsigned long long)(block),
|
||
|
(unsigned char *)verity_io_real_digest(v, io),
|
||
|
0, v->digest_size);
|
||
|
print_block_data((unsigned long long)(block),
|
||
|
(unsigned char *)want_digest,
|
||
|
0, v->digest_size);
|
||
|
}
|
||
|
#endif
|
||
|
return r;
|
||
|
}
|