Skip to content

Fix potential 'use-after-free' by Admin 'SHOW PROCESSLIST'#5180

Merged
renecannao merged 2 commits intov3.0from
v3.0-fix_use_after_free_in_proclist
Oct 24, 2025
Merged

Fix potential 'use-after-free' by Admin 'SHOW PROCESSLIST'#5180
renecannao merged 2 commits intov3.0from
v3.0-fix_use_after_free_in_proclist

Conversation

@JavierJF
Copy link
Collaborator

@JavierJF JavierJF commented Oct 23, 2025

Issue

The following use-after-free is very simple to reproduce:

  1. Start a test performing SET statements or any other query that is internally re-writed.
  2. In parallel, issue SHOW PROCESSLIST to the Admin interface.

The following 'use-after-free' is likely to take place:

==2023703==ERROR: AddressSanitizer: heap-use-after-free on address 0x7be6e8c4b880 at pc 0x7fb6ea11b078 bp 0x7bb6bf234590 sp 0x7bb6bf233d38
READ of size 3 at 0x7be6e8c4b880 thread T48
    #0 0x7fb6ea11b077 in strncpy /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:627
    #1 0x55f68a014e40 in MySQL_Session::get_current_query(int) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:8151
    #2 0x55f689f722c8 in MySQL_Threads_Handler::SQL3_Processlist(processlist_config_t) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:5028
    #3 0x55f68a4c7035 in ProxySQL_Admin::stats___mysql_processlist() /home/javjarfer/Projects/proxysql_dev/lib/ProxySQL_Admin_Stats.cpp:786
    #4 0x55f68a0c775d in ProxySQL_Admin::GenericRefreshStatistics(char const*, unsigned int, bool) /home/javjarfer/Projects/proxysql_dev/lib/ProxySQL_Admin.cpp:1470
    #5 0x55f68a53e0e3 in void admin_session_handler<MySQL_Session>(MySQL_Session*, void*, _PtrSize_t*) /home/javjarfer/Projects/proxysql_dev/lib/Admin_Handler.cpp:2812
    #6 0x55f689fd957b in MySQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_QUERY___not_mysql(_PtrSize_t&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:3543
    #7 0x55f689fe1695 in MySQL_Session::get_pkts_from_client(bool&, _PtrSize_t&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:4338
    #8 0x55f689fe8f3a in MySQL_Session::handler() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:4952
    #9 0x55f68a0cbc5f in child_mysql(void*) /home/javjarfer/Projects/proxysql_dev/lib/ProxySQL_Admin.cpp:2046
    #10 0x7fb6ea05e11a in asan_thread_start /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:239
    #11 0x7fb6e8e969ca  (/usr/lib/libc.so.6+0x969ca) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)

0x7be6e8c4b880 is located 0 bytes inside of 26-byte region [0x7be6e8c4b880,0x7be6e8c4b89a)
freed by thread T4 here:
    #0 0x7fb6ea11f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51
    #1 0x55f689fcbced in MySQL_Session::handler_again___status_SETTING_GENERIC_VARIABLE(int*, char const*, char const*, bool, bool) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:2565
    #2 0x55f68a42c71b in update_server_variable(MySQL_Session*, int, int&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Variables.cpp:487
    #3 0x55f68a42a2de in MySQL_Variables::update_variable(MySQL_Session*, session_status, int&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Variables.cpp:313
    #4 0x55f689fec0f2 in MySQL_Session::handler() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:5295
    #5 0x55f689f65e5b in MySQL_Thread::process_all_sessions() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:4028
    #6 0x55f689f5f8d4 in MySQL_Thread::run() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:3388
    #7 0x55f689cce72c in mysql_worker_thread_func(void*) /home/javjarfer/Projects/proxysql_dev/src/main.cpp:524
    #8 0x7fb6ea05e11a in asan_thread_start /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:239
    #9 0x7fb6e8e969ca  (/usr/lib/libc.so.6+0x969ca) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)

