IMA源码分析——度量事件记录与显示

来源:互联网 发布:黑暗之魂1萝莉捏脸数据 编辑:程序博客网 时间:2024/06/02 05:49

    • 初始化
    • 处理度量事件
      • ima_add_digest_entry
    • 显示度量事件
      • 初始化
      • ascii_runtime_measurements
        • start
        • next
        • show
        • stop

IMA(Integrity Measurement Architecture)是内核中一个用来度量所有二进制加载、模块加载、动态链接库加载的模块,以用来记录平台的完整性证据

本文对IMA度量事件的记录与显示部分进行源码分析,本文环境为ubuntu14.04.4,利用apt-get install linux-source后编译进入的内核版本为:

root@vtpm:/sys/kernel/security/ima# uname -r3.13.11-ckt39root@vtpm:/sys/kernel/security/ima# 

初始化

内核中维护度量的双向链表ima_measurementssecurity/integrity/ima/ima.h进行了extern引用:

extern struct list_head ima_measurements;   /* list of all measurements */

真正的定义在security/integrity/ima/ima_queue.c

LIST_HEAD(ima_measurements);    /* list of all measurements */

LIST_HEAD在于定义双向链表结构体,list_head结构体有next与prev两个指针,而LIST_HEAD_INIT则是将两个指针全部指向自身

struct list_head {    struct list_head *next, *prev;};#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \    struct list_head name = LIST_HEAD_INIT(name)

处理度量事件

当产生度量事件后,内核将一步步地调用到ima_queue.c::ima_add_template_entry()。该函数首先判断digest是否已经存在,若已经存在则表明是未被修改的程序等再次加载,因而可以忽略。另外,关于为度量事件建立hash的代码,我们之后再来分析。。。

if (!violation) {    memcpy(digest, entry->digest, sizeof digest);    if (ima_lookup_digest_entry(digest)) {        audit_cause = "hash_exists";        result = -EEXIST;        goto out;    }}result = ima_add_digest_entry(entry);if (result < 0) {    audit_cause = "ENOMEM";    audit_info = 0;    goto out;}

entry的类型是struct ima_template_entry *,实际上记录的度量事件的信息(如度量值等等),其定义如下:

/* IMA template descriptor definition */struct ima_template_desc {    char *name;    char *fmt;    int num_fields;    struct ima_template_field **fields;};struct ima_template_entry {    u8 digest[TPM_DIGEST_SIZE]; /* sha1 or md5 measurement hash */    struct ima_template_desc *template_desc; /* template descriptor */    u32 template_data_len;    struct ima_field_data template_data[0]; /* template related data */};struct ima_queue_entry {    struct hlist_node hnext;    /* place in hash collision list */    struct list_head later;     /* place in ima_measurements list */    struct ima_template_entry *entry;};

ima_add_digest_entry

再来看该函数ima_add_digest_entry函数,该函数输入参数为度量事件(entry),函数执行时首先将entry放入ima_queue_entry结构体中:

struct ima_queue_entry *qe;unsigned int key;qe = kmalloc(sizeof(*qe), GFP_KERNEL);if (qe == NULL) {    pr_err("IMA: OUT OF MEMORY ERROR creating queue entry.\n");    return -ENOMEM;}qe->entry = entry;

再借助ima_queue_entry结构体的第二个参数struct list_head later与记录所有度量事件的双向链表ima_measurements建立联系:

INIT_LIST_HEAD(&qe->later);list_add_tail_rcu(&qe->later, &ima_measurements);

INIT_LIST_HEADinclude/linux/list.h中,用来将list的两个指针全部指向自身:

static inline void INIT_LIST_HEAD(struct list_head *list){    list->next = list;    list->prev = list;}

list_add_tail_rcu类似于include/linux/list.h中的list_add_tail

