Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-34205

ASAN stack-buffer-overflow in strxnmov | frm_file_exists

Details

    Description

      DROP TABLE `##################################################_long`.`#################################################_long`;
      

      Leads to

      10.5.25 33e4fbf04578c40c02306b65083c6d9a90ca8b2b (Optimized, UBASAN)

      ==3483292==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x14ecb1bd9dc0 at pc 0x55bb2294c593 bp 0x14ecb1bd8f70 sp 0x14ecb1bd8f60
      WRITE of size 1 at 0x14ecb1bd9dc0 thread T30
          #0 0x55bb2294c592 in strxnmov /test/10.5_opt_san/strings/strxnmov.c:71
          #1 0x55bb21487ac6 in frm_file_exists /test/10.5_opt_san/storage/innobase/handler/ha_innodb.cc:13316
          #2 0x55bb21487ac6 in ha_innobase::delete_table(char const*, enum_sql_command) /test/10.5_opt_san/storage/innobase/handler/ha_innodb.cc:13416
          #3 0x55bb1fcf98ff in hton_drop_table /test/10.5_opt_san/sql/handler.cc:561
          #4 0x55bb1fd28bc8 in ha_delete_table(THD*, handlerton*, char const*, st_mysql_const_lex_string const*, st_mysql_const_lex_string const*, bool) /test/10.5_opt_san/sql/handler.cc:2856
          #5 0x55bb1fd2a065 in delete_table_force /test/10.5_opt_san/sql/handler.cc:5171
          #6 0x55bb1e9f6d80 in plugin_foreach_with_mask(THD*, char (*)(THD*, st_plugin_int*, void*), int, unsigned int, void*) /test/10.5_opt_san/sql/sql_plugin.cc:2553
          #7 0x55bb1fd204b3 in ha_delete_table_force(THD*, char const*, st_mysql_const_lex_string const*, st_mysql_const_lex_string const*) /test/10.5_opt_san/sql/handler.cc:5217
          #8 0x55bb1ee78e58 in mysql_rm_table_no_locks(THD*, TABLE_LIST*, bool, bool, bool, bool, bool, bool) /test/10.5_opt_san/sql/sql_table.cc:2601
          #9 0x55bb1ee7e915 in mysql_rm_table(THD*, TABLE_LIST*, bool, bool, bool, bool) /test/10.5_opt_san/sql/sql_table.cc:2164
          #10 0x55bb1e9bc342 in mysql_execute_command(THD*) /test/10.5_opt_san/sql/sql_parse.cc:5072
          #11 0x55bb1e93a8cd in mysql_parse(THD*, char*, unsigned int, Parser_state*, bool, bool) /test/10.5_opt_san/sql/sql_parse.cc:8203
          #12 0x55bb1e999b87 in dispatch_command(enum_server_command, THD*, char*, unsigned int, bool, bool) /test/10.5_opt_san/sql/sql_parse.cc:1891
          #13 0x55bb1e9a5cd2 in do_command(THD*) /test/10.5_opt_san/sql/sql_parse.cc:1375
          #14 0x55bb1f1c9c28 in do_handle_one_connection(CONNECT*, bool) /test/10.5_opt_san/sql/sql_connect.cc:1415
          #15 0x55bb1f1cc29c in handle_one_connection /test/10.5_opt_san/sql/sql_connect.cc:1317
          #16 0x14ecd8c2b608 in start_thread /build/glibc-SzIz7B/glibc-2.31/nptl/pthread_create.c:477
          #17 0x14ecd7ea0132 in __clone (/lib/x86_64-linux-gnu/libc.so.6+0x11f132)
      [..]
      

      Bug confirmed present in:
      MariaDB: 10.5.25 (dbg), 10.5.25 (opt)

      Bug (or feature/syntax) confirmed not present in:
      MariaDB: 10.6.18 (opt), 10.11.8 (opt), 11.0.6 (opt), 11.1.5 (opt), 11.2.4 (opt), 11.3.3 (opt), 11.4.2 (opt), 11.5.0 (opt), 10.6.18 (dbg), 10.11.8 (dbg), 11.0.6 (dbg), 11.1.5 (dbg), 11.2.4 (dbg), 11.3.3 (dbg), 11.4.2 (dbg), 11.5.0 (dbg)

      Attachments

        Issue Links

          Activity

            The following code was added in MDEV-11412:

            static bool frm_file_exists(const char *path)
            {
              char buff[FN_REFLEN];
              strxnmov(buff, FN_REFLEN, path, reg_ext, NullS);
              return !access(buff, F_OK);
            }
            

            Based on the ASAN report, it looks like we are invoking strnxmov incorrectly, or there is a bug in strnxmov that causes it to exceed the specified size FN_REFLEN (512 bytes). As far as I can tell, the maximum possible name length here is something like 645 bytes (including a path component separator character FN_LIBCHAR and the reg_ext suffix .frm). If there is some ./ prepended, the name could be even longer. We also need to reserve one byte for the terminating NUL character.

            If strxnmov() itself did not cause a buffer overflow, the call would still have to be fixed to detect that an overflow would have occurred, to avoid invoking access on a truncated string of 512 characters that is not NUL terminated. Something like this, using a standard library function:

              char buff[FN_REFLEN];
              return snprintf(buff, sizeof buff, "%s%s", path, reg_ext) < sizeof buff &&
                !access(buff, F_OK);
            

            That is, if the file name length exceeds 511 bytes, we assume that the file does not exist. Linux for example limits the path component length to 255 bytes, so 512 bytes are just enough for databasename/filename.frm and a terminating NUL character.

            marko Marko Mäkelä added a comment - The following code was added in MDEV-11412 : static bool frm_file_exists( const char *path) { char buff[FN_REFLEN]; strxnmov(buff, FN_REFLEN, path, reg_ext, NullS); return !access(buff, F_OK); } Based on the ASAN report, it looks like we are invoking strnxmov incorrectly, or there is a bug in strnxmov that causes it to exceed the specified size FN_REFLEN (512 bytes). As far as I can tell, the maximum possible name length here is something like 645 bytes (including a path component separator character FN_LIBCHAR and the reg_ext suffix .frm ). If there is some ./ prepended, the name could be even longer. We also need to reserve one byte for the terminating NUL character. If strxnmov() itself did not cause a buffer overflow, the call would still have to be fixed to detect that an overflow would have occurred, to avoid invoking access on a truncated string of 512 characters that is not NUL terminated. Something like this, using a standard library function: char buff[FN_REFLEN]; return snprintf(buff, sizeof buff, "%s%s" , path, reg_ext) < sizeof buff && !access(buff, F_OK); That is, if the file name length exceeds 511 bytes, we assume that the file does not exist. Linux for example limits the path component length to 255 bytes, so 512 bytes are just enough for databasename/filename.frm and a terminating NUL character.

            On Windows, debug compilation, it also crashes, thanks to runtime checks (/RTC1), which is on by default in debug compile

            Error:Run-Time Check Failure #2 - Stack around the variable 'buff' was corrupted. At :0
            240521 14:50:53 [ERROR] mysqld got exception 0x80000003 ;
            Sorry, we probably made a mistake, and this is a bug.
             
            Your assistance in bug reporting will enable us to fix this for the next release.
            To report this bug, see https://mariadb.com/kb/en/reporting-bugs
             
            We will try our best to scrape up some info that will hopefully help
            diagnose the problem, but since we have already crashed,
            something is definitely wrong and this may fail.
             
            Server version: 10.5.26-MariaDB-debug-log source revision: 310fd6ff695541247db766baf3ade1e3b4db16e9
            key_buffer_size=1048576
            read_buffer_size=131072
            max_used_connections=3
            max_threads=65537
            thread_count=1
            It is possible that mysqld could use up to
            key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 25170481 K  bytes of memory
            Hope that's ok; if not, decrease some variables in the equation.
             
            Thread pointer: 0x2c9a6e58f28
            Attempting backtrace. You can use the following information to find out
            where mysqld died. If you see no messages after this, something went
            terribly wrong...
            server.dll!handle_rtc_failure()[my_init.c:303]
            server.dll!failwithmessage()[error.cpp:213]
            server.dll!_RTC_StackFailure()[error.cpp:263]
            server.dll!_RTC_CheckStackVars()[stack.cpp:69]
            server.dll!frm_file_exists()[ha_innodb.cc:13318]
            server.dll!ha_innobase::delete_table()[ha_innodb.cc:13415]
            server.dll!ha_innobase::delete_table()[ha_innodb.cc:13515]
            server.dll!hton_drop_table()[handler.cc:568]
            server.dll!ha_delete_table()[handler.cc:2863]
            server.dll!delete_table_force()[handler.cc:5178]
            server.dll!plugin_foreach_with_mask()[sql_plugin.cc:2554]
            server.dll!ha_delete_table_force()[handler.cc:5224]
            server.dll!mysql_rm_table_no_locks()[sql_table.cc:2601]
            server.dll!mysql_rm_table()[sql_table.cc:2164]
            server.dll!mysql_execute_command()[sql_parse.cc:5073]
            server.dll!mysql_parse()[sql_parse.cc:8204]
            server.dll!dispatch_command()[sql_parse.cc:1895]
            server.dll!do_command()[sql_parse.cc:1376]
            server.dll!threadpool_process_request()[threadpool_common.cc:365]
            server.dll!tp_callback()[threadpool_common.cc:199]
            server.dll!tp_callback()[threadpool_win.cc:343]
            server.dll!work_callback()[threadpool_win.cc:394]
            ntdll.dll!RtlHashUnicodeString()
            ntdll.dll!RtlClearThreadWorkOnBehalfTicket()
            KERNEL32.DLL!BaseThreadInitThunk()
            ntdll.dll!RtlUserThreadStart()
             
            Trying to get some variables.
            Some pointers may be invalid and cause the dump to abort.
            Query (0x2c9a6e624f0): DROP TABLE `##################################################_long`.`#################################################_long`
            Connection ID (thread ID): 4
            Status: NOT_KILLED
             
            Optimizer switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_pushdown=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materialization=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bka=on,optimize_join_buffer_size=on,table_elimination=on,extended_keys=on,exists_to_in=on,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on,condition_pushdown_for_subquery=on,rowid_filter=on,condition_pushdown_from_having=on,not_null_range_scan=off
             
            The manual page at https://mariadb.com/kb/en/how-to-produce-a-full-stack-trace-for-mariadbd/ contains
            information that should help you find out what is causing the crash.
            Writing a core file at E:\10.5\xxx\mysql-test\var\mysqld.1\data
            Minidump written to E:\10.5\xxx\mysql-test\var\mysqld.1\data\mariadbd.dmp
            ----------SERVER LOG END-------------
             
             
             - found 'mariadbd.dmp' (0/5)
             
            Trying 'cdb' to get a backtrace
            Output from cdb follows. Faulting thread is printed twice,with and without function parameters
            Search for STACK_TEXT to see the stack trace of
            the faulting thread. Callstacks of other threads are printed after it.
             
            Microsoft (R) Windows Debugger Version 10.0.19041.685 AMD64
            Copyright (c) Microsoft Corporation. All rights reserved.
             
             
            Loading Dump File [E:\10.5\xxx\mysql-test\var\log\main.drop\mysqld.1\data\mariadbd.dmp]
            User Mini Dump File: Only registers, stack and portions of memory are available
             
             
            Response                         Time (ms)     Location
            OK                                             C:\Windows\System32
            OK                                             E:\10.5\xxx\sql\Debug
            OK                                             .
             
            Response                         Time (ms)     Location
            OK                                             C:\Windows\System32
            OK                                             E:\10.5\xxx\sql\Debug
            OK                                             .
            Deferred                                       srv*C:\symbols*https://msdl.microsoft.com/download/symbols
            Symbol search path is: C:\Windows\System32;E:\10.5\xxx\sql\Debug;.;srv*C:\symbols*https://msdl.microsoft.com/download/symbols
            Executable search path is: C:\Windows\System32;E:\10.5\xxx\sql\Debug;.
            Windows 10 Version 22631 MP (16 procs) Free x64
            Product: WinNt, suite: SingleUserTS
            22621.1.amd64fre.ni_release.220506-1250
            Machine Name:
            Debug session time: Tue May 21 14:50:54.000 2024 (UTC + 2:00)
            System Uptime: 6 days 13:50:42.180
            Process Uptime: 0 days 0:00:01.000
            ....................................
            This dump file has a breakpoint exception stored in it.
            The stored exception information can be accessed via .ecxr.
            For analysis of this file, run !analyze -v
            ntdll!NtGetContextThread:
                         ret
            0:016> cdb: Reading initial command '!sym prompts off; !analyze -v; .ecxr; !for_each_frame dv /t;!uniqstack -p;q'
            quiet mode - symbol prompts off
             
             
            KEY_VALUES_STRING: 1
             
                Key  : Analysis.CPU.Sec
                Value: 0
             
                Key  : Analysis.DebugAnalysisProvider.CPP
                Value: Create: 8007007e on DESKTOP
             
                Key  : Analysis.DebugData
                Value: CreateObject
             
                Key  : Analysis.DebugModel
                Value: CreateObject
             
                Key  : Analysis.Elapsed.Sec
                Value: 0
             
                Key  : Analysis.Memory.CommitPeak.Mb
                Value: 240
             
                Key  : Analysis.System
                Value: CreateObject
             
                Key  : Timeline.OS.Boot.DeltaSec
                Value: 568242
            

            wlad Vladislav Vaintroub added a comment - On Windows, debug compilation, it also crashes, thanks to runtime checks (/RTC1), which is on by default in debug compile Error:Run-Time Check Failure #2 - Stack around the variable 'buff' was corrupted. At :0 240521 14:50:53 [ERROR] mysqld got exception 0x80000003 ; Sorry, we probably made a mistake, and this is a bug.   Your assistance in bug reporting will enable us to fix this for the next release. To report this bug, see https://mariadb.com/kb/en/reporting-bugs   We will try our best to scrape up some info that will hopefully help diagnose the problem, but since we have already crashed, something is definitely wrong and this may fail.   Server version: 10.5.26-MariaDB-debug-log source revision: 310fd6ff695541247db766baf3ade1e3b4db16e9 key_buffer_size=1048576 read_buffer_size=131072 max_used_connections=3 max_threads=65537 thread_count=1 It is possible that mysqld could use up to key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 25170481 K bytes of memory Hope that's ok; if not, decrease some variables in the equation.   Thread pointer: 0x2c9a6e58f28 Attempting backtrace. You can use the following information to find out where mysqld died. If you see no messages after this, something went terribly wrong... server.dll!handle_rtc_failure()[my_init.c:303] server.dll!failwithmessage()[error.cpp:213] server.dll!_RTC_StackFailure()[error.cpp:263] server.dll!_RTC_CheckStackVars()[stack.cpp:69] server.dll!frm_file_exists()[ha_innodb.cc:13318] server.dll!ha_innobase::delete_table()[ha_innodb.cc:13415] server.dll!ha_innobase::delete_table()[ha_innodb.cc:13515] server.dll!hton_drop_table()[handler.cc:568] server.dll!ha_delete_table()[handler.cc:2863] server.dll!delete_table_force()[handler.cc:5178] server.dll!plugin_foreach_with_mask()[sql_plugin.cc:2554] server.dll!ha_delete_table_force()[handler.cc:5224] server.dll!mysql_rm_table_no_locks()[sql_table.cc:2601] server.dll!mysql_rm_table()[sql_table.cc:2164] server.dll!mysql_execute_command()[sql_parse.cc:5073] server.dll!mysql_parse()[sql_parse.cc:8204] server.dll!dispatch_command()[sql_parse.cc:1895] server.dll!do_command()[sql_parse.cc:1376] server.dll!threadpool_process_request()[threadpool_common.cc:365] server.dll!tp_callback()[threadpool_common.cc:199] server.dll!tp_callback()[threadpool_win.cc:343] server.dll!work_callback()[threadpool_win.cc:394] ntdll.dll!RtlHashUnicodeString() ntdll.dll!RtlClearThreadWorkOnBehalfTicket() KERNEL32.DLL!BaseThreadInitThunk() ntdll.dll!RtlUserThreadStart()   Trying to get some variables. Some pointers may be invalid and cause the dump to abort. Query (0x2c9a6e624f0): DROP TABLE `##################################################_long`.`#################################################_long` Connection ID (thread ID): 4 Status: NOT_KILLED   Optimizer switch: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,index_merge_sort_intersection=off,engine_condition_pushdown=off,index_condition_pushdown=on,derived_merge=on,derived_with_keys=on,firstmatch=on,loosescan=on,materialization=on,in_to_exists=on,semijoin=on,partial_match_rowid_merge=on,partial_match_table_scan=on,subquery_cache=on,mrr=off,mrr_cost_based=off,mrr_sort_keys=off,outer_join_with_cache=on,semijoin_with_cache=on,join_cache_incremental=on,join_cache_hashed=on,join_cache_bka=on,optimize_join_buffer_size=on,table_elimination=on,extended_keys=on,exists_to_in=on,orderby_uses_equalities=on,condition_pushdown_for_derived=on,split_materialized=on,condition_pushdown_for_subquery=on,rowid_filter=on,condition_pushdown_from_having=on,not_null_range_scan=off   The manual page at https://mariadb.com/kb/en/how-to-produce-a-full-stack-trace-for-mariadbd/ contains information that should help you find out what is causing the crash. Writing a core file at E:\10.5\xxx\mysql-test\var\mysqld.1\data Minidump written to E:\10.5\xxx\mysql-test\var\mysqld.1\data\mariadbd.dmp ----------SERVER LOG END-------------     - found 'mariadbd.dmp' (0/5)   Trying 'cdb' to get a backtrace Output from cdb follows. Faulting thread is printed twice,with and without function parameters Search for STACK_TEXT to see the stack trace of the faulting thread. Callstacks of other threads are printed after it.   Microsoft (R) Windows Debugger Version 10.0.19041.685 AMD64 Copyright (c) Microsoft Corporation. All rights reserved.     Loading Dump File [E:\10.5\xxx\mysql-test\var\log\main.drop\mysqld.1\data\mariadbd.dmp] User Mini Dump File: Only registers, stack and portions of memory are available     Response Time (ms) Location OK C:\Windows\System32 OK E:\10.5\xxx\sql\Debug OK .   Response Time (ms) Location OK C:\Windows\System32 OK E:\10.5\xxx\sql\Debug OK . Deferred srv*C:\symbols*https://msdl.microsoft.com/download/symbols Symbol search path is: C:\Windows\System32;E:\10.5\xxx\sql\Debug;.;srv*C:\symbols*https://msdl.microsoft.com/download/symbols Executable search path is: C:\Windows\System32;E:\10.5\xxx\sql\Debug;. Windows 10 Version 22631 MP (16 procs) Free x64 Product: WinNt, suite: SingleUserTS 22621.1.amd64fre.ni_release.220506-1250 Machine Name: Debug session time: Tue May 21 14:50:54.000 2024 (UTC + 2:00) System Uptime: 6 days 13:50:42.180 Process Uptime: 0 days 0:00:01.000 .................................... This dump file has a breakpoint exception stored in it. The stored exception information can be accessed via .ecxr. For analysis of this file, run !analyze -v ntdll!NtGetContextThread: ret 0:016> cdb: Reading initial command '!sym prompts off; !analyze -v; .ecxr; !for_each_frame dv /t;!uniqstack -p;q' quiet mode - symbol prompts off     KEY_VALUES_STRING: 1   Key : Analysis.CPU.Sec Value: 0   Key : Analysis.DebugAnalysisProvider.CPP Value: Create: 8007007e on DESKTOP   Key : Analysis.DebugData Value: CreateObject   Key : Analysis.DebugModel Value: CreateObject   Key : Analysis.Elapsed.Sec Value: 0   Key : Analysis.Memory.CommitPeak.Mb Value: 240   Key : Analysis.System Value: CreateObject   Key : Timeline.OS.Boot.DeltaSec Value: 568242

            OK to push

            sanja Oleksandr Byelkin added a comment - OK to push

            The bug for the reported test case should have been fixed in MDEV-25691 (MariaDB Server 10.6.1), which removed the function frm_file_exists() and the related tweak in ha_innobase::delete_table(). I see that the fix is correcting invocations of strxnmov() in several other places that also affects 10.6 and later major versions.

            marko Marko Mäkelä added a comment - The bug for the reported test case should have been fixed in MDEV-25691 (MariaDB Server 10.6.1), which removed the function frm_file_exists() and the related tweak in ha_innobase::delete_table() . I see that the fix is correcting invocations of strxnmov() in several other places that also affects 10.6 and later major versions.

            I did not try to figure out whether other strxnmov overwrites are exploitable, or even it is possible to reproduce ASAN failure, just looked for the common pattern, passing sizeof(buffer), where sizeof(buffer)-1 is more commonly used.

            wlad Vladislav Vaintroub added a comment - I did not try to figure out whether other strxnmov overwrites are exploitable, or even it is possible to reproduce ASAN failure, just looked for the common pattern, passing sizeof(buffer), where sizeof(buffer)-1 is more commonly used.

            Well, this specific crash was no longer in 10.6, as Marko notes above. However, while fixing this specific 10.5 crash, I also checked and fixed similar wrong invocations of strnxmov , which could mean yet undiscovered similar crashes would be fixed as well.

            wlad Vladislav Vaintroub added a comment - Well, this specific crash was no longer in 10.6, as Marko notes above. However, while fixing this specific 10.5 crash, I also checked and fixed similar wrong invocations of strnxmov , which could mean yet undiscovered similar crashes would be fixed as well.

            People

              wlad Vladislav Vaintroub
              ramesh Ramesh Sivaraman
              Votes:
              0 Vote for this issue
              Watchers:
              8 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved:

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.