Linux内核nftables子系统研究与漏洞分析

发布时间 2022-07-18

一、背景


近期,开源安全社区oss-security披露了多个Linux内核netfilter模块相关漏洞,漏洞均出现在netfilter子系统nftables中,其中两个漏洞在内核中存在多年,并且均可用于内核权限提升。漏洞编号分别为:CVE-2022-32250,该漏洞类型为释放重引用;CVE-2022-1972,该漏洞类型为越界读写;CVE-2022-34918,该漏洞类型为堆溢出。


二、相关介绍


2.1 netfilter简单介绍


netfilter是一个开源项目,用于执行数据包过滤,也就是Linux防火墙。这个项目经常被提到iptables,它是用于配置防火墙的用户级应用程序。2014年,netfilter 防火墙添加了一个新子系统,称为nftables,可以通过nftables用户级应用程序进行配置。


图1.png


2.2 nftables简单介绍


nftables取代了流行的{ip,ip6,arp,eb}表。该软件提供了一个新的内核数据包分类框架,该框架基于特定于网络的虚拟机 (VM) 和新的nft用户空间命令行工具。nftables重用了现有的netfilter子系统,例如现有的钩子基础设施、连接跟踪系统、NAT、用户空间队列和日志子系统。对于nftables,只需要扩展expression即可,用户自行编写expression,然后让nftables虚拟机执行它。nftables框架的数据结构如下所示:


图2.png


Table为chain的容器,chain为rule的容器,rule为expression的容器,expression响应action。构造成由table->chain->rule->expression四级组成的数据结构。


三、nftables子系统实现分析



通过发送netlink消息数据包来操作nftables,从netlink到nftables的调用过程如下所示:


图3.png


在nfnetlink_rcv_batch()函数中对netlink消息进行操作。从netlink消息中剥离出nftable载荷,并依次进行对应处理。进入nfnetlink_rcv_batch()函数,首先根据subsys_id获得nfnetlink_subsystem。


图4.png


这里nftables类型为0xa。获得subsystem后,然后拿到子系统对应的回调客户端。通过nfnetlink_find_client()实现该功能。


图5.png


对应nftables回调客户端,在\net\netfilter\nf_tables_api.c直接找到定义:


图6.png


.cb数据域便是回调客户端。可以看到针对不同的nftables操作,定义了多个回调客户端,例如table的增删改查操作。


图7.png


然后再从netlink消息中剥离出netlink载荷,根据不同的消息类型进行不同的分发处理,消息类型如下所示:


图8.png


依次调用nc->call_batch()进一步处理。


图9.png


开始剥洋葱式分析,第一层操作创建一个table,响应函数为nf_tables_newtable()。


图10.png


先通过nla[NFTA_TABLE_NAME]来查找是否存在该table,如果存在,调用nf_tables_updtable(),如果不存在就创建该表。


图11.png


创建完成后,然后就是必要的初始化操作。


图12.png


初始化table->chains链表,table->sets链表,table->objects链表,table->flowtables链表。然后将table加到nftbales上下文中,最后将table链到net->nft.tables中。


第二步操作创建一个chain,响应函数为nf_tables_newchain()。首先先找table,无table直接退出。


图13.png


找到table后,就找chain是否存在,存在进入update,不存在则添加一个新chain。


图14.png


这里提供了两种方式寻找chain,通过nla[NFTA_CHAIN_HANDLE]和nla[NFTA_CHAIN_NAME]进行寻找。未找到就调用nf_tables_addchain()创建之。


图15.png


具体看该函数实现,首先分配一个chain,然后初始化chain->rules链表,并设置chain->hanle和chain->table。随后进行初始化chain->name等操作,并将chain链到table->chains中。


图16.png


第三步操作创建一个rule,响应函数为nf_tables_newrule(),首先相继获取table和chain,如果设置了nla[NFTA_RULE_EXPRESSIONS],会先把所有的expression遍历出来,计算其总值放在size中。


图17.png


如果设置了nla[NFTA_RULE_USERDATA],获取userdata的大小放在usize中,最后分配内存,创建一个rule,随即初始化相关数据域。


图18.png


第四步操作创建expression,其实这一步和创建rule是连在一起的。都在nf_tables_newrule()函数中实现。expresssion总共有如下多种类型。


图19.png


将用户层传进来的expression剥离出来后,依次放在rule中。


图20.png


这里调用了nf_tables_newexpr()函数,会根据expression类型对其进行初始化。


图21.png


以上就是一个完整的table->chain->rule->expression的创建过程。


四、相关漏洞分析


其实,回调客户端中还提供了其他元素的创建操作,比如创建set(集合)。set只需要依附于table即可,可构成table->set->expression数据结构。


4.1 CVE-2022-32250

该漏洞是释放重引用漏洞,出现在nf_tables_newset()函数中,该函数是创建一个set。set中也可以包含各种expression。首先看下nf_tables_newset()函数实现。


图22.png


同样地,先获取table,再获取set,如果set不存在就创建之。接下来根据set类型获取对应的ops操作集,并确定set的大小,并分配之。


图23.png


接下来,如果设置了nla[NFTA_SET_EXPR],并进入调用nft_set_elem_expr_alloc()函数进行处理。


图24.png


进入该函数看具体实现。


图25.png


行5128,首先进入nft_expr_init()函数分配一个expr,该函数具体实现如下所示。


图26.png


行2686,调用kzalloc()分配一个expr,这是第一次操作,nft_expr结构体定义为:


图27.png


data为动态数组,nft_expr本身是个不固定大小的结构体,使用时候才确定的大小,data处可以存放多种类型的expression,并匹配对应的ops操作集合。这里以nft_lookup为例子,nft_lookup结构体定义如下:


图28.png


将expr合起拼接等于nft_lookup_expr,分配完expr后,就进入nf_tables_newexpr()函数,初始化expr(nft_lookup_expr),该函数实现如下所示:


图29.png


行2652,调用对应的ops->init()函数进行初始化,具体看对应的nft_lookup_init()函数实现。