[PATCH linux dev-4.10 v3 29/31] drivers: fsi: occ: Fix client memory management
Eddie James
eajames at linux.vnet.ibm.com
Fri Oct 6 06:24:22 AEDT 2017
From: "Edward A. James" <eajames at us.ibm.com>
Potential for bad memory access in the worker function. Now fixed by
using reference counters.
Signed-off-by: Edward A. James <eajames at us.ibm.com>
---
drivers/fsi/occ.c | 92 ++++++++++++++++++++++++++-----------------------------
1 file changed, 43 insertions(+), 49 deletions(-)
diff --git a/drivers/fsi/occ.c b/drivers/fsi/occ.c
index 1770823..f2b5b95 100644
--- a/drivers/fsi/occ.c
+++ b/drivers/fsi/occ.c
@@ -70,15 +70,11 @@ struct occ_response {
* and cleared if the transfer fails or occ_worker_getsram completes.
* XFR_COMPLETE is set when a transfer fails or finishes occ_worker_getsram.
* XFR_CANCELED is set when the transfer's client is released.
- * XFR_WAITING is set from read() if the transfer isn't complete and
- * O_NONBLOCK wasn't specified. Cleared in read() when transfer completes or
- * fails.
*/
enum {
XFR_IN_PROGRESS,
XFR_COMPLETE,
XFR_CANCELED,
- XFR_WAITING,
};
struct occ_xfr {
@@ -104,6 +100,7 @@ enum {
};
struct occ_client {
+ struct kref kref;
struct occ *occ;
struct occ_xfr xfr;
spinlock_t lock; /* lock access to the client state */
@@ -140,6 +137,24 @@ static int occ_enqueue_xfr(struct occ_xfr *xfr)
return 0;
}
+static void occ_get_client(struct occ_client *client)
+{
+ kref_get(&client->kref);
+}
+
+static void occ_client_release(struct kref *kref)
+{
+ struct occ_client *client = container_of(kref, struct occ_client,
+ kref);
+
+ kfree(client);
+}
+
+static void occ_put_client(struct occ_client *client)
+{
+ kref_put(&client->kref, occ_client_release);
+}
+
static struct occ_client *occ_open_common(struct occ *occ, unsigned long flags)
{
struct occ_client *client;
@@ -152,6 +167,7 @@ static struct occ_client *occ_open_common(struct occ *occ, unsigned long flags)
return ERR_PTR(-ENOMEM);
client->occ = occ;
+ kref_init(&client->kref);
spin_lock_init(&client->lock);
init_waitqueue_head(&client->wait);
@@ -187,6 +203,7 @@ static ssize_t occ_read_common(struct occ_client *client, char __user *ubuf,
if (len > OCC_SRAM_BYTES)
return -EINVAL;
+ occ_get_client(client);
spin_lock_irq(&client->lock);
if (!test_bit(CLIENT_XFR_PENDING, &client->flags)) {
@@ -207,8 +224,6 @@ static ssize_t occ_read_common(struct occ_client *client, char __user *ubuf,
goto done;
}
- set_bit(XFR_WAITING, &xfr->flags);
-
spin_unlock_irq(&client->lock);
rc = wait_event_interruptible(client->wait,
@@ -220,15 +235,12 @@ static ssize_t occ_read_common(struct occ_client *client, char __user *ubuf,
spin_lock_irq(&client->lock);
- if (test_bit(XFR_CANCELED, &xfr->flags)) {
- spin_unlock_irq(&client->lock);
- kfree(client);
- return -EBADFD;
- }
-
- clear_bit(XFR_WAITING, &xfr->flags);
if (!test_bit(XFR_COMPLETE, &xfr->flags)) {
- rc = -EINTR;
+ if (occ->cancel || test_bit(XFR_CANCELED, &xfr->flags))
+ rc = -ECANCELED;
+ else
+ rc = -EINTR;
+
goto done;
}
}
@@ -259,6 +271,7 @@ static ssize_t occ_read_common(struct occ_client *client, char __user *ubuf,
done:
spin_unlock_irq(&client->lock);
+ occ_put_client(client);
return rc;
}
@@ -282,6 +295,7 @@ static ssize_t occ_write_common(struct occ_client *client,
if (len > (OCC_CMD_DATA_BYTES + 3) || len < 3)
return -EINVAL;
+ occ_get_client(client);
spin_lock_irq(&client->lock);
if (test_bit(CLIENT_XFR_PENDING, &client->flags)) {
@@ -331,6 +345,7 @@ static ssize_t occ_write_common(struct occ_client *client,
done:
spin_unlock_irq(&client->lock);
+ occ_put_client(client);
return rc;
}
@@ -349,38 +364,26 @@ static int occ_release_common(struct occ_client *client)
spin_lock_irq(&client->lock);
- if (!test_bit(CLIENT_XFR_PENDING, &client->flags)) {
- spin_unlock_irq(&client->lock);
- kfree(client);
- return 0;
- }
+ set_bit(XFR_CANCELED, &xfr->flags);
+ if (!test_bit(CLIENT_XFR_PENDING, &client->flags))
+ goto done;
spin_lock_irq(&occ->list_lock);
- set_bit(XFR_CANCELED, &xfr->flags);
if (!test_bit(XFR_IN_PROGRESS, &xfr->flags)) {
/* already deleted from list if complete */
if (!test_bit(XFR_COMPLETE, &xfr->flags))
list_del(&xfr->link);
-
- spin_unlock_irq(&occ->list_lock);
-
- if (test_bit(XFR_WAITING, &xfr->flags)) {
- /* blocking read; let reader clean up */
- wake_up_interruptible(&client->wait);
- spin_unlock_irq(&client->lock);
- return 0;
- }
-
- spin_unlock_irq(&client->lock);
-
- kfree(client);
- return 0;
}
- /* operation is in progress; let worker clean up */
spin_unlock_irq(&occ->list_lock);
+
+ wake_up_all(&client->wait);
+
+done:
spin_unlock_irq(&client->lock);
+
+ occ_put_client(client);
return 0;
}
@@ -586,7 +589,7 @@ static int occ_trigger_attn(struct device *sbefifo)
static void occ_worker(struct work_struct *work)
{
- int rc = 0, empty, waiting, canceled;
+ int rc = 0, empty;
u16 resp_data_length;
unsigned long start;
const unsigned long timeout = msecs_to_jiffies(OCC_TIMEOUT_MS);
@@ -609,6 +612,8 @@ static void occ_worker(struct work_struct *work)
return;
}
+ client = to_client(xfr);
+ occ_get_client(client);
resp = (struct occ_response *)xfr->buf;
set_bit(XFR_IN_PROGRESS, &xfr->flags);
@@ -664,29 +669,18 @@ static void occ_worker(struct work_struct *work)
mutex_unlock(&occ->occ_lock);
xfr->rc = rc;
- client = to_client(xfr);
-
- /* lock client to prevent race with read() */
- spin_lock_irq(&client->lock);
-
set_bit(XFR_COMPLETE, &xfr->flags);
- waiting = test_bit(XFR_WAITING, &xfr->flags);
-
- spin_unlock_irq(&client->lock);
spin_lock_irq(&occ->list_lock);
clear_bit(XFR_IN_PROGRESS, &xfr->flags);
list_del(&xfr->link);
empty = list_empty(&occ->xfrs);
- canceled = test_bit(XFR_CANCELED, &xfr->flags);
spin_unlock_irq(&occ->list_lock);
- if (waiting)
- wake_up_interruptible(&client->wait);
- else if (canceled)
- kfree(client);
+ wake_up_interruptible(&client->wait);
+ occ_put_client(client);
if (!empty)
goto again;
--
1.8.3.1
More information about the openbmc
mailing list