[Devel] [PATCH vz10] ve/vtty: fix use-after-free on concurrent tty close and reopen

Pavel Tikhomirov ptikhomirov at virtuozzo.com
Mon Jun 29 18:24:24 MSK 2026



On 6/29/26 16:35, Pavel Tikhomirov wrote:
> 
> 
> On 6/28/26 23:30, Eva Kurchatova wrote:
>> Two races in the vtty subsystem lead 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):
>>
>> Race 1: double-final between concurrent vtty master and slave close.
>>
>> In tty_release(), the vttys (slave) side has o_tty == NULL because its
>> driver subtype is PTY_TYPE_SLAVE, so the final-close check depends
>> solely on !slave->count.  When master and slave close concurrently,
>> both sides can independently determine final == true: the slave sees
>> slave->count == 0, the master sees both counts == 0 (after the slave
>> count underflows to -1 and gets reset).  Both then call
>> tty_release_struct -> release_tty, and the second caller hits a
>> use-after-free.
>>
>> Fix this by adding a vtty-specific check after computing final: for
>> vttys closes, also verify that the peer vttym count is not positive.
>> This is safe without holding the vttym lock because the vttym side of
>> tty_release holds tty_lock_slave(vttys) while decrementing vttym->count,
>> so while we hold tty_lock(vttys) the vttym cannot have decremented yet.
>>
>> Race 2: 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.
>>
>> Fix this by detecting a dead pair in vtty_open_master(): if both vttym
>> and vttys counts are zero the pair is dying, so clear the vtty map entry
>> and fall through to create a fresh pair.  The concurrent release_tty
>> will find driver_data == NULL in vtty_shutdown and harmlessly skip the
>> already-cleared map.
>>
>> Fixes: dfe187803cf9 ("ve/vtty: Don't close unread master peer if slave is nonzero")
>> Signed-off-by: Eva Kurchatova <eva.kurchatova at virtuozzo.com>
>>
>> https://virtuozzo.atlassian.net/browse/VSTOR-136511
>> Feature: fix vtty panic
>> ---
>>  drivers/tty/pty.c    | 10 ++++++++++
>>  drivers/tty/tty_io.c | 15 +++++++++++++++
>>  2 files changed, 25 insertions(+)
>>
>> diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c
>> index f8610c77817a..46cb744e85b1 100644
>> --- a/drivers/tty/pty.c
>> +++ b/drivers/tty/pty.c
>> @@ -1062,6 +1062,16 @@ int vtty_open_master(envid_t veid, int idx)
>>  		goto err_install;
>>  	}
>>  
>> +	/*
>> +	 * If both master and slave counts are zero, the pair is dying;
>> +	 * A concurrent tty_release_struct is about to free it.
>> +	 * Clear the map so release_tty's vtty_shutdown sees driver_data == NULL
>> +	 * and skips, then fall through to create a fresh pair.
>> +	 */
>> +	if (tty && tty->count == 0 && tty->link->count == 0) {
>> +		vtty_map_clear(tty->link);
>> +		tty = NULL;
>> +	}
> 
> Would returning error (-EBUSY) here be a simpler fix?
> 
> Making alternative point where one does vtty_map_clear() is a tricky change
> one should make sure that they don't clear new map twice accidentally.
> So this can be tricky to rebase in future.
> 
>>  	if (!tty) {
>>  		tty = tty_init_dev(vttys_driver, idx);
>>  		if (IS_ERR(tty)) {
>> diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c
>> index 797a9d0ddd1e..e9ba53cdfe7f 100644
>> --- a/drivers/tty/tty_io.c
>> +++ b/drivers/tty/tty_io.c
>> @@ -1864,6 +1864,21 @@ int tty_release(struct inode *inode, struct file *filp)
>>  	/* check whether both sides are closing ... */
>>  	final = !tty->count && !(o_tty && o_tty->count);
>>  
>> +#ifdef CONFIG_VE
>> +	/*
>> +	 * vtty: prevent double-final between concurrent master and slave
>> +	 * close.  For vtty slaves o_tty is NULL (PTY_TYPE_SLAVE), so the
>> +	 * standard final check only looks at slave->count.
>> +	 * When the master is closing concurrently, it holds tty_lock_slave(slave)
>> +	 * while decrementing master->count, so while we hold tty_lock(slave),
>> +	 * the master cannot have decremented yet - master->count is still > 0.
>> +	 * If master->count is already 0 here, the master already returned
>> +	 * from tty_release with final = false, therefore we are closing it.
>> +	 */
>> +	if (final && !o_tty && tty->link && !vtty_is_master(tty) && tty->link->count > 0)
>> +		final = 0;

By the way, the check seems to broad, maybe restrict it to vtty driver?
E.g.: "&& tty->driver == vttys_driver"

> 
> Let's consider this order of events:
> 
> vtty_open_master - vtty_open_master
> close - tty_release
> 
> Initial state master=0, slave=1 (no console attached, one getty fd on the slave).
> Precisely the commit's "attach vs getty respawn" case. getty closes its slave fd while vzctl opens the master:
> 
> vtty_open_master  @pty.c:1071      read slave==1, master==0     → "alive", not dying
> tty_release       @tty_io.c:1833   slave-- : 1 → 0
> tty_release       @tty_io.c:1878   read master==0               → Hunk-1 does NOT fire → final = TRUE
> vtty_open_master  @pty.c:1090      EBUSY check: master==0       → passes
> vtty_open_master  @pty.c:1102      master++ : 0 → 1
> vtty_open_master  @pty.c:1103      slave++  : 0 → 1   + fd_install   (hands out fd)
> vtty_open_master  @pty.c:1107      mutex_unlock
> tty_release       @tty_io.c:1894   tty_release_struct → tty_mutex → release_tty → frees the pair
> 
> Looks like use after free. Functions above use completely different locks so the above event order seems plausible.
> 
> p.s. Would not it be easier to add extra count from vtty master to the slave tty, so that slave can only check it's own counter? Proper locking surely still needed.
> 
>> +#endif
>> +
>>  	tty_unlock_slave(o_tty);
>>  	tty_unlock(tty);
>>  
> 

-- 
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.



More information about the Devel mailing list