nftables Adventures: Bug Hunting and N-day Exploitation (CVE-2023-31248)
nftables简介
nftables是一个现代的包过滤框架,旨在取代传统的{ip,ip6,arp,eb}_tables(xtables)基础设施。它重用了现有的netfilter钩子,这些钩子作为处理程序的入口点,对数据包执行各种操作。nftables表对象包含链对象列表,链对象包含规则对象列表,规则最终包含表达式,这些表达式执行伪状态机的操作。
表(Tables)
表是顶级对象,包含链、集合、对象和流表。在内部,表由struct nft_table表示。
|
|
表可以有多个不同的标志。用户可以在创建表时设置NFT_TABLE_F_DORMANT和/或NFT_TABLE_F_OWNER标志。休眠状态标志(NFT_TABLE_F_DORMANT)可以在nf_tables_updtable中更新。如果设置了NFT_TABLE_F_DORMANT(0x1),表将进入休眠状态,其所有基本链钩子将被取消注册,但表不会被删除。还有内部设置的__NFT_TABLE_F_UPDATE标志,包括__NFT_TABLE_F_WAS_AWAKEN和__NFT_TABLE_F_WAS_DORMANT。
链(Chains)
链可以是基本链,它们注册了netfilter钩子且不能被跳转至;也可以是普通链,它们没有注册钩子但可以被跳转至。在内部,链由struct nft_chain表示。
|
|
基本链由struct nft_base_chain表示。
|
|
规则(Rules)
规则包含nftables表达式。在内部,规则由struct nft_rule表示。
|
|
表达式(Expressions)
表达式充当状态机的操作。有许多表达式,例如:
- Bitwise:执行位操作
- Immediate:将数据加载到寄存器中。还允许跳转/转到另一个普通链
- Byteorder:更改主机/网络字节序
- Compare:比较两个寄存器中的值
- Counter:在规则中启用计数器
在内部,表达式由struct nft_expr表示。
|
|
每个表达式还有一个struct nft_expr_ops表示各种操作。
|
|
生成掩码系统(Genmask System)
许多nftables对象有一个2位的生成掩码,指定对象在当前和/或下一代中是否活动。如果设置了位,对象在该代中不活动。有一个整体的gencursor定义代表当前代的位。对象可以有以下状态:
- 在当前和下一代都活动(例如未更改的对象)
- 在当前代活动,在下一代不活动(例如标记为删除的对象)
- 在当前代不活动,在下一代活动(例如新创建的对象)
控制平面、事务系统和事务工作器
在nftables中,用户空间请求的操作(通过netlink消息)在控制平面中执行,包括nf_tables_newtable、nf_tables_updtable、nf_tables_newchain等函数。控制平面负责对象的创建和分配、在下一代中激活/停用对象、链接对象以及修改对象的“use”引用计数。然而,新创建的对象在创建后不会立即激活;它们只在提交阶段新代开始时激活。控制平面中涉及对象创建或更新的所有操作都会向事务列表添加新事务。
当netlink批处理事务被认为是有效的(即控制平面中的所有操作不返回错误)时,进入提交阶段并调用nf_tables_commit。将启动新代,所有新创建的对象变为活动状态,并执行事务列表中的操作。提交阶段还负责取消链接要删除的对象,并排队负责销毁对象的异步事务工作器(nf_tables_trans_destroy_work)。
异步事务工作器运行时,将调用nft_commit_release,最终调用销毁和释放标记为删除的对象的函数。
nftables休眠状态链钩子停用错误
在研究nftables时,通过手动源代码审查,我能够识别出一个导致警告splat的错误。错误报告可以在这里看到:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/netfilter/nf_tables_api.c?id=c9bd26513b3a11b3adb3c2ed8a31a01a87173ff1
当新创建的表通过nf_tables_updtable从活动更新为休眠时,表标志设置为NFT_TABLE_F_DORMANT,并且由于没有设置__NFT_TABLE_F_UPDATE标志,将设置__NFT_TABLE_F_WAS_AWAKEN标志。当表从活动更新为休眠时,链钩子直到调用nf_tables_commit才停用。然而,当表从休眠更新为活动时,NFT_TABLE_F_DORMANT标志被清零。然后检查是否设置了任何__NFT_TABLE_F_UPDATE标志,如果没有设置,链钩子立即由nf_tables_table_enable激活(即在调用nf_tables_commit之前)。此代码行为如下所示:
|
|
可以以某种方式激活/停用表,使得在某个时间点,一些链已注册而一些未注册。这可以通过将活动表更新为休眠来实现,以便设置__NFT_TABLE_F_WAS_AWAKEN标志(这是__NFT_TABLE_F_UPDATE标志之一),然后将休眠表更新为活动。由于设置了__NFT_TABLE_F_UPDATE标志之一,跳过nf_tables_table_enable,留下一些链未注册。当删除活动表时,nf_tables_unregister_hook仅检查NFT_TABLE_F_DORMANT标志是否清零。如果标志未设置,所有基本链被假定为活动,因此所有链钩子将停用,即使它们最初未注册。这导致显示以下警告:
|
|
触发错误
要触发错误,应采取以下步骤(在同一批处理事务中):
- 创建表“test_table” – 此表是活动的 [1]
- 将表“test_table”从活动更新为休眠 [2] a. 设置NFT_TABLE_F_DORMANT和__NFT_TABLE_F_WAS_AWAKEN表标志
- 添加基本链“chain1” – 此基本链添加到休眠表,因此未注册 [3]
- 将表“test_table”从休眠更新为活动 [4] a. NFT_TABLE_F_DORMANT标志清零,但__NFT_TABLE_F_WAS_AWAKEN标志仍设置,导致跳过nf_tables_enable_table
- 使用nft实用程序删除活动表“test_table”:nft delete table test_table [5]
表在删除时是活动的,因此当表被刷新时,所有基本链被视为已注册并将取消注册。然而,由于基本链“chain1”从未注册,内核将尝试取消注册未注册的链,导致警告。
|
|