nft_chain can be looked up by name, handle or ID. Let's go through the functions that do the job.
Lookup by name:
static struct nft_chain *nft_chain_lookup(struct net *net,
struct nft_table *table,
const struct nlattr *nla, u8 genmask)
{
char search[NFT_CHAIN_MAXNAMELEN + 1];
struct rhlist_head *tmp, *list;
struct nft_chain *chain;
if (nla == NULL)
return ERR_PTR(-EINVAL);
nla_strscpy(search, nla, sizeof(search));
WARN_ON(!rcu_read_lock_held() &&
!lockdep_commit_lock_is_held(net));
chain = ERR_PTR(-ENOENT);
rcu_read_lock();
list = rhltable_lookup(&table->chains_ht, search, nft_chain_ht_params);
if (!list)
goto out_unlock;
rhl_for_each_entry_rcu(chain, tmp, list, rhlhead) {
if (nft_active_genmask(chain, genmask))
goto out_unlock;
}
chain = ERR_PTR(-ENOENT);
out_unlock:
rcu_read_unlock();
return chain;
}
Lookup by handle:
static struct nft_chain *
nft_chain_lookup_byhandle(const struct nft_table *table, u64 handle, u8 genmask)
{
struct nft_chain *chain;
list_for_each_entry(chain, &table->chains, list) {
if (chain->handle == handle &&
nft_active_genmask(chain, genmask))
return chain;
}
return ERR_PTR(-ENOENT);
}
Lookup by ID:
static struct nft_chain *nft_chain_lookup_byid(const struct net *net,
const struct nft_table *table,
const struct nlattr *nla)
{
struct nftables_pernet *nft_net = nft_pernet(net);
u32 id = ntohl(nla_get_be32(nla));
struct nft_trans *trans;
list_for_each_entry(trans, &nft_net->commit_list, list) {
struct nft_chain *chain = trans->ctx.chain;
if (trans->msg_type == NFT_MSG_NEWCHAIN &&
chain->table == table &&
id == nft_trans_chain_id(trans))
return chain;
}
return ERR_PTR(-ENOENT);
}
In both nft_chain_lookup and nft_chain_lookup_byhandle, they check if the chain is active by calling nft_active_genmask. A chain will be deactivated if the user send a DELETE message for that chain. This check ensures that another object will not be able to refer to a deactivated chain. However in nft_chain_lookup_byid, the check will not be conducted. That means we can refer to a deactivated chain. But at cleanup stage, if chain->use is not 0, a warning will be issued and the chain won't be freed. We must find a way to make a reference to a deactivated chain while still satisfy the condition to free it.
Netfilter transaction will not free the deleted objects when commiting. Instead, Netfilter will run a deferred task to delete it later. Therefore, we can achieve Use-After-Free condition like this:
Batch 1:
- Create table
- Create chain victim
- Mark chain victim as deleted
- Create chain attack
- Create rule belong to attack chain, with a nft_immediate expression refer to victim by ID => victim->use == 1
- Commit the batch => Cleanup task will be queued
Batch 2:
- Mark the rule we created in the previous batch as deleted => victim->use == 0
- Wait for the cleanup task to complete => victim will be freed
- Fail the batch using some invalid input => the rule will not be marked as deleted anymore
The nft_immediate expression in the rule still refer to the freed chain. We have achieved Use-After-Free condition. From here we can spray fake chain object to leak (using nft_immediate dump function) or to execute code (need to create fake rule and fake expression as well to call expression ops). This primitive can be used multiple times reliably.
暂无评论