greybus: operation: fix potential message corruption

Make sure to allocate the message transfer-buffer separately from the
containing message structure to avoid data corruption on systems without
DMA-coherent caches.

The message structure contains state that is updated while the buffer
may be used for DMA, something which could lead to data corruption due
to cache-line sharing on some architectures.

Use the (renamed) message cache for the message structure itself and
allocate the buffer separately.

If the additional allocation is a concern, the message structures
could eventually be allocated as part of the operation structure.

Signed-off-by: Johan Hovold <johan@hovoldconsulting.com>
Reviewed-by: Alex Elder <elder@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
Johan Hovold 2015-04-07 11:27:17 +02:00 committed by Greg Kroah-Hartman
parent 7cf7bca9ec
commit 1e5613b4a6
2 changed files with 31 additions and 43 deletions

View file

@ -24,7 +24,7 @@
#define GB_OPERATION_MESSAGE_SIZE_MAX 4096
static struct kmem_cache *gb_operation_cache;
static struct kmem_cache *gb_simple_message_cache;
static struct kmem_cache *gb_message_cache;
/* Workqueue to handle Greybus operation completions. */
static struct workqueue_struct *gb_operation_workqueue;
@ -229,7 +229,7 @@ static void gb_operation_message_init(struct greybus_host_device *hd,
struct gb_operation_msg_hdr *header;
u8 *buffer;
buffer = &message->buffer[0];
buffer = message->buffer;
header = (struct gb_operation_msg_hdr *)(buffer + hd->buffer_headroom);
message->header = header;
@ -270,8 +270,7 @@ static void gb_operation_message_init(struct greybus_host_device *hd,
* The headers for inbound messages don't need to be initialized;
* they'll be filled in by arriving data.
*
* Our message structure consists of:
* message structure
* Our message buffers have the following layout:
* headroom
* message header \_ these combined are
* message payload / the message size
@ -291,34 +290,37 @@ gb_operation_message_alloc(struct greybus_host_device *hd, u8 type,
hd->buffer_size_max = GB_OPERATION_MESSAGE_SIZE_MAX;
}
/* Allocate the message. Use the slab cache for simple messages */
if (payload_size) {
if (message_size > hd->buffer_size_max) {
pr_warn("requested message size too big (%zu > %zu)\n",
if (message_size > hd->buffer_size_max) {
pr_warn("requested message size too big (%zu > %zu)\n",
message_size, hd->buffer_size_max);
return NULL;
}
size = sizeof(*message) + hd->buffer_headroom + message_size;
message = kzalloc(size, gfp_flags);
} else {
message = kmem_cache_zalloc(gb_simple_message_cache, gfp_flags);
return NULL;
}
/* Allocate the message structure and buffer. */
message = kmem_cache_zalloc(gb_message_cache, gfp_flags);
if (!message)
return NULL;
size = hd->buffer_headroom + message_size;
message->buffer = kzalloc(size, gfp_flags);
if (!message->buffer)
goto err_free_message;
/* Initialize the message. Operation id is filled in later. */
gb_operation_message_init(hd, message, 0, payload_size, type);
return message;
err_free_message:
kmem_cache_free(gb_message_cache, message);
return NULL;
}
static void gb_operation_message_free(struct gb_message *message)
{
if (message->payload_size)
kfree(message);
else
kmem_cache_free(gb_simple_message_cache, message);
kfree(message->buffer);
kmem_cache_free(gb_message_cache, message);
}
/*
@ -937,32 +939,18 @@ EXPORT_SYMBOL_GPL(gb_operation_sync);
int gb_operation_init(void)
{
size_t size;
BUILD_BUG_ON(GB_OPERATION_MESSAGE_SIZE_MAX >
U16_MAX - sizeof(struct gb_operation_msg_hdr));
/*
* A message structure consists of:
* - the message structure itself
* - the headroom set aside for the host device
* - the message header
* - space for the message payload
* Messages with no payload are a fairly common case and
* have a known fixed maximum size, so we use a slab cache
* for them.
*/
size = sizeof(struct gb_message) + GB_BUFFER_HEADROOM_MAX +
sizeof(struct gb_operation_msg_hdr);
gb_simple_message_cache = kmem_cache_create("gb_simple_message_cache",
size, 0, 0, NULL);
if (!gb_simple_message_cache)
gb_message_cache = kmem_cache_create("gb_message_cache",
sizeof(struct gb_message), 0, 0, NULL);
if (!gb_message_cache)
return -ENOMEM;
gb_operation_cache = kmem_cache_create("gb_operation_cache",
sizeof(struct gb_operation), 0, 0, NULL);
if (!gb_operation_cache)
goto err_simple;
goto err_destroy_message_cache;
gb_operation_workqueue = alloc_workqueue("greybus_operation", 0, 1);
if (!gb_operation_workqueue)
@ -972,9 +960,9 @@ int gb_operation_init(void)
err_operation:
kmem_cache_destroy(gb_operation_cache);
gb_operation_cache = NULL;
err_simple:
kmem_cache_destroy(gb_simple_message_cache);
gb_simple_message_cache = NULL;
err_destroy_message_cache:
kmem_cache_destroy(gb_message_cache);
gb_message_cache = NULL;
return -ENOMEM;
}
@ -985,6 +973,6 @@ void gb_operation_exit(void)
gb_operation_workqueue = NULL;
kmem_cache_destroy(gb_operation_cache);
gb_operation_cache = NULL;
kmem_cache_destroy(gb_simple_message_cache);
gb_simple_message_cache = NULL;
kmem_cache_destroy(gb_message_cache);
gb_message_cache = NULL;
}

View file

@ -82,7 +82,7 @@ struct gb_message {
void *payload;
size_t payload_size;
u8 buffer[];
void *buffer;
};
/*