Index: sys/boot/geli/geliboot.h =================================================================== --- sys/boot/geli/geliboot.h +++ sys/boot/geli/geliboot.h @@ -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)) Index: sys/boot/geli/geliboot.c =================================================================== --- sys/boot/geli/geliboot.c +++ sys/boot/geli/geliboot.c @@ -73,30 +73,35 @@ 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 = (off_t)(lastsector * DEV_BSIZE) & ~ + (off_t)(DEV_GELIBOOT_BSIZE - 1); + error = read_func(NULL, dskp, alignsector, &buf, + (size_t)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 +148,7 @@ * 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, @@ -193,7 +198,7 @@ } 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 +212,7 @@ return (0); } - /* Disk not found */ + /* Disk not found. */ return (2); } @@ -229,35 +234,48 @@ 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); Index: sys/boot/i386/libi386/biosdisk.c =================================================================== --- sys/boot/i386/libi386/biosdisk.c +++ sys/boot/i386/libi386/biosdisk.c @@ -706,15 +706,38 @@ { #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 @@ 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 */ Index: sys/boot/i386/zfsboot/zfsboot.c =================================================================== --- sys/boot/i386/zfsboot/zfsboot.c +++ sys/boot/i386/zfsboot/zfsboot.c @@ -46,18 +46,20 @@ #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 @@ /* * 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 @@ 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 @@ 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;