svn commit: r297629 - in head/sys/boot: geli i386/libi386 i386/zfsboot

Allan Jude allanjude at FreeBSD.org
Wed Apr 6 23:21:46 UTC 2016


Author: allanjude
Date: Wed Apr  6 23:21:44 2016
New Revision: 297629
URL: https://svnweb.freebsd.org/changeset/base/297629

Log:
  Fix GELIBoot support for GELI sector size is > 512
  
  Add support for 4k sector GELI encrypted partitions to the bootloader
  This is the default created by the installer
  
  Because the IV is different for each sector, and the XTS tweak carries forward you can not decrypt a partial sector if the starting offset is not 0
  
  Make boot2 and the loader read in 4k aligned chunks
  
  Reviewed by:	ed, oshogbo
  Sponsored by:	ScaleEngine Inc.
  Differential Revision:	https://reviews.freebsd.org/D5820

Modified:
  head/sys/boot/geli/geliboot.c
  head/sys/boot/geli/geliboot.h
  head/sys/boot/i386/libi386/biosdisk.c
  head/sys/boot/i386/zfsboot/zfsboot.c

Modified: head/sys/boot/geli/geliboot.c
==============================================================================
--- head/sys/boot/geli/geliboot.c	Wed Apr  6 23:17:05 2016	(r297628)
+++ head/sys/boot/geli/geliboot.c	Wed Apr  6 23:21:44 2016	(r297629)
@@ -73,30 +73,34 @@ geli_taste(int read_func(void *vdev, voi
     size_t bytes), struct dsk *dskp, daddr_t lastsector)
 {
 	struct g_eli_metadata md;
-	u_char buf[DEV_BSIZE];
+	u_char buf[DEV_GELIBOOT_BSIZE];
 	int error;
+	off_t alignsector;
 
-	error = read_func(NULL, dskp, (off_t) lastsector * DEV_BSIZE, &buf,
-	    (size_t) DEV_BSIZE);
+	alignsector = (lastsector * DEV_BSIZE) &
+	    ~(off_t)(DEV_GELIBOOT_BSIZE - 1);
+	error = read_func(NULL, dskp, alignsector, &buf, DEV_GELIBOOT_BSIZE);
 	if (error != 0) {
 		return (error);
 	}
-	error = eli_metadata_decode(buf, &md);
+	/* Extract the last DEV_BSIZE bytes from the block. */
+	error = eli_metadata_decode(buf + (DEV_GELIBOOT_BSIZE - DEV_BSIZE),
+	    &md);
 	if (error != 0) {
 		return (error);
 	}
 
 	if ((md.md_flags & G_ELI_FLAG_ONETIME)) {
-		/* Swap device, skip it */
+		/* Swap device, skip it. */
 		return (1);
 	}
 	if (!(md.md_flags & G_ELI_FLAG_BOOT)) {
-		/* Disk is not GELI boot device, skip it */
+		/* Disk is not GELI boot device, skip it. */
 		return (1);
 	}
 	if (md.md_iterations < 0) {
-		/* XXX TODO: Support loading key files */
-		/* Disk does not have a passphrase, skip it */
+		/* XXX TODO: Support loading key files. */
+		/* Disk does not have a passphrase, skip it. */
 		return (1);
 	}
 	geli_e = malloc(sizeof(struct geli_entry));
@@ -143,7 +147,7 @@ geli_attach(struct dsk *dskp, const char
 		 * Prepare Derived-Key from the user passphrase.
 		 */
 		if (geli_e->md.md_iterations < 0) {
-			/* XXX TODO: Support loading key files */
+			/* XXX TODO: Support loading key files. */
 			return (1);
 		} else if (geli_e->md.md_iterations == 0) {
 			g_eli_crypto_hmac_update(&ctx, geli_e->md.md_salt,
@@ -151,8 +155,8 @@ geli_attach(struct dsk *dskp, const char
 			g_eli_crypto_hmac_update(&ctx, passphrase,
 			    strlen(passphrase));
 		} else if (geli_e->md.md_iterations > 0) {
-			printf("Calculating GELI Decryption Key disk%dp%d @ %lu "
-			    "iterations...\n", dskp->unit,
+			printf("Calculating GELI Decryption Key disk%dp%d @ %lu"
+			    " iterations...\n", dskp->unit,
 			    (dskp->slice > 0 ? dskp->slice : dskp->part),
 			    geli_e->md.md_iterations);
 			u_char dkey[G_ELI_USERKEYLEN];
@@ -193,7 +197,7 @@ geli_attach(struct dsk *dskp, const char
 		}
 		bzero(&mkey, sizeof(mkey));
 
-		/* Initialize the per-sector IV */
+		/* Initialize the per-sector IV. */
 		switch (geli_e->sc.sc_ealgo) {
 		case CRYPTO_AES_XTS:
 			break;
@@ -207,7 +211,7 @@ geli_attach(struct dsk *dskp, const char
 		return (0);
 	}
 
-	/* Disk not found */
+	/* Disk not found. */
 	return (2);
 }
 
@@ -229,35 +233,49 @@ geli_read(struct dsk *dskp, off_t offset
 	u_char iv[G_ELI_IVKEYLEN];
 	u_char *pbuf;
 	int error;
-	off_t os;
+	off_t dstoff;
 	uint64_t keyno;
-	size_t n, nb;
+	size_t n, nsec, secsize;
 	struct g_eli_key gkey;
 
+	pbuf = buf;
 	SLIST_FOREACH_SAFE(geli_e, &geli_head, entries, geli_e_tmp) {
 		if (geli_same_device(geli_e, dskp) != 0) {
 			continue;
 		}
 
-		nb = bytes / DEV_BSIZE;
-		for (n = 0; n < nb; n++) {
-			os = offset + (n * DEV_BSIZE);
-			pbuf = buf + (n * DEV_BSIZE);
+		secsize = geli_e->sc.sc_sectorsize;
+		nsec = bytes / secsize;
+		if (nsec == 0) {
+			/*
+			 * A read of less than the GELI sector size has been
+			 * requested. The caller provided destination buffer may
+			 * not be big enough to boost the read to a full sector,
+			 * so just attempt to decrypt the truncated sector.
+			 */
+			secsize = bytes;
+			nsec = 1;
+		}
+
+		for (n = 0, dstoff = offset; n < nsec; n++, dstoff += secsize) {
 
-			g_eli_crypto_ivgen(&geli_e->sc, os, iv, G_ELI_IVKEYLEN);
+			g_eli_crypto_ivgen(&geli_e->sc, dstoff, iv,
+			    G_ELI_IVKEYLEN);
 
-			/* Get the key that corresponds to this offset */
-			keyno = (os >> G_ELI_KEY_SHIFT) / DEV_BSIZE;
+			/* Get the key that corresponds to this offset. */
+			keyno = (dstoff >> G_ELI_KEY_SHIFT) / secsize;
 			g_eli_key_fill(&geli_e->sc, &gkey, keyno);
 
 			error = geliboot_crypt(geli_e->sc.sc_ealgo, 0, pbuf,
-			    DEV_BSIZE, gkey.gek_key, geli_e->sc.sc_ekeylen, iv);
+			    secsize, gkey.gek_key,
+			    geli_e->sc.sc_ekeylen, iv);
 
 			if (error != 0) {
 				bzero(&gkey, sizeof(gkey));
 				printf("Failed to decrypt in geli_read()!");
 				return (error);
 			}
+			pbuf += secsize;
 		}
 		bzero(&gkey, sizeof(gkey));
 		return (0);

Modified: head/sys/boot/geli/geliboot.h
==============================================================================
--- head/sys/boot/geli/geliboot.h	Wed Apr  6 23:17:05 2016	(r297628)
+++ head/sys/boot/geli/geliboot.h	Wed Apr  6 23:21:44 2016	(r297629)
@@ -55,6 +55,9 @@
 #ifndef DEV_BSIZE
 #define DEV_BSIZE 			512
 #endif
+#ifndef DEV_GELIBOOT_BSIZE
+#define DEV_GELIBOOT_BSIZE		4096
+#endif
 
 #ifndef MIN
 #define    MIN(a,b) (((a) < (b)) ? (a) : (b))

Modified: head/sys/boot/i386/libi386/biosdisk.c
==============================================================================
--- head/sys/boot/i386/libi386/biosdisk.c	Wed Apr  6 23:17:05 2016	(r297628)
+++ head/sys/boot/i386/libi386/biosdisk.c	Wed Apr  6 23:21:44 2016	(r297629)
@@ -706,15 +706,38 @@ bd_read(struct disk_devdesc *dev, daddr_
 {
 #ifdef LOADER_GELI_SUPPORT
 	struct dsk dskp;
-	off_t p_off;
-	int err, n;
+	off_t p_off, diff;
+	daddr_t alignlba;
+	int err, n, alignblks;
+	char *tmpbuf;
 
 	/* if we already know there is no GELI, skip the rest */
 	if (geli_status[dev->d_unit][dev->d_slice] != ISGELI_YES)
 		return (bd_io(dev, dblk, blks, dest, 0));
 
 	if (geli_status[dev->d_unit][dev->d_slice] == ISGELI_YES) {
-		err = bd_io(dev, dblk, blks, dest, 0);
+		/*
+		 * Align reads to DEV_GELIBOOT_BSIZE bytes because partial
+		 * sectors cannot be decrypted. Round the requested LBA down to
+		 * nearest multiple of DEV_GELIBOOT_BSIZE bytes.
+		 */
+		alignlba = dblk &
+		    ~(daddr_t)((DEV_GELIBOOT_BSIZE / BIOSDISK_SECSIZE) - 1);
+		/*
+		 * Round number of blocks to read up to nearest multiple of
+		 * DEV_GELIBOOT_BSIZE
+		 */
+		alignblks = blks + (dblk - alignlba) +
+		    ((DEV_GELIBOOT_BSIZE / BIOSDISK_SECSIZE) - 1) &
+		    ~(int)((DEV_GELIBOOT_BSIZE / BIOSDISK_SECSIZE) - 1);
+		diff = (dblk - alignlba) * BIOSDISK_SECSIZE;
+		/*
+		 * Use a temporary buffer here because the buffer provided by
+		 * the caller may be too small.
+		 */
+		tmpbuf = alloca(alignblks * BIOSDISK_SECSIZE);
+
+		err = bd_io(dev, alignlba, alignblks, tmpbuf, 0);
 		if (err)
 			return (err);
 
@@ -726,13 +749,14 @@ bd_read(struct disk_devdesc *dev, daddr_
 		dskp.start = dev->d_offset;
 
 		/* GELI needs the offset relative to the partition start */
-		p_off = dblk - dskp.start;
+		p_off = alignlba - dskp.start;
 
-		err = geli_read(&dskp, p_off * BIOSDISK_SECSIZE, dest,
-		    blks * BIOSDISK_SECSIZE);
+		err = geli_read(&dskp, p_off * BIOSDISK_SECSIZE, tmpbuf,
+		    alignblks * BIOSDISK_SECSIZE);
 		if (err)
 			return (err);
 
+		bcopy(tmpbuf + diff, dest, blks * BIOSDISK_SECSIZE);
 		return (0);
 	}
 #endif /* LOADER_GELI_SUPPORT */

Modified: head/sys/boot/i386/zfsboot/zfsboot.c
==============================================================================
--- head/sys/boot/i386/zfsboot/zfsboot.c	Wed Apr  6 23:17:05 2016	(r297628)
+++ head/sys/boot/i386/zfsboot/zfsboot.c	Wed Apr  6 23:21:44 2016	(r297629)
@@ -46,18 +46,20 @@ __FBSDID("$FreeBSD$");
 
 #include "libzfs.h"
 
-#define ARGS		0x900
-#define NOPT		14
-#define NDEV		3
-
-#define BIOS_NUMDRIVES	0x475
-#define DRV_HARD	0x80
-#define DRV_MASK	0x7f
-
-#define TYPE_AD		0
-#define TYPE_DA		1
-#define TYPE_MAXHARD	TYPE_DA
-#define TYPE_FD		2
+#define ARGS			0x900
+#define NOPT			14
+#define NDEV			3
+
+#define BIOS_NUMDRIVES		0x475
+#define DRV_HARD		0x80
+#define DRV_MASK		0x7f
+
+#define TYPE_AD			0
+#define TYPE_DA			1
+#define TYPE_MAXHARD		TYPE_DA
+#define TYPE_FD			2
+
+#define DEV_GELIBOOT_BSIZE	4096
 
 extern uint32_t _end;
 
@@ -104,13 +106,13 @@ static struct bios_smap smap;
 /*
  * The minimum amount of memory to reserve in bios_extmem for the heap.
  */
-#define	HEAP_MIN	(3 * 1024 * 1024)
+#define	HEAP_MIN		(3 * 1024 * 1024)
 
 static char *heap_next;
 static char *heap_end;
 
 /* Buffers that must not span a 64k boundary. */
-#define READ_BUF_SIZE	8192
+#define READ_BUF_SIZE		8192
 struct dmadat {
 	char rdbuf[READ_BUF_SIZE];	/* for reading large things */
 	char secbuf[READ_BUF_SIZE];	/* for MBR/disklabel */
@@ -198,8 +200,9 @@ static int
 vdev_read(vdev_t *vdev, void *priv, off_t off, void *buf, size_t bytes)
 {
 	char *p;
-	daddr_t lba;
-	unsigned int nb;
+	daddr_t lba, alignlba;
+	off_t alignoff, diff;
+	unsigned int nb, alignnb;
 	struct dsk *dsk = (struct dsk *) priv;
 
 	if ((off & (DEV_BSIZE - 1)) || (bytes & (DEV_BSIZE - 1)))
@@ -208,24 +211,52 @@ vdev_read(vdev_t *vdev, void *priv, off_
 	p = buf;
 	lba = off / DEV_BSIZE;
 	lba += dsk->start;
+	/* Align reads to 4k else 4k sector GELIs will not decrypt. */
+	alignoff = off & ~ (off_t)(DEV_GELIBOOT_BSIZE - 1);
+	/* Round LBA down to nearest multiple of DEV_GELIBOOT_BSIZE bytes. */
+	alignlba = alignoff / DEV_BSIZE;
+	/*
+	 * The read must be aligned to DEV_GELIBOOT_BSIZE bytes relative to the
+	 * start of the GELI partition, not the start of the actual disk.
+	 */
+	alignlba += dsk->start;
+	diff = (lba - alignlba) * DEV_BSIZE;
+
 	while (bytes > 0) {
 		nb = bytes / DEV_BSIZE;
 		if (nb > READ_BUF_SIZE / DEV_BSIZE)
 			nb = READ_BUF_SIZE / DEV_BSIZE;
-		if (drvread(dsk, dmadat->rdbuf, lba, nb))
+		/*
+		 * Ensure that the read size plus the leading offset does not
+		 * exceed the size of the read buffer.
+		 */
+		if (nb * DEV_BSIZE + diff > READ_BUF_SIZE)
+		    nb -= diff / DEV_BSIZE;
+		/*
+		 * Round the number of blocks to read up to the nearest multiple
+		 * of DEV_GELIBOOT_BSIZE.
+		 */
+		alignnb = nb + (diff / DEV_BSIZE) +
+		    (DEV_GELIBOOT_BSIZE / DEV_BSIZE - 1) & ~
+		    (unsigned int)(DEV_GELIBOOT_BSIZE / DEV_BSIZE - 1);
+
+		if (drvread(dsk, dmadat->rdbuf, alignlba, alignnb))
 			return -1;
 #ifdef LOADER_GELI_SUPPORT
 		/* decrypt */
 		if (is_geli(dsk) == 0) {
-		    if (geli_read(dsk, ((lba - dsk->start) * DEV_BSIZE),
-			dmadat->rdbuf, nb * DEV_BSIZE))
-			    return (-1);
+			if (geli_read(dsk, ((alignlba - dsk->start) *
+			    DEV_BSIZE), dmadat->rdbuf, alignnb * DEV_BSIZE))
+				return (-1);
 		}
 #endif
-		memcpy(p, dmadat->rdbuf, nb * DEV_BSIZE);
+		memcpy(p, dmadat->rdbuf + diff, nb * DEV_BSIZE);
 		p += nb * DEV_BSIZE;
 		lba += nb;
+		alignlba += alignnb;
 		bytes -= nb * DEV_BSIZE;
+		/* Don't need the leading offset after the first block. */
+		diff = 0;
 	}
 
 	return 0;


More information about the svn-src-all mailing list