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_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# 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用来记录与显示给用户空间的双向链表的代码已经分析完毕~~

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