root/stand/libsa/geli/gelidev.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2018 Ian Lepore <ian@FreeBSD.org>
 *
 * 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.
 */

#include <stand.h>
#include <stdarg.h>
#include <uuid.h>
#include <sys/disk.h>
#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;
}