IMA源码分析——度量事件记录与显示
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用来记录与显示给用户空间的双向链表的代码已经分析完毕~~
更多推荐
所有评论(0)