diff mbox series

tty: rpmsg: Fix race condition releasing tty port

Message ID 20211213195346.12894-1-arnaud.pouliquen@foss.st.com (mailing list archive)
State Superseded
Headers show
Series tty: rpmsg: Fix race condition releasing tty port | expand

Commit Message

Arnaud POULIQUEN Dec. 13, 2021, 7:53 p.m. UTC
In current implementation 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 but also 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 (inspired from vcc.c):
- moves the management of the port in the install and clean-up tty ops,
- allocates the tty_port struct independently of the rpmsg_tty_port structure,
- uses tty_vhangup and tty_port_hangup.

Fixes: 7c0408d80579 ("tty: add rpmsg driver")
Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
---
Applied and tested on fa55b7dcdc43 ("Linux 5.16-rc1", 2021-11-14)
---
 drivers/tty/rpmsg_tty.c | 81 ++++++++++++++++++++++++++++++++---------
 1 file changed, 63 insertions(+), 18 deletions(-)

Comments

Jiri Slaby Dec. 14, 2021, 6:43 a.m. UTC | #1
Hi,

On 13. 12. 21, 20:53, Arnaud Pouliquen wrote:
> In current implementation 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 but also 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 (inspired from vcc.c):
> - moves the management of the port in the install and clean-up tty ops,
> - allocates the tty_port struct independently of the rpmsg_tty_port structure,

This looks rather wrong. Why not to use tty_port refcounting?

> - uses tty_vhangup and tty_port_hangup.

OK, but don't store a tty pointer as it looks racy. You should use 
tty_port_tty_get instead.

Hm, we look we need tty_port_tty_vhangup (aside from 
tty_port_tty_hangup). There are plenty of drivers doing:
     tty = tty_port_tty_get(port);
     if (tty) {
             tty_vhangup(port->tty);
             tty_kref_put(tty);


> Fixes: 7c0408d80579 ("tty: add rpmsg driver")
> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>

thanks,
Arnaud POULIQUEN Dec. 14, 2021, 3:38 p.m. UTC | #2
Hello Jiri,

On 12/14/21 7:43 AM, Jiri Slaby wrote:
> Hi,
> 
> On 13. 12. 21, 20:53, Arnaud Pouliquen wrote:
>> In current implementation 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 but also 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 (inspired from vcc.c):
>> - moves the management of the port in the install and clean-up tty ops,
>> - allocates the tty_port struct independently of the rpmsg_tty_port structure,
> 
> This looks rather wrong. Why not to use tty_port refcounting?

Please could you detail what seems rather wrong for you? Everything or do the
tty_port port struct independently of the rpmsg_tty_port structure?

Concerning the tty_port refcounting:
Yes it also an option that I have already tried without success, before
implementing this patch.
That said, as you pointed it out, I reimplemented it today in another way, and
this time it seems that it works without any runtime warning or error.
I need to perform more test to confirm, then I will propose a V2 based on
tty_port refcountingt and the .destruct tty_port_operations.

> 
>> - uses tty_vhangup and tty_port_hangup.
> 
> OK, but don't store a tty pointer as it looks racy. You should use
> tty_port_tty_get instead.
> 
> Hm, we look we need tty_port_tty_vhangup (aside from tty_port_tty_hangup). There
> are plenty of drivers doing:
>     tty = tty_port_tty_get(port);
>     if (tty) {
>             tty_vhangup(port->tty);
>             tty_kref_put(tty);

I would like to first fix the issue in rpmsg_tty.c in separate thread.
But yes this should not take me too much time to propose this helper next.

Thanks,
Arnaud

> 
> 
>> Fixes: 7c0408d80579 ("tty: add rpmsg driver")
>> Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@foss.st.com>
> 
> thanks,
diff mbox series

Patch

diff --git a/drivers/tty/rpmsg_tty.c b/drivers/tty/rpmsg_tty.c
index dae2a4e44f38..50a79b1ea53c 100644
--- a/drivers/tty/rpmsg_tty.c
+++ b/drivers/tty/rpmsg_tty.c
@@ -27,11 +27,14 @@  static DEFINE_MUTEX(idr_lock);	/* protects tty_idr */
 static struct tty_driver *rpmsg_tty_driver;
 
 struct rpmsg_tty_port {
-	struct tty_port		port;	 /* TTY port data */
+	struct tty_port		*port;	 /* TTY port data */
+	struct tty_struct	*tty;	 /* TTY associated structure */
 	int			id;	 /* TTY rpmsg index */
 	struct rpmsg_device	*rpdev;	 /* rpmsg device */
 };
 
+static const struct tty_port_operations rpmsg_tty_port_ops = { };
+
 static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *priv, u32 src)
 {
 	struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
@@ -39,21 +42,60 @@  static int rpmsg_tty_cb(struct rpmsg_device *rpdev, void *data, int len, void *p
 
 	if (!len)
 		return -EINVAL;
-	copied = tty_insert_flip_string(&cport->port, data, len);
+	copied = tty_insert_flip_string(cport->port, data, len);
 	if (copied != len)
 		dev_err_ratelimited(&rpdev->dev, "Trunc buffer: available space is %d\n", copied);
-	tty_flip_buffer_push(&cport->port);
+	tty_flip_buffer_push(cport->port);
 
 	return 0;
 }
 
 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 rpmsg_tty_port *cport;
+	struct tty_port *port;
+
+	mutex_lock(&idr_lock);
+	cport = idr_find(&tty_idr, tty->index);
+	mutex_unlock(&idr_lock);
+
+	if (!cport)
+		return -ENXIO;
+
+	port = kzalloc(sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	tty_port_init(port);
+	port->ops = &rpmsg_tty_port_ops;
 
 	tty->driver_data = cport;
 
-	return tty_port_install(&cport->port, driver, tty);
+	cport->port = port;
+	cport->tty = tty;
+
+	return tty_port_install(port, driver, tty);
+}
+
+static void rpmsg_tty_cleanup(struct tty_struct *tty)
+{
+	struct tty_port *port = tty->port;
+	struct rpmsg_tty_port *cport;
+
+	WARN_ON(!port);
+
+	mutex_lock(&idr_lock);
+	cport = idr_find(&tty_idr, tty->index);
+	mutex_unlock(&idr_lock);
+
+	if (cport) {
+		cport->tty = NULL;
+		cport->port = NULL;
+	}
+
+	tty_port_destroy(port);
+	kfree(port);
+	tty->port = NULL;
 }
 
 static int rpmsg_tty_open(struct tty_struct *tty, struct file *filp)
@@ -106,12 +148,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)
@@ -146,8 +195,6 @@  static void rpmsg_tty_release_cport(struct rpmsg_tty_port *cport)
 	kfree(cport);
 }
 
-static const struct tty_port_operations rpmsg_tty_port_ops = { };
-
 static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
 {
 	struct rpmsg_tty_port *cport;
@@ -159,13 +206,9 @@  static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
 	if (IS_ERR(cport))
 		return dev_err_probe(dev, PTR_ERR(cport), "Failed to alloc tty port\n");
 
-	tty_port_init(&cport->port);
-	cport->port.ops = &rpmsg_tty_port_ops;
-
-	tty_dev = tty_port_register_device(&cport->port, rpmsg_tty_driver,
-					   cport->id, dev);
+	tty_dev = tty_register_device(rpmsg_tty_driver, cport->id, dev);
 	if (IS_ERR(tty_dev)) {
-		ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty port\n");
+		ret = dev_err_probe(dev, PTR_ERR(tty_dev), "Failed to register tty\n");
 		goto err_destroy;
 	}
 
@@ -179,7 +222,6 @@  static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
 	return 0;
 
 err_destroy:
-	tty_port_destroy(&cport->port);
 	rpmsg_tty_release_cport(cport);
 
 	return ret;
@@ -188,16 +230,19 @@  static int rpmsg_tty_probe(struct rpmsg_device *rpdev)
 static void rpmsg_tty_remove(struct rpmsg_device *rpdev)
 {
 	struct rpmsg_tty_port *cport = dev_get_drvdata(&rpdev->dev);
+	struct tty_struct *tty = cport->tty;
 
 	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);
+	/*
+	 * If there's a process with the device open, do a synchronous hangup of the TTY.
+	 * This may cause the process to call close asynchronously, but it's not guaranteed.
+	 */
+	if (tty)
+		tty_vhangup(tty);
 
 	tty_unregister_device(rpmsg_tty_driver, cport->id);
 
-	tty_port_destroy(&cport->port);
 	rpmsg_tty_release_cport(cport);
 }