tty_port: Restore tty port default ops on unregistering
Subsystems (3)
PATCH 1/1 tty_port: Restore tty port default ops on unregistering
When a port is unregistered from serdev, its serdev specific client_ops pointer is nullified, which can lead to future null pointer dereference. Fix that by restoring default client_ops when port is unregistered from serdev.
port registering/unregistering can happen several times, at least it happens when statically registered 8250 ISA port are replaced at boot time by e.g. device-tree defined 8250 ports.
--- drivers/tty/tty_port.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
drivers/tty/tty_port.c +3 -1
@@ -203,8 +203,10 @@ void tty_port_unregister_device(struct tty_port *port,int ret; ret =serdev_tty_port_unregister (port); -if (ret ==0 ) +if (ret ==0 ) { + port->client_ops = &default_client_ops;return ; + }tty_unregister_device (driver, index); }
Signature · 1 line
2.7.4
Quoted 4 lines · Loic Poulain
When a port is unregistered from serdev, its serdev specific
client_opspointer is nullified, which can lead to future null pointer dereference. Fix that by restoring defaultclient_opswhen port is unregistered from serdev.
Hmm, yeah, this seems like something we should fix as 8250 appears to allow reregistering ports, but...
Quoted 3 lines
port registering/unregistering can happen several times, at least it happens when statically registered 8250 ISA port are replaced at boot time by e.g. device-tree defined 8250 ports.
How did the serdev controller get registered in the first place here if you're talking about statically registered 8250 ISA ports (i.e. where is the client defined)?
Did you actually ever hit this one?
Quoted 11 lines
Signed-off-by Loic Poulain <[email protected]>--- drivers/tty/tty_port.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)drivers/tty/tty_port.c +0
-0@@ -203,8 +203,10 @@ void tty_port_unregister_device(struct tty_port *port,int ret;
drivers/tty/tty_port.c void tty_port_unregister_device(struct tty_port *port, · line 212
Quoted 10 lines
drivers/tty/tty_port.c +3
-1ret =serdev_tty_port_unregister (port); -if (ret ==0 ) +if (ret ==0 ) { + port->client_ops = &default_client_ops;return ; + }tty_unregister_device (driver, index); }
Johan
A message that is not in the archive (sent off-list or not yet received): CAMZdPi8R_bj4+HFjCmiRTJ2KgToMMuX0fbopGBOsMPMhJ4xsXQ@mail.gmail.com
Quoted 15 lines · Loic Poulain
Quoted 14 lines · Johan Hovold
Quoted 4 lines · Loic Poulain
When a port is unregistered from serdev, its serdev specific
client_opspointer is nullified, which can lead to future null pointer dereference. Fix that by restoring defaultclient_opswhen port is unregistered from serdev.Hmm, yeah, this seems like something we should fix as 8250 appears to allow reregistering ports, but...
Quoted 3 lines
port registering/unregistering can happen several times, at least it happens when statically registered 8250 ISA port are replaced at boot time by e.g. device-tree defined 8250 ports.
Quoted 7 lines
Quoted 5 lines
How did the serdev controller get registered in the first place here if you're talking about statically registered 8250 ISA ports (i.e. where is the client defined)?
Did you actually ever hit this one?
Yes, but never in the mainline.
Ok, thanks for confirming.
Quoted 9 lines
Actually a set of changes [1] [2] in the AOSP common kernel mades the tty ports bound to serdev by default, except for one(s) with console attached. So with some platforms the ISA ttyS0 is firstly registered with serdev and then replaced (unregistered/registered) later in the boot by the 8250 port enumerated from the fdt and used for console (expecting default tty ops).
TBH I disagree with the way it has been done in the AOSP, but it highlighted this possible null ops path, which can be fixed either by resetting the default ops on tty port unregister (this patch), or on register.
Yeah, those AOSP commits doesn't make much sense, especially not the first one since it doesn't provide any means to define the client.
Quoted 4 lines
[1]
https://android.googlesource.com/kernel/common/+/c550a54f23026e92633c5145e8b919cf590a5624
[2]
https://android.googlesource.com/kernel/common/+/cc3b00864e3b316ff106f948834d7e0cc6244921
I spent some time looking into this today, and this doesn't seem to be an issue for mainline unless we are reloading 8250 drivers.
Specifically, the statically defined ports that 8250 core would register when an 8250 driver is being unbound could end up with a NULL client ops.
This is seems unlikely to be an issue in practice, but I think we should plug this nonetheless.
I already fixed the related issue of serdev not resetting the client_ops on failed registration in aee5da783878 ("serdev: fix tty-port client deregistration"), and I think the unregistration case should be handled similarly by the serdev ttyport controller driver (rather than by tty core).
I've prepared a patch with a commit message outlining how this may affect mainline and included a stable tag. This should fix the issue in AOSP as well, even that first commit [1] above really should be reverted.
Johan
PATCH 1/1 serdev: ttyport: restore client ops on deregistration
The serdev tty-port controller driver should reset the tty-port client operations also on deregistration to avoid a NULL-pointer dereference in case the port is later re-registered as a normal tty device.
Note that this can only happen with tty drivers such as 8250 which have statically allocated port structures that can end up being reused and where a later registration would not register a serdev controller (e.g. due to registration errors or if the devicetree has been changed in between).
Specifically, this can be an issue for any statically defined ports that would be registered by 8250 core when an 8250 driver is being unbound.
--- drivers/tty/serdev/serdev-ttyport.c | 6 ++---- drivers/tty/tty_port.c | 5 +++-- include/linux/tty.h | 2 ++ 3 files changed, 7 insertions(+), 6 deletions(-)
drivers/tty/serdev/serdev-ttyport.c +2 -4
@@ -265,7 +265,6 @@ struct device *serdev_tty_port_register(struct tty_port *port,struct device *parent, struct tty_driver *drv, int idx) { - const struct tty_port_client_operations *old_ops; struct serdev_controller *ctrl;struct serport *serport;int ret;
@@ -284,7 +283,6 @@ struct device *serdev_tty_port_register(struct tty_port *port, ctrl->ops = &ctrl_ops; - old_ops = port->client_ops; port->client_ops = &client_ops; port->client_data = ctrl;
@@ -297,7 +295,7 @@ struct device *serdev_tty_port_register(struct tty_port *port, err_reset_data: port->client_data =NULL ; - port->client_ops = old_ops; + port->client_ops = &tty_port_default_client_ops;serdev_controller_put (ctrl);return ERR_PTR (ret);
@@ -312,8 +310,8 @@ int serdev_tty_port_unregister(struct tty_port *port)return -ENODEV ;serdev_controller_remove (ctrl); - port->client_ops =NULL ; port->client_data =NULL ; + port->client_ops = &tty_port_default_client_ops;serdev_controller_put (ctrl);return 0 ;
drivers/tty/tty_port.c +3 -2
@@ -52,10 +52,11 @@ static void tty_port_default_wakeup(struct tty_port *port) } } -static const struct tty_port_client_operations default_client_ops = { +const struct tty_port_client_operations tty_port_default_client_ops = { .receive_buf = tty_port_default_receive_buf, .write_wakeup = tty_port_default_wakeup, }; +EXPORT_SYMBOL_GPL (tty_port_default_client_ops);void tty_port_init (struct tty_port *port) {
@@ -68,7 +69,7 @@ void tty_port_init(struct tty_port *port)spin_lock_init (&port->lock); port->close_delay = (50 *HZ ) /100 ; port->closing_wait = (3000 *HZ ) /100 ; - port->client_ops = &default_client_ops; + port->client_ops = &tty_port_default_client_ops;kref_init (&port->kref); }EXPORT_SYMBOL (tty_port_init);
include/linux/tty.h +2 -0
@@ -225,6 +225,8 @@ struct tty_port_client_operations {void (*write_wakeup)(struct tty_port *port); }; +extern const struct tty_port_client_operations tty_port_default_client_ops; +struct tty_port {struct tty_bufhead buf;/* Locked internally */ struct tty_struct *tty;/* Back pointer */
Signature · 1 line
2.24.1