previously allocated by thread T4 here:
    #0 0x7fb6ea120cb5 in malloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:67
    #1 0x55f689fcb7b4 in MySQL_Session::handler_again___status_SETTING_GENERIC_VARIABLE(int*, char const*, char const*, bool, bool) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:2533
    #2 0x55f68a42c71b in update_server_variable(MySQL_Session*, int, int&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Variables.cpp:487
    #3 0x55f68a42a2de in MySQL_Variables::update_variable(MySQL_Session*, session_status, int&) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Variables.cpp:313
    #4 0x55f689fec0f2 in MySQL_Session::handler() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:5295
    #5 0x55f689f65e5b in MySQL_Thread::process_all_sessions() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:4028
    #6 0x55f689f5f8d4 in MySQL_Thread::run() /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:3388
    #7 0x55f689cce72c in mysql_worker_thread_func(void*) /home/javjarfer/Projects/proxysql_dev/src/main.cpp:524
    #8 0x7fb6ea05e11a in asan_thread_start /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:239
    #9 0x7fb6e8e969ca  (/usr/lib/libc.so.6+0x969ca) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)

Thread T48 created by T3 here:
    #0 0x7fb6ea11732c in pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:250
    #1 0x55f68a0ce000 in admin_main_loop(void*) /home/javjarfer/Projects/proxysql_dev/lib/ProxySQL_Admin.cpp:2290
    #2 0x7fb6ea05e11a in asan_thread_start /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:239
    #3 0x7fb6e8e969ca  (/usr/lib/libc.so.6+0x969ca) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)

Thread T3 created by T0 here:
    #0 0x7fb6ea11732c in pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:250
    #1 0x55f68a5ba512 in ProxySQL_Admin::init(bootstrap_info_t const&) /home/javjarfer/Projects/proxysql_dev/lib/Admin_Bootstrap.cpp:1060
    #2 0x55f689cd2bfb in ProxySQL_Main_init_Admin_module(bootstrap_info_t const&) /home/javjarfer/Projects/proxysql_dev/src/main.cpp:944
    #3 0x55f689cd68f4 in ProxySQL_Main_init_phase2___not_started(bootstrap_info_t const&) /home/javjarfer/Projects/proxysql_dev/src/main.cpp:1427
    #4 0x55f689ce6ee3 in main /home/javjarfer/Projects/proxysql_dev/src/main.cpp:2993
    #5 0x7fb6e8e27674  (/usr/lib/libc.so.6+0x27674) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
    #6 0x7fb6e8e27728 in __libc_start_main (/usr/lib/libc.so.6+0x27728) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
    #7 0x55f689ccc464 in _start (/home/javjarfer/Projects/proxysql_dev/src/proxysql+0x802464) (BuildId: 8de9e0b8d88c67d3575c76282cfc7693c0c2601a)

Thread T4 created by T0 here:
    #0 0x7fb6ea11732c in pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:250
    #1 0x55f689f5624f in MySQL_Threads_Handler::create_thread(unsigned int, void* (*)(void*), bool) /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Thread.cpp:2447
    #2 0x55f689cd37f5 in ProxySQL_Main_init_MySQL_Threads_Handler_module() /home/javjarfer/Projects/proxysql_dev/src/main.cpp:992
    #3 0x55f689cd7232 in ProxySQL_Main_init_phase3___start_all() /home/javjarfer/Projects/proxysql_dev/src/main.cpp:1503
    #4 0x55f689ce6f94 in main /home/javjarfer/Projects/proxysql_dev/src/main.cpp:3004
    #5 0x7fb6e8e27674  (/usr/lib/libc.so.6+0x27674) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
    #6 0x7fb6e8e27728 in __libc_start_main (/usr/lib/libc.so.6+0x27728) (BuildId: 4fe011c94a88e8aeb6f2201b9eb369f42b4a1e9e)
    #7 0x55f689ccc464 in _start (/home/javjarfer/Projects/proxysql_dev/src/proxysql+0x802464) (BuildId: 8de9e0b8d88c67d3575c76282cfc7693c0c2601a)

SUMMARY: AddressSanitizer: heap-use-after-free /home/javjarfer/Projects/proxysql_dev/lib/MySQL_Session.cpp:8151 in MySQL_Session::get_current_query(int)
Shadow bytes around the buggy address:
  0x7be6e8c4b600: fa fa fd fd fd fa fa fa fd fd fd fa fa fa fd fd
  0x7be6e8c4b680: fd fa fa fa fd fd fd fd fa fa fd fd fd fd fa fa
  0x7be6e8c4b700: fd fd fd fa fa fa fd fd fd fd fa fa fd fd fd fa
  0x7be6e8c4b780: fa fa fd fd fd fd fa fa fd fd fd fd fa fa fa fa
  0x7be6e8c4b800: fa fa fa fa fd fd fd fd fa fa fd fd fd fa fa fa
