crypto: hash - Fix test underflow in shash_ahash_digest
authorHerbert Xu <herbert@gondor.apana.org.au>
Thu, 13 Mar 2025 05:14:58 +0000 (13:14 +0800)
committerHerbert Xu <herbert@gondor.apana.org.au>
Fri, 21 Mar 2025 09:33:38 +0000 (17:33 +0800)
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: 5f7082ed4f48 ("crypto: hash - Export shash through hash")
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
crypto/ahash.c

index 9c26175c21a8f79fc4b37d0bb359a143c09879a6..1fe5948802957a5a34f493732ca55e03812d784b 100644 (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);
-
-       if (nbytes &&
-           (sg = req->src, offset = sg->offset,
-            nbytes <= min(sg->length, ((unsigned int)(PAGE_SIZE)) - offset))) {
-               void *data;
-
-               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);
+       data = req->svirt;
+       if (!nbytes || ahash_request_isvirt(req))
+               return crypto_shash_digest(desc, data, nbytes, req->result);
+
+       sg = req->src;
+       if (nbytes > sg->length)
+               return 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);