# HG changeset patch
# User Taylor R Campbell <riastradh@NetBSD.org>
# Date 1589054629 0
#      Sat May 09 20:03:49 2020 +0000
# Branch trunk
# Node ID 014ac3549e14ac678a828f6e4669760c0bbf6d22
# Parent  c9b857fdb00e22635600438b4c8b50c1b9c47d74
Implement swap encryption.

Enabled by sysctl -w vm.swap_encrypt=1.  Key is generated lazily when
we first need to swap a page.  Key is chosen independently for each
swap device.  The ith swap page is encrypted with AES256-CBC using
AES256_k(le32enc(i) || 0^96) as the initialization vector.  Can be
changed at any time; no need for compatibility with on-disk formats.

diff -r c9b857fdb00e -r 014ac3549e14 sys/uvm/uvm_swap.c
--- a/sys/uvm/uvm_swap.c	Sat May 09 19:53:57 2020 +0000
+++ b/sys/uvm/uvm_swap.c	Sat May 09 20:03:49 2020 +0000
@@ -42,6 +42,7 @@
 #include <sys/buf.h>
 #include <sys/bufq.h>
 #include <sys/conf.h>
+#include <sys/cprng.h>
 #include <sys/proc.h>
 #include <sys/namei.h>
 #include <sys/disklabel.h>
@@ -64,6 +65,8 @@
 
 #include <miscfs/specfs/specdev.h>
 
+#include <crypto/rijndael/rijndael-api-fst.h>
+
 /*
  * uvm_swap.c: manage configuration and i/o to swap space.
  */
@@ -143,6 +146,11 @@ struct swapdev {
 	int			swd_maxactive;	/* max active i/o reqs */
 	struct bufq_state	*swd_tab;	/* buffer list */
 	int			swd_active;	/* number of active buffers */
+
+	uint8_t			*swd_encmap;	/* bitmap of encrypted slots */
+	keyInstance		swd_enckey;	/* AES key expanded for enc */
+	keyInstance		swd_deckey;	/* AES key expanded for dec */
+	bool			swd_encinit;	/* true if keys initialized */
 };
 
 /*
@@ -200,6 +208,7 @@ static struct workqueue *sw_reg_workqueu
 
 /* tuneables */
 u_int uvm_swapisfull_factor = 99;
