* [RFC] tty: rpmsg: possible lookup-to-reference lifetime race
@ 2026-09-21 16:28 Sang-Hoon Choi
2026-09-21 18:50 ` Greg Kroah-Hartman
0 siblings, 1 reply; 3+ messages in thread
From: Sang-Hoon Choi @ 2026-09-21 16:28 UTC (permalink / raw)
To: Arnaud Pouliquen
Cc: Greg Kroah-Hartman, Jiri Slaby, linux-remoteproc, linux-serial,
linux-kernel, Changyul Lee
Hi,
I would like to report a possible lifetime gap between looking up a port
in tty_idr and acquiring its reference in rpmsg_tty_install().
The source reviewed is mainline commit
5dd1818b15d98d4a20806cd00b1b40320b06004f.
The install path calls idr_find() without idr_lock, stores the result in
tty->driver_data, and calls tty_port_get(&cport->port). The port destructor
removes the IDR entry under idr_lock, drops the mutex, and frees cport.
If the last reference is dropped during that lookup-to-get interval, the
following order appears possible:
rpmsg_tty_install() rpmsg_tty_destruct_port()
------------------- -------------------------
cport = idr_find(...)
take idr_lock
idr_remove(...)
drop idr_lock
kfree(cport)
tty_port_get(&cport->port)
tty_port_get() uses kref_get_unless_zero(), but this cannot protect memory
that has already been freed. The install path also does not check for a
missing ID or a failed reference acquisition before tty_port_install().
TTY installation is serialized by tty_mutex, but the RPMsg remove path
does not explicitly take that mutex around its final tty_port_put().
The destructor can consequently run from RPMsg removal, not just from
TTY cleanup. I have not established all cross-subsystem ordering rules.
A draft takes idr_lock across idr_find() and tty_port_get(), and returns
-ENODEV if no live reference is obtained. This uses the mutex already
taken by the destructor before removing the entry. Its scope is this
lookup/destruction gap; it does not establish safety of every probe or
remove interaction.
The draft was compile-checked as rpmsg_tty.o with W=1 in an x86
allmodconfig build at the commit above and passed checkpatch. There is
no runtime reproducer with an RPMsg endpoint or sanitizer trace. Device
permissions and the ability to remove the endpoint also remain relevant
to any security assessment.
Does another TTY or RPMsg lifetime rule prevent this particular overlap?
Reported-by: Changyul Lee <lcy8047@gmail.com>
Assisted-by: LLM
Thanks,
Sang-Hoon Choi
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [RFC] tty: rpmsg: possible lookup-to-reference lifetime race
2026-09-21 16:28 [RFC] tty: rpmsg: possible lookup-to-reference lifetime race Sang-Hoon Choi
@ 2026-09-21 18:50 ` Greg Kroah-Hartman
2026-09-22 15:17 ` [PATCH] tty: rpmsg: close port lookup-to-get race Sang-Hoon Choi
0 siblings, 1 reply; 3+ messages in thread
From: Greg Kroah-Hartman @ 2026-09-21 18:50 UTC (permalink / raw)
To: Sang-Hoon Choi
Cc: Arnaud Pouliquen, Jiri Slaby, linux-remoteproc, linux-serial,
linux-kernel, Changyul Lee
On Tue, Sep 22, 2026 at 01:28:34AM +0900, Sang-Hoon Choi wrote:
> Hi,
>
> I would like to report a possible lifetime gap between looking up a port
> in tty_idr and acquiring its reference in rpmsg_tty_install().
>
> The source reviewed is mainline commit
> 5dd1818b15d98d4a20806cd00b1b40320b06004f.
>
> The install path calls idr_find() without idr_lock, stores the result in
> tty->driver_data, and calls tty_port_get(&cport->port). The port destructor
> removes the IDR entry under idr_lock, drops the mutex, and frees cport.
>
> If the last reference is dropped during that lookup-to-get interval, the
> following order appears possible:
>
> rpmsg_tty_install() rpmsg_tty_destruct_port()
> ------------------- -------------------------
Why would these ever run at the same time? How can that happen?
> cport = idr_find(...)
> take idr_lock
> idr_remove(...)
> drop idr_lock
> kfree(cport)
> tty_port_get(&cport->port)
>
> tty_port_get() uses kref_get_unless_zero(), but this cannot protect memory
> that has already been freed. The install path also does not check for a
> missing ID or a failed reference acquisition before tty_port_install().
>
> TTY installation is serialized by tty_mutex, but the RPMsg remove path
> does not explicitly take that mutex around its final tty_port_put().
> The destructor can consequently run from RPMsg removal, not just from
> TTY cleanup. I have not established all cross-subsystem ordering rules.
>
> A draft takes idr_lock across idr_find() and tty_port_get(), and returns
> -ENODEV if no live reference is obtained. This uses the mutex already
> taken by the destructor before removing the entry. Its scope is this
> lookup/destruction gap; it does not establish safety of every probe or
> remove interaction.
>
> The draft was compile-checked as rpmsg_tty.o with W=1 in an x86
> allmodconfig build at the commit above and passed checkpatch. There is
> no runtime reproducer with an RPMsg endpoint or sanitizer trace. Device
> permissions and the ability to remove the endpoint also remain relevant
> to any security assessment.
>
> Does another TTY or RPMsg lifetime rule prevent this particular overlap?
>
> Reported-by: Changyul Lee <lcy8047@gmail.com>
> Assisted-by: LLM
Why not have your LLM make a patch for this that you can test and get
credit for the fix if there really is an issue?
And again, how can those two things ever run at the same time in a real
system controlled by a normal user (i.e. not root permissions)?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH] tty: rpmsg: close port lookup-to-get race
2026-09-21 18:50 ` Greg Kroah-Hartman
@ 2026-09-22 15:17 ` Sang-Hoon Choi
0 siblings, 0 replies; 3+ messages in thread
From: Sang-Hoon Choi @ 2026-09-22 15:17 UTC (permalink / raw)
To: Arnaud Pouliquen
Cc: Sang-Hoon Choi, Greg Kroah-Hartman, Jiri Slaby, linux-remoteproc,
linux-serial, linux-kernel, Changyul Lee
rpmsg_tty_install() obtains cport from tty_idr before taking a port
reference. rpmsg_tty_destruct_port() removes the entry under idr_lock
and frees cport. If channel removal drops the last reference between
idr_find() and tty_port_get(), the install path dereferences freed
memory.
The first-open path and channel removal can run concurrently. tty_mutex
serializes TTY initialization, but rpmsg_tty_remove() does not take that
mutex. tty_unregister_device() prevents later opens through cdev_del(),
but cdev_del() does not wait for an open which has already entered the
driver. Before rpmsg_tty_install() finishes, the port is not attached to
the new TTY, so tty_port_tty_hangup() does not close this interval.
RPMsg channel removal may be initiated asynchronously by the remote
processor or transport. In the test, the local process only needs
permission to open the TTY node; channel removal is initiated
independently.
I reproduced this with a UML kernel built with KASAN and a synthetic
RPMsg device using the real rpmsg_tty probe and remove paths. Test-only
synchronization forces removal after idr_find() and before
tty_port_get(). The opening process drops to UID 1000 and GID 1000 first.
The unpatched kernel reports:
BUG: KASAN: slab-use-after-free in rpmsg_tty_install
Read of size 4 ... by task init/23
CPU: 0 UID: 1000 PID: 23
The allocation stack ends in rpmsg_tty_probe(). The free stack is
rpmsg_tty_remove() -> tty_port_put() -> rpmsg_tty_destruct_port(), and
the invalid read is in rpmsg_tty_install().
Take idr_lock across idr_find() and tty_port_get(). If the entry is gone
or its reference count has reached zero, fail the installation with
-ENODEV. With the same forced overlap, the UID 1000 open returns ENODEV
and KASAN stays quiet.
The test uses a synthetic transport and deliberately widens the race
window. It demonstrates the lifetime bug and an unprivileged opener, but
does not show that an unprivileged user can deliberately cause channel
removal on every RPMsg platform. I am therefore reporting this as a
normal lifetime bug.
Fixes: 7c0408d80579 ("tty: add rpmsg driver")
Reported-by: Changyul Lee <lcy8047@gmail.com>
Link: https://lore.kernel.org/all/179000811428.1227592.8003229121862460039.idr-bug-84@gmail.com/
Assisted-by: LLM
Signed-off-by: Sang-Hoon Choi <csh0052@gmail.com>
---
Greg, thanks for the feedback. I went back and reproduced the race under
KASAN before preparing this patch. I can also provide the test-only
instrumentation and the complete KASAN log if useful.
drivers/tty/rpmsg_tty.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/tty/rpmsg_tty.c b/drivers/tty/rpmsg_tty.c
index c5fd6d9b3..b2765bae2 100644
--- a/drivers/tty/rpmsg_tty.c
+++ b/drivers/tty/rpmsg_tty.c
@@ -49,12 +49,19 @@ 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;
+ struct rpmsg_tty_port *cport;
+ struct tty_port *port = NULL;
- tty->driver_data = cport;
+ mutex_lock(&idr_lock);
+ cport = idr_find(&tty_idr, tty->index);
+ if (cport)
+ port = tty_port_get(&cport->port);
+ mutex_unlock(&idr_lock);
+
+ if (!port)
+ return -ENODEV;
- port = tty_port_get(&cport->port);
+ tty->driver_data = cport;
return tty_port_install(port, driver, tty);
}
--
2.43.0
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-22 15:17 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-21 16:28 [RFC] tty: rpmsg: possible lookup-to-reference lifetime race Sang-Hoon Choi
2026-09-21 18:50 ` Greg Kroah-Hartman
2026-09-22 15:17 ` [PATCH] tty: rpmsg: close port lookup-to-get race Sang-Hoon Choi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®