
Ein Proof of Concept für CVE-2025-40019
CVE-2025-40019 ist der Encrypted Salt-Sector Initialization Vector Mechanismus (crypto/essiv.c), der eine unzureichende Validierung der Länge der assoziierten Authentifizierungsdaten (AAD) im Verhältnis zur Größe des Initialisierungsvektors (IV) aufweist, was zu einem Speicherzugriff außerhalb der Grenzen führt. Der in diesem Repository bereitgestellte Code ist lediglich ein Bug-Trigger, doch dieser Fehler ist leicht ausnutzbar.
Ich stieß auf diesen Fehler in der kernelctf-Tabelle hier. Da es keinen Blogbeitrag oder PoC zu diesem Fehler gab, beschloss ich, das Krypto-Subsystem des Linux-Kernels zu untersuchen, um herauszufinden, was das Problem verursacht hat.

Um die Notwendigkeit von ESSIV zu verstehen, müssen wir uns ansehen, wie Daten auf der Festplatte gespeichert werden. Festplattenverschlüsselung (wie LUKS oder dm-crypt) arbeitet typischerweise auf Sektorebene. Jeder Sektor muss unabhängig verschlüsselt werden, sodass das Lesen eines Sektors nicht das Lesen der gesamten Festplatte erfordert.
Im Standard-CBC-Modus (Cipher Block Chaining) wird für jede Verschlüsselungsoperation ein Initialisierungsvektor (IV) benötigt. Frühe Implementierungen der Festplattenverschlüsselung verwendeten die Sektornummer als IV.
Da Sektornummern jedoch vorhersagbar sind, kann ein Angreifer einen 'Watermarking-Angriff' durchführen. Indem er speziell präparierte Daten in einen bekannten Sektor schreibt, kann der Angreifer Muster im Chiffretext beobachten, die das Vorhandensein bestimmter Dateien offenbaren, wodurch die Vertraulichkeit der Verschlüsselung effektiv umgangen wird.
ESSIV (Encrypted Salt-Sector Initialization Vector) wurde entwickelt, um den IV unvorhersagbar zu machen. Es funktioniert wie folgt:
Dies stellt sicher, dass selbst wenn ein Angreifer die Sektornummer kennt, er den IV nicht ohne Kenntnis des geheimen Schlüssels vorhersagen kann.
Jeder Algorithmus, sei es eine Basis-Chiffre wie AES oder ein Wrapper wie ESSIV, implementiert eine Struktur, die der Kernel verwendet, um Aufrufe zu leiten:
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.
};
Die Schwachstelle ist ein klassischer Fall der Annahme, dass benutzerbereitgestellte Metadaten (die AAD-Länge) stets die internen Anforderungen der kryptografischen Transformation erfüllen. Im 'In-Place'- oder Entschlüsselungspfad (!enc) berechnet der Code einen Offset: req->assoclen - crypto_aead_ivsize(tfm). Aber es wird nie überprüft, ob req->assoclen < ivsize ist. Das bedeutet, dass dieser Offset negativ sein kann.
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;
.
.
.
Betrachtet man die Funktion scatterwalk_map_and_copy, sieht man, dass sie lediglich einen memcopy in die scatterlist sg durchführt:
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);
}
Der Patch für diesen Fehler war sehr einfach; er überprüft lediglich, ob assoclen kleiner als ivsize ist.
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;