+bool uvm_swap_encryption = false;
 
 /*
  * prototypes
@@ -221,6 +230,10 @@ static void sw_reg_start(struct swapdev 
 
 static int uvm_swap_io(struct vm_page **, int, int, int);
 
+static void uvm_swap_genkey(struct swapdev *);
+static void uvm_swap_encrypt(struct swapdev *, void *, int);
+static void uvm_swap_decrypt(struct swapdev *, void *, int);
+
 /*
  * uvm_swap_init: init the swap system data structures and locks
  *
@@ -888,6 +901,13 @@ swap_on(struct lwp *l, struct swapdev *s
 	blist_free(sdp->swd_blist, addr, size);
 
 	/*
+	 * allocate space to for swap encryption state and mark the
+	 * keys uninitialized so we generate them lazily
+	 */
+	sdp->swd_encmap = kmem_zalloc(howmany(npages, NBBY), KM_SLEEP);
+	sdp->swd_encinit = false;
+
+	/*
 	 * if the vnode we are swapping to is the root vnode
 	 * (i.e. we are swapping to the miniroot) then we want
 	 * to make sure we don't overwrite it.   do a statfs to
@@ -1059,6 +1079,9 @@ swap_off(struct lwp *l, struct swapdev *
 	vmem_free(swapmap, sdp->swd_drumoffset, sdp->swd_drumsize);
 	blist_destroy(sdp->swd_blist);
 	bufq_free(sdp->swd_tab);
+	kmem_free(sdp->swd_encmap, howmany(sdp->swd_npages, NBBY));
+	explicit_memset(&sdp->swd_enckey, 0, sizeof sdp->swd_enckey);
+	explicit_memset(&sdp->swd_deckey, 0, sizeof sdp->swd_deckey);
 	kmem_free(sdp, sizeof(*sdp));
 	return (0);
 }
@@ -1769,7 +1792,7 @@ uvm_swap_io(struct vm_page **pps, int st
 	struct	buf *bp;
 	vaddr_t kva;
 	int	error, mapinflags;
-	bool write, async;
+	bool write, async, swap_encrypt;
 	UVMHIST_FUNC("uvm_swap_io"); UVMHIST_CALLED(pdhist);
 
 	UVMHIST_LOG(pdhist, "<- called, startslot=%jd, npages=%jd, flags=%jd",
@@ -1777,6 +1800,7 @@ uvm_swap_io(struct vm_page **pps, int st
 
 	write = (flags & B_READ) == 0;
 	async = (flags & B_ASYNC) != 0;
+	swap_encrypt = atomic_load_relaxed(&uvm_swap_encryption);
 
 	/*
 	 * allocate a buf for the i/o.
@@ -1802,9 +1826,62 @@ uvm_swap_io(struct vm_page **pps, int st
 	mapinflags = !write ?
 		UVMPAGER_MAPIN_WAITOK|UVMPAGER_MAPIN_READ :
 		UVMPAGER_MAPIN_WAITOK|UVMPAGER_MAPIN_WRITE;
+	if (write && swap_encrypt)	/* need to encrypt in-place */
+		mapinflags |= UVMPAGER_MAPIN_READ;
 	kva = uvm_pagermapin(pps, npages, mapinflags);
 
 	/*
+	 * encrypt writes in place if requested
+	 */
+
+	if (write) do {
+		struct swapdev *sdp;
+		int i;
+
+		/*
+		 * Get the swapdev so we can discriminate on the
+		 * encryption state.  There may or may not be an
+		 * encryption key generated; we may or may not be asked
+		 * to encrypt swap.
+		 *
+		 * 1. NO ENCRYPTION KEY, NO ENCRYPTION: Nothing to do.
+		 *
+		 * 2. NO ENCRYPTION KEY, BUT ENCRYPTION: Generate a
+		 *    key, encrypt, and mark the slots encrypted.
+		 *
+		 * 3. ENCRYPTION KEY, BUT NO ENCRYPTION: The slots may
+		 *    already be marked encrypted from a past life.
+		 *    Mark them not encrypted.
+		 *
+		 * 4. ENCRYPTION KEY, ENCRYPTION: Encrypt and mark the
+		 *    slots encrypted.
+		 */
+		sdp = swapdrum_getsdp(startslot);
+		if (!sdp->swd_encinit) {
+			if (!swap_encrypt)
+				break;
+			uvm_swap_genkey(sdp);
+		}
+		KASSERT(sdp->swd_encinit);
+
+		if (swap_encrypt) {
+			for (i = 0; i < npages; i++) {
+				int s = startslot + i;
+				KDASSERT(swapdrum_getsdp(s) == sdp);
+				uvm_swap_encrypt(sdp,
+				    (void *)(kva + i*PAGE_SIZE), s);
+				sdp->swd_encmap[s/NBBY] |= 1u << (s%NBBY);
+			}
+		} else {
+			for (i = 0; i < npages; i++) {
+				int s = startslot + i;
+				KDASSERT(swapdrum_getsdp(s) == sdp);
+				sdp->swd_encmap[s/NBBY] &= ~(1u << (s%NBBY));
+			}
+		}
+	} while (0);
+
+	/*
 	 * fill in the bp/sbp.   we currently route our i/o through
 	 * /dev/drum's vnode [swapdev_vp].
 	 */
