lkml.org 
[lkml]   [2014]   [Sep]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Date
    Subject[PATCH 15/18] usbip: Add encryption support to kernel
    From: Dominik Paulus <dominik.paulus@fau.de>

    This adds code performing the actual encryption and authentication
    operations in the usbip kernel code. The whole data stream may now be
    encrypted and authenticated with AES-GCM and symmetric 128 bit keys.

    Signed-off-by: Maximilian Eschenbacher <maximilian@eschenbacher.email>
    Signed-off-by: Fjodor Schelichow <fjodor.schelichow@hotmail.com>
    Signed-off-by: Johannes Stadlinger <johannes.stadlinger@fau.de>
    Signed-off-by: Dominik Paulus <dominik.paulus@fau.de>
    Signed-off-by: Tobias Polzer <tobias.polzer@fau.de>
    ---
    drivers/usb/usbip/Kconfig | 2 +-
    drivers/usb/usbip/stub.h | 3 +
    drivers/usb/usbip/stub_dev.c | 8 +
    drivers/usb/usbip/usbip_common.c | 351 ++++++++++++++++++++++++++++++++++++++-
    drivers/usb/usbip/usbip_common.h | 22 +++
    drivers/usb/usbip/vhci_hcd.c | 4 +-
    drivers/usb/usbip/vhci_sysfs.c | 10 ++
    7 files changed, 396 insertions(+), 4 deletions(-)

    diff --git a/drivers/usb/usbip/Kconfig b/drivers/usb/usbip/Kconfig
    index bd99e9e..4ad96ef 100644
    --- a/drivers/usb/usbip/Kconfig
    +++ b/drivers/usb/usbip/Kconfig
    @@ -1,6 +1,6 @@
    config USBIP_CORE
    tristate "USB/IP support"
    - depends on USB && NET
    + depends on USB && NET && CRYPTO_GCM && CRYPTO_AES && CRYPTO_CRYPTD
    ---help---
    This enables pushing USB packets over IP to allow remote
    machines direct access to USB devices. It provides the
    diff --git a/drivers/usb/usbip/stub.h b/drivers/usb/usbip/stub.h
    index b2d3d55..0b982d6 100644
    --- a/drivers/usb/usbip/stub.h
    +++ b/drivers/usb/usbip/stub.h
    @@ -26,6 +26,9 @@
    #include <linux/types.h>
    #include <linux/usb.h>
    #include <linux/wait.h>
    +#include <linux/crypto.h>
    +#include <linux/err.h>
    +#include <linux/scatterlist.h>

    #define STUB_BUSID_OTHER 0
    #define STUB_BUSID_REMOV 1
    diff --git a/drivers/usb/usbip/stub_dev.c b/drivers/usb/usbip/stub_dev.c
    index d237351..fdfab0f 100644
    --- a/drivers/usb/usbip/stub_dev.c
    +++ b/drivers/usb/usbip/stub_dev.c
    @@ -21,6 +21,7 @@
    #include <linux/file.h>
    #include <linux/kthread.h>
    #include <linux/module.h>
    +#include <linux/kfifo.h>

    #include "usbip_common.h"
    #include "stub.h"
    @@ -118,6 +119,12 @@ static ssize_t store_sockfd(struct device *dev, struct device_attribute *attr,

    spin_unlock_irq(&sdev->ud.lock);

    + if (sdev->ud.use_crypto) {
    + err = usbip_init_crypto(&sdev->ud, sendkey, recvkey);
    + if (err < 0)
    + goto err;
    + }
    +
    sdev->ud.tcp_rx = kthread_get_run(stub_rx_loop, &sdev->ud,
    "stub_rx");
    sdev->ud.tcp_tx = kthread_get_run(stub_tx_loop, &sdev->ud,
    @@ -272,6 +279,7 @@ static void stub_shutdown_connection(struct usbip_device *ud)
    }

    /* 3. free used data */
    + usbip_deinit_crypto(ud);
    stub_device_cleanup_urbs(sdev);

    /* 4. free stub_unlink */
    diff --git a/drivers/usb/usbip/usbip_common.c b/drivers/usb/usbip/usbip_common.c
    index 559fe53..3610842 100644
    --- a/drivers/usb/usbip/usbip_common.c
    +++ b/drivers/usb/usbip/usbip_common.c
    @@ -26,6 +26,8 @@
    #include <linux/module.h>
    #include <linux/moduleparam.h>
    #include <net/sock.h>
    +#include <linux/scatterlist.h>
    +#include <linux/crypto.h>

    #include "usbip_common.h"

    @@ -581,17 +583,362 @@ static void usbip_pack_iso(struct usbip_iso_packet_descriptor *iso,
    }
    }

    +int usbip_init_crypto(struct usbip_device *ud, unsigned char *sendkey, unsigned
    + char *recvkey)
    +{
    + int ret;
    +
    + ud->use_crypto = 1;
    +
    + ud->tfm_recv = crypto_alloc_aead("gcm(aes)", 0, 0);
    + if (IS_ERR(ud->tfm_recv))
    + return PTR_ERR(ud->tfm_recv);
    + ud->tfm_send = crypto_alloc_aead("gcm(aes)", 0, 0);
    + if (IS_ERR(ud->tfm_send)) {
    + ret = PTR_ERR(ud->tfm_send);
    + goto err_free_recv;
    + }
    + ret = kfifo_alloc(&ud->recv_queue, RECVQ_SIZE, GFP_KERNEL);
    + if (ret)
    + goto err_free_send;
    +
    + if (crypto_aead_setkey(ud->tfm_send, sendkey, USBIP_KEYSIZE) ||
    + crypto_aead_setkey(ud->tfm_recv, recvkey, USBIP_KEYSIZE) ||
    + crypto_aead_setauthsize(ud->tfm_send, USBIP_AUTHSIZE) ||
    + crypto_aead_setauthsize(ud->tfm_recv, USBIP_AUTHSIZE)) {
    + ret = -EINVAL;
    + goto err_free_fifo;
    + }
    +
    + ud->ctr_send = 0;
    + ud->ctr_recv = 0;
    +
    + return 0;
    +
    +err_free_fifo:
    + kfifo_free(&ud->recv_queue);
    +err_free_send:
    + crypto_free_aead(ud->tfm_send);
    +err_free_recv:
    + crypto_free_aead(ud->tfm_recv);
    +
    + return ret;
    +}
    +EXPORT_SYMBOL_GPL(usbip_init_crypto);
    +
    +void usbip_deinit_crypto(struct usbip_device *ud)
    +{
    + if (ud->use_crypto) {
    + crypto_free_aead(ud->tfm_send);
    + crypto_free_aead(ud->tfm_recv);
    + kfifo_free(&ud->recv_queue);
    + ud->use_crypto = 0;
    + }
    +}
    +EXPORT_SYMBOL_GPL(usbip_deinit_crypto);
    +
    +struct tcrypt_result {
    + struct completion completion;
    + int err;
    +};
    +
    +static void tcrypt_complete(struct crypto_async_request *req, int err)
    +{
    + struct tcrypt_result *res = req->data;
    +
    + if (err == -EINPROGRESS)
    + return;
    +
    + res->err = err;
    + complete(&res->completion);
    +}
    +
    +#define USBIP_ENCRYPT 1
    +#define USBIP_DECRYPT 0
    +/*
    + * Perform encryption/decryption on one chunk of data.
    + * Uses global crypto state stored in usbip_device.
    + * Parameters:
    + * encrypt: USBIP_ENCRYPT to perform encryption, USBIP_DECRYPT to perform
    + * decryption
    + * packetsize: Size of the encrypted packet, including the authentication tag,
    + * not including the associated data (length field).
    + * plaintext and ciphertext have to be appropiately managed by the caller
    + * (i.e. they must be at least packetsize bytes long).
    + * Returns: 0 on success, negative error code on failure
    + */
    +static int usbip_crypt(struct usbip_device *ud, int encrypt,
    + uint32_t packetsize, unsigned char *plaintext,
    + unsigned char *ciphertext)
    +{
    + struct crypto_aead *tfm;
    + struct aead_request *req;
    + struct tcrypt_result result;
    + struct scatterlist plain, cipher, assoc;
    + char iv[16];
    + u64 *iv_num;
    + u64 iv_net;
    + const int plainsize = packetsize - USBIP_AUTHSIZE;
    + int ret;
    +
    + /* Currently, this is guaranteed by the caller */
    + if (packetsize < USBIP_AUTHSIZE)
    + return -EINVAL;
    +
    + memset(iv, 0, sizeof(iv));
    + if (encrypt) {
    + tfm = ud->tfm_send;
    + iv_num = &ud->ctr_send;
    + } else {
    + tfm = ud->tfm_recv;
    + iv_num = &ud->ctr_recv;
    + }
    + iv_net = cpu_to_be64(*iv_num);
    + memcpy(iv, &iv_net, sizeof(iv_net));
    +
    + req = aead_request_alloc(tfm, GFP_KERNEL);
    + if (!req)
    + return -ENOMEM;
    +
    + init_completion(&result.completion);
    + aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
    + tcrypt_complete, &result);
    +
    + sg_init_one(&cipher, ciphertext, packetsize);
    + sg_init_one(&plain, plaintext, plainsize);
    + crypto_aead_clear_flags(tfm, ~0);
    +
    + if (encrypt)
    + aead_request_set_crypt(req, &plain, &cipher, plainsize, iv);
    + else
    + aead_request_set_crypt(req, &cipher, &plain, packetsize, iv);
    + packetsize = cpu_to_be32(packetsize);
    + sg_init_one(&assoc, &packetsize, sizeof(packetsize));
    + /* Associated data: Unencrypted length tag */
    + aead_request_set_assoc(req, &assoc, sizeof(packetsize));
    +
    + if (encrypt)
    + ret = crypto_aead_encrypt(req);
    + else
    + ret = crypto_aead_decrypt(req);
    +
    + switch (ret) {
    + case 0: /* Success */
    + break;
    + case -EINPROGRESS:
    + case -EBUSY:
    + wait_for_completion(&result.completion);
    + ret = result.err;
    + break;
    + default:
    + aead_request_free(req);
    + return ret;
    + }
    +
    + aead_request_free(req);
    +
    + (*iv_num)++; /* Increment IV */
    +
    + return ret;
    +}
    +
    +/*
    + * Wrapper to kernel_recvmsg. If necessary, also does the necessary decryption.
    + * If decryption is enabled, you _MUST_ pass 1 as parameter for num, i.e.
    + * only receive into a single continuous buffer.
    + */
    int usbip_recvmsg(struct usbip_device *ud, struct msghdr *msg,
    struct kvec *vec, size_t num, size_t size, int flags)
    {
    - return kernel_recvmsg(ud->tcp_socket, msg, vec, num, size, flags);
    + int ret;
    + size_t total = 0;
    + unsigned char *plainbuf, *cipherbuf;
    +
    + if (!ud->use_crypto)
    + return kernel_recvmsg(ud->tcp_socket, msg, vec, num, size,
    + flags);
    +
    + if (vec[0].iov_len < size)
    + return -EINVAL;
    + if (num != 1)
    + return -EINVAL;
    +
    + plainbuf = kmalloc(USBIP_PACKETSIZE, GFP_KERNEL);
    + if (!plainbuf)
    + return -ENOMEM;
    + cipherbuf = kmalloc(USBIP_PACKETSIZE, GFP_KERNEL);
    + if (!cipherbuf) {
    + kfree(plainbuf);
    + return -ENOMEM;
    + }
    +
    + while (total < size) {
    + uint32_t packetsize;
    + struct kvec recvvec;
    +
    + /*
    + * We use a global kfifo to buffer unrequested plaintext bytes.
    + * Flush this buffer first before receiving new data.
    + */
    + if (kfifo_len(&ud->recv_queue)) {
    + size_t next = min_t(size_t, kfifo_len(&ud->recv_queue),
    + size - total);
    + /* No error checking necessary - see previous line */
    + ret = kfifo_out(&ud->recv_queue,
    + vec[0].iov_base + total, next);
    + total += next;
    + continue;
    + }
    +
    + /* See usbip_sendmsg() for the format of one encrypted packet */
    +
    + /*
    + * Receive size of next crypto packet
    + */
    + recvvec.iov_base = &packetsize;
    + recvvec.iov_len = sizeof(packetsize);
    +
    + ret = kernel_recvmsg(ud->tcp_socket, msg, &recvvec, 1,
    + sizeof(packetsize), flags);
    + packetsize = be32_to_cpu(packetsize);
    + if (ret < 0) {
    + total = ret;
    + goto err;
    + } else if (ret != sizeof(packetsize)) {
    + total = -EBADMSG;
    + goto err;
    + }
    +
    + if (packetsize > USBIP_PACKETSIZE) {
    + total = -EBADMSG;
    + goto err;
    + }
    +
    + /*
    + * Receive the rest of the packet
    + */
    + recvvec.iov_base = cipherbuf;
    + recvvec.iov_len = packetsize;
    + ret = kernel_recvmsg(ud->tcp_socket, msg, &recvvec, 1,
    + packetsize, flags);
    + if (ret <= 0) {
    + total = ret;
    + goto err;
    + } else if (ret != packetsize) {
    + total = -EBADMSG;
    + goto err;
    + }
    +
    + /*
    + * Decrypt the packet. This will also authenticate the length
    + * field
    + */
    + ret = usbip_crypt(ud, 0, packetsize, plainbuf, cipherbuf);
    + if (ret != 0) {
    + total = ret;
    + goto err;
    + }
    +
    + /*
    + * Add this packet to our global buffer (It will be stored in
    + * the user buffer in the next loop iteration) No error
    + * checking necessary - we already know the packet is going to
    + * fit.
    + */
    + (void) kfifo_in(&ud->recv_queue, plainbuf, packetsize -
    + USBIP_AUTHSIZE);
    + }
    +
    +err:
    + kfree(plainbuf);
    + kfree(cipherbuf);
    +
    + return total;
    }
    EXPORT_SYMBOL_GPL(usbip_recvmsg);

    int usbip_sendmsg(struct usbip_device *ud, struct msghdr *msg,
    struct kvec *vec, size_t num, size_t size)
    {
    - return kernel_sendmsg(ud->tcp_socket, msg, vec, num, size);
    + int i = 0, ret, offset = 0;
    + size_t total = 0;
    + unsigned char *cipherbuf;
    +
    + /* If crypto is disabled, we just wrap the normal kernel calls. */
    + if (!ud->use_crypto)
    + return kernel_sendmsg(ud->tcp_socket, msg, vec, num, size);
    +
    + cipherbuf = kmalloc(USBIP_PACKETSIZE, GFP_KERNEL);
    + if (!cipherbuf)
    + return -ENOMEM;
    +
    + /*
    + * The receiver has to decrypt whole packets. To avoid the need
    + * to allocate large buffers at the receiving side, we split the
    + * data to be sent in USBIP_PACKETSIZE large chunks that can be
    + * decrypted separately. See below for the format of each chunk.
    + */
    +
    + /* Iterate over all kvecs, splitting them up as necessary. */
    + for (i = 0; i != num && size; ) {
    + /* Compute the remaining number of bytes to send for
    + * this kvec */
    + const size_t PLAIN_SIZE = min_t(size_t, vec[i].iov_len - offset,
    + min_t(size_t, size,
    + USBIP_PACKETSIZE - USBIP_AUTHSIZE));
    + const size_t PACKET_SIZE = PLAIN_SIZE + USBIP_AUTHSIZE;
    + uint32_t packet_size_net = cpu_to_be32(PACKET_SIZE);
    + struct kvec sendvec[2];
    +
    + if (PLAIN_SIZE == 0) {
    + ++i;
    + offset = 0;
    + continue;
    + }
    +
    + /*
    + * One encrypted packet consists of:
    + * - An unencrypted, authenticated length tag (exactly 4
    + * bytes) containing the length of the packet.
    + * - Up to USBIP_PACKETSIZE - USBIP_AUTHSIZE bytes of user
    + * payload, encrypted
    + * - Exactly USBIP_AUTHSIZE bytes authentication tag.
    + * Note: The packet length is also authenticated, but has
    + * - for obvious reasons - to be sent in plaintext. This
    + * packet format will be parsed by usbip_recvmsg (see above).
    + */
    + ret = usbip_crypt(ud, 1, PACKET_SIZE, vec[i].iov_base + offset,
    + cipherbuf);
    + if (ret != 0) {
    + kfree(cipherbuf);
    + return ret;
    + }
    +
    + /* Length field */
    + sendvec[0].iov_base = &packet_size_net;
    + sendvec[0].iov_len = sizeof(packet_size_net);
    + /* Payload and authentication tag */
    + sendvec[1].iov_base = cipherbuf;
    + sendvec[1].iov_len = PACKET_SIZE;
    + ret = kernel_sendmsg(ud->tcp_socket, msg, sendvec,
    + ARRAY_SIZE(sendvec), sendvec[0].iov_len +
    + sendvec[1].iov_len);
    + if (ret < 0) {
    + kfree(cipherbuf);
    + return ret;
    + }
    + if (ret != sendvec[0].iov_len + sendvec[1].iov_len) {
    + kfree(cipherbuf);
    + return -EPROTO;
    + }
    + offset += PLAIN_SIZE;
    + size -= PLAIN_SIZE;
    + total += PLAIN_SIZE;
    + }
    +
    + kfree(cipherbuf);
    +
    + return total;
    }
    EXPORT_SYMBOL_GPL(usbip_sendmsg);

    diff --git a/drivers/usb/usbip/usbip_common.h b/drivers/usb/usbip/usbip_common.h
    index 8b0ac52..6831d99 100644
    --- a/drivers/usb/usbip/usbip_common.h
    +++ b/drivers/usb/usbip/usbip_common.h
    @@ -30,15 +30,28 @@
    #include <linux/usb.h>
    #include <linux/wait.h>
    #include <uapi/linux/usbip.h>
    +#include <linux/kfifo.h>

    #define USBIP_VERSION "1.0.0"

    /*
    + * Length of the authentication tag associated with each packet, in bytes. Can
    + * be set to 4, 8, 12, 13, 14, 15 or 16. See crypto_gcm_setauthsize in
    + * crypto/gcm.c. Increasing this will increase crypto protocol overhead.
    + */
    +#define USBIP_AUTHSIZE 4
    +/*
    * Length of symmetric keys. Currently, this should be fixed at 16 bytes.
    * Will break code if changed, look at userspace and stub_dev.c/vhci_sysfs.c
    * where this constant is used before changing.
    */
    #define USBIP_KEYSIZE 16
    +/*
    + * Maximum size of encrypted packets. Decreasing this will increase overhead
    + * and decrease memory usage.
    + */
    +#define USBIP_PACKETSIZE 1024
    +#define RECVQ_SIZE (2*USBIP_PACKETSIZE)

    #undef pr_fmt

    @@ -285,6 +298,11 @@ struct usbip_device {

    /* Crypto support */
    int use_crypto;
    + struct crypto_aead *tfm_recv;
    + struct crypto_aead *tfm_send;
    + /* Counters to be used as IVs */
    + u64 ctr_send, ctr_recv;
    + DECLARE_KFIFO_PTR(recv_queue, char);
    };

    #define kthread_get_run(threadfn, data, namefmt, ...) \
    @@ -308,6 +326,10 @@ struct usbip_device {
    void usbip_dump_urb(struct urb *purb);
    void usbip_dump_header(struct usbip_header *pdu);

    +int usbip_init_crypto(struct usbip_device *ud, unsigned char *sendkey,
    + unsigned char *recvkey);
    +void usbip_deinit_crypto(struct usbip_device *ud);
    +
    int usbip_recv(struct usbip_device *ui, void *buf, int size);

    void usbip_pack_pdu(struct usbip_header *pdu, struct urb *urb, int cmd,
    diff --git a/drivers/usb/usbip/vhci_hcd.c b/drivers/usb/usbip/vhci_hcd.c
    index c02374b..13db326 100644
    --- a/drivers/usb/usbip/vhci_hcd.c
    +++ b/drivers/usb/usbip/vhci_hcd.c
    @@ -785,7 +785,9 @@ static void vhci_shutdown_connection(struct usbip_device *ud)
    kthread_stop_put(vdev->ud.tcp_tx);
    vdev->ud.tcp_tx = NULL;
    }
    - pr_info("stop threads\n");
    + pr_info("stopped threads\n");
    +
    + usbip_deinit_crypto(&vdev->ud);

    /* active connection is closed */
    if (vdev->ud.tcp_socket) {
    diff --git a/drivers/usb/usbip/vhci_sysfs.c b/drivers/usb/usbip/vhci_sysfs.c
    index fa948ad..19d6141 100644
    --- a/drivers/usb/usbip/vhci_sysfs.c
    +++ b/drivers/usb/usbip/vhci_sysfs.c
    @@ -20,6 +20,8 @@
    #include <linux/kthread.h>
    #include <linux/file.h>
    #include <linux/net.h>
    +#include <linux/crypto.h>
    +#include <linux/kfifo.h>

    #include "usbip_common.h"
    #include "vhci.h"
    @@ -237,6 +239,13 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr,
    /* begin a lock */
    spin_lock(&the_controller->lock);
    vdev = port_to_vdev(rhport);
    + if (use_crypto) {
    + ret = usbip_init_crypto(&vdev->ud, sendkey, recvkey);
    + if (ret < 0) {
    + spin_unlock(&the_controller->lock);
    + return ret;
    + }
    + }
    spin_lock(&vdev->ud.lock);

    if (vdev->ud.status != VDEV_ST_NULL) {
    @@ -247,6 +256,7 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr,
    sockfd_put(socket);

    dev_err(dev, "port %d already used\n", rhport);
    + usbip_deinit_crypto(&vdev->ud);
    return -EINVAL;
    }

    --
    2.1.0


    \
     
     \ /
      Last update: 2014-09-17 00:21    [W:5.016 / U:0.044 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site