From 660571080116fd8b1c5797f081ff897eb792f99f Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Thu, 7 May 2026 16:45:39 +1000 Subject: [PATCH 1/3] MDEV-38838 Memory loss in CONNECT from handler::ha_rnd_init This was a leak in <=unixODBC 2.3.15 where a cache was allocated but not returned. Using the suite.pm for the connect engine we see if the connect engine is compiled with ASAN (that includes LeakSanitizer) and we check the unixODBC version to see if it is a version (currently unreleased) that has a inst_logClose symbol which is part of the unixODBC leak fix. If it is not a leak fixed version, a LD_PRELOAD of the unixODBC library occurs so that the LeakSanitizer suppression can resolve the save_ini_cache entry. Alterates considered: 1. __clear_ini_cache in libodbcinst (2.3.12+) is an interface, for which there is no header define. A CMake would need to do full compile and link to check its existance. Workable, just wanted a simplier solution. It looked like it shoud be a better interface in unixODBC. 2. could skip asan tests on CONNECT/ODBC, but this is skipping coverage we want. --- mysql-test/lsan.supp | 3 ++ storage/connect/mysql-test/connect/suite.pm | 58 ++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/mysql-test/lsan.supp b/mysql-test/lsan.supp index 3f460d4544edc..9724685325fa5 100644 --- a/mysql-test/lsan.supp +++ b/mysql-test/lsan.supp @@ -15,3 +15,6 @@ leak:gnutls_x509_trust_list_init leak:gnutls_subject_alt_names_init leak:__gmp_default_allocate leak:__gmp_default_reallocate + +# unixODBC leak +leak:save_ini_cache diff --git a/storage/connect/mysql-test/connect/suite.pm b/storage/connect/mysql-test/connect/suite.pm index 2dabbc82e7d46..e0f715eb1cffc 100644 --- a/storage/connect/mysql-test/connect/suite.pm +++ b/storage/connect/mysql-test/connect/suite.pm @@ -10,7 +10,63 @@ return "No CONNECT engine" unless $ENV{HA_CONNECT_SO} or return "Not run for embedded server" if $::opt_embedded_server and $ENV{HA_CONNECT_SO}; -sub is_default { 1 } +sub is_default { 1 } + +# To allow the lsan suppression on unixodbc to work +# the llvm-symbolizer needs to be are of the address +# resolution even after the HA_CONNECT_SO has been dlclosed. + +# Check OS and file existence +if ($^O =~ /linux|darwin|unix/i && -x "/usr/bin/readelf") +{ + my $asan_symbols = 0; + my $file = $::plugindir . '/' . $ENV{HA_CONNECT_SO}; + + # Open readelf -s output and scan for __asan symbols + open(my $sh, '-|', "readelf -s '$file'") + or die "Failed to run readelf: $!\n"; + while (<$sh>) { $asan_symbols = 1 if /__asan/; } + close($sh); + + if ($asan_symbols && -x "/usr/bin/ldd") + { + # To allow the lsan suppression on unixodbc to work + # the llvm-symbolizer needs to be aware of the address + # resolution even after the HA_CONNECT_SO has been dlclosed. + + my $lib_path; + open(my $ldd, '-|', "ldd $file") or die "Failed to run ldd: $!"; + + while (<$ldd>) + { + chomp; + # Example ldd line: libodbc.so.2 => /usr/lib/x86_64-linux-gnu/libodbc.so.2 (0x00007f...) + if (/libodbc\.so(?:\.\d+)*\s+=>\s+(\S+)/) + { + $lib_path = $1; + last; # stop after the first match + } + } + close $ldd; + + # assuming odbcinst is in the same path so we can check + # if it has fixed version. + my $libodbcinst = $lib_path; + $libodbcinst =~ s/odbc/odbcinst/; + my $leakfixed= 0; + if ( -f $libodbcinst ) + { + open(my $sh, '-|', "readelf -s '$libodbcinst'") + or die "Failed to run readelf: $!\n"; + while (<$sh>) { $leakfixed = 1 if /inst_logClose/; } + close($sh); + } + if (!$leakfixed && $lib_path) + { + $ENV{LD_PRELOAD} = $ENV{LD_PRELOAD} ? "$ENV{LD_PRELOAD}:$lib_path" : $lib_path; + } + } +} bless { }; From 99c7b9ffa43a52777e3a3e352273e8941346f4f4 Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Fri, 8 May 2026 11:58:23 +1000 Subject: [PATCH 2/3] mtr: extend LSAN_OPTIONS rather than overwrite Its useful to add own LSAN_OPTIONS like report_objects=1 so lets make sure MTR doesn't overwrite this. --- mysql-test/mariadb-test-run.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mysql-test/mariadb-test-run.pl b/mysql-test/mariadb-test-run.pl index 279168c4172cb..5166b23bbc8cd 100755 --- a/mysql-test/mariadb-test-run.pl +++ b/mysql-test/mariadb-test-run.pl @@ -1644,7 +1644,8 @@ sub command_line_setup { # $ENV{ASAN_OPTIONS}= "log_path=${opt_vardir}/log/asan:" . $ENV{ASAN_OPTIONS}; # Add leak suppressions - $ENV{LSAN_OPTIONS}= "suppressions=${glob_mysql_test_dir}/lsan.supp:print_suppressions=0" + $ENV{LSAN_OPTIONS}= "suppressions=${glob_mysql_test_dir}/lsan.supp:print_suppressions=0:" + . ($ENV{LSAN_OPTIONS} || '') if -f "$glob_mysql_test_dir/lsan.supp" and not IS_WINDOWS; mtr_verbose("ASAN_OPTIONS=$ENV{ASAN_OPTIONS}"); From 051346a5365927d7c53a1aa4317e589ec905990d Mon Sep 17 00:00:00 2001 From: Daniel Black Date: Sun, 10 May 2026 16:11:38 +1000 Subject: [PATCH 3/3] mtr: under rr use LSAN_OPTIONS=report_objects When we have a rr recording of the process, a memory location is a useful thing to have in the err log of leaks. The output comes out like: Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x55b08dbccdb8 in malloc (/build/sql/mariadbd+0x1b89db8) (BuildId: 4d72569bed63ce1fbf55d51e4d4a84b610efd87d) #1 0x7b3fe387a1b1 () #2 0x7b3fe37c492f () #3 0x7b3fe3058407 () #4 0x7b3fe2f09493 () Objects leaked above: 0x7b7feafe0990 (40 bytes) Now once the location is resolved we can check that we resolving the right object leak. --- mysql-test/lib/My/Debugger.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/mysql-test/lib/My/Debugger.pm b/mysql-test/lib/My/Debugger.pm index fa40f297b2859..643389ca18b0c 100644 --- a/mysql-test/lib/My/Debugger.pm +++ b/mysql-test/lib/My/Debugger.pm @@ -84,6 +84,7 @@ my %debuggers = ( push @::global_suppressions, qr/InnoDB: native AIO failed/; ::mtr_error('rr requires kernel.perf_event_paranoid <= 1') if ::mtr_grab_file('/proc/sys/kernel/perf_event_paranoid') > 1; + $ENV{LSAN_OPTIONS}= "report_objects=1:" . ($ENV{LSAN_OPTIONS} || '') } }, valgdb => {