@@ -1861,6 +1938,31 @@ uvm_swap_io(struct vm_page **pps, int st
 	error = biowait(bp);
 
 	/*
+	 * decrypt reads in place if needed
+	 */
+
+	if (!write) do {
+		struct swapdev *sdp;
+		int i;
+
+		sdp = swapdrum_getsdp(startslot);
+		if (!sdp->swd_encinit)
+			/*
+			 * If there's no encryption key, there's no way
+			 * any of these slots can be encrypted, so
+			 * nothing to do here.
+			 */
+			break;
+		for (i = 0; i < npages; i++) {
+			int s = startslot + i;
+			KDASSERT(swapdrum_getsdp(s) == sdp);
+			if ((sdp->swd_encmap[s/NBBY] & (1u << (s%NBBY))) == 0)
+				continue;
+			uvm_swap_decrypt(sdp, (void *)(kva + i*PAGE_SIZE), s);
+		}
+	} while (0);
+
+	/*
 	 * kill the pager mapping
 	 */
 
@@ -1880,3 +1982,97 @@ uvm_swap_io(struct vm_page **pps, int st
 
 	return (error);
 }
+
+/*
+ * uvm_swap_genkey(sdp)
+ *
+ *	Generate a key for swap encryption.
+ */
+static void
+uvm_swap_genkey(struct swapdev *sdp)
+{
+	uint8_t key[32];
+
+	KASSERT(!sdp->swd_encinit);
+
+	cprng_strong(kern_cprng, key, sizeof key, 0);
+	rijndael_makeKey(&sdp->swd_enckey, DIR_ENCRYPT, 256, key);
+	rijndael_makeKey(&sdp->swd_deckey, DIR_DECRYPT, 256, key);
+	explicit_memset(key, 0, sizeof key);
+
+	sdp->swd_encinit = true;
+}
+
+/*
+ * uvm_swap_encrypt(sdp, kva, slot)
+ *
+ *	Encrypt one page of data at kva for the specified slot number
+ *	in the swap device.
+ */
+static void
+uvm_swap_encrypt(struct swapdev *sdp, void *kva, int slot)
+{
+	cipherInstance aes;
+	uint8_t preiv[16] = {0}, iv[16];
+	int ok __diagused, nbits __diagused;
+
+	/* iv := AES_k(le32enc(slot) || 0^96) */
+	le32enc(preiv, slot);
+	ok = rijndael_cipherInit(&aes, MODE_ECB, NULL);
+	KASSERT(ok);
+	nbits = rijndael_blockEncrypt(&aes, &sdp->swd_enckey, preiv,
+	    /*length in bits*/128, iv);
+	KASSERT(nbits == 1);
+
+	/* *kva := AES-CBC_k(iv, *kva) */
+	ok = rijndael_cipherInit(&aes, MODE_CBC, iv);
+	KASSERT(ok);
+	nbits = rijndael_blockEncrypt(&aes, &sdp->swd_enckey, kva,
+	    /*length in bits*/PAGE_SIZE*NBBY, kva);
+	KASSERT(nbits == PAGE_SIZE*NBBY);
+
+	explicit_memset(&iv, 0, sizeof iv);
+	explicit_memset(&aes, 0, sizeof aes);
+}
+
+/*
+ * uvm_swap_decrypt(sdp, kva, slot)
+ *
+ *	Decrypt one page of data at kva for the specified slot number
+ *	in the swap device.
+ */
+static void
+uvm_swap_decrypt(struct swapdev *sdp, void *kva, int slot)
+{
+	cipherInstance aes;
+	uint8_t preiv[16] = {0}, iv[16];
+	int ok __diagused, nbits __diagused;
+
+	/* iv := AES_k(le32enc(slot) || 0^96) */
+	le32enc(preiv, slot);
+	ok = rijndael_cipherInit(&aes, MODE_ECB, NULL);
+	KASSERT(ok);
+	nbits = rijndael_blockEncrypt(&aes, &sdp->swd_enckey, preiv,
+	    /*length in bits*/128, iv);
+	KASSERT(nbits == 1);
+
+	/* *kva := AES-CBC^{-1}_k(iv, *kva) */
+	ok = rijndael_cipherInit(&aes, MODE_CBC, iv);
+	KASSERT(ok);
+	nbits = rijndael_blockEncrypt(&aes, &sdp->swd_deckey, kva,
+	    /*length in bits*/PAGE_SIZE*NBBY, kva);
+	KASSERT(nbits == PAGE_SIZE*NBBY);
+
+	explicit_memset(&iv, 0, sizeof iv);
+	explicit_memset(&aes, 0, sizeof aes);
+}
+
+SYSCTL_SETUP(sysctl_uvmswap_setup, "sysctl uvmswap setup")
+{
+
+	sysctl_createv(clog, 0, NULL, NULL,
+	    CTLFLAG_PERMANENT|CTLFLAG_READWRITE, CTLTYPE_BOOL, "swap_encrypt",
+	    SYSCTL_DESCR("Encrypt when data swapped out to disk"),
+	    NULL, 0, &uvm_swap_encryption, 0,
+	    CTL_VM, CTL_CREATE, CTL_EOL);
+}