bcachefs: Fix memory corruption in encryption path
authorKent Overstreet <kent.overstreet@gmail.com>
Fri, 3 Jun 2022 06:34:14 +0000 (02:34 -0400)
committerKent Overstreet <kent.overstreet@linux.dev>
Sun, 22 Oct 2023 21:09:33 +0000 (17:09 -0400)
When do_encrypt() was passed a vmalloc address and the buffer spanned
more than a single page, we were encrypting/decrypting completely
different pages than the ones intended.

Signed-off-by: Kent Overstreet <kent.overstreet@gmail.com>
fs/bcachefs/checksum.c

index 317efd047a463a0f12b214b826d144671f5438dc..e9a444f75b93da6a1389833a949d7160b4513e0b 100644 (file)
@@ -114,15 +114,41 @@ static inline int do_encrypt(struct crypto_sync_skcipher *tfm,
                              struct nonce nonce,
                              void *buf, size_t len)
 {
-       struct scatterlist sg;
-
-       sg_init_table(&sg, 1);
-       sg_set_page(&sg,
-                   is_vmalloc_addr(buf)
-                   ? vmalloc_to_page(buf)
-                   : virt_to_page(buf),
-                   len, offset_in_page(buf));
-       return do_encrypt_sg(tfm, nonce, &sg, len);
+       if (!is_vmalloc_addr(buf)) {
+               struct scatterlist sg;
+
+               sg_init_table(&sg, 1);
+               sg_set_page(&sg,
+                           is_vmalloc_addr(buf)
+                           ? vmalloc_to_page(buf)
+                           : virt_to_page(buf),
+                           len, offset_in_page(buf));
+               return do_encrypt_sg(tfm, nonce, &sg, len);
+       } else {
+               unsigned pages = buf_pages(buf, len);
+               struct scatterlist *sg;
+               size_t orig_len = len;
+               int ret, i;
+
+               sg = kmalloc_array(sizeof(*sg), pages, GFP_KERNEL);
+               if (!sg)
+                       return -ENOMEM;
+
+               sg_init_table(sg, pages);
+
+               for (i = 0; i < pages; i++) {
+                       unsigned offset = offset_in_page(buf);
+                       unsigned pg_len = min(len, PAGE_SIZE - offset);
+
+                       sg_set_page(sg + i, vmalloc_to_page(buf), pg_len, offset);
+                       buf += pg_len;
+                       len -= pg_len;
+               }
+
+               ret = do_encrypt_sg(tfm, nonce, sg, orig_len);
+               kfree(sg);
+               return ret;
+       }
 }
 
 int bch2_chacha_encrypt_key(struct bch_key *key, struct nonce nonce,