
CVE-2025-40019の概念実証
CVE-2025-40019 は、暗号化ソルトセクター初期化ベクトル機構 (crypto/essiv.c) における、関連認証データ (AAD) 長の初期化ベクトル (IV) サイズに対する検証不十分に起因する、範囲外メモリアクセスの脆弱性です。 このリポジトリに含まれるコードは単なるバグトリガーですが、このバグは容易に悪用可能です。
私は kernelctf のスプレッドシートこちらでこのバグを見つけました。このバグに関するブログ記事や PoC がなかったため、Linux カーネルの暗号サブシステムを調査し、問題の原因を探ることにしました。

ESSIV の必要性を理解するには、まずデータがディスクにどのように保存されるかを理解する必要があります。ディスク暗号化(LUKS や dm-crypt など)は通常、セクタ単位で動作します。各セクタは独立して暗号化される必要があり、1つのセクタを読み取るためにディスク全体を読み取る必要がありません。
標準のCBC(暗号ブロックチェーン)モードでは、暗号化操作ごとに初期化ベクトル(IV)が必要です。初期のディスク暗号化実装では、セクタ番号がIVとして使用されていました。 しかし、セクタ番号は予測可能であるため、攻撃者は「ウォーターマーキング攻撃」を実行できます。既知のセクタに特別に細工されたデータを書き込むことで、攻撃者は暗号文のパターンから特定ファイルの存在を推測でき、暗号化の機密性を実質的に回避できます。
ESSIV(Encrypted Salt-Sector Initialization Vector)は、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長)が常に暗号変換の内部要件を満たすと想定している、古典的なケースです。 「インプレース」または復号化(!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;