CVE-2021-1782,iOS 凭证中的野生漏洞

admin 2022年4月28日18:18:40评论94 views字数 5585阅读18分37秒阅读模式

点击上方蓝字“Ots安全”一起玩耍

这篇博文是我对一个在 2021 年初被利用并修补的漏洞的分析。就像上周发布 的关于 ASN.1 解析器错误的文章一样,这篇博文基于我在分析补丁时所做的笔记并尝试了解 XNU 凭证子系统。我希望这篇文章可以作为缺失的文档,说明凭证子系统的某些内部如何工作以及导致此漏洞的怪癖。


CVE-2021-1782 已在 iOS 14.4 中修复,正如推特上的@s1guza 所述:

CVE-2021-1782,iOS 凭证中的野生漏洞

此漏洞已于 2021 年 1 月 26 日修复,Apple 于 2021 年 5 月 28 日更新了 iOS 14.4 发行说明,表明该问题可能已被积极利用:

CVE-2021-1782,iOS 凭证中的野生漏洞

Vouchers

什么是voucher?

内核代码有一个简洁的描述:

凭证是对特定资源管理器属性值(它们本身是引用计数的)的引用计数的不可变(一次创建)索引集。

这个定义在技术上是正确的,尽管它本身可能并不是那么有用。

要真正了解此漏洞的根本原因和可利用性,需要涵盖大量凭证代码库。XNU 的这一部分非常晦涩难懂,而且非常复杂。

凭证是键和值的引用计数表。指向所有已创建凭证的指针都存储在全局ivht_bucket 哈希表中。

对于一组特定的键和值,应该只有一个凭证对象。在创建凭证期间,有一个重复数据删除阶段,新凭证将与哈希表中的所有现有凭证进行比较,以确保它们保持唯一性,如果发现重复,则返回对现有凭证的引用。

这是凭证的结构:

struct ipc_voucher {  iv_index_t iv_hash ; /* 校验和哈希 */         iv_index_t iv_sum ; /* 值的校验和 */           os_refcnt_t iv_refs ; /* 引用计数 */         iv_index_t iv_table_size ; /* 凭证表的大小 */   iv_index_t iv_inline_table [ IV_ENTRIES_INLINE ];  iv_entry_t iv_table ; /* 凭证属性条目表 */         ipc_port_t iv_port ; /* 代表凭证的端口 */         queue_chain_t iv_hash_link ; /* 哈希链上的链接 */   };#define  IV_ENTRIES_INLINE MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN

凭证代码库是以非常通用、可扩展的方式编写的,尽管它的实际使用和支持的功能集非常少。


Key

凭证中的密钥不是任意的。键是 凭证的iv_table的索引;一个值在iv_table 表中的位置决定了它存储在哪个“键”下。虽然凭证代码库支持在运行时添加新的密钥类型,但并未使用此功能,并且只有少量固定的、众所周知的密钥:

#define  MACH_VOUCHER_ATTR_KEY_ALL (( mach_voucher_attr_key_t )~ 0 )#define  MACH_VOUCHER_ATTR_KEY_NONE (( mach_voucher_attr_key_t ) 0 )
/* 其他知名键将被添加到这里 */#define  MACH_VOUCHER_ATTR_KEY_ATM (( mach_voucher_attr_key_t ) 1 )#define  MACH_VOUCHER_ATTR_KEY_IMPORTANCE (( mach_voucher_attr_key_t ) 2 )#define  MACH_VOUCHER_ATTR_KEY_BANK (( mach_voucher_attr_key_t ) 3 )#define  MACH_VOUCHER_ATTR_KEY_PTHPRIORITY (( mach_voucher_attr_key_t ) 4 )#define  MACH_VOUCHER_ATTR_KEY_USER_DATA (( mach_voucher_attr_key_t ) 7 )#define  MACH_VOUCHER_ATTR_KEY_TEST (( mach_voucher_attr_key_t ) 8 )#define MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN MACH_VOUCHER_ATTR_KEY_TEST

ipc_voucher 中的iv_inline_table有 8 个条目。但其中只有四个实际受支持并具有任何相关功能。ATM 凭证属性已被弃用,支持它们的代码已不存在,因此只有 IMPORTANCE (2)、BANK (3)、PTHPRIORITY (4) 和 USER_DATA (7) 是有效密钥。关于何时应该使用术语键以及何时使用属性存在一些混淆(也许是我自己);我将交替使用它们来指代这些键值和它们管理的相应值的“类型”。稍后再谈。


价值观

凭证iv_table中的每个条目 都是一个iv_index_t :

typedef  natural_t iv_index_t ;

每个值又是一个索引;这次进入每个键 的值缓存,抽象为由以下结构表示的“凭证属性缓存控制对象”:

struct ipc_voucher_attr_control {os_refcnt_t ivac_refs ;boolean_t ivac_is_growth ; /* 是正在增长的表 */     ivac_entry_t ivac_table ; /* 凭证属性值条目表 */           iv_index_t ivac_table_size ; /* attr 值表的大小 */     iv_index_t ivac_init_table_size ; /* attr 值表的大小 */ iv_index_t ivac_freelist ; /* 第一个空闲元素的索引 */       ipc_port_t ivac_port ; /* 访问缓存控制的端口 */           lck_spin_t ivac_lock_data ;iv_index_t ivac_key_index ; /* 该值的键索引 */       };

这些是通过另一个全局表间接访问的:

static ipc_voucher_global_table_elementiv_global_table[MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN];

(同样,代码中的注释表明,将来这个表可能会变大并允许在用户空间中管理属性,但现在它只是一个固定大小的数组。)

该表中的每个元素都具有以下结构:

