通过hijack了解bio

Posted by faxiang1230 on June 13, 2019

通过hijack了解bio

背景

bio是block io,它是一个描述硬盘里面的位置与page cache的页对应关系的数据结构,每个bio对应的 硬盘里面一块连续的位置,每一块硬盘里面连续的位置,可能对应着page cache的多页,或者一页, 所以它里面会有一个bio_vec *bi_io_vec的表。 而每个bio_vec都只能描述一个页内数据的连续的数据的偏移和长度:

image

通过bi_sectors和bi_size来描述硬盘中起始位置和长度。 一个bio的处理:一个bio通常通过submit_bio来提交给设备队列,下面会经过bio聚合,转化成request,磁盘调度队列,块设备驱动,磁盘处理,对于bio来说大部分都是异步,所以处理结束通过bio->bi_end_io来作为callback获取结果,外部的接口使用bio_endio。同步的读写是通过等待callback事件到来从而完成同步读写。

中间贴的只是代码片段,介绍怎么做的,不能直接运行的。获取全部的代码片段,请联系我

目标

进行块设备加密,磁盘落盘的数据是密文,而对于文件系统来说是明文.加密算法因为用过sm4,所以就选用了最简单的sm4(ecb)加解密。

设计

submit_bio是写IO的起手势,所以通过hook submit_bio来进行写加密,保证提交给下面的数据是经过加密的就好了。而对于读IO可以通过在IO完成之后通知给文件系统之前进行解密,可以hook bio_endio来进行数据解密,确保page cache上的数据是经过解密后的明文。

写加密

bio只是管理的数据结构,数据仍然是存放在page cache中的,最终的结果要求是page cache中的所述句仍然是明文,提交给磁盘的数据是密文,所以一块数据是搞不定这件事了,我们需要重新申请一块内存来存放密文数据。 ``` long origin_submit_bio(int rw, struct bio *bio); void hook_submit_bio(int rw, struct bio *bio) { ret = try_crypt_bio(rw, bio); if (ret != 0) { origin_submit_bio(rw, bio); } return; } int bfx_bio_alloc_pages(struct bio *bio, gfp_t gfp) { int i; struct bio_vec *bv;

bio_for_each_segment(bv, bio, i) {
    bv->bv_page = alloc_page(gfp);
    if (!bv->bv_page) {
        while (bv-- != bio->bi_io_vec + bio->bi_idx)
            __free_page(bv->bv_page);
        return -ENOMEM;
    }
}

return 0; }

void bfx_crypt_callback(struct bio *bio, int error) { unsigned int i; struct bio_vec *bv; struct bio *origin = (struct bio *)bio->bi_private;

bio_for_each_segment_all(bv, bio, i) {
    BUG_ON(!bv->bv_page);
    __free_page(bv->bv_page);
    bv->bv_page = NULL;
}

bio_endio(origin, error);

bio_put(bio); } struct bio* bfx_bio_clone(struct bio *origin) {
struct bio *new = NULL;
new = bio_clone(origin, GFP_NOIO);
if (!new)
    return NULL;

if (bfx_bio_alloc_pages(new, GFP_NOIO)) {
    bio_put(new);
    return NULL;
}

new->bi_private = origin;
new->bi_end_io = bfx_crypt_callback;
return new; } int try_crypt_bio(int rw, struct bio *bio) {
sm4_key_t key;
int i, j;                                                                                                                                  
char buffer[SM4_BLOCK_SIZE * 2 + 1];
struct bio_vec *bv;
struct bio *new;

if (!(rw & WRITE)) {
    return -1;
}

new = bfx_bio_clone(bio);
if (!new) {
    BUG();
    return -ENOMEM;
}
new->bi_rw |= rw;
bio->bi_rw |= rw;

sm4_set_key1(&key, mykey, 16);

bio_for_each_segment(bv, bio, i) {
    void *p1 = kmap(bv->bv_page);
    void *p2 = kmap(new->bi_io_vec[i].bv_page);
    /* Encrypt your bio data */
    /*
    for (j = bv->bv_offset; j < bv->bv_offset + bv->bv_len; j+= SM4_BLOCK_SIZE) {
        sm4_do_crypt(key.rkey_enc, (u32 *)(p2 + j), (u32 *)(p1 + j));
    }
    */

    kunmap(bv->bv_page);
    kunmap(new->bi_io_vec[i].bv_page);
}

generic_make_request(new);
return 0; } ``` ### 读解密 相对于写操作,读IO就比较简单了,从磁盘中读取到了密文数据到page cache中,我们只需要在文件系统看到这块page cache前解密就好,不需要倒腾内存,只需要原地解密。

遍历bio指向的内存数据:

void origin_bio_endio(struct bio *bio, int error);
void hook_bio_endio(struct bio *bio, int error)
{
    try_decrypt_bio(bio, error);

    origin_bio_endio(bio, error);                                                                                                           
    return;
}
void try_decrypt_bio(struct bio *bio, int error)
{
    sm4_key_t key;
    int i, j;
    char buffer[SM4_BLOCK_SIZE];
    char *ptr = NULL;
    unsigned long flags;
    struct bio_vec *bv;

    if (bio_data_dir(bio) == WRITE)
        return;

    sm4_set_key1(&key, mykey, 16);

    bio_for_each_segment(bv, bio, i) {
        ptr = bvec_kmap_irq(bv, &flags);
        for (j = 0; j < bv->bv_len; j+= SM4_BLOCK_SIZE) {
            /* Decrypt ptr pointer buffer */
            /*
            sm4_do_crypt(key.rkey_dec, (u32 *)buffer, (u32 *)(ptr + j));
            memcpy(ptr + j, buffer, SM4_BLOCK_SIZE);
            */
        }
        bvec_kunmap_irq(ptr, &flags);
    }   
}

后续改进

因为加解密完全使用cpu来进行,速度肯定比较慢,最终的结果就是IO速率大幅度下降.

一个选择就是使用有硬件加密卡支持的算法,提升速率;

另外一个选择就是启动一个线程,先将bio缓存下来慢慢加解密,为了提升体验,可以提升读IO的优先级,让用户优先看到数据,至于写放在后台慢慢整吧。

参考

https://lwn.net/Articles/26404/
www.eeworld.com.cn/mp/ymc/a52704.jspx
drivers/md/dm-crypt.c