| Bug #83456 | Race condition in mysql_set_character_set, ASAN failure with multi-source rpl | ||
|---|---|---|---|
| Submitted: | 20 Oct 2016 10:38 | Modified: | 24 May 2017 9:54 |
| Reporter: | Sven Sandberg | Email Updates: | |
| Status: | Closed | Impact on me: | |
| Category: | MySQL Server: Charsets | Severity: | S3 (Non-critical) |
| Version: | 5.7 | OS: | Any |
| Assigned to: | CPU Architecture: | Any | |
[20 Oct 2016 10:41]
Sven Sandberg
Posted by developer: Note: the non-thread-safeness has probably been there since forever, but it probably only manifests itself in 5.7 because we introduced multi-source replication which will call these functions from different threads at about the same time. I don't think the server would use different MYSQL objects concurrently before that. Therefore I set the version to 5.7.
[20 Oct 2016 12:06]
Sven Sandberg
Posted by developer: Last line of Description should be: 4. T2 executes 1.2, where strdup will read freed memory. (Thanks Daogang for spotting that.)
[24 May 2017 9:54]
Erlend Dahl
Fixed in 8.0.2.

Description: mysql_set_character_set and mysql_set_character_set_with_default_collation (both defined in client.cc) will temporarily change the global variable charsets_dir. This makes them non-thread-safe. In particular, this can cause access to freed memory in the replication receiver thread (aka IO thread) when using multi-source replication. In multi-source replication, two such threads are likely to start executing at nearly the same time. Then both threads can do the following at about the same time: 1. From connect_to_master: 1.1. call mysql_options(mysql, MYSQL_SET_CHARSET_DIR, (char *) charsets_dir), which will 1.2. set mysql->options.charset_dir to strdup(charsets_dir). 2. From connect_to_master: 2.1. call mysql_reconnect, which will call 2.2. mysql_set_character_set, which will 2.3. temporarily change the global charsets_dir to mysql->options.charset_dir, then 2.4. do some work, and finally 2.5. change back charsets_dir to its previous value 3. From handle_slave_io, call mysql_close, which calls mysql_close_free_options, which frees mysql->options.charset_dir. Since no locks are protecting the global charsets_dir, the following can happen: 1. T1 executes 1, 2.1, 2.2, 2.3, leaving the global charsets_dir set to the T1's mysql->options.charset_dir. 2. T2 executes 1.1 (thus passing T1's mysql->options.charset_dir as the third argument to mysql_set_options) 3. T1 continues to execute 2.4, 2.5, 3 (restoring the global charsets_dir and freeing T1's mysql->options.charset_dir) 4. T2 executes 2.2, where strdup will read freed memory. How to repeat: This would explain the failure of rpl.rpl_multi_source_slave_files at http://pb2.no.oracle.com/?action=archive_download&archive_id=20851995&pretty=please (The reason that the memory access in AddressSanitizer output is 32 bytes into the freed memory block is that my_malloc (in my_malloc.c) adds a 32 byte header.) Suggested fix: Don't modify charsets_dir. Instead, pass the string you need as parameter to whatever function it is used in.