[PATCH 5/7] staging: usbip: Add encryption support to kernel

From: Dominik Paulus
Date: Thu Sep 19 2013 - 10:12:31 EST


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: Dominik Paulus <dominik.paulus@xxxxxx>
Signed-off-by: Tobias Polzer <tobias.polzer@xxxxxx>
---
drivers/staging/usbip/Kconfig | 2 +-
drivers/staging/usbip/stub.h | 3 +
drivers/staging/usbip/stub_dev.c | 8 +
drivers/staging/usbip/usbip_common.c | 342 ++++++++++++++++++++++++++++++++++-
drivers/staging/usbip/usbip_common.h | 22 +++
drivers/staging/usbip/vhci_hcd.c | 4 +-
drivers/staging/usbip/vhci_sysfs.c | 10 +
7 files changed, 387 insertions(+), 4 deletions(-)

diff --git a/drivers/staging/usbip/Kconfig b/drivers/staging/usbip/Kconfig
index 8860009..87220af 100644
--- a/drivers/staging/usbip/Kconfig
+++ b/drivers/staging/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
default N
---help---
This enables pushing USB packets over IP to allow remote
diff --git a/drivers/staging/usbip/stub.h b/drivers/staging/usbip/stub.h
index cfe75d1..2aaea3a 100644
--- a/drivers/staging/usbip/stub.h
+++ b/drivers/staging/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/staging/usbip/stub_dev.c b/drivers/staging/usbip/stub_dev.c
index f7bc6e1..0e61efb 100644
--- a/drivers/staging/usbip/stub_dev.c
+++ b/drivers/staging/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"
@@ -137,6 +138,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,
@@ -298,6 +305,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/staging/usbip/usbip_common.c b/drivers/staging/usbip/usbip_common.c
index f46c3d2..0729df6 100644
--- a/drivers/staging/usbip/usbip_common.c
+++ b/drivers/staging/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"

@@ -626,17 +628,353 @@ 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)) {
+ crypto_free_aead(ud->tfm_recv);
+ return -PTR_ERR(ud->tfm_send);
+ }
+ ret = kfifo_alloc(&ud->recv_queue, RECVQ_SIZE, GFP_KERNEL);
+ if (ret) {
+ crypto_free_aead(ud->tfm_recv);
+ crypto_free_aead(ud->tfm_send);
+ return ret;
+ }
+
+ if (crypto_aead_setkey(ud->tfm_send, sendkey, USBIP_KEYSIZE) != 0 ||
+ crypto_aead_setkey(ud->tfm_recv, recvkey,
+ USBIP_KEYSIZE) != 0 ||
+ crypto_aead_setauthsize(ud->tfm_send,
+ USBIP_AUTHSIZE) != 0 ||
+ crypto_aead_setauthsize(ud->tfm_recv,
+ USBIP_AUTHSIZE)) {
+ crypto_free_aead(ud->tfm_recv);
+ crypto_free_aead(ud->tfm_send);
+ kfifo_free(&ud->recv_queue);
+ }
+
+ ud->ctr_send = 0;
+ ud->ctr_recv = 0;
+
+ return 0;
+}
+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);
+}
+
+/*
+ * Perform encryption/decryption on one chunk of data.
+ * Uses global crypto state stored in usbip_device.
+ * Parameters:
+ * encrypt: 1 to perform encryption, 0 to perform decryption operation
+ * 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
+ */
+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;
+
+ 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 (IS_ERR(req))
+ return -PTR_ERR(req);
+
+ 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);
+ break;
+ default:
+ aead_request_free(req);
+ return ret;
+ }
+
+ aead_request_free(req);
+
+ (*iv_num)++; /* Increment IV */
+
+ return 0;
+}
+
+/*
+ * 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 crypto is disabled, we just wrap the normal kernel calls. */
+ 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 (IS_ERR(plainbuf))
+ return -PTR_ERR(plainbuf);
+ cipherbuf = kmalloc(USBIP_PACKETSIZE, GFP_KERNEL);
+ if (IS_ERR(cipherbuf)) {
+ kfree(plainbuf);
+ return -PTR_ERR(cipherbuf);
+ }
+
+ 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, ((char *)
+ 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 (IS_ERR(cipherbuf))
+ return -PTR_ERR(cipherbuf);
+
+ /*
+ * 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/staging/usbip/usbip_common.h b/drivers/staging/usbip/usbip_common.h
index 99f9dab..2b71c58 100644
--- a/drivers/staging/usbip/usbip_common.h
+++ b/drivers/staging/usbip/usbip_common.h
@@ -29,15 +29,28 @@
#include <linux/types.h>
#include <linux/usb.h>
#include <linux/wait.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

@@ -300,6 +313,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, ...) \
@@ -323,6 +341,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);
struct socket *sockfd_to_socket(unsigned int sockfd);

diff --git a/drivers/staging/usbip/vhci_hcd.c b/drivers/staging/usbip/vhci_hcd.c
index d7974cb..0b72bcf 100644
--- a/drivers/staging/usbip/vhci_hcd.c
+++ b/drivers/staging/usbip/vhci_hcd.c
@@ -786,7 +786,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/staging/usbip/vhci_sysfs.c b/drivers/staging/usbip/vhci_sysfs.c
index 1ef3f25..ce46c16 100644
--- a/drivers/staging/usbip/vhci_sysfs.c
+++ b/drivers/staging/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"
@@ -227,6 +229,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) {
+ int 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) {
@@ -237,6 +246,7 @@ static ssize_t store_attach(struct device *dev, struct device_attribute *attr,
fput(socket->file);

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

--
1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/