typedef struct  ipc_voucher_global_table_element {         ipc_voucher_attr_manager_t ivgte_manager ;        ipc_voucher_attr_control_t ivgte_control ;        mach_voucher_attr_key_t ivgte_key ;}  ipc_voucher_global_table_element ;

iv_global_table 和每个凭证的iv_table都由(key-1) 索引,而不是key ,因此 userdata 条目是[6] ,而不是[7] ,即使数组仍然有 8 个条目。


ipc_voucher_attr_control_t 提供了一个用于管理“值”的抽象接口,而ipc_voucher_attr_manager_t提供 了“特定于类型”的逻辑来实现每种类型的语义(这里的类型是指“key”或“attr”类型。)让我们更具体地看一下那是什么意思。这是ipc_voucher_attr_manager_t的定义:

static ipc_voucher_attr_manager {  ipc_voucher_attr_manager_release_value_t ivam_release_value ;  ipc_voucher_attr_manager_get_value_t ivam_get_value ;  ipc_voucher_attr_manager_extract_content_t ivam_extract_content ;  ipc_voucher_attr_manager_command_t ivam_command ;  ipc_voucher_attr_manager_release_t ivam_release ;  ipc_voucher_attr_manager_flags ivam_flags ;};

ivam_flags 是一个 包含一些标志的int ;其他五个字段是定义特定 attr 类型语义的函数指针。这是user_data 类型的ipc_voucher_attr_manager 结构:

const struct  ipc_voucher_attr_manager user_data_manager = {    . ivam_release_value =    user_data_release_value ,  . ivam_get_value =        user_data_get_value ,  . ivam_extract_content =  user_data_extract_content ,  . ivam_command =          user_data_command ,  . ivam_release =          user_data_release ,  . ivam_flags =            IVAM_FLAGS_NONE ,};

这五个函数指针是从通用凭证代码到特定类型代码的唯一接口。界面可能看起来很简单,但其中有一些微妙之处;我们稍后再谈!

让我们回到通用的ipc_voucher_attr_control 结构,它以与类型无关的方式维护每个键的“值”。最重要的字段是ivac_entry_t ivac_table ,它是ivac_entry_s的数组。它是该表的索引,存储在每个凭证的iv_table中。

这是该表中每个条目的结构:

struct ivac_entry_s {  iv_value_handle_t ivace_value ;  iv_value_refs_t ivace_layered : 1 , /* 分层有效入口 */                       ivace_releasing : 1 , /* 正在发布 */                     ivace_free : 1 , /* 在freelist */                         ivace_persist : 1 , /* 保留条目,不要                                              count made refs */                    ivace_refs :28/* 引用计数 */        union{     iv_value_refs_t ivaceu_made ; /* 计数(非分层) */           iv_index_t ivaceu_layer ; /* 下一个有效层                                               (layered)*/  }  ivace_u ;  iv_index_t ivace_next ; /* 散列或空闲列表 */         iv_index_t ivace_index ; /* 哈希头(独立) */       };

iace_refs 是此表索引的引用计数。请注意,此条目是内联 在数组中的;所以这个引用计数变为零不会导致ivac_entry_s 被释放回内核分配器(例如区域分配器)。相反,它将这个表索引移动到一个空条目的空闲列表中。表可以增长但永远不会缩小。


非免费的表条目在ivace_value中存储特定类型的“句柄” 。这是该类型的 typedef 链:

iv_value_handle_t ivace_valuetypedef  mach_voucher_attr_value_handle_t iv_value_handle_t ;typedef  uint64_t mach_voucher_attr_value_handle_t

句柄是一个uint64_t 但实际上 attrs 可以(并且确实)在那里存储指针,隐藏在强制转换后面。


attr_control做出的保证是 对于特定的ivac_value 将永远只有一个(实时)ivac_entr y_s 。这意味着每次新的ivac_value 需要ivac_entry时 ,都需要搜索attr_control的ivac_table 以查看是否已经存在匹配的值。为了加快这一速度,使用中的ivac_entries 在哈希桶中链接在一起,以便可以搜索(希望显着)更短的条目链接列表,而不是对整个表进行线性扫描。(请注意,它不是指针的链表;链中的每个链接都是表中的索引。)


用户数据属性

user_data 是四种支持的、已实现的凭证属性类型之一。它的唯一目的是管理任意用户控制数据的缓冲区。由于attr_control 仅对ivace_value  (它是一个指针)执行重复数据删除,因此 userdata attr 管理器负责确保具有相同缓冲区值(匹配长度和字节)的 userdata 值具有相同的指针。


为此,它维护了一个user_data_value_element 结构的哈希表,该结构包装了一个可变大小的字节缓冲区:

struct user_data_value_element {  mach_voucher_attr_value_reference_t e_made ;  mach_voucher_attr_content_size_t e_size ;  iv_index_t e_sum ;  iv_index_t e_hash ;  queue_chain_t e_hash_link ;  uint8_t e_data [];};

每个内联e_data 缓冲区最大可达 16KB。e_hash_link 存储哈希表桶列表指针。


e_made 不是一个简单的引用计数。查看代码,您会注意到它没有减少的地方。由于应该(几乎)总是在ivace_entry 和user_data_value_element之间存在 1:1 映射,因此 该结构不需要进行引用计数。然而,有一个非常复杂的竞争条件(这不是 导致漏洞的竞争条件!)它需要e_made 字段。这种竞争条件是有记录的,我们最终会到达那里......

CVE-2021-1782,iOS 凭证中的野生漏洞

原文始发于微信公众号(Ots安全):CVE-2021-1782,iOS 凭证中的野生漏洞

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月28日18:18:40
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2021-1782,iOS 凭证中的野生漏洞http://cn-sec.com/archives/950653.html

发表评论

匿名网友 填写信息