git: 1f152c027551 - stable/12 - loader: implement GELI writes

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Fri, 08 Oct 2021 16:09:01 UTC
The branch stable/12 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=1f152c027551e887d150ea837afc06cec3fe5fcf

commit 1f152c027551e887d150ea837afc06cec3fe5fcf
Author:     Toomas Soome <tsoome@FreeBSD.org>
AuthorDate: 2020-07-11 06:51:42 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2021-10-08 08:11:27 +0000

    loader: implement GELI writes
    
    Bug: https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=247482
    
    This patch is based on initial work from allanjude.
    
    PR:             247482
    
    (cherry picked from commit de776da3239ee3edc5f77bd0e48d0bb15262024b)
---
 stand/i386/gptboot/gptboot.c         |  2 +-
 stand/libsa/geli/geliboot.c          |  8 ++--
 stand/libsa/geli/geliboot.h          | 10 ++++-
 stand/libsa/geli/geliboot_crypto.c   | 33 +++++++++-------
 stand/libsa/geli/geliboot_internal.h |  4 +-
 stand/libsa/geli/gelidev.c           | 75 ++++++++++++++++++++++++------------
 6 files changed, 86 insertions(+), 46 deletions(-)

diff --git a/stand/i386/gptboot/gptboot.c b/stand/i386/gptboot/gptboot.c
index 7b5423ede6d8..78d876554c86 100644
--- a/stand/i386/gptboot/gptboot.c
+++ b/stand/i386/gptboot/gptboot.c
@@ -610,7 +610,7 @@ dskread(void *buf, daddr_t lba, unsigned nblk)
 #ifdef LOADER_GELI_SUPPORT
 	if (err == 0 && gdsk.gdev != NULL) {
 		/* Decrypt */
-		if (geli_read(gdsk.gdev, lba * DEV_BSIZE, buf,
+		if (geli_io(gdsk.gdev, GELI_DECRYPT, lba * DEV_BSIZE, buf,
 		    nblk * DEV_BSIZE))
 			return (err);
 	}
diff --git a/stand/libsa/geli/geliboot.c b/stand/libsa/geli/geliboot.c
index 00b9af93573a..954a3ec34044 100644
--- a/stand/libsa/geli/geliboot.c
+++ b/stand/libsa/geli/geliboot.c
@@ -310,7 +310,8 @@ found_key:
 }
 
 int
-geli_read(struct geli_dev *gdev, off_t offset, u_char *buf, size_t bytes)
+geli_io(struct geli_dev *gdev, geli_op_t enc, off_t offset, u_char *buf,
+    size_t bytes)
 {
 	u_char iv[G_ELI_IVKEYLEN];
 	u_char *pbuf;
@@ -343,12 +344,13 @@ geli_read(struct geli_dev *gdev, off_t offset, u_char *buf, size_t bytes)
 		keyno = (dstoff >> G_ELI_KEY_SHIFT) / secsize;
 		g_eli_key_fill(&gdev->sc, &gkey, keyno);
 
-		error = geliboot_crypt(gdev->sc.sc_ealgo, 0, pbuf, secsize,
+		error = geliboot_crypt(gdev->sc.sc_ealgo, enc, pbuf, secsize,
 		    gkey.gek_key, gdev->sc.sc_ekeylen, iv);
 
 		if (error != 0) {
 			explicit_bzero(&gkey, sizeof(gkey));
-			printf("Failed to decrypt in geli_read()!");
+			printf("%s: Failed to %s!", __func__,
+			    enc ? "encrypt" : "decrypt");
 			return (error);
 		}
 		pbuf += secsize;
diff --git a/stand/libsa/geli/geliboot.h b/stand/libsa/geli/geliboot.h
index aeebde4b0a2e..0c2ef817e97d 100644
--- a/stand/libsa/geli/geliboot.h
+++ b/stand/libsa/geli/geliboot.h
@@ -50,6 +50,11 @@
 #define	GELI_KEYBUF_SIZE		(sizeof(struct keybuf) + \
     (GELI_MAX_KEYS * sizeof(struct keybuf_ent)))
 
+typedef enum geli_op {
+	GELI_DECRYPT,
+	GELI_ENCRYPT
+} geli_op_t;
+
 extern void pwgets(char *buf, int n, int hide);
 
 typedef u_char geli_ukey[G_ELI_USERKEYLEN];
@@ -73,9 +78,10 @@ struct preloaded_file;
 typedef int (*geli_readfunc)(void *vdev, void *readpriv, off_t offbytes,
     void *buf, size_t sizebytes);
 
-struct geli_dev * geli_taste(geli_readfunc readfunc, void *readpriv,
+struct geli_dev *geli_taste(geli_readfunc readfunc, void *readpriv,
     daddr_t lastsector, const char *namefmt, ...);
-int geli_read(struct geli_dev *gdev, off_t offset, u_char *buf, size_t bytes);
+int geli_io(struct geli_dev *gdev, geli_op_t, off_t offset, u_char *buf,
+    size_t bytes);
 int geli_havekey(struct geli_dev *gdev);
 int geli_passphrase(struct geli_dev *gdev, char *pw);
 
diff --git a/stand/libsa/geli/geliboot_crypto.c b/stand/libsa/geli/geliboot_crypto.c
index 82a137c06212..50c62bccf1c7 100644
--- a/stand/libsa/geli/geliboot_crypto.c
+++ b/stand/libsa/geli/geliboot_crypto.c
@@ -35,7 +35,7 @@
 #include "geliboot.h"
 
 int
-geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
+geliboot_crypt(u_int algo, geli_op_t enc, u_char *data, size_t datasize,
     const u_char *key, size_t keysize, u_char *iv)
 {
 	keyInstance aeskey;
@@ -49,7 +49,7 @@ geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
 		err = rijndael_makeKey(&aeskey, !enc, keysize, 
 		    (const char *)key);
 		if (err < 0) {
-			printf("Failed to setup decryption keys: %d\n", err);
+			printf("Failed to setup crypo keys: %d\n", err);
 			return (err);
 		}
 
@@ -59,18 +59,20 @@ geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
 			return (err);
 		}
 
-		if (enc == 0) {
-			/* decrypt */
+		switch (enc) {
+		case GELI_DECRYPT:
 			blks = rijndael_blockDecrypt(&cipher, &aeskey, data, 
 			    datasize * 8, data);
-		} else {
-			/* encrypt */
+			break;
+		case GELI_ENCRYPT:
 			blks = rijndael_blockEncrypt(&cipher, &aeskey, data, 
 			    datasize * 8, data);
+			break;
 		}
 		if (datasize != (blks / 8)) {
-			printf("Failed to decrypt the entire input: "
-			    "%u != %zu\n", blks, datasize);
+			printf("Failed to %s the entire input: %u != %zu\n",
+			    enc ? "decrypt" : "encrypt",
+			    blks, datasize);
 			return (1);
 		}
 		break;
@@ -84,14 +86,15 @@ geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
 		enc_xform_aes_xts.reinit((caddr_t)ctxp, iv);
 
 		switch (enc) {
-		case 0: /* decrypt */
+		case GELI_DECRYPT:
 			for (i = 0; i < datasize; i += AES_XTS_BLOCKSIZE) {
 				enc_xform_aes_xts.decrypt((caddr_t)ctxp, data + i);
 			}
 			break;
-		case 1: /* encrypt */
+		case GELI_ENCRYPT:
 			for (i = 0; i < datasize; i += AES_XTS_BLOCKSIZE) {
-				enc_xform_aes_xts.encrypt((caddr_t)ctxp, data + i);
+				enc_xform_aes_xts.encrypt((caddr_t)ctxp,
+				    data + i);
 			}
 			break;
 		}
@@ -105,7 +108,7 @@ geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
 }
 
 static int
-g_eli_crypto_cipher(u_int algo, int enc, u_char *data, size_t datasize,
+g_eli_crypto_cipher(u_int algo, geli_op_t enc, u_char *data, size_t datasize,
     const u_char *key, size_t keysize)
 {
 	u_char iv[keysize];
@@ -123,7 +126,8 @@ g_eli_crypto_encrypt(u_int algo, u_char *data, size_t datasize,
 	if (algo == CRYPTO_AES_XTS)
 		algo = CRYPTO_AES_CBC;
 
-	return (g_eli_crypto_cipher(algo, 1, data, datasize, key, keysize));
+	return (g_eli_crypto_cipher(algo, GELI_ENCRYPT, data, datasize, key,
+	    keysize));
 }
 
 int
@@ -135,5 +139,6 @@ g_eli_crypto_decrypt(u_int algo, u_char *data, size_t datasize,
 	if (algo == CRYPTO_AES_XTS)
 		algo = CRYPTO_AES_CBC;
 
-	return (g_eli_crypto_cipher(algo, 0, data, datasize, key, keysize));
+	return (g_eli_crypto_cipher(algo, GELI_DECRYPT, data, datasize, key,
+	    keysize));
 }
diff --git a/stand/libsa/geli/geliboot_internal.h b/stand/libsa/geli/geliboot_internal.h
index 2352a844f7dd..2af74466179f 100644
--- a/stand/libsa/geli/geliboot_internal.h
+++ b/stand/libsa/geli/geliboot_internal.h
@@ -55,6 +55,8 @@
 #define STAND_H /* We don't want stand.h in {gpt,zfs,gptzfs}boot */
 #include <opencrypto/xform_enc.h>
 
+#include "geliboot.h"
+
 #define GELIDEV_NAMELEN	32
 
 struct geli_dev {
@@ -65,7 +67,7 @@ struct geli_dev {
 	char                    *name; /* for prompting; it ends in ':' */
 };
 
-int geliboot_crypt(u_int algo, int enc, u_char *data, size_t datasize,
+int geliboot_crypt(u_int algo, geli_op_t  enc, u_char *data, size_t datasize,
     const u_char *key, size_t keysize, u_char *iv);
 
 #endif /* _GELIBOOT_INTERNAL_H_ */
diff --git a/stand/libsa/geli/gelidev.c b/stand/libsa/geli/gelidev.c
index 751255636112..e1444c9b7fcd 100644
--- a/stand/libsa/geli/gelidev.c
+++ b/stand/libsa/geli/gelidev.c
@@ -115,10 +115,6 @@ geli_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf,
 	char *iobuf;
 	int rc;
 
-	/* We only handle reading; no write support. */
-	if ((rw & F_MASK) != F_READ)
-		return (EOPNOTSUPP);
-
 	gdesc = (struct geli_devdesc *)devdata;
 
 	/*
@@ -139,34 +135,63 @@ geli_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf,
 	alnsize  = alnend - alnstart;
 
 	/*
-	 * If alignment requires us to read more than the size of the provided
-	 * buffer, allocate a temporary buffer.
+	 * If alignment requires us to read/write more than the size of the
+	 * provided buffer, allocate a temporary buffer.
+	 * The writes will always get temporary buffer because of encryption.
 	 */
-	if (alnsize <= size)
+	if (alnsize <= size && (rw & F_MASK) == F_READ)
 		iobuf = buf;
 	else if ((iobuf = malloc(alnsize)) == NULL)
 		return (ENOMEM);
 
-	/*
-	 * Read the encrypted data using the host provider, then decrypt it.
-	 */
-	rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, rw,
-	    alnstart / DEV_BSIZE, alnsize, iobuf, NULL);
-	if (rc != 0)
-		goto out;
-	rc = geli_read(gdesc->gdev, alnstart, iobuf, alnsize);
-	if (rc != 0)
-		goto out;
+	switch (rw & F_MASK) {
+	case F_READ:
+		/*
+		 * Read the encrypted data using the host provider,
+		 * then decrypt it.
+		 */
+		rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, rw,
+		    alnstart / DEV_BSIZE, alnsize, iobuf, NULL);
+		if (rc != 0)
+			goto out;
+		rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf,
+		    alnsize);
+		if (rc != 0)
+			goto out;
 
-	/*
-	 * If we had to use a temporary buffer, copy the requested part of the
-	 * data to the caller's buffer.
-	 */
-	if (iobuf != buf)
-		memcpy(buf, iobuf + (reqstart - alnstart), size);
+		/*
+		 * If we had to use a temporary buffer, copy the requested
+		 * part of the data to the caller's buffer.
+		 */
+		if (iobuf != buf)
+			memcpy(buf, iobuf + (reqstart - alnstart), size);
+
+		if (rsize != NULL)
+			*rsize = size;
+		break;
+	case F_WRITE:
+		if (iobuf != buf) {
+			/* Read, decrypt, then modify.  */
+			rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc,
+			    F_READ, alnstart / DEV_BSIZE, alnsize, iobuf, NULL);
+			if (rc != 0)
+				goto out;
+			rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf,
+			    alnsize);
+			if (rc != 0)
+				goto out;
+			/* Copy data to iobuf */
+			memcpy(iobuf + (reqstart - alnstart), buf, size);
+		}
 
-	if (rsize != NULL)
-		*rsize = size;
+		/* Encrypt and write it. */
+		rc = geli_io(gdesc->gdev, GELI_ENCRYPT, alnstart, iobuf,
+		    alnsize);
+		if (rc != 0)
+			goto out;
+		rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc,
+		    rw, alnstart / DEV_BSIZE, alnsize, iobuf, NULL);
+	}
 out:
 	if (iobuf != buf)
 		free(iobuf);