Commit 5530a93f authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-17092 use-after-poison around lock_trx_handle_wait_low

There was a race condition where the connection of the
victim of a KILL statement is disconnected while the
KILL statement is executing.

As a side effect of this fix, we will make XA PREPARE
transactions immune to KILL statements.

Starting with MariaDB 10.2, we have a pool of trx_t objects.
trx_free() would only free memory to the pool. We poison the
contents of freed objects in the pool in order to catch misuse.

trx_free(): Unpoison also trx->mysql_thd and trx->state.
This is to counter the poisoning of *trx in trx_pools->mem_free().
Unpoison only on AddressSanitizer or Valgrind, but not on MemorySanitizer.

Pool: Unpoison allocated objects only on AddressSanitizer or
Valgrind, but not on MemorySanitizer.

innobase_kill_query(): Properly protect trx, acquiring also
trx_sys_t::mutex and checking trx_t::mysql_thd and trx_t::state.
parent e2c74938
...@@ -5201,13 +5201,43 @@ static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels) ...@@ -5201,13 +5201,43 @@ static void innobase_kill_query(handlerton*, THD* thd, enum thd_kill_levels)
} }
#endif /* WITH_WSREP */ #endif /* WITH_WSREP */
if (trx_t* trx = thd_to_trx(thd)) { if (trx_t* trx= thd_to_trx(thd))
ut_ad(trx->mysql_thd == thd); {
/* Cancel a pending lock request if there are any */ lock_mutex_enter();
lock_trx_handle_wait(trx); trx_sys_mutex_enter();
} trx_mutex_enter(trx);
/* It is possible that innobase_close_connection() is concurrently
being executed on our victim. In that case, trx->mysql_thd would
be reset before invoking trx_free(). Even if the trx object is later
reused for another client connection or a background transaction,
its trx->mysql_thd will differ from our thd.
If trx never performed any changes, nothing is really protecting
the trx_free() call or the changes of trx_t::state when the
transaction is being rolled back and trx_commit_low() is being
executed.
The function trx_allocate_for_mysql() acquires
trx_sys_t::mutex, but trx_allocate_for_background() will not.
Luckily, background transactions cannot be read-only, because
for read-only transactions, trx_start_low() will avoid acquiring
any of the trx_sys_t::mutex, lock_sys_t::mutex, trx_t::mutex before
assigning trx_t::state.
At this point, trx may have been reallocated for another client
connection, or for a background operation. In that case, either
trx_t::state or trx_t::mysql_thd should not match our expectations. */
bool cancel= trx->mysql_thd == thd && trx->state == TRX_STATE_ACTIVE &&
!trx->lock.was_chosen_as_deadlock_victim;
trx_sys_mutex_exit();
if (!cancel);
else if (lock_t *lock= trx->lock.wait_lock)
lock_cancel_waiting_and_release(lock);
lock_mutex_exit();
trx_mutex_exit(trx);
}
DBUG_VOID_RETURN; DBUG_VOID_RETURN;
} }
......
/***************************************************************************** /*****************************************************************************
Copyright (c) 2013, 2014, Oracle and/or its affiliates. All Rights Reserved. Copyright (c) 2013, 2014, Oracle and/or its affiliates. All Rights Reserved.
Copyright (c) 2018, 2020, MariaDB Corporation.
This program is free software; you can redistribute it and/or modify it under This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software the terms of the GNU General Public License as published by the Free Software
...@@ -86,11 +87,15 @@ struct Pool { ...@@ -86,11 +87,15 @@ struct Pool {
for (Element* elem = m_start; elem != m_last; ++elem) { for (Element* elem = m_start; elem != m_last; ++elem) {
ut_ad(elem->m_pool == this); ut_ad(elem->m_pool == this);
#ifdef __SANITIZE_ADDRESS__
/* Unpoison the memory for AddressSanitizer */ /* Unpoison the memory for AddressSanitizer */
MEM_UNDEFINED(&elem->m_type, sizeof elem->m_type); MEM_UNDEFINED(&elem->m_type, sizeof elem->m_type);
#endif
#ifdef HAVE_valgrind
/* Declare the contents as initialized for Valgrind; /* Declare the contents as initialized for Valgrind;
we checked this in mem_free(). */ we checked this in mem_free(). */
UNIV_MEM_VALID(&elem->m_type, sizeof elem->m_type); UNIV_MEM_VALID(&elem->m_type, sizeof elem->m_type);
#endif
Factory::destroy(&elem->m_type); Factory::destroy(&elem->m_type);
} }
...@@ -127,13 +132,18 @@ struct Pool { ...@@ -127,13 +132,18 @@ struct Pool {
#if defined HAVE_valgrind || defined __SANITIZE_ADDRESS__ #if defined HAVE_valgrind || defined __SANITIZE_ADDRESS__
if (elem) { if (elem) {
# ifdef __SANITIZE_ADDRESS__
/* Unpoison the memory for AddressSanitizer */ /* Unpoison the memory for AddressSanitizer */
MEM_UNDEFINED(&elem->m_type, sizeof elem->m_type); MEM_UNDEFINED(&elem->m_type, sizeof elem->m_type);
# endif
# ifdef HAVE_valgrind
/* Declare the memory initialized for Valgrind. /* Declare the memory initialized for Valgrind.
The trx_t that are released to the pool are The trx_t that are released to the pool are
actually initialized; we checked that by actually initialized; we checked that by
UNIV_MEM_ASSERT_RW() in mem_free() below. */ UNIV_MEM_ASSERT_RW() in mem_free() below. */
UNIV_MEM_VALID(&elem->m_type, sizeof elem->m_type); UNIV_MEM_VALID(&elem->m_type, sizeof elem->m_type);
# endif
} }
#endif #endif
......
...@@ -408,14 +408,26 @@ trx_free(trx_t*& trx) ...@@ -408,14 +408,26 @@ trx_free(trx_t*& trx)
ut_ad(trx->will_lock == 0); ut_ad(trx->will_lock == 0);
trx_pools->mem_free(trx); trx_pools->mem_free(trx);
#ifdef __SANITIZE_ADDRESS__
/* Unpoison the memory for innodb_monitor_set_option; /* Unpoison the memory for innodb_monitor_set_option;
it is operating also on the freed transaction objects. */ it is operating also on the freed transaction objects. */
MEM_UNDEFINED(&trx->mutex, sizeof trx->mutex); MEM_UNDEFINED(&trx->mutex, sizeof trx->mutex);
MEM_UNDEFINED(&trx->undo_mutex, sizeof trx->undo_mutex); MEM_UNDEFINED(&trx->undo_mutex, sizeof trx->undo_mutex);
/* Declare the contents as initialized for Valgrind; /* For innobase_kill_connection() */
we checked that it was initialized in trx_pools->mem_free(trx). */ MEM_UNDEFINED(&trx->state, sizeof trx->state);
MEM_UNDEFINED(&trx->mysql_thd, sizeof trx->mysql_thd);
#endif
#ifdef HAVE_valgrind
/* Unpoison the memory for innodb_monitor_set_option;
it is operating also on the freed transaction objects.
We checked that these were initialized in
trx_pools->mem_free(trx). */
UNIV_MEM_VALID(&trx->mutex, sizeof trx->mutex); UNIV_MEM_VALID(&trx->mutex, sizeof trx->mutex);
UNIV_MEM_VALID(&trx->undo_mutex, sizeof trx->undo_mutex); UNIV_MEM_VALID(&trx->undo_mutex, sizeof trx->undo_mutex);
/* For innobase_kill_connection() */
UNIV_MEM_VALID(&trx->state, sizeof trx->state);
UNIV_MEM_VALID(&trx->mysql_thd, sizeof trx->mysql_thd);
#endif
trx = NULL; trx = NULL;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment