Commit a26700cc authored by Nayuta Yanagisawa's avatar Nayuta Yanagisawa

MDEV-28352 Spider: heap-use-after-free in ha_spider::lock_tables(), heap freed by spider_commit()

The heap-use-after-free is caused by the following mechanism:

  * In the execution of FLUSH TABLE WITH READ LOCK, the function
    spider_free_trx_conn() is called and the connections held by
    SPIDER_TRX::trx_conn_hash are freed.

  * Then, an instance of ha_spider maintains the freed connections
    because they are also referenced from ha_spider::conns.
    The ha_spider instance is kept in a lock structure until the
    corresponding table is unlocked.

  * Spider accesses ha_spider::conns on the implicit UNLOCK TABLE
    issued by BEGIN.

In the first place, when the connections have been freed, it means
that there are really no remote table locked by Spider.
Thus, there is no need for Spider to access ha_spider::cons on the
implicit UNLOCK TABLE.

We can fix the bug by removing the above mentioned access to
ha_spider::conns. We also modified spider_free_trx_conn() so that it
frees the connections only when no table is locked to reduce the
chance of another heap-use-after-free on ha_spider::conns.
parent 773f1dad
......@@ -1180,10 +1180,8 @@ int ha_spider::external_lock(
backup_error_status();
DBUG_ENTER("ha_spider::external_lock");
DBUG_PRINT("info",("spider this=%p", this));
DBUG_PRINT("info",("spider lock_type=%x", lock_type));
DBUG_PRINT("info", ("spider sql_command=%d", thd_sql_command(thd)));
/* Beginning of wide_handler setup */
if (wide_handler->stage == SPD_HND_STAGE_EXTERNAL_LOCK)
{
/* Only the stage executor deals with table locks. */
......@@ -1194,7 +1192,7 @@ int ha_spider::external_lock(
}
else
{
/* Update the stage executor when the stage changes */
/* Update the stage executor when the stage changes. */
wide_handler->stage= SPD_HND_STAGE_EXTERNAL_LOCK;
wide_handler->stage_executor= this;
}
......@@ -1203,35 +1201,30 @@ int ha_spider::external_lock(
wide_handler->external_lock_type= lock_type;
wide_handler->sql_command = thd_sql_command(thd);
/* We treat BEGIN as if UNLOCK TABLE. */
if (wide_handler->sql_command == SQLCOM_BEGIN)
{
wide_handler->sql_command = SQLCOM_UNLOCK_TABLES;
}
if (lock_type == F_UNLCK &&
wide_handler->sql_command != SQLCOM_UNLOCK_TABLES)
{
DBUG_RETURN(0);
}
trx = spider_get_trx(thd, TRUE, &error_num);
trx= spider_get_trx(thd, TRUE, &error_num);
if (error_num)
{
DBUG_RETURN(error_num);
}
wide_handler->trx = trx;
wide_handler->trx= trx;
/* End of wide_handler setup */
/* Question: Why the following if block is necessary? Why here? */
if (store_error_num)
{
DBUG_RETURN(store_error_num);
}
DBUG_ASSERT(wide_handler->sql_command != SQLCOM_RENAME_TABLE &&
wide_handler->sql_command != SQLCOM_DROP_DB);
/* We treat BEGIN as if UNLOCK TABLE. */
if (wide_handler->sql_command == SQLCOM_BEGIN)
{
wide_handler->sql_command = SQLCOM_UNLOCK_TABLES;
}
const uint sql_command= wide_handler->sql_command;
DBUG_ASSERT(sql_command != SQLCOM_RENAME_TABLE &&
sql_command != SQLCOM_DROP_DB);
if (wide_handler->sql_command == SQLCOM_DROP_TABLE ||
wide_handler->sql_command == SQLCOM_ALTER_TABLE)
if (sql_command == SQLCOM_DROP_TABLE || sql_command == SQLCOM_ALTER_TABLE)
{
if (trx->locked_connections)
{
......@@ -1242,44 +1235,41 @@ int ha_spider::external_lock(
DBUG_RETURN(0);
}
if (lock_type != F_UNLCK)
if (lock_type == F_UNLCK)
{
if (unlikely((error_num= spider_internal_start_trx(this))))
if (sql_command != SQLCOM_UNLOCK_TABLES)
{
DBUG_RETURN(error_num);
DBUG_RETURN(0); /* Unlock remote tables only by UNLOCK TABLES. */
}
if (wide_handler->sql_command != SQLCOM_SELECT &&
wide_handler->sql_command != SQLCOM_HA_READ)
if (!trx->locked_connections)
{
trx->updated_in_this_trx= TRUE;
DBUG_PRINT("info", ("spider trx->updated_in_this_trx=TRUE"));
DBUG_RETURN(0); /* No remote table actually locked by Spider */
}
}
if (wide_handler->lock_table_type > 0 ||
wide_handler->sql_command == SQLCOM_UNLOCK_TABLES)
else
{
if (wide_handler->sql_command == SQLCOM_UNLOCK_TABLES)
if (unlikely((error_num= spider_internal_start_trx(this))))
{
/* lock tables does not call reset() */
/* unlock tables does not call store_lock() */
wide_handler->lock_table_type = 0;
DBUG_RETURN(error_num);
}
/* lock/unlock tables */
if (partition_handler && partition_handler->handlers)
if (sql_command != SQLCOM_SELECT && sql_command != SQLCOM_HA_READ)
{
for (uint roop_count= 0; roop_count < partition_handler->no_parts;
++roop_count)
{
if (unlikely((error_num =
partition_handler->handlers[roop_count]->lock_tables())))
trx->updated_in_this_trx= TRUE;
}
if (!wide_handler->lock_table_type)
{
DBUG_RETURN(error_num);
DBUG_RETURN(0); /* No need to actually lock remote tables. */
}
}
if (!partition_handler || !partition_handler->handlers)
{
DBUG_RETURN(lock_tables()); /* Non-partitioned table */
}
else if (unlikely((error_num= lock_tables())))
for (uint i= 0; i < partition_handler->no_parts; ++i)
{
if (unlikely((error_num= partition_handler->handlers[i]->lock_tables())))
{
DBUG_RETURN(error_num);
}
......
wait_timeout : MDEV-26045
mdev_27239 : failed with ASAN build
......@@ -93,128 +93,51 @@ uchar *spider_trx_ha_get_key(
DBUG_RETURN((uchar*) trx_ha->table_name);
}
int spider_free_trx_conn(
SPIDER_TRX *trx,
bool trx_free
) {
int roop_count;
/*
Try to free the connections held by the given transaction.
*/
int spider_free_trx_conn(SPIDER_TRX *trx, bool trx_free)
{
int loop_count= 0;
SPIDER_CONN *conn;
HASH *conn_hash= &trx->trx_conn_hash;
DBUG_ENTER("spider_free_trx_conn");
roop_count = 0;
if (
trx_free ||
spider_param_conn_recycle_mode(trx->thd) != 2
) {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_conn_hash,
roop_count)))
{
spider_conn_clear_queue_at_commit(conn);
if (conn->table_lock)
{
DBUG_ASSERT(!trx_free);
roop_count++;
} else
spider_free_conn_from_trx(trx, conn, FALSE, trx_free, &roop_count);
}
trx->trx_conn_adjustment++;
} else {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_conn_hash,
roop_count)))
DBUG_ASSERT(!trx_free || !trx->locked_connections);
/* Clear the connection queues in any case. */
while ((conn= (SPIDER_CONN *) my_hash_element(conn_hash, loop_count)))
{
spider_conn_clear_queue_at_commit(conn);
if (conn->table_lock)
{
DBUG_ASSERT(!trx_free);
} else
conn->error_mode = 1;
roop_count++;
}
}
#if defined(HS_HAS_SQLCOM) && defined(HAVE_HANDLERSOCKET)
roop_count = 0;
if (
trx_free ||
spider_param_hs_r_conn_recycle_mode(trx->thd) != 2
) {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_hs_r_conn_hash,
roop_count)))
{
if (conn->table_lock)
{
DBUG_ASSERT(!trx_free);
roop_count++;
} else
spider_free_conn_from_trx(trx, conn, FALSE, trx_free, &roop_count);
loop_count++;
}
trx->trx_hs_r_conn_adjustment++;
} else {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_hs_r_conn_hash,
roop_count)))
{
if (conn->table_lock)
if (trx_free || spider_param_conn_recycle_mode(trx->thd) != 2)
{
DBUG_ASSERT(!trx_free);
} else
conn->error_mode = 1;
roop_count++;
}
}
roop_count = 0;
if (
trx_free ||
spider_param_hs_w_conn_recycle_mode(trx->thd) != 2
) {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_hs_w_conn_hash,
roop_count)))
/* Free connections only when no connection is locked. */
if (!trx->locked_connections)
{
if (conn->table_lock)
loop_count= 0;
while ((conn= (SPIDER_CONN *) my_hash_element(conn_hash, loop_count)))
{
DBUG_ASSERT(!trx_free);
roop_count++;
} else
spider_free_conn_from_trx(trx, conn, FALSE, trx_free, &roop_count);
spider_free_conn_from_trx(trx, conn, FALSE, trx_free, &loop_count);
}
trx->trx_hs_w_conn_adjustment++;
} else {
while ((conn = (SPIDER_CONN*) my_hash_element(&trx->trx_hs_w_conn_hash,
roop_count)))
{
if (conn->table_lock)
{
DBUG_ASSERT(!trx_free);
} else
conn->error_mode = 1;
roop_count++;
}
trx->trx_conn_adjustment++;
DBUG_RETURN(0);
}
#endif
#if defined(HS_HAS_SQLCOM) && defined(HAVE_HANDLERSOCKET)
if (trx_free)
{
while ((conn = (SPIDER_CONN*) my_hash_element(
&trx->trx_direct_hs_r_conn_hash, 0)))
loop_count= 0;
while ((conn= (SPIDER_CONN *) my_hash_element(conn_hash, loop_count)))
{
#ifdef HASH_UPDATE_WITH_HASH_VALUE
my_hash_delete_with_hash_value(&trx->trx_direct_hs_r_conn_hash,
conn->conn_key_hash_value, (uchar*) conn);
#else
my_hash_delete(&trx->trx_direct_hs_r_conn_hash, (uchar*) conn);
#endif
spider_free_conn(conn);
}
while ((conn = (SPIDER_CONN*) my_hash_element(
&trx->trx_direct_hs_w_conn_hash, 0)))
if (!conn->table_lock)
{
#ifdef HASH_UPDATE_WITH_HASH_VALUE
my_hash_delete_with_hash_value(&trx->trx_direct_hs_w_conn_hash,
conn->conn_key_hash_value, (uchar*) conn);
#else
my_hash_delete(&trx->trx_direct_hs_w_conn_hash, (uchar*) conn);
#endif
spider_free_conn(conn);
conn->error_mode= 1;
}
loop_count++;
}
#endif
DBUG_RETURN(0);
}
......@@ -3417,6 +3340,12 @@ int spider_commit(
trx->bulk_access_conn_first = NULL;
#endif
/*
We do (almost) nothing if the following two conditions are both met:
* This is just the end of a statement, not an explicit commit.
* The autocommit is OFF or we are in an explicit transaction.
*/
if (all || (!thd_test_options(thd, OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)))
{
if (trx->trx_start)
......
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