static inline void list_add_tail(struct list_head *new, struct list_head *head){    __list_add(new, head->prev, head);}static inline void __list_add(struct list_head *new,                  struct list_head *prev,                  struct list_head *next){    next->prev = new;    new->next = next;    new->prev = prev;    prev->next = new;}

目的是将new插入到双向链表head之前,由于是双向链表,因此,对于函数ima_add_digest_entry来说,实际上就是将qe->later(也就是当前的度量事件entry)加入到ima_measurements的最后。从而形成了一个队列

显示度量事件

在用户空间IMA的的文件在/sys/kernel/security/ima,其中ascii_runtime_measurements即是以ascii显示的所有度量日志

root@vtpm:/sys/kernel/security/ima# lsascii_runtime_measurements  binary_runtime_measurements  policy  runtime_measurements_count  violationsroot@vtpm:/sys/kernel/security/ima# 

初始化

回到内核的IMA源码,ima的初始化函数在security/ima/ima_init.c::ima_init()函数中

int __init ima_init(void){    ...    return ima_fs_init();}

函数最后调用了ima_fs_init(),该函数在ima_fs.c中,在这个函数中创建了用户空间的/sys/kernel/security/ima目录以及其下的所有文件

int __init ima_fs_init(void){    ima_dir = securityfs_create_dir("ima", NULL);    binary_runtime_measurements =        securityfs_create_file("binary_runtime_measurements",                   S_IRUSR | S_IRGRP, ima_dir, NULL,                   &ima_measurements_ops);    ascii_runtime_measurements =        securityfs_create_file("ascii_runtime_measurements",                   S_IRUSR | S_IRGRP, ima_dir, NULL,                   &ima_ascii_measurements_ops);    ...    ima_policy = securityfs_create_file("policy",                        S_IWUSR,                        ima_dir, NULL,                        &ima_measure_policy_ops);

我们具体来看当在用户空间cat ascii_runtime_measurements时,内核对应的代码

ascii_runtime_measurements

在ima_fs_init中,我们知道文件ascii_runtime_measurements对应的file_operations结构体为ima_ascii_measurements_ops

static const struct file_operations ima_ascii_measurements_ops = {    .open = ima_ascii_measurements_open,    .read = seq_read,    .llseek = seq_lseek,    .release = seq_release,};

struct file_operations是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针的集合,每个被打开的文件都对应于一系列的操作,用来执行一系列的系统调用

对于文件ascii_runtime_measurements,四个操作里只有open被指向到其他函数(ima_ascii_measurements_open),其他操作均采用默认的seq操作

ima_ascii_measurements_open将对该文件的open定向到ima_ascii_measurements_seqops,这个seq_operations定义了open操作时,的start、next、stop、show操作

static const struct seq_operations ima_ascii_measurements_seqops = {    .start = ima_measurements_start,    .next = ima_measurements_next,    .stop = ima_measurements_stop,    .show = ima_ascii_measurements_show};static int ima_ascii_measurements_open(struct inode *inode, struct file *file){    return seq_open(file, &ima_ascii_measurements_seqops);}

seq_operations针对的是序列文件(seq_file),使用它能够将Linux内核里面常用的数据结构通过文件(主要关注proc文件)导出到用户空间。使用Seq_file,用户必须抽象出一个链接对象,然后可以依次遍历这个链接对象。这个链接对象可以是链表,数组,哈希表等等。Seq_file必须实现四个操作函数:start(), next(), show(), stop()。

  • start():
    主要实现初始化工作,在遍历一个链接对象开始时,调用。返回一个链接对象的偏移或者SEQ_START_TOKEN(表征这是所有循环的开始)。出错返回ERR_PTR。该函数返回值就是show()、next()函数第二个参数。

  • stop():
    当所有链接对象遍历结束时调用。主要完成一些清理工作。

  • next():
    用来在遍历中寻找下一个链接对象。返回下一个链接对象或者NULL(遍历结束)。

  • show():
    对遍历对象进行操作的函数。主要是调用seq_printf(), seq_puts()之类的函数,打印出这个对象节点的信息。

用户态调用一次读操作,seq_file流程为:该函数调用struct seq_operations结构提顺序为:start->show->next->show…->next->show->next->stop->start->stop来读取文件

start

我们首先来看seq_operarion中的start函数:

static void *ima_measurements_start(struct seq_file *m, loff_t *pos){    loff_t l = *pos;    struct ima_queue_entry *qe;    /* we need a lock since pos could point beyond last element */    rcu_read_lock();    list_for_each_entry_rcu(qe, &ima_measurements, later) {        if (!l--) {            rcu_read_unlock();            return qe;        }    }    rcu_read_unlock();    return NULL;}

list_for_each_entry_rcu类似于list_for_each_entry。以下是与之相关的宏定义:

#define container_of(ptr, type, member) ({          \    const typeof(((type *)0)->member)*__mptr = (ptr);    \             (type *)((char *)__mptr - offsetof(type, member)); })#define list_entry(ptr, type, member) \    container_of(ptr, type, member)#define list_next_entry(pos, member) \    list_entry((pos)->member.next, typeof(*(pos)), member)#define list_first_entry(ptr, type, member) \    list_entry((ptr)->next, type, member)#define list_for_each_entry(pos, head, member)              \    for (pos = list_first_entry(head, typeof(*pos), member);    \         &pos->member != (head);                    \         pos = list_next_entry(pos, member))
  • list_for_each_entry,传入的形参中,member为later,head为&ima_measurements, pos为qe

  • 调用list_first_entry时,传入的形参中,member为later,ptr指向ima_measurement件,type为typeof(pos),也就是struct ima_queue_entry

  • 调用list_entry时,传入的形参中,member为later,ptr指向ima_measurement的的第一个度量事件,type为struct ima_queue_entry*

  • container_of的目的在于通过传入的ima_measurement的的第一个度量事件,找到这个事件对应的ima_queue_entry指针,并且返回

依次返回后,list_for_each_entry_rcu(qe, &ima_measurements, later) {相当于返回一个for循环语句,对ima_measurement进行遍历,而循环值则是对应的每个ima_queue_entry指针,并用qe指向这个指针

这样start函数相当于返回了ima_measurement保存的第*pos个度量事件的ima_queue_entry结构体

next

第二个参数为目前正在show的度量事件,next调用list_entry_rcu,并将形参定义为qe->later.next找到接下来的度量事件

若找到的度量事件已经达到ima_measurements的头,则返回空

static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos){    struct ima_queue_entry *qe = v;    /* lock protects when reading beyond last element     * against concurrent list-extension     */    rcu_read_lock();    qe = list_entry_rcu(qe->later.next, struct ima_queue_entry, later);    rcu_read_unlock();    (*pos)++;    return (&qe->later == &ima_measurements) ? NULL : qe;}

show

show是将每个度量事件打印到用户空间,实际上是将start或者是next传送给它的qe按照一定格式打印

static int ima_measurements_show(struct seq_file *m, void *v){    /* the list never shrinks, so we don't need a lock here */    struct ima_queue_entry *qe = v;    struct ima_template_entry *e;    int namelen;    u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX;    bool is_ima_template = false;    int i;    /* get entry */    e = qe->entry;    if (e == NULL)        return -1;    /*     * 1st: PCRIndex     * PCR used is always the same (config option) in     * little-endian format     */    ima_putc(m, &pcr, sizeof pcr);    /* 2nd: template digest */    ima_putc(m, e->digest, TPM_DIGEST_SIZE);    /* 3rd: template name size */    namelen = strlen(e->template_desc->name);    ima_putc(m, &namelen, sizeof namelen);

stop

stop函数函数体为空,不需要做任何操作


至此,IMA用来记录与显示给用户空间的双向链表的代码已经分析完毕~~

0 0
原创粉丝点击