[MDEV-17092] ERROR: AddressSanitizer: use-after-poison around lock_trx_handle_wait_low Created: 2018-08-29 Updated: 2022-08-24 Resolved: 2020-05-25 |
|
| Status: | Closed |
| Project: | MariaDB Server |
| Component/s: | Storage Engine - InnoDB |
| Affects Version/s: | 10.2.18 |
| Fix Version/s: | 10.5.4, 10.2.33, 10.3.24, 10.4.14 |
| Type: | Bug | Priority: | Major |
| Reporter: | Matthias Leich | Assignee: | Marko Mäkelä |
| Resolution: | Fixed | Votes: | 0 |
| Labels: | None | ||
| Environment: |
Ubuntu 17.04 but most probably unimportant. |
||
| Issue Links: |
|
||||||||||||||||||||||||||||||||
| Description |
|
|
| Comments |
| Comment by Marko Mäkelä [ 2018-08-29 ] | ||||||||||||||||||||||||
|
I believe that this is a race condition between innobase_kill_query() and trx_free(). The thread associated with the thd->real_id is idle outside InnoDB, so it must have been freed the transaction object already:
Also, thd_to_trx() would now return NULL to innobase_kill_query() in the core dump:
It seems to me that in 10.2, we should probably acquire trx->mutex and trx_sys->mutex after invoking thd_to_trx(), then recheck thd_to_trx(), and finally acquire lock_sys->mutex. In 10.3, trx_sys->mutex does not exist any more, so we must skip acquiring it. Perhaps we should do something smart about trx->id or other fields instead? trx_free() is not acquiring any mutex in 10.3. Acquiring trx->mutex could trigger use-after-poison, so perhaps we should rewrite the poisoning so that it would always leave the mutex alone?
| ||||||||||||||||||||||||
| Comment by Matthias Leich [ 2018-08-30 ] | ||||||||||||||||||||||||
|
| ||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-05-25 ] | ||||||||||||||||||||||||
|
I debugged this a bit further in a short rr replay trace. Based on a disassembly of the code that called __asan_report_load1(), I found out the address of the ASAN poison bits and set an access watchpoint:
Armed with awatch, I narrowed the problem to the following area:
Here, KILL is invoked slightly before the client disconnects. The innobase_close_connection() would invoke trx_free(), and this would happen before lock_trx_handle_wait_low() dereferences the trx->lock.was_chosen_as_deadlock_victim. I believe that the correct fix is to check somewhere in innobase_kill_query() that the trx actually is registered in the system. This bug is somewhat similar to | ||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-05-25 ] | ||||||||||||||||||||||||
|
I believe that in MariaDB 10.1 and MySQL 5.6 and earlier, this race condition between KILL and client disconnect may cause memory corruption, because free() would be invoked on the trx_t object. A side effect of this fix is that transactions in the XA PREPARE state will be immune to the KILL statement. | ||||||||||||||||||||||||
| Comment by Sergey Vojtovich [ 2020-06-19 ] | ||||||||||||||||||||||||
|
Sad to see 5530a93f4. My comments are based on 10.5. First of all it doesn't really fix the broader problem, but rather silences symptoms.
While going through THE GAP concurrent thread may release (or even free?) trx back to trx_pool and another thread may reuse it. In effect it may break innocent transaction, even worse it may break innocent transaction of another user which it is not permitted to kill. Secondly, trx is found via server_threads/thd->ha_data and not via trx_sys.trx_list. The latter is officially protected by trx_sys.mutex, the former is not. The fact that trx_sys.mutex helps here is pure coincidence and can in no way be considered as meaningful design. Thirdly, the bug is in the server and not in InnoDB. Consider the following... KILL thread:
InnoDB thd->ha_data is reset only upon disconnect, in ha_close_connection(). Relevant pseudo code:
If you look carefully into the above, you can conclude that thd->free_connection() can be called concurrently with KILL/thd->awake(). Which is the bug. And it is partially fixed in THD::~THD(), that is destructor waits for KILL completion:
However this only works when THD is actually destroyed, whereas it must've been called when THD is released to the thread cache. If the above empty critical section would've been moved to thd->free_connection(), the whole 5530a93f4 would not be needed. | ||||||||||||||||||||||||
| Comment by Sergey Vojtovich [ 2020-06-21 ] | ||||||||||||||||||||||||
|
I must revoke my first claim because:
So there seem to be no practical issues with the fix, despite absence of meaningful design: the fact that KILL may not be completed up through victim THD being released to thread cache/stay cached/re-initialised for another connection sounds fragile. | ||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-07-28 ] | ||||||||||||||||||||||||
|
svoj, sorry, I only now noticed your comments. I agree that the homebrew memory pool is a somewhat unfortunate design (which MariaDB inherited from MySQL). We might want to revise it at some point. Maybe trx_t should be a part of the InnoDB data of THD. Maybe removing unnecessary bloat from it would fix any memory allocator overhead. I am not proud of my solution, but I did not want to touch any code outside InnoDB. Do you have a better solution in mind? (Note: | ||||||||||||||||||||||||
| Comment by Sergey Vojtovich [ 2020-07-28 ] | ||||||||||||||||||||||||
|
@marko, yes, there's better solution that I've explained in my previous comment. InnoDB code should stay intact (as if fix for this bug was not pushed) and the real problem should be fixed in the server. I'm concerned (as far as I can be in the current situation) that trx_sys.mutex (or whatever it is called now) is abused for unrelated matters. A healthy code that we've designed back in May (with Eugene IIRC) was supposed to encapsulate trx_sys.trx_list and trx_sys.mutex so that the latter exclusively protects the former. Now this code looks less healthy because it got infected by another sick code. | ||||||||||||||||||||||||
| Comment by Marko Mäkelä [ 2020-08-22 ] | ||||||||||||||||||||||||
|
I filed |