Skip to content

Commit

Permalink
Input: serio_raw - fix memory leak when closing char device
Browse files Browse the repository at this point in the history
Apparently we never freed memory allocated when users open our char
devices nor removed old users from the list of connected clients.

Also unregister misc device immediately upon disconnecting the port
instead of waiting until last user drops off (refcounting in misc
device code will make sure needed pieces stay around while they
are needed) and make sure we are not holing holding serio_raw_mutex
when registering/unregistering misc device. This should fix potential
deadlock between serio_raw and misc device code uncovered by lockdep
and reported by Thomas Tuttle.

Reviewed-by: Wanlong Gao <gaowanlong@cn.fujitsu.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
  • Loading branch information
Dmitry Torokhov committed Oct 11, 2011
1 parent 8c1c10d commit 550eca7
Showing 1 changed file with 29 additions and 25 deletions.
54 changes: 29 additions & 25 deletions drivers/input/serio/serio_raw.c
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ struct serio_raw_client {

static DEFINE_MUTEX(serio_raw_mutex);
static LIST_HEAD(serio_raw_list);
static unsigned int serio_raw_no;

/*********************************************************************
* Interface with userspace (file operations) *
Expand Down Expand Up @@ -117,14 +116,11 @@ static int serio_raw_open(struct inode *inode, struct file *file)
return retval;
}

static void serio_raw_cleanup(struct kref *kref)
static void serio_raw_free(struct kref *kref)
{
struct serio_raw *serio_raw =
container_of(kref, struct serio_raw, kref);

misc_deregister(&serio_raw->dev);
list_del_init(&serio_raw->node);

put_device(&serio_raw->serio->dev);
kfree(serio_raw);
}
Expand All @@ -134,11 +130,14 @@ static int serio_raw_release(struct inode *inode, struct file *file)
struct serio_raw_client *client = file->private_data;
struct serio_raw *serio_raw = client->serio_raw;

mutex_lock(&serio_raw_mutex);
serio_pause_rx(serio_raw->serio);
list_del(&client->node);
serio_continue_rx(serio_raw->serio);

kref_put(&serio_raw->kref, serio_raw_cleanup);
kfree(client);

kref_put(&serio_raw->kref, serio_raw_free);

mutex_unlock(&serio_raw_mutex);
return 0;
}

Expand Down Expand Up @@ -281,6 +280,7 @@ static irqreturn_t serio_raw_interrupt(struct serio *serio, unsigned char data,

static int serio_raw_connect(struct serio *serio, struct serio_driver *drv)
{
static atomic_t serio_raw_no = ATOMIC_INIT(0);
struct serio_raw *serio_raw;
int err;

Expand All @@ -290,10 +290,8 @@ static int serio_raw_connect(struct serio *serio, struct serio_driver *drv)
return -ENOMEM;
}

mutex_lock(&serio_raw_mutex);

snprintf(serio_raw->name, sizeof(serio_raw->name),
"serio_raw%d", serio_raw_no++);
"serio_raw%ld", (long)atomic_inc_return(&serio_raw_no) - 1);
kref_init(&serio_raw->kref);
INIT_LIST_HEAD(&serio_raw->client_list);
init_waitqueue_head(&serio_raw->wait);
Expand All @@ -305,9 +303,14 @@ static int serio_raw_connect(struct serio *serio, struct serio_driver *drv)

err = serio_open(serio, drv);
if (err)
goto out_free;
goto err_free;

err = mutex_lock_killable(&serio_raw_mutex);
if (err)
goto err_close;

list_add_tail(&serio_raw->node, &serio_raw_list);
mutex_unlock(&serio_raw_mutex);

serio_raw->dev.minor = PSMOUSE_MINOR;
serio_raw->dev.name = serio_raw->name;
Expand All @@ -324,22 +327,20 @@ static int serio_raw_connect(struct serio *serio, struct serio_driver *drv)
dev_err(&serio->dev,
"failed to register raw access device for %s\n",
serio->phys);
goto out_close;
goto err_unlink;
}

dev_info(&serio->dev, "raw access enabled on %s (%s, minor %d)\n",
serio->phys, serio_raw->name, serio_raw->dev.minor);
goto out;
return 0;

out_close:
serio_close(serio);
err_unlink:
list_del_init(&serio_raw->node);
out_free:
err_close:
serio_close(serio);
err_free:
serio_set_drvdata(serio, NULL);
put_device(&serio->dev);
kfree(serio_raw);
out:
mutex_unlock(&serio_raw_mutex);
kref_put(&serio_raw->kref, serio_raw_free);
return err;
}

Expand Down Expand Up @@ -382,14 +383,17 @@ static void serio_raw_disconnect(struct serio *serio)
{
struct serio_raw *serio_raw = serio_get_drvdata(serio);

mutex_lock(&serio_raw_mutex);
misc_deregister(&serio_raw->dev);

serio_close(serio);
mutex_lock(&serio_raw_mutex);
serio_raw->dead = true;
list_del_init(&serio_raw->node);
mutex_unlock(&serio_raw_mutex);

serio_raw_hangup(serio_raw);
kref_put(&serio_raw->kref, serio_raw_cleanup);

mutex_unlock(&serio_raw_mutex);
serio_close(serio);
kref_put(&serio_raw->kref, serio_raw_free);

serio_set_drvdata(serio, NULL);
}
Expand Down

0 comments on commit 550eca7

Please sign in to comment.