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

    xiaoxiao2021-03-25  20

    初始化处理度量事件 ima_add_digest_entry 显示度量事件 初始化ascii_runtime_measurements startnextshowstop

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

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

    root@vtpm:/sys/kernel/security/ima# uname -r 3.13.11-ckt39 root@vtpm:/sys/kernel/security/ima#

    初始化

    内核中维护度量的双向链表ima_measurements在security/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_HEAD在include/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# ls ascii_runtime_measurements binary_runtime_measurements policy runtime_measurements_count violations root@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用来记录与显示给用户空间的双向链表的代码已经分析完毕~~

    转载请注明原文地址: https://ju.6miu.com/read-300349.html

    最新回复(0)