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
This commit is contained in:
Allan Jude 2016-04-06 23:21:44 +00:00
parent 8e7676ca32
commit f33ff2af70
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=297629
4 changed files with 124 additions and 48 deletions

View File

@ -73,30 +73,34 @@ geli_taste(int read_func(void *vdev, void *priv, off_t off, void *buf,
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 *passphrase)
* 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 *passphrase)
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 *passphrase)
}
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 *passphrase)
return (0);
}
/* Disk not found */
/* Disk not found. */
return (2);
}
@ -229,35 +233,49 @@ geli_read(struct dsk *dskp, off_t offset, u_char *buf, size_t bytes)
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;
}
g_eli_crypto_ivgen(&geli_e->sc, os, iv, G_ELI_IVKEYLEN);
for (n = 0, dstoff = offset; n < nsec; n++, dstoff += secsize) {
/* Get the key that corresponds to this offset */
keyno = (os >> G_ELI_KEY_SHIFT) / DEV_BSIZE;
g_eli_crypto_ivgen(&geli_e->sc, dstoff, iv,
G_ELI_IVKEYLEN);
/* 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);

View File

@ -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))

View File

@ -706,15 +706,38 @@ bd_read(struct disk_devdesc *dev, daddr_t dblk, int blks, caddr_t dest)
{
#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_t dblk, int blks, caddr_t dest)
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 */

View File

@ -46,18 +46,20 @@ __FBSDID("$FreeBSD$");
#include "libzfs.h"
#define ARGS 0x900
#define NOPT 14
#define NDEV 3
#define ARGS 0x900
#define NOPT 14
#define NDEV 3
#define BIOS_NUMDRIVES 0x475
#define DRV_HARD 0x80
#define DRV_MASK 0x7f
#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 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_t off, void *buf, size_t bytes)
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;