blob: e34558b35198db3e2f5613029a3e71967f96cabe [file] [log] [blame]
From b54481aa9fb9cab7a0ca3257c4a6ba6082ef9652 Mon Sep 17 00:00:00 2001
From: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Date: Fri, 25 Jan 2019 15:03:15 +0100
Subject: [PATCH 11/15] tty: n_r3964: fix race with add_msg() and read_telegram
read_telegram() could race with the client block access code that can be
called through add_msg() as it did not hold any locks.
Fix that up by properly holding the client->lock when touching the next
message to be read structures. Gyrations ensue due to the data having
to be copied to userspace, but the potential corruption problem is now
gone, to be replaced with a possible race where we free a block that no
one sent to userspace yet. Given that no one has reported this problem
in the 20+ years of this code, I'll take the potential race condition
over a known corruption problems.
Reported-by: Jann Horn <jannh@google.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
drivers/tty/n_r3964.c | 52 ++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 42 insertions(+), 10 deletions(-)
--- a/drivers/tty/n_r3964.c
+++ b/drivers/tty/n_r3964.c
@@ -193,8 +193,6 @@ static void receive_char(struct r3964_in
static void receive_error(struct r3964_info *pInfo, const char flag);
static void on_timeout(struct timer_list *t);
static int enable_signals(struct r3964_info *pInfo, struct pid *pid, int arg);
-static int read_telegram(struct r3964_info *pInfo, struct pid *pid,
- unsigned char __user * buf);
static void add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
int error_code, struct rx_block_header *pBlock);
static struct r3964_message *remove_msg(struct r3964_client_info *client);
@@ -831,6 +829,10 @@ static int read_telegram(struct r3964_in
{
struct r3964_client_info *pClient;
struct rx_block_header *block;
+ unsigned long flags;
+ unsigned int length;
+ int retval = 0;
+ u8 *data;
if (!buf) {
return -EINVAL;
@@ -841,18 +843,47 @@ static int read_telegram(struct r3964_in
return -EINVAL;
}
+ spin_lock_irqsave(&pClient->lock, flags);
+
block = pClient->next_block_to_read;
- if (!block) {
- return 0;
- } else {
- if (copy_to_user(buf, block->data, block->length))
- return -EFAULT;
+ if (!block)
+ goto exit;
+
+ /*
+ * Duplicate the data so we can release the lock while we copy to
+ * userspace
+ */
+ length = block->length;
+ data = kmemdup(block->data, length, GFP_ATOMIC);
+ if (!data) {
+ retval = -ENOMEM;
+ goto exit;
+ }
+
+ spin_unlock_irqrestore(&pClient->lock, flags);
- remove_client_block(pClient);
- return block->length;
+ if (copy_to_user(buf, data, length)) {
+ kfree(data);
+ return -EFAULT;
}
+ kfree(data);
- return -EINVAL;
+ /*
+ * Copy succeeded, so grab the lock again, and then drop the buffer, as
+ * remove_client_block() has to have the lock held.
+ *
+ * Note, the client's next_block_to_read could have changed here, so
+ * worst case, we just dropped a buffer that wasn't read. But, nothing
+ * was corrupted or accidentally freed, so we are doing better than we
+ * used to. Ideally the whole issue of "read_telegram" would be handled
+ * some other way as this races with the add_msg() path in a bad manner
+ */
+ spin_lock_irqsave(&pClient->lock, flags);
+ remove_client_block(pClient);
+
+exit:
+ spin_unlock_irqrestore(&pClient->lock, flags);
+ return retval;
}
static void __add_msg(struct r3964_client_info *pClient, int msg_id, int arg,
@@ -935,6 +966,7 @@ static struct r3964_message *remove_msg(
}
static void remove_client_block(struct r3964_client_info *client)
+ __must_hold(&client->lock)
{
struct rx_block_header *block;