crypto: hash - Fix test underflow in shash_ahash_digest

The test on PAGE_SIZE - offset in shash_ahash_digest can underflow,
leading to execution of the fast path even if the data cannot be
mapped into a single page.

Fix this by splitting the test into four cases:

1) nbytes > sg->length: More than one SG entry, slow path.
2) !IS_ENABLED(CONFIG_HIGHMEM): fast path.
3) nbytes > (unsigned int)PAGE_SIZE - offset: Two highmem pages, slow path.
4) Highmem fast path.

Fixes: 5f7082ed4f ("crypto: hash - Export shash through hash")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
This commit is contained in:
Herbert Xu
2025-03-13 13:14:58 +08:00
parent da6f9bf40a
commit 480db50095
+26 -14
View File
@@ -16,6 +16,7 @@
#include <linux/cryptouser.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
@@ -201,25 +202,36 @@ int shash_ahash_digest(struct ahash_request *req, struct shash_desc *desc)
unsigned int nbytes = req->nbytes;
struct scatterlist *sg;
unsigned int offset;
struct page *page;
const u8 *data;
int err;
if (ahash_request_isvirt(req))
return crypto_shash_digest(desc, req->svirt, nbytes,
req->result);
data = req->svirt;
if (!nbytes || ahash_request_isvirt(req))
return crypto_shash_digest(desc, data, nbytes, req->result);
if (nbytes &&
(sg = req->src, offset = sg->offset,
nbytes <= min(sg->length, ((unsigned int)(PAGE_SIZE)) - offset))) {
void *data;
sg = req->src;
if (nbytes > sg->length)
return crypto_shash_init(desc) ?:
shash_ahash_finup(req, desc);
data = kmap_local_page(sg_page(sg));
err = crypto_shash_digest(desc, data + offset, nbytes,
req->result);
kunmap_local(data);
} else
err = crypto_shash_init(desc) ?:
shash_ahash_finup(req, desc);
page = sg_page(sg);
offset = sg->offset;
data = lowmem_page_address(page) + offset;
if (!IS_ENABLED(CONFIG_HIGHMEM))
return crypto_shash_digest(desc, data, nbytes, req->result);
page += offset >> PAGE_SHIFT;
offset = offset_in_page(offset);
if (nbytes > (unsigned int)PAGE_SIZE - offset)
return crypto_shash_init(desc) ?:
shash_ahash_finup(req, desc);
data = kmap_local_page(page);
err = crypto_shash_digest(desc, data + offset, nbytes,
req->result);
kunmap_local(data);
return err;
}
EXPORT_SYMBOL_GPL(shash_ahash_digest);