前言
测试版本: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[])
{
...