forked from Minki/linux
tty: rpmsg: Fix race condition releasing tty port
The tty_port struct is part of the rpmsg_tty_port structure.
The issue is that the rpmsg_tty_port structure is freed on
rpmsg_tty_remove while it is still referenced in the tty_struct.
Its release is not predictable due to workqueues.
For instance following ftrace shows that rpmsg_tty_close is called after
rpmsg_tty_release_cport:
nr_test.sh-389 [000] ..... 212.093752: rpmsg_tty_remove <-rpmsg_dev_
remove
cat-1191 [001] ..... 212.095697: tty_release <-__fput
nr_test.sh-389 [000] ..... 212.099166: rpmsg_tty_release_cport <-rpm
sg_tty_remove
cat-1191 [001] ..... 212.115352: rpmsg_tty_close <-tty_release
cat-1191 [001] ..... 212.115371: release_tty <-tty_release_str
As consequence, the port must be free only when user has released the TTY
interface.
This path :
- Introduce the .destruct port tty ops function to release the allocated
rpmsg_tty_port structure.
- Introduce the .hangup tty ops function to call tty_port_hangup.
- Manages the tty port refcounting to trig the .destruct port ops,
- Introduces the rpmsg_tty_cleanup function to ensure that the TTY is
removed before decreasing the port refcount.
Fixes: 7c0408d805
("tty: add rpmsg driver")
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
Link: https://lore.kernel.org/r/20220104163545.34710-1-arnaud.pouliquen@foss.st.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
f23653fe64
commit
db7f19c0aa
@ -50,10 +50,17 @@ static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *p
|
||||
static int rpmsg_tty_install(struct tty_driver *driver, struct tty_struct *tty)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = idr_find(&tty_idr, tty->index);
|
||||
struct tty_port *port;
|
||||
|
||||
tty->driver_data = cport;
|
||||
|
||||
return tty_port_install(&cport->port, driver, tty);
|
||||
port = tty_port_get(&cport->port);
|
||||
return tty_port_install(port, driver, tty);
|
||||
}
|
||||
|
||||
static void rpmsg_tty_cleanup(struct tty_struct *tty)
|
||||
{
|
||||
tty_port_put(tty->port);
|
||||
}
|
||||
|
||||
static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
|
||||
@ -106,12 +113,19 @@ static unsigned int rpmsg_tty_write_room(struct tty_struct *tty)
|
||||
return size;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_hangup(struct tty_struct *tty)
|
||||
{
|
||||
tty_port_hangup(tty->port);
|
||||
}
|
||||
|
||||
static const struct tty_operations rpmsg_tty_ops = {
|
||||
.install = rpmsg_tty_install,
|
||||
.open = rpmsg_tty_open,
|
||||
.close = rpmsg_tty_close,
|
||||
.write = rpmsg_tty_write,
|
||||
.write_room = rpmsg_tty_write_room,
|
||||
.hangup = rpmsg_tty_hangup,
|
||||
.cleanup = rpmsg_tty_cleanup,
|
||||
};
|
||||
|
||||
static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
|
||||
@ -137,8 +151,10 @@ static struct rpmsg_tty_port *rpmsg_tty_alloc_cport(void)
|
||||
return cport;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_release_cport(struct rpmsg_tty_port *cport)
|
||||
static void rpmsg_tty_destruct_port(struct tty_port *port)
|
||||
{
|
||||
struct rpmsg_tty_port *cport = container_of(port, struct rpmsg_tty_port, port);
|
||||
|
||||
mutex_lock(&idr_lock);
|
||||
idr_remove(&tty_idr, cport->id);
|
||||
mutex_unlock(&idr_lock);
|
||||
@ -146,7 +162,10 @@ static void rpmsg_tty_release_cport(struct rpmsg_tty_port *cport)
|
||||
kfree(cport);
|
||||
}
|
||||
|
||||
static const struct tty_port_operations rpmsg_tty_port_ops = { };
|
||||
static const struct tty_port_operations rpmsg_tty_port_ops = {
|
||||
.destruct = rpmsg_tty_destruct_port,
|
||||
};
|
||||
|
||||
|
||||
static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
|
||||
{
|
||||
@ -166,7 +185,8 @@ static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
|
||||
cport->id, dev);
|
||||
if (IS_ERR(tty_dev)) {
|
||||
ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
|
||||
goto err_destroy;
|
||||
tty_port_put(&cport->port);
|
||||
return ret;
|
||||
}
|
||||
|
||||
cport->rpdev = rpdev;
|
||||
@ -177,12 +197,6 @@ static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
|
||||
rpdev->src, rpdev->dst, cport->id);
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy:
|
||||
tty_port_destroy(&cport->port);
|
||||
rpmsg_tty_release_cport(cport);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
|
||||
@ -192,13 +206,11 @@ static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
|
||||
dev_dbg(&rpdev->dev, "Removing rpmsg tty device %d\n", cport->id);
|
||||
|
||||
/* User hang up to release the tty */
|
||||
if (tty_port_initialized(&cport->port))
|
||||
tty_port_tty_hangup(&cport->port, false);
|
||||
tty_port_tty_hangup(&cport->port, false);
|
||||
|
||||
tty_unregister_device(rpmsg_tty_driver, cport->id);
|
||||
|
||||
tty_port_destroy(&cport->port);
|
||||
rpmsg_tty_release_cport(cport);
|
||||
tty_port_put(&cport->port);
|
||||
}
|
||||
|
||||
static struct rpmsg_device_id rpmsg_driver_tty_id_table[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user