
Uma Prova de Conceito para CVE-2025-40019
CVE-2025-40019 é o mecanismo de Vetor de Inicialização Salt-Setor Criptografado (crypto/essiv.c) que possui validação insuficiente do comprimento dos Dados de Autenticação Associada (AAD) em relação ao tamanho do Vetor de Inicialização (IV), levando a acesso à memória fora dos limites. O código fornecido neste repositório é apenas um gatilho do bug, mas este bug é facilmente explorável.
Encontrei este bug na planilha do kernelctf aqui. Como não havia post de blog ou poc associado a este bug, decidi explorar o subsistema crypto do kernel Linux para explorar o que causou o problema.

Para entender a necessidade do ESSIV, devemos examinar como os dados são armazenados no disco. A criptografia de disco (como LUKS ou dm-crypt) geralmente opera em setores. Cada setor deve ser criptografado independentemente, de modo que a leitura de um setor não exija a leitura de todo o disco.
No modo CBC (Cipher Block Chaining) padrão, um Vetor de Inicialização (IV) é necessário para cada operação de criptografia. Implementações iniciais de criptografia de disco usavam o Número do Setor como IV.
No entanto, como os números dos setores são previsíveis, um atacante pode realizar um "ataque de marca d'água". Escrevendo dados especialmente criados em um setor conhecido, o atacante pode observar padrões no texto cifrado que revelam a presença de arquivos específicos, contornando efetivamente a confidencialidade da criptografia.
O ESSIV (Vetor de Inicialização Salt-Setor Criptografado) foi projetado para tornar o IV imprevisível. Ele funciona:
Isso garante que, mesmo que um atacante saiba o número do setor, ele não pode prever o IV sem conhecer a chave secreta.
Todo algoritmo, seja uma cifra base como AES ou um wrapper como ESSIV, implementa uma estrutura que o kernel usa para rotear chamadas:
struct skcipher_alg {
int (*setkey)(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen);
int (*encrypt)(struct skcipher_request *req);
int (*decrypt)(struct skcipher_request *req);
// ...
struct skcipher_alg_common co; // Contains ivsize, chunksize, etc.
};
A vulnerabilidade é um caso clássico de assumir que metadados fornecidos pelo usuário (o comprimento do AAD) sempre atenderão aos requisitos internos do transform criptográfico. No caminho "in-place" ou de descriptografia (!enc), o código calcula um offset: req->assoclen - crypto_aead_ivsize(tfm). Mas ele nunca verifica se req->assoclen < ivsize. o que significa que este offset pode ser negativo.
static int essiv_aead_crypt(struct aead_request *req, bool enc)
{
struct crypto_aead *tfm = crypto_aead_reqtfm(req);
const struct essiv_tfm_ctx *tctx = crypto_aead_ctx(tfm);
struct essiv_aead_request_ctx *rctx = aead_request_ctx(req);
struct aead_request *subreq = &rctx->aead_req;
struct scatterlist *src = req->src;
int err;
crypto_cipher_encrypt_one(tctx->essiv_cipher, req->iv, req->iv);
/*
* dm-crypt embeds the sector number and the IV in the AAD region, so
* we have to copy the converted IV into the right scatterlist before
* we pass it on.
*/
rctx->assoc = NULL;
if (req->src == req->dst || !enc) {
scatterwalk_map_and_copy(req->iv, req->dst,
req->assoclen - crypto_aead_ivsize(tfm), // <------- bug !
crypto_aead_ivsize(tfm), 1);
} else {
u8 *iv = (u8 *)aead_request_ctx(req) + tctx->ivoffset;
int ivsize = crypto_aead_ivsize(tfm);
int ssize = req->assoclen - ivsize;
struct scatterlist *sg;
int nents;
.
.
.
Olhando para a função scatterwalk_map_and_copy, podemos ver que ela apenas copia (memcpy) para o scatterlist sg:
static inline void scatterwalk_map_and_copy(void *buf, struct scatterlist *sg,
unsigned int start,
unsigned int nbytes, int out)
{
if (out)
memcpy_to_sglist(sg, start, buf, nbytes);
else
memcpy_from_sglist(buf, sg, start, nbytes);
}
O patch para este bug foi muito simples: ele apenas verifica se o assoclen é menor que ivisize
diff --git a/crypto/essiv.c b/crypto/essiv.c
index d003b78fcd855a..a47a3eab693519 100644
--- a/crypto/essiv.c
+++ b/crypto/essiv.c
@@ -186,9 +186,14 @@ static int essiv_aead_crypt(struct aead_request *req, bool enc)
const struct essiv_tfm_ctx *tctx = crypto_aead_ctx(tfm);
struct essiv_aead_request_ctx *rctx = aead_request_ctx(req);
struct aead_request *subreq = &rctx->aead_req;
+ int ivsize = crypto_aead_ivsize(tfm);
+ int ssize = req->assoclen - ivsize;
struct scatterlist *src = req->src;
int err;
+ if (ssize < 0)
+ return -EINVAL;
+
crypto_cipher_encrypt_one(tctx->essiv_cipher, req->iv, req->iv);
/*
@@ -198,19 +203,12 @@ static int essiv_aead_crypt(struct aead_request *req, bool enc)
*/
rctx->assoc = NULL;
if (req->src == req->dst || !enc) {
- scatterwalk_map_and_copy(req->iv, req->dst,
- req->assoclen - crypto_aead_ivsize(tfm),
- crypto_aead_ivsize(tfm), 1);
+ scatterwalk_map_and_copy(req->iv, req->dst, ssize, ivsize, 1);
} else {
u8 *iv = (u8 *)aead_request_ctx(req) + tctx->ivoffset;
- int ivsize = crypto_aead_ivsize(tfm);
- int ssize = req->assoclen - ivsize;
struct scatterlist *sg;
int nents;
- if (ssize < 0)
- return -EINVAL;
-
nents = sg_nents_for_len(req->src, ssize);
if (nents < 0)
return -EINVAL;