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_4KSEC +#define DEV_4KSEC 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,14 +73,18 @@ size_t bytes), struct dsk *dskp, daddr_t lastsector) { struct g_eli_metadata md; - u_char buf[DEV_BSIZE]; + u_char buf[DEV_BSIZE], rawbuf[DEV_4KSEC]; int error; + off_t alignbyte; - error = read_func(NULL, dskp, (off_t) lastsector * DEV_BSIZE, &buf, - (size_t) DEV_BSIZE); + alignbyte = (off_t)(lastsector * DEV_BSIZE) & ~ (off_t)(DEV_4KSEC - 1); + error = read_func(NULL, dskp, alignbyte, &rawbuf, (size_t) DEV_4KSEC); if (error != 0) { return (error); } + /* Extract the last 512 bytes from the 4k sector */ + memcpy(&buf, rawbuf + (DEV_4KSEC - DEV_BSIZE), DEV_BSIZE); + error = eli_metadata_decode(buf, &md); if (error != 0) { return (error); @@ -229,35 +233,47 @@ 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 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; + 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,29 @@ { #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 4k else 4k sector GELIs will not decrypt */ + /* Round LBA down to nearest multiple of 4096 bytes */ + alignlba = dblk & ~ (daddr_t)(8 - 1); + /* Round number of blocks up to nearest multiple of 4096 */ + alignblks = blks + (dblk - alignlba) + (8 - 1) & ~ (int)(8 - 1); + diff = (dblk - alignlba) * BIOSDISK_SECSIZE; + /* + * Use a temporary buffer here because the buffer provided by + * the caller may be too small, and we don't want to overflow it + */ + tmpbuf = alloca(alignblks * BIOSDISK_SECSIZE); + + err = bd_io(dev, alignlba, alignblks, tmpbuf, 0); if (err) return (err); @@ -726,13 +740,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 @@ -59,6 +59,8 @@ #define TYPE_MAXHARD TYPE_DA #define TYPE_FD 2 +#define DEV_4KSEC 4096 + extern uint32_t _end; #ifdef GPT @@ -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,42 @@ 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_4KSEC - 1); + /* Round LBA down to nearest multiple of 4096 bytes */ + alignlba = alignoff / DEV_BSIZE; + /* The read must be 4k aligned to the GELI partition, not the 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)) + /* Don't overflow the read buffer */ + if (nb * DEV_BSIZE + diff > READ_BUF_SIZE) + nb -= diff / DEV_BSIZE; + /* Round number of blocks up to nearest multiple of 4096 */ + alignnb = nb + (diff / DEV_BSIZE) + (DEV_4KSEC / DEV_BSIZE - 1) + & ~ (unsigned int)(DEV_4KSEC / 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 leadingg offset after the first block */ + diff = 0; } return 0;