All Virtuozzo development lists (kernel + QEMU)
 help / color / mirror / Atom feed
From: Konstantin Khorenko <khorenko@virtuozzo.com>
Subject: [Devel] [PATCH RHEL10 COMMIT] ve/vtty: fix use-after-free on concurrent tty close and reopen
Date: Mon, 10 Aug 2026 16:31:27 +0200	[thread overview]
Message-ID: <202608101431.67AEVRoc029985@f0.sw.ru> (raw)
In-Reply-To: <20260810014009.81168-1-eva.kurchatova@virtuozzo.com>

The commit is pushed to "branch-rh10-6.12.0-211.39.1.16.x.vz10-ovz" and will appear at git at bitbucket.org:openvz/vzkernel.git
after rh10-6.12.0-211.39.1.16.3.vz10
------>
commit c5028cd61a694555ec2af47b2e6e2b4f8e013de9
Author: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
Date:   Mon Aug 10 04:39:52 2026 +0300

    ve/vtty: fix use-after-free on concurrent tty close and reopen
    
    A race in the vtty subsystem leads to use-after-free of tty_struct
    objects when vzctl console attach/detach cycles run concurrently with
    container-side tty open/close (e.g. SAK-triggered getty respawn):
    vtty_open_master reopens a dying tty pair.
    
    After tty_release() sets final == true and releases tty_lock, there is
    a window before release_tty() runs under tty_mutex. During this window,
    vtty_open_master() can find the old vttym in the vtty map with count == 0,
    pass the ">= 1" check (which was designed as a "one vttym at a time"
    guard, not a liveness check), re-increment the counts, and hand out a
    file descriptor pointing to a tty_struct that is about to be freed.
    
    vtty_open_master() moreover increments both counts with only tty_mutex
    held, while tty_release() decrements them under tty_lock of the vttys
    peer, so the two paths do not exclude each other at all. The resulting
    data race underflows the counts, and lets both peers assume final == true:
    the slave sees slave->count == 0, the master sees both counts == 0 (after
    the slave count underflowed and got reset).
    Both then call tty_release_struct -> release_tty, and the second caller
    hits a use-after-free.
    
    Fix this by taking tty_lock(vttys) in vtty_open_master() to serialize
    with a concurrent tty_release() on the vttys side, so we read the slave
    count after any in-flight close has decremented it. If vttys->count
    has reached zero the pair is dying, so return -EBUSY. Incrementing
    vttys->count under the same tty_lock prevents a concurrent tty_release()
    from seeing zero and entering the final-close path.
    
    A pair vtty_open_master() has just allocated itself is exempt from the
    zero check: it is not reachable by anyone else yet and its zero count is
    merely the initial state, vtty_install() hands that reference over to the
    master peer below. Treating it as dying would break every attach to a
    console which has no container-side opener.
    
    The vttym count is manipulated under the same tty_lock as well, so that
    every reference of a vtty pair is only ever touched under tty_lock of its
    vttys peer.
    
    The lock nesting pattern of tty_mutex -> tty_lock is preserved, which
    prevents the kind of A->B, B->A circular locking dependency.
    
    Fixes: 62dce02f0c99 ("ve/tty: vt -- Implement per VE support for console and terminals")
    
    https://virtuozzo.atlassian.net/browse/VSTOR-136511
    Feature: tty: virtual Container console
    Signed-off-by: Eva Kurchatova <eva.kurchatova@virtuozzo.com>
    Reviewed-by: Vasileios Almpanis <vasileios.almpanis@virtuozzo.com>
---
 drivers/tty/pty.c | 41 +++++++++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
index f8610c77817a5..14d46bb9c293d 100644
--- a/drivers/tty/pty.c
+++ b/drivers/tty/pty.c
@@ -1022,9 +1022,10 @@ void vtty_alloc_tty_struct(const struct tty_driver *driver,
 
 int vtty_open_master(envid_t veid, int idx)
 {
-	struct tty_struct *tty;
+	struct tty_struct *tty, *slave;
 	struct file *file;
 	char devname[64];
+	bool created = false;
 	int fd, ret;
 
 	if (!vtty_match_index(idx))
@@ -1074,15 +1075,41 @@ int vtty_open_master(envid_t veid, int idx)
 		tty_set_lock_subclass(tty);
 #endif
 		tty = tty->link;
+		created = true;
 	}
 
+	slave = tty->link;
+
+	/*
+	 * Every reference of a vtty pair is manipulated under the tty_lock
+	 * of its slave peer: tty_release() takes it for both peers (on the
+	 * master side via tty_lock_slave()) and tty_open() takes it before
+	 * tty_reopen(). Take it here as well so that we neither race with
+	 * an in-flight close nor resurrect a pair which is already gone.
+	 */
+	tty_lock(slave);
+
 	/* One master at a time */
 	if (tty->count >= 1) {
 		ret = -EBUSY;
-		goto err_install;
+		goto err_unlock;
 	}
 
-	vtty_drop_context();
+	/*
+	 * A zero slave count on a pair looked up in the map means the last
+	 * user is gone and tty_release() has already committed to the final
+	 * close, release_tty() is only waiting for tty_mutex to free it.
+	 * Never hand out a reference to a tty which is about to be freed,
+	 * report -EBUSY so that the next attempt allocates a fresh pair.
+	 *
+	 * A pair we've just created above is exempt: it is not reachable by
+	 * anyone else yet and its zero count is merely the initial state,
+	 * vtty_install() gives its reference away to the master peer below.
+	 */
+	if (!created && slave->count == 0) {
+		ret = -EBUSY;
+		goto err_unlock;
+	}
 
 	/*
 	 * We're the master peer so increment
@@ -1090,7 +1117,11 @@ int vtty_open_master(envid_t veid, int idx)
 	 */
 	tty_add_file(tty, file);
 	tty->count++;
-	tty->link->count++;
+	slave->count++;
+	tty_unlock(slave);
+
+	vtty_drop_context();
+
 	fd_install(fd, file);
 	vtty_open(tty, file);
 
@@ -1099,6 +1130,8 @@ int vtty_open_master(envid_t veid, int idx)
 out:
 	return ret;
 
+err_unlock:
+	tty_unlock(slave);
 err_install:
 	vtty_drop_context();
 	mutex_unlock(&tty_mutex);

      parent reply	other threads:[~2026-08-10 14:31 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-10  1:39 [Devel] [PATCH vz10 v3] " Eva Kurchatova
2026-08-10  8:26 ` Vasileios Almpanis
2026-08-10 14:31 ` Konstantin Khorenko [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=202608101431.67AEVRoc029985@f0.sw.ru \
    --to=khorenko@virtuozzo.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.