/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Ian Lepore * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include "disk.h" #include "geliboot.h" #include "geliboot_internal.h" static int geli_dev_init(void); static int geli_dev_strategy(void *, int, daddr_t, size_t, char *, size_t *); static int geli_dev_open(struct open_file *f, ...); static int geli_dev_close(struct open_file *f); static int geli_dev_ioctl(struct open_file *, u_long, void *); static int geli_dev_print(int); static void geli_dev_cleanup(void); /* * geli_devsw is static because it never appears in any arch's global devsw * array. Instead, when devopen() opens a DEVT_DISK device, it then calls * geli_probe_and_attach(), and if we find that the disk_devdesc describes a * geli-encrypted partition, we create a geli_devdesc which references this * devsw and has a pointer to the original disk_devdesc of the underlying host * disk. Then we manipulate the open_file struct to reference the new * geli_devdesc, effectively routing all IO operations through our code. */ static struct devsw geli_devsw = { .dv_name = "disk", .dv_type = DEVT_DISK, .dv_init = geli_dev_init, .dv_strategy = geli_dev_strategy, .dv_open = geli_dev_open, .dv_close = geli_dev_close, .dv_ioctl = geli_dev_ioctl, .dv_print = geli_dev_print, .dv_cleanup = geli_dev_cleanup, .dv_fmtdev = disk_fmtdev, .dv_parsedev = disk_parsedev, }; /* * geli_devdesc instances replace the disk_devdesc in an open_file struct when * the partition is encrypted. We keep a reference to the original host * disk_devdesc so that we can read the raw encrypted data using it. */ struct geli_devdesc { struct disk_devdesc ddd; /* Must be first. */ struct disk_devdesc *hdesc; /* disk/slice/part hosting geli vol */ struct geli_dev *gdev; /* geli_dev entry */ }; /* * A geli_readfunc that reads via a disk_devdesc passed in readpriv. This is * used to read the underlying host disk data when probing/tasting to see if the * host provider is geli-encrypted. */ static int diskdev_read(void *vdev, void *readpriv, off_t offbytes, void *buf, size_t sizebytes) { struct disk_devdesc *ddev; ddev = (struct disk_devdesc *)readpriv; return (ddev->dd.d_dev->dv_strategy(ddev, F_READ, offbytes / DEV_BSIZE, sizebytes, buf, NULL)); } static int geli_dev_init(void) { /* * Since geli_devsw never gets referenced in any arch's global devsw * table, this function should never get called. */ panic("%s: should never be called", __func__); return (ENXIO); } static int geli_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf, size_t *rsize) { struct geli_devdesc *gdesc; off_t alnend, alnstart, reqend, reqstart; size_t alnsize; char *iobuf; int rc; gdesc = (struct geli_devdesc *)devdata; /* * We can only decrypt full geli blocks. The blk arg is expressed in * units of DEV_BSIZE blocks, while size is in bytes. Convert * everything to bytes, and calculate the geli-blocksize-aligned start * and end points. * * Note: md_sectorsize must be cast to a signed type for the round2 * macros to work correctly (otherwise they get zero-extended to 64 bits * and mask off the high order 32 bits of the requested start/end). */ reqstart = blk * DEV_BSIZE; reqend = reqstart + size; alnstart = rounddown2(reqstart, (int)gdesc->gdev->md.md_sectorsize); alnend = roundup2(reqend, (int)gdesc->gdev->md.md_sectorsize); alnsize = alnend - alnstart; /* * If alignment requires us to read/write more than the size of the * provided buffer, allocate a temporary buffer. * The writes will always get temporary buffer because of encryption. */ if (alnsize <= size && (rw & F_MASK) == F_READ) iobuf = buf; else if ((iobuf = malloc(alnsize)) == NULL) return (ENOMEM); switch (rw & F_MASK) { case F_READ: /* * Read the encrypted data using the host provider, * then decrypt it. */ rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, rw, alnstart / DEV_BSIZE, alnsize, iobuf, NULL); if (rc != 0) goto out; rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf, alnsize); if (rc != 0) goto out; /* * If we had to use a temporary buffer, copy the requested * part of the data to the caller's buffer. */ if (iobuf != buf) memcpy(buf, iobuf + (reqstart - alnstart), size); if (rsize != NULL) *rsize = size; break; case F_WRITE: if (iobuf != buf) { /* Read, decrypt, then modify. */ rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, F_READ, alnstart / DEV_BSIZE, alnsize, iobuf, NULL); if (rc != 0) goto out; rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf, alnsize); if (rc != 0) goto out; /* Copy data to iobuf */ memcpy(iobuf + (reqstart - alnstart), buf, size); } /* Encrypt and write it. */ rc = geli_io(gdesc->gdev, GELI_ENCRYPT, alnstart, iobuf, alnsize); if (rc != 0) goto out; rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, rw, alnstart / DEV_BSIZE, alnsize, iobuf, NULL); } out: if (iobuf != buf) free(iobuf); return (rc); } static int geli_dev_open(struct open_file *f, ...) { /* * Since geli_devsw never gets referenced in any arch's global devsw * table, this function should never get called. */ panic("%s: should never be called", __func__); return (ENXIO); } static int geli_dev_close(struct open_file *f) { struct geli_devdesc *gdesc; /* * Detach the geli_devdesc from the open_file and reattach the * underlying host provider's disk_devdesc; this undoes the work done at * the end of geli_probe_and_attach(). Call the host provider's * dv_close() (because that's what our caller thought it was doing). */ gdesc = (struct geli_devdesc *)f->f_devdata; f->f_devdata = gdesc->hdesc; f->f_dev = gdesc->hdesc->dd.d_dev; free(gdesc); f->f_dev->dv_close(f); return (0); } static int geli_dev_ioctl(struct open_file *f, u_long cmd, void *data) { struct geli_devdesc *gdesc; struct g_eli_metadata *md; gdesc = (struct geli_devdesc *)f->f_devdata; md = &gdesc->gdev->md; switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = md->md_sectorsize; break; case DIOCGMEDIASIZE: *(uint64_t *)data = md->md_provsize; break; default: return (ENOTTY); } return (0); } static int geli_dev_print(int verbose) { /* * Since geli_devsw never gets referenced in any arch's global devsw * table, this function should never get called. */ panic("%s: should never be called", __func__); return (ENXIO); } static void geli_dev_cleanup(void) { /* * Since geli_devsw never gets referenced in any arch's global devsw * table, this function should never get called. */ panic("%s: should never be called", __func__); } /* * geli_probe_and_attach() is called from devopen() after it successfully calls * the dv_open() method of a DEVT_DISK device. We taste the partition described * by the disk_devdesc, and if it's geli-encrypted and we can decrypt it, we * create a geli_devdesc and store it into the open_file struct in place of the * underlying provider's disk_devdesc, effectively attaching our code to all IO * processing for the partition. Not quite the elegant stacking provided by * geom in the kernel, but it gets the job done. */ void geli_probe_and_attach(struct open_file *f) { static char gelipw[GELI_PW_MAXLEN]; const char *envpw; struct geli_dev *gdev; struct geli_devdesc *gdesc; struct disk_devdesc *hdesc; uint64_t hmediasize; daddr_t hlastblk; int rc; hdesc = (struct disk_devdesc *)(f->f_devdata); /* We only work on DEVT_DISKs */ if (hdesc->dd.d_dev->dv_type != DEVT_DISK) return; /* Get the last block number for the host provider. */ if (hdesc->dd.d_dev->dv_ioctl(f, DIOCGMEDIASIZE, &hmediasize) != 0) return; hlastblk = (hmediasize / DEV_BSIZE) - 1; /* Taste the host provider. If it's not geli-encrypted just return. */ gdev = geli_taste(diskdev_read, hdesc, hlastblk, devformat(&hdesc->dd)); if (gdev == NULL) return; /* * It's geli, try to decrypt it with existing keys, or prompt for a * passphrase if we don't yet have a cached key for it. */ if ((rc = geli_havekey(gdev)) != 0) { envpw = getenv("kern.geom.eli.passphrase"); if (envpw != NULL) { /* Use the cached passphrase */ bcopy(envpw, &gelipw, GELI_PW_MAXLEN); } if ((rc = geli_passphrase(gdev, gelipw)) == 0) { /* Passphrase is good, cache it. */ setenv("kern.geom.eli.passphrase", gelipw, 1); } explicit_bzero(gelipw, sizeof(gelipw)); if (rc != 0) return; } /* * It's geli-encrypted and we can decrypt it. Create a geli_devdesc, * store a reference to the underlying provider's disk_devdesc in it, * then attach it to the openfile struct in place of the host provider. */ if ((gdesc = malloc(sizeof(*gdesc))) == NULL) return; gdesc->ddd.dd.d_dev = &geli_devsw; gdesc->ddd.dd.d_opendata = NULL; gdesc->ddd.dd.d_unit = hdesc->dd.d_unit; gdesc->ddd.d_offset = hdesc->d_offset; gdesc->ddd.d_partition = hdesc->d_partition; gdesc->ddd.d_slice = hdesc->d_slice; gdesc->hdesc = hdesc; gdesc->gdev = gdev; f->f_dev = gdesc->ddd.dd.d_dev; f->f_devdata = gdesc; }