CVE-2022-34918:nftables 中 nft_setelem_parse_data 错误验证导致的类型混淆,从而导致的堆溢出写

前言

测试版本:v5.17.15

编译选项:

CONFIG_NF_TABLES=y
CONFIG_NETFILTER_NETLINK=y
CONFIG_BINFMT_MISC=y
CONFIG_USER_NS=y
CONFIG_E1000=y
CONFIG_E1000E=y

漏洞分析

patch 如下:

diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 51144fc66889b..d6b59beab3a98 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -5213,13 +5213,20 @@ static int nft_setelem_parse_data(struct nft_ctx *ctx, struct nft_set *set,
 				  struct nft_data *data,
 				  struct nlattr *attr)
 {
   
+	u32 dtype;
 	int err;
 
 	err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr);
 	if (err < 0)
 		return err;
 
-	if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) {
   
+	if (set->dtype == NFT_DATA_VERDICT)
+		dtype = NFT_DATA_VERDICT;
+	else
+		dtype = NFT_DATA_VALUE;
+
+	if (dtype != desc->type ||
+	    set->dlen != desc->len) {
   
 		nft_data_release(data, desc->type);
 		return -EINVAL;

可以看到 patch 主要打在了 nft_setelem_parse_data 函数中,其主要修改了相关检查方式:

static int nft_setelem_parse_data(struct nft_ctx *ctx, 
								  struct nft_set *set,
								  struct nft_data_desc *desc,
								  struct nft_data *data,
								  struct nlattr *attr)
{
   
	int err;
	// NFT_DATA_VALUE_MAXLEN = 64
	// 解析 attr 属性到 data 中,并设置 desc
	err = nft_data_init(ctx, data, NFT_DATA_VALUE_MAXLEN, desc, attr);
	if (err < 0) return err;
	// 如果 type 不是 NFT_DATA_VERDICT ,则需要检查 desc->len ?= set->dlen
	// 这里可以想一下:
	//	1、为什么 type != NFT_DATA_VERDICT 时,需要检查 desc->len ?= set->dlen?
	//	2、如果 desc->len != set->dlen 通过了检查,后面会出现什么问题呢?
	//	3、为什么 type == NFT_DATA_VERDICT 时,就不需要检查 desc->len ?= set->dlen?
	if (desc->type != NFT_DATA_VERDICT && desc->len != set->dlen) {
   
		nft_data_release(data, desc->type);
		return -EINVAL;
	}
	return 0;
}

nft_data_init 函数主要就是解析添加 elem 的类型:

/**
 * enum nft_data_attributes - nf_tables data netlink attributes
 *
 * @NFTA_DATA_VALUE: generic data (NLA_BINARY)
 * @NFTA_DATA_VERDICT: nf_tables verdict (NLA_NESTED: nft_verdict_attributes)
 */
enum nft_data_attributes {
   
	NFTA_DATA_UNSPEC,
	NFTA_DATA_VALUE,
	NFTA_DATA_VERDICT,
	__NFTA_DATA_MAX
};
#define NFTA_DATA_MAX		(__NFTA_DATA_MAX - 1)

/**
 *	nft_data_init - parse nf_tables data netlink attributes
 *
 *	@ctx: context of the expression using the data
 *	@data: destination struct nft_data
 *	@size: maximum data length
 *	@desc: data description
 *	@nla: netlink attribute containing data
 *
 *	Parse the netlink data attributes and initialize a struct nft_data.
 *	The type and length of data are returned in the data description.
 *
 *	The caller can indicate that it only wants to accept data of type
 *	NFT_DATA_VALUE by passing NULL for the ctx argument.
 */
int nft_data_init(const struct nft_ctx *ctx,
		  struct nft_data *data, unsigned int size, // size = 64
		  struct nft_data_desc *desc, const struct nlattr *nla)
{
   
	struct nlattr *tb[NFTA_DATA_MAX + 1];
	int err;
	// 解析嵌套 nla 属性到 tb 数组中
	err = nla_parse_nested_deprecated(tb, NFTA_DATA_MAX, nla, nft_data_policy, NULL);
	if (err < 0) return err;
	// 处理 NFTA_DATA_VALUE 属性
	if (tb[NFTA_DATA_VALUE])
		return nft_value_init(ctx, data, size, desc, tb[NFTA_DATA_VALUE]);
	// 处理 NFTA_DATA_VERDICT 属性
	if (tb[NFTA_DATA_VERDICT] && ctx != NULL)
		return nft_verdict_init(ctx, data, desc, tb[NFTA_DATA_VERDICT]);
	return -EINVAL;
}

如果添加元素是 NFTA_DATA_VALUE 则调用 nft_value_init 函数:

static int nft_value_init(const struct nft_ctx *ctx,
			  struct nft_data *data, unsigned int size,
			  struct nft_data_desc *desc, const struct nlattr *nla)
{
   
	unsigned int len;
	
	len = nla_len(nla); // 属性长度
	if (len == 0) return -EINVAL;
	if (len > size) return -EOVERFLOW; // size = 64
	// 拷贝 nlattr 数据到 data->data 中
	nla_memcpy(data->data, nla, len);
	// 对于 NFT_DATA_VALUE 类型,desc->len = nla_len(nla)
	desc->type = NFT_DATA_VALUE;
	desc->len  = len;
	return 0;
}

如果添加元素是 NFT_DATA_VERDICT 则调用 nft_verdict_init 函数:

/**
 * enum nft_verdict_attributes - nf_tables verdict netlink attributes
 *
 * @NFTA_VERDICT_CODE: nf_tables verdict (NLA_U32: enum nft_verdicts)
 * @NFTA_VERDICT_CHAIN: jump target chain name (NLA_STRING)
 * @NFTA_VERDICT_CHAIN_ID: jump target chain ID (NLA_U32)
 */
enum nft_verdict_attributes {
   
	NFTA_VERDICT_UNSPEC,
	NFTA_VERDICT_CODE,
	NFTA_VERDICT_CHAIN,
	NFTA_VERDICT_CHAIN_ID,
	__NFTA_VERDICT_MAX
};
#define NFTA_VERDICT_MAX	(__NFTA_VERDICT_MAX -

static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
						    struct nft_data_desc *desc, const struct nlattr *nla)
{
   
	u8 genmask = nft_genmask_next(ctx->net);
	struct nlattr *tb[NFTA_VERDICT_MAX + 1];
	struct nft_chain *chain;
	int err;
	// 处理 nla 迭代属性到 tb 中
	err = nla_parse_nested_deprecated(tb, NFTA_VERDICT_MAX, nla, nft_verdict_policy, NULL);
	if (err < 0) return err;
	if (!tb[NFTA_VERDICT_CODE])	return -EINVAL;
	// 获取 verdict.code
	data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));

	switch (data->verdict.code) {
   
	default:
		switch (data->verdict.code & NF_VERDICT_MASK) {
   
			case NF_ACCEPT:
			case NF_DROP:
			case NF_QUEUE:
				break;
			default:
				return -EINVAL;
		}
		fallthrough;
	case NFT_CONTINUE:
	case NFT_BREAK:
	case NFT_RETURN:
		break;
	// 如果是 JUMP/GOTO 则需要设置 target chain
	case NFT_JUMP:
	case NFT_GOTO:
		if (tb[NFTA_VERDICT_CHAIN]) {
   
			chain = nft_chain_lookup(ctx->net, ctx->table, tb[NFTA_VERDICT_CHAIN], genmask);
		} else if (tb[NFTA_VERDICT_CHAIN_ID]) {
   
			chain = nft_chain_lookup_byid(ctx->net, tb[NFTA_VERDICT_CHAIN_ID]);
			if (IS_ERR(chain)) return PTR_ERR(chain);
		} else {
   
			return -EINVAL;
		}

		if (IS_ERR(chain) return PTR_ERR(chain);
		// 必须得是 base_chain???
		if (nft_is_base_chain(chain)) return -EOPNOTSUPP;

		chain->use++;
		data->verdict.chain = chain;
		break;
	}
	// 当为 NFT_DATA_VERDICT 时,desc->len = 16
	desc->len = sizeof(data->verdict);
	desc->type = NFT_DATA_VERDICT;
	return 0;
}

要回答上面那三个问题,我们得先看下 set->len 的值是如何被设置的,set->dlen 的值是在其被创建时确定的,创建 set 的函数为 nf_tables_newset

static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
							const struct nlattr * const nla[])
{
   
	...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值