=>0x7be6e8c4b880:[fd]fd fd fd fa fa fd fd fd fa fa fa fa fa fa fa
  0x7be6e8c4b900: fa fa fd fd fd fd fa fa fd fd fd fa fa fa fd fd
  0x7be6e8c4b980: fd fd fa fa fd fd fd fa fa fa fa fa fa fa fa fa
  0x7be6e8c4ba00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7be6e8c4ba80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7be6e8c4bb00: fa fa fa fa fd fd fd fa fa fa fd fd fd fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

Solution

Instead of using the query present in myconn, which could be allocated and freed from Session in case of rewrites. We use the query present in Session (CurrentQuery), which scope is well defined, and shouldn't point to invalid memory.

A small compilation warning was also fixed.

Accesses by 'stats___mysql_processlist' to 'myconn->query.ptr' could
lead to invalid memory accesses, as the pointed query could already have
been free by the session after being issued.
As per the GCC manual, calls to 'strn*' function family shall be
replaced by 'memcpy' when truncation (no null termination) is expected.
@sonarqubecloud
Copy link

@gemini-code-assist
Copy link

Summary of Changes

Hello @JavierJF, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves a critical use-after-free vulnerability in ProxySQL that could be triggered by concurrent SHOW PROCESSLIST queries and query rewrites. By switching to a more reliable source for the current query string, this change ensures memory safety and prevents potential crashes or undefined behavior.

Highlights

  • Use-After-Free Vulnerability: This PR addresses a potential use-after-free vulnerability that could occur when running SHOW PROCESSLIST in the Admin interface while other queries are being rewritten.
  • Root Cause: The vulnerability arises from using the query string in myconn, which can be freed by the Session during query rewrites, leading to a dangling pointer.
  • Solution: The fix replaces the usage of myconn->query.ptr with CurrentQuery.QueryPointer, which has a well-defined scope and prevents access to freed memory.
  • Code Changes: The code changes involve modifying the MySQL_Session::get_current_query function to use the CurrentQuery.QueryPointer and CurrentQuery.QueryLength instead of the potentially freed myconn->query.
  • String Copying: The code changes involve using memcpy instead of strncpy when truncating queries.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a critical use-after-free vulnerability that occurs during SHOW PROCESSLIST on the admin interface when a query is being rewritten. By switching from the potentially stale myconn->query.ptr to the session-scoped CurrentQuery.QueryPointer, the race condition is resolved. The change from strncpy to memcpy is also a good improvement for safer memory handling. I've suggested one minor change for consistency.

memcpy(res, query_ptr, query_len - 3);
memcpy(res + (query_len - 3), "...", 3);
} else {
strncpy(res, query_ptr, query_len);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with the changes in the if (trunc_query) block, it would be better to use memcpy here as well instead of strncpy. While the current code is safe due to the manual null-termination on line 8153, using memcpy consistently makes the intent clearer (copying a known number of bytes) and is generally safer, reducing reliance on the specifics of strncpy's behavior.

memcpy(res, query_ptr, query_len);

@JavierJF JavierJF requested a review from renecannao October 23, 2025 15:15
@renecannao
Copy link
Contributor

Can one of the admins verify this patch?

wazir-ahmed
wazir-ahmed previously approved these changes Oct 23, 2025
Copy link
Member

@wazir-ahmed wazir-ahmed left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JavierJF We have to fix PgSQL_Session::get_current_query() too.

@wazir-ahmed wazir-ahmed dismissed their stale review October 23, 2025 16:24

@JavierJF We have to fix PgSQL_Session::get_current_query() too.

@renecannao renecannao merged commit 4f8c599 into v3.0 Oct 24, 2025
4 of 5 checks passed
@JavierJF
Copy link
Collaborator Author

JavierJF commented Oct 24, 2025

@wazir-ahmed thanks for the heads-up, your comment is addressed in #5182. Maybe we could reduce duplication in between sessions, but that looks like a future rework.

@renecannao renecannao deleted the v3.0-fix_use_after_free_in_proclist branch March 7, 2026 20:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants