原文链接

简介

本文主要讲述的是一种动态内存的只读保护机制。

原文

内核开发者可以对想保护的数据设置为read-only权限,借助于MMU来避免恶意攻击者的篡改。kernel目前已经支持只读内存保护,但这些内存必须在操作系统自举完成前被初始化,所以局限性很大。Igor Stoppa的一组patch弥补了这篇空白,他提出一组新API。

已有的只读保护机制,最直观当属const修复符,但它是编译时检查。post-init read-only data mechanism是kernel目前已支持的只读保护机制,它来自于grsecurity patch,所有的数据必须在操作系统引导阶段完成初始化,在此之后则不允许修改。那么操作系统初始化后,如果想保护动态申请的内存该怎么办呢?目前kernel并无此类机制,Igor Stoppa则提出了”protectable memory allocator”(简称pmalloc),核心思想是创建一个pool,所有的只读对象从pool中分配。API如下:

  • 创建pool对象,返回pool句柄:
    #include <linux/pmalloc.h>

    struct pmalloc_pool *pool = pmalloc_create_pool();
  • 从pool中申请内存,返回内存地址,此时可读写:
    void *pmalloc(struct pmalloc_pool *pool, size_t size);
    void *pzalloc(struct pmalloc_pool *pool, size_t size);
    void *pmalloc_array(struct pmalloc_pool *pool, size_t n, size_t size);
    void *pcalloc(struct pmalloc_pool *pool, size_t n, size_t size);
    char *pstrdup(struct pmalloc_pool *pool, const char *s);

上述API与用户态的libc接口很像,其基础接口是pmalloc(),其他都是一些变种。

  • 用户对返回的地址进行初始化,然后设置read-only权限,返回后则不能再进行修改:
 void pmalloc_protect_pool(struct pmalloc_pool *pool);

调用此函数之后,用户可以接着申请内存,即此API修改的是已申请内存相对应的页表权限。由于页表权限都是以4k为单位,所以此处有可能造成内存浪费。

  • 销毁:
 void pmalloc_destroy_pool(struct pmalloc_pool *pool);

(译者注:pmalloc并未提供free()接口,所以作为allocater而言,其实现是比较简单的。当然,它的目的是安全,而不是内存分配。)

pmalloc()是基于vmalloc(),所以不能在原子上下文中使用。需要注意的是,pmalloc是通过修改vmalloc()申请的内存地址页表权限,所以攻击者完全可以绕过此处的地址,而直接通过system memory map来篡改数据。(译者注:有点像栈上的const局部变量,可以通过指针直接修改。)

最后,pmalloc这一组patch还缺乏明确的使用案例,社区也不打算直接merge此类缺少use case的patch,后续待观望。

译者总结

纯从安全角度考虑,pmalloc无法做到绝对安全。如果攻击者已经取得内核态的可写权限,那么可以直接修改页表项获得pmalloc申请对象的可写权限。译者觉得pmalloc机制更适合用于发现kernel自身的代码bug,保留第一案发现场,这一点对于内核开发调试具有很重要的意义。pmalloc不支持free,这一点会限制其使用场景;不过若需支持free,allocator逻辑的复杂度就上去了,有点小题大做。pmalloc还需要更多的use case来证明其价值。