CVE-2025-40019 是加密盐扇区初始化向量机制(crypto/essiv.c)中的一个漏洞,该机制对关联认证数据(AAD)长度相对于初始化向量(IV)大小的验证不足,导致越界内存访问。 此仓库中的代码仅为漏洞触发程序,但该漏洞易于利用。
我在 kernelctf 的电子表格中发现了这个漏洞 链接。由于该漏洞没有相关的博客文章或 PoC,我决定探索 Linux 内核的加密子系统,以查明问题原因。

要理解 ESSIV 的必要性,我们必须先了解数据在磁盘上的存储方式。磁盘加密(如 LUKS 或 dm-crypt)通常按扇区操作。每个扇区必须独立加密,这样读取一个扇区时无需读取整个磁盘。
在标准 CBC(密文分组链接)模式下,每次加密操作都需要一个初始化向量(IV)。早期的磁盘加密实现使用扇区号作为 IV。
然而,由于扇区号是可预测的,攻击者可以执行“水印攻击”。通过将特制数据写入已知扇区,攻击者可以观察密文中的模式,从而发现特定文件的存在,有效绕过加密的机密性。
ESSIV(加密盐扇区初始化向量)旨在使 IV 不可预测。其工作原理如下:
这确保了即使攻击者知道扇区号,也无法在没有密钥的情况下预测 IV。
每个算法,无论是像 AES 这样的基础密码,还是像 ESSIV 这样的包装器,都实现了一个结构,内核利用该结构来路由调用:
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; // 包含 ivsize, chunksize 等
};
该漏洞是一个典型案例:假设用户提供的元数据(AAD 长度)始终满足加密转换的内部要求。
在“就地”或解密(!enc)路径中,代码计算了一个偏移量:req->assoclen - crypto_aead_ivsize(tfm)。但它从未检查 req->assoclen < ivsize。
这意味着该偏移量可能为负值。
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 将扇区号和 IV 嵌入到 AAD 区域中,因此
* 在传递之前,我们必须将转换后的 IV 复制到正确的 scatterlist 中。
*/
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);
} 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;
.
.
.
查看 scatterwalk_map_and_copy 函数,我们可以看到它只是对 scatterlist sg 执行 memcpy:
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);
}
该漏洞的补丁非常简单,它只是检查 assoclen 是否小于 ivsize:
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;
当我们提供 assoclen < ivsize 时,会导致此崩溃。通过堆上对象排布并将它们放置在 scatterlist 旁边,可以利用该漏洞。
root@syzkaller:/mnt/shared# ls
pwn pwn.c
root@syzkaller:/mnt/shared# ./pwn
aad_len=8, ivsize=16
[ 28.256679] BUG: kernel NULL pointer dereference, address: 000000000000000c
[ 28.258348] #PF: supervisor read access in kernel mode
[ 28.259377] #PF: error_code(0x0000) - not-present page
[ 28.260349] PGD 0 P4D 0
[ 28.260904] Oops: Oops: 0000 [#1] SMP PTI
[ 28.261605] CPU: 0 UID: 0 PID: 178 Comm: pwn Not tainted 6.17.0-rc1-00082-gc0d36727bf39 #8 PREEMPT(voluntary)
[ 28.263236] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014
[ 28.264805] RIP: 0010:memcpy_to_sglist+0x3a/0x90
[ 28.265607] Code: 00 41 54 55 53 48 83 ec 20 65 48 8b 05 27 31 c6 01 48 89 44 24 18 31 c0 48 89 e7 f3 ab 45 85 ed 74 3a 89 f3 49 89 d4 48 89 e5 <41> 8b 40 0c 39 d8 73 0fb
[ 28.269176] RSP: 0018:ffffc900001e7c80 EFLAGS: 00010202
[ 28.270229] RAX: 0000000000000000 RBX: 00000000ffffffb8 RCX: 0000000000000000
[ 28.271392] RDX: ffff8881027a4930 RSI: 00000000fffffff8 RDI: ffff888102b3f820
[ 28.272514] RBP: ffffc900001e7c80 R08: 0000000000000000 R09: 0000000000000000
[ 28.273681] R10: 0000000000000011 R11: 0000000000000081 R12: ffff8881027a4930
[ 28.274997] R13: 0000000000000010 R14: ffff888102b3fa90 R15: ffff888102b3f820
[ 28.276423] FS: 00007fb27ef28540(0000) GS:ffff8881b8986000(0000) knlGS:0000000000000000
[ 28.277835] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 28.278831] CR2: 000000000000000c CR3: 000000010298e000 CR4: 00000000000006f0
[ 28.280253] Call Trace:
[ 28.280723] <TASK>
[ 28.281147] essiv_aead_crypt+0x6d/0x230
[ 28.281845] aead_recvmsg+0x442/0x500
[ 28.282630] sock_recvmsg_nosec+0x57/0x80
[ 28.283493] sock_read_iter+0x7a/0xc0
[ 28.284209] vfs_read+0x14c/0x1e0
[ 28.284811] ksys_read+0x74/0xc0
[ 28.285368] do_syscall_64+0xca/0x1c0
[ 28.286120] entry_SYSCALL_64_after_hwframe+0x77/0x7f
[ 28.287068] RIP: 0033:0x7fb27ee3e46e
[ 28.287675] Code: c0 e9 b6 fe ff ff 50 48 8d 3d ce 07 0b 00 e8 69 01 02 00 66 0f 1f 84 00 00 00 00 00 64 8b 04 25 18 00 00 00 85 c0 75 14 0f 05 <48> 3d 00 f0 ff ff 77 5a8
[ 28.290739] RSP: 002b:00007ffcc5ad39a8 EFLAGS: 00000246 ORIG_RAX: 0000000000000000
[ 28.292152] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007fb27ee3e46e
[ 28.293253] RDX: 0000000000000040 RSI: 00007ffcc5ad3a60 RDI: 0000000000000004
[ 28.294356] RBP: 00007ffcc5ad3ba0 R08: 0000000000000000 R09: 00007ffcc5ad3887
[ 28.295495] R10: fffffffffffffd8d R11: 0000000000000246 R12: 000055995fbd3150
[ 28.296693] R13: 0000000000000000 R14: 0000000000000000 R15: 0000000000000000
[ 28.297842] </TASK>
[ 28.298208] Modules linked in:
[ 28.298966] CR2: 000000000000000c
[ 28.299630] ---[ end trace 0000000000000000 ]---
[ 28.300389] RIP: 0010:memcpy_to_sglist+0x3a/0x90
[ 28.301266] Code: 00 41 54 55 53 48 83 ec 20 65 48 8b 05 27 31 c6 01 48 89 44 24 18 31 c0 48 89 e7 f3 ab 45 85 ed 74 3a 89 f3 49 89 d4 48 89 e5 <41> 8b 40 0c 39 d8 73 0fb
[ 28.304232] RSP: 0018:ffffc900001e7c80 EFLAGS: 00010202
[ 28.305035] RAX: 0000000000000000 RBX: 00000000ffffffb8 RCX: 0000000000000000
[ 28.306210] RDX: ffff8881027a4930 RSI: 00000000fffffff8 RDI: ffff888102b3f820
[ 28.307396] RBP: ffffc900001e7c80 R08: 0000000000000000 R09: 0000000000000000
[ 28.308668] R10: 0000000000000011 R11: 0000000000000081 R12: ffff8881027a4930
[ 28.309798] R13: 0000000000000010 R14: ffff888102b3fa90 R15: ffff888102b3f820
[ 28.310996] FS: 00007fb27ef28540(0000) GS:ffff8881b8986000(0000) knlGS:0000000000000000
[ 28.312321] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 28.313229] CR2: 000000000000000c CR3: 000000010298e000 CR4: 00000000000006f0
Killed