/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2018 Ian Lepore <[email protected]>4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8* 1. Redistributions of source code must retain the above copyright9* notice, this list of conditions and the following disclaimer.10* 2. Redistributions in binary form must reproduce the above copyright11* notice, this list of conditions and the following disclaimer in the12* documentation and/or other materials provided with the distribution.13*14* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND15* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE16* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE17* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE18* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL19* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS20* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)21* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT22* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY23* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF24* SUCH DAMAGE.25*/2627#include <stand.h>28#include <stdarg.h>29#include <uuid.h>30#include <sys/disk.h>31#include "disk.h"32#include "geliboot.h"33#include "geliboot_internal.h"3435static int geli_dev_init(void);36static int geli_dev_strategy(void *, int, daddr_t, size_t, char *, size_t *);37static int geli_dev_open(struct open_file *f, ...);38static int geli_dev_close(struct open_file *f);39static int geli_dev_ioctl(struct open_file *, u_long, void *);40static int geli_dev_print(int);41static void geli_dev_cleanup(void);4243/*44* geli_devsw is static because it never appears in any arch's global devsw45* array. Instead, when devopen() opens a DEVT_DISK device, it then calls46* geli_probe_and_attach(), and if we find that the disk_devdesc describes a47* geli-encrypted partition, we create a geli_devdesc which references this48* devsw and has a pointer to the original disk_devdesc of the underlying host49* disk. Then we manipulate the open_file struct to reference the new50* geli_devdesc, effectively routing all IO operations through our code.51*/52static struct devsw geli_devsw = {53.dv_name = "disk",54.dv_type = DEVT_DISK,55.dv_init = geli_dev_init,56.dv_strategy = geli_dev_strategy,57.dv_open = geli_dev_open,58.dv_close = geli_dev_close,59.dv_ioctl = geli_dev_ioctl,60.dv_print = geli_dev_print,61.dv_cleanup = geli_dev_cleanup,62.dv_fmtdev = disk_fmtdev,63.dv_parsedev = disk_parsedev,64};6566/*67* geli_devdesc instances replace the disk_devdesc in an open_file struct when68* the partition is encrypted. We keep a reference to the original host69* disk_devdesc so that we can read the raw encrypted data using it.70*/71struct geli_devdesc {72struct disk_devdesc ddd; /* Must be first. */73struct disk_devdesc *hdesc; /* disk/slice/part hosting geli vol */74struct geli_dev *gdev; /* geli_dev entry */75};767778/*79* A geli_readfunc that reads via a disk_devdesc passed in readpriv. This is80* used to read the underlying host disk data when probing/tasting to see if the81* host provider is geli-encrypted.82*/83static int84diskdev_read(void *vdev, void *readpriv, off_t offbytes,85void *buf, size_t sizebytes)86{87struct disk_devdesc *ddev;8889ddev = (struct disk_devdesc *)readpriv;9091return (ddev->dd.d_dev->dv_strategy(ddev, F_READ, offbytes / DEV_BSIZE,92sizebytes, buf, NULL));93}9495static int96geli_dev_init(void)97{9899/*100* Since geli_devsw never gets referenced in any arch's global devsw101* table, this function should never get called.102*/103panic("%s: should never be called", __func__);104return (ENXIO);105}106107static int108geli_dev_strategy(void *devdata, int rw, daddr_t blk, size_t size, char *buf,109size_t *rsize)110{111struct geli_devdesc *gdesc;112off_t alnend, alnstart, reqend, reqstart;113size_t alnsize;114char *iobuf;115int rc;116117gdesc = (struct geli_devdesc *)devdata;118119/*120* We can only decrypt full geli blocks. The blk arg is expressed in121* units of DEV_BSIZE blocks, while size is in bytes. Convert122* everything to bytes, and calculate the geli-blocksize-aligned start123* and end points.124*125* Note: md_sectorsize must be cast to a signed type for the round2126* macros to work correctly (otherwise they get zero-extended to 64 bits127* and mask off the high order 32 bits of the requested start/end).128*/129130reqstart = blk * DEV_BSIZE;131reqend = reqstart + size;132alnstart = rounddown2(reqstart, (int)gdesc->gdev->md.md_sectorsize);133alnend = roundup2(reqend, (int)gdesc->gdev->md.md_sectorsize);134alnsize = alnend - alnstart;135136/*137* If alignment requires us to read/write more than the size of the138* provided buffer, allocate a temporary buffer.139* The writes will always get temporary buffer because of encryption.140*/141if (alnsize <= size && (rw & F_MASK) == F_READ)142iobuf = buf;143else if ((iobuf = malloc(alnsize)) == NULL)144return (ENOMEM);145146switch (rw & F_MASK) {147case F_READ:148/*149* Read the encrypted data using the host provider,150* then decrypt it.151*/152rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc, rw,153alnstart / DEV_BSIZE, alnsize, iobuf, NULL);154if (rc != 0)155goto out;156rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf,157alnsize);158if (rc != 0)159goto out;160161/*162* If we had to use a temporary buffer, copy the requested163* part of the data to the caller's buffer.164*/165if (iobuf != buf)166memcpy(buf, iobuf + (reqstart - alnstart), size);167168if (rsize != NULL)169*rsize = size;170break;171case F_WRITE:172if (iobuf != buf) {173/* Read, decrypt, then modify. */174rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc,175F_READ, alnstart / DEV_BSIZE, alnsize, iobuf, NULL);176if (rc != 0)177goto out;178rc = geli_io(gdesc->gdev, GELI_DECRYPT, alnstart, iobuf,179alnsize);180if (rc != 0)181goto out;182/* Copy data to iobuf */183memcpy(iobuf + (reqstart - alnstart), buf, size);184}185186/* Encrypt and write it. */187rc = geli_io(gdesc->gdev, GELI_ENCRYPT, alnstart, iobuf,188alnsize);189if (rc != 0)190goto out;191rc = gdesc->hdesc->dd.d_dev->dv_strategy(gdesc->hdesc,192rw, alnstart / DEV_BSIZE, alnsize, iobuf, NULL);193}194out:195if (iobuf != buf)196free(iobuf);197198return (rc);199}200201static int202geli_dev_open(struct open_file *f, ...)203{204205/*206* Since geli_devsw never gets referenced in any arch's global devsw207* table, this function should never get called.208*/209panic("%s: should never be called", __func__);210return (ENXIO);211}212213static int214geli_dev_close(struct open_file *f)215{216struct geli_devdesc *gdesc;217218/*219* Detach the geli_devdesc from the open_file and reattach the220* underlying host provider's disk_devdesc; this undoes the work done at221* the end of geli_probe_and_attach(). Call the host provider's222* dv_close() (because that's what our caller thought it was doing).223*/224gdesc = (struct geli_devdesc *)f->f_devdata;225f->f_devdata = gdesc->hdesc;226f->f_dev = gdesc->hdesc->dd.d_dev;227free(gdesc);228f->f_dev->dv_close(f);229return (0);230}231232static int233geli_dev_ioctl(struct open_file *f, u_long cmd, void *data)234{235struct geli_devdesc *gdesc;236struct g_eli_metadata *md;237238gdesc = (struct geli_devdesc *)f->f_devdata;239md = &gdesc->gdev->md;240241switch (cmd) {242case DIOCGSECTORSIZE:243*(u_int *)data = md->md_sectorsize;244break;245case DIOCGMEDIASIZE:246*(uint64_t *)data = md->md_provsize;247break;248default:249return (ENOTTY);250}251252return (0);253}254255static int256geli_dev_print(int verbose)257{258259/*260* Since geli_devsw never gets referenced in any arch's global devsw261* table, this function should never get called.262*/263panic("%s: should never be called", __func__);264return (ENXIO);265}266267static void268geli_dev_cleanup(void)269{270271/*272* Since geli_devsw never gets referenced in any arch's global devsw273* table, this function should never get called.274*/275panic("%s: should never be called", __func__);276}277278279/*280* geli_probe_and_attach() is called from devopen() after it successfully calls281* the dv_open() method of a DEVT_DISK device. We taste the partition described282* by the disk_devdesc, and if it's geli-encrypted and we can decrypt it, we283* create a geli_devdesc and store it into the open_file struct in place of the284* underlying provider's disk_devdesc, effectively attaching our code to all IO285* processing for the partition. Not quite the elegant stacking provided by286* geom in the kernel, but it gets the job done.287*/288void289geli_probe_and_attach(struct open_file *f)290{291static char gelipw[GELI_PW_MAXLEN];292const char *envpw;293struct geli_dev *gdev;294struct geli_devdesc *gdesc;295struct disk_devdesc *hdesc;296uint64_t hmediasize;297daddr_t hlastblk;298int rc;299300hdesc = (struct disk_devdesc *)(f->f_devdata);301302/* We only work on DEVT_DISKs */303if (hdesc->dd.d_dev->dv_type != DEVT_DISK)304return;305/* Get the last block number for the host provider. */306if (hdesc->dd.d_dev->dv_ioctl(f, DIOCGMEDIASIZE, &hmediasize) != 0)307return;308hlastblk = (hmediasize / DEV_BSIZE) - 1;309310/* Taste the host provider. If it's not geli-encrypted just return. */311gdev = geli_taste(diskdev_read, hdesc, hlastblk, devformat(&hdesc->dd));312if (gdev == NULL)313return;314315/*316* It's geli, try to decrypt it with existing keys, or prompt for a317* passphrase if we don't yet have a cached key for it.318*/319if ((rc = geli_havekey(gdev)) != 0) {320envpw = getenv("kern.geom.eli.passphrase");321if (envpw != NULL) {322/* Use the cached passphrase */323bcopy(envpw, &gelipw, GELI_PW_MAXLEN);324}325if ((rc = geli_passphrase(gdev, gelipw)) == 0) {326/* Passphrase is good, cache it. */327setenv("kern.geom.eli.passphrase", gelipw, 1);328}329explicit_bzero(gelipw, sizeof(gelipw));330if (rc != 0)331return;332}333334/*335* It's geli-encrypted and we can decrypt it. Create a geli_devdesc,336* store a reference to the underlying provider's disk_devdesc in it,337* then attach it to the openfile struct in place of the host provider.338*/339if ((gdesc = malloc(sizeof(*gdesc))) == NULL)340return;341gdesc->ddd.dd.d_dev = &geli_devsw;342gdesc->ddd.dd.d_opendata = NULL;343gdesc->ddd.dd.d_unit = hdesc->dd.d_unit;344gdesc->ddd.d_offset = hdesc->d_offset;345gdesc->ddd.d_partition = hdesc->d_partition;346gdesc->ddd.d_slice = hdesc->d_slice;347gdesc->hdesc = hdesc;348gdesc->gdev = gdev;349f->f_dev = gdesc->ddd.dd.d_dev;350f->f_devdata = gdesc;351}352353354