作者:wzt
原文鏈接:https://mp.weixin.qq.com/s/Ua4CV3Vn8piw6z6BrpJyCQ

Ios13增加了判斷對象是否屬于zone的安全檢查,這將導致以前通過偽造內核對象的漏洞利用手段變得困難了很多,比如ipc_port,偽造的對象通常來自于用戶空間,那么在引用這個對象時,xnu引入了zone_require、zone_id_require、zone_owns三個函數來做對象合法性檢查。

osfmk/kern/zalloc.c:
void
zone_require(zone_t zone, void *addr)
{
        if (__probable(from_general_submap(addr, zone_elem_size(zone)) &&[1]
            (zone_has_index(zone, zone_native_meta_from_addr(addr)->zm_index)))) {  [2]
                return;
        }

#if CONFIG_GZALLOC
        if (__probable(gzalloc_enabled())) {
                return;
        }
#endif
        zone_require_panic(zone, addr);[3]
}

[1]處的from_general_submap判斷對象的地址是否在整個大的有效范圍內。

#define from_general_submap(addr, size) \
        zone_range_contains(&zone_info.zi_general_range, (vm_offset_t)(addr), size)

#define zone_range_load(r, rmin, rmax) \
        ({ rmin = (r)->min_address; rmax = (r)->max_address; })
#endif

__header_always_inline bool
zone_range_contains(const struct zone_map_range *r, vm_offset_t addr, vm_offset_t size)
{
        vm_offset_t rmin, rmax;

        zone_range_load(r, rmin, rmax);
        return (addr >= rmin) & (addr + size >= rmin) & (addr + size <= rmax);
}

[2] 處的zone_has_index才是判斷對象是否屬于某個特定的zone。通過zone_native_meta_from_addr函數將對象地址轉為對應的meta數據結構,meta的zm_index指示的是meta對應的zone在zone_array數組里的索引,這樣通過索引加上zone_array數組地址就能判斷是否指向了正確的zone地址。

static inline bool

zone_has_index(zone_t z, zone_id_t zid)
{
        return zone_array + zid == z;
}

__abortlike
static void
zone_require_panic(zone_t zone, void *addr)
{
        uint32_t zindex;
        zone_t other;

        if (!from_zone_map(addr, zone_elem_size(zone))) {
                panic("zone_require failed: address not in a zone (addr: %p)", addr);
        }

        zindex = zone_native_meta_from_addr(addr)->zm_index;
        other = &zone_array[zindex];
        if (zindex >= os_atomic_load(&num_zones, relaxed) || !other->z_self) {
                panic("zone_require failed: invalid zone index %d "
                    "(addr: %p, expected: %s%s)", zindex,
                    addr, zone_heap_name(zone), zone->z_name);
        } else {
                panic("zone_require failed: address in unexpected zone id %d (%s%s) "
                    "(addr: %p, expected: %s%s)",
                    zindex, zone_heap_name(other), other->z_name,
                    addr, zone_heap_name(zone), zone->z_name);
        }
}

當比對失敗時,[3]處調用了zone_require_panic來試圖分析是哪種情況造成的并panic系統。

zone_require是zalloc內存分配器提供的kpi接口, 當其他內核子系統需要判斷某個object是否合法時要主動調用,因為如果在內存分配器的alloc或free路徑里在去判斷對象合法性時為時已晚,漏洞攻擊已經完成了。

Ios13也是類似的邏輯:

同時還可以發現, 最新的xnu內存分配器對對象、meta、zone的合法性做了極其嚴格的檢查,這些優秀的漏洞緩解方法需要移植到linux中去。

static struct zone_page_metadata *

zone_allocated_element_resolve(zone_t zone, vm_offset_t addr,
    vm_offset_t *pagep, zone_addr_kind_t *kindp)
{
        struct zone_page_metadata *meta;
        zone_addr_kind_t kind;
        vm_offset_t page;
        vm_offset_t esize = zone_elem_size(zone); 

        kind = zone_addr_kind(addr, esize);
        page = trunc_page(addr);
        meta = zone_meta_from_addr(addr, kind); 

        if (kind == ZONE_ADDR_NATIVE) {
                if (meta->zm_secondary_page) {
                        if (meta->zm_percpu) {
                                zone_invalid_element_addr_panic(zone, addr);
                        }
                        page -= ptoa(meta->zm_page_count);
                        meta -= meta->zm_page_count;
                }

        } else if (!zone->allows_foreign) {
                zone_page_metadata_foreign_confusion_panic(zone, addr);
#if __LP64__
        } else if (!from_foreign_range(addr, esize)) {
                zone_invalid_foreign_addr_panic(zone, addr);
#else
        } else if (!pmap_kernel_va(addr)) {
                zone_invalid_element_addr_panic(zone, addr);
#endif
        }

        if (!zone_allocated_element_offset_is_valid(zone, addr, page, kind)) {
                zone_invalid_element_addr_panic(zone, addr);
        }

        if (!zone_has_index(zone, meta->zm_index)) {
                zone_page_metadata_index_confusion_panic(zone, addr, meta);
        }
        if (kindp) {
                *kindp = kind;
        }

        if (pagep) {
                *pagep = page;
        }

        return meta;
}

zone_metadata_corruption(zone_t zone, struct zone_page_metadata *meta,
    const char *kind)
{
        panic("zone metadata corruption: %s (meta %p, zone %s%s)",
            kind, meta, zone_heap_name(zone), zone->z_name);
}

zone_invalid_element_addr_panic(zone_t zone, vm_offset_t addr)
{
        panic("zone element pointer validation failed (addr: %p, zone %s%s)",
            (void *)addr, zone_heap_name(zone), zone->z_name);
}

zone_page_metadata_index_confusion_panic(zone_t zone, vm_offset_t addr,
    struct zone_page_metadata *meta)
{
        panic("%p not in the expected zone %s%s (%d != %d)",
            (void *)addr, zone_heap_name(zone), zone->z_name,
            meta->zm_index, zone_index(zone));
}

zone_page_metadata_native_queue_corruption(zone_t zone, zone_pva_t *queue)
{
        panic("foreign metadata index %d enqueued in native head %p from zone %s%s",
            queue->packed_address, queue, zone_heap_name(zone),
            zone->z_name);
}

zone_page_metadata_list_corruption(zone_t zone, struct zone_page_metadata *meta)
{
        panic("metadata list corruption through element %p detected in zone %s%s",
            meta, zone_heap_name(zone), zone->z_name);
}

zone_page_metadata_foreign_queue_corruption(zone_t zone, zone_pva_t *queue)
{
        panic("native metadata index %d enqueued in foreign head %p from zone %s%s",
            queue->packed_address, queue, zone_heap_name(zone), zone->z_name);
}

zone_page_metadata_foreign_confusion_panic(zone_t zone, vm_offset_t addr)
{
        panic("manipulating foreign address %p in a native-only zone %s%s",
            (void *)addr, zone_heap_name(zone), zone->z_name);
}

zone_invalid_foreign_addr_panic(zone_t zone, vm_offset_t addr)
{
        panic("addr %p being freed to foreign zone %s%s not from foreign range",
            (void *)addr, zone_heap_name(zone), zone->z_name);
}

zone_page_meta_accounting_panic(zone_t zone, struct zone_page_metadata *meta,
    const char *kind)
{
        panic("accounting mismatch (%s) for zone %s%s, meta %p", kind,
            zone_heap_name(zone), zone->z_name, meta);
}

Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1486/