perl5-dbi / DBD-mysql

MySQL driver for the Perl5 Database Interface (DBI)
https://metacpan.org/module/DBD::mysql
Other
63 stars 72 forks source link

segfault using ParamValues from $sth #447

Open myrrhlin opened 1 day ago

myrrhlin commented 1 day ago

DBD::mysql version

4.050

MySQL client version

8.0.39

Server version

8.0.39-0ubuntu0.20.04.1

Operating system version

Ubuntu 20.04.6 LTS

What happened?

Started work in an older codebase. I tried using DBI::Log module in a test script, and got a segfault.

$ perl test.pl
-- Wed Oct 15 13:36:21 2024
--  /home/pyrrhlin/repo/path/DB.pm 160
SET NAMES 'utf8'

Segmentation fault (core dumped)

As you can see, the first query, using $dbh->do() is fine. The second query is an $sth->execute(42) on a SELECT statement with one placeholder, an integer id field, that causes the segfault. (Note: nowhere did we use $sth->bind_param.)

I used perl debugger to find the offending statement, and it's this one:

%values = %{$sth->{ParamValues}};

So this module uses the statement handle attribute ParamValues to find out what the bound parameter values were. This is a sometimes supported interface from DBI. It's expected to return a hashref, where

keys of the hash are the 'names' of the placeholders, typically integers starting at 1. Returns undef if not supported by the driver

However, it should be noted that:

Some drivers cannot provide valid values for some or all of these attributes until after $sth->execute has been successfully called. Typically the attribute will be undef in these situations

So, it's clear to me that DBI::Log should not be trying to read this data before execution, because for some drivers (including obviously mysql), the data won't be there yet. Regardless, if the attribute value was undef, as DBI docs suggest, the line causing the segfault would not have been executed. To have entered the if block, the attribute must have had some true value.

In the debugger again, I find what it is:

  DB<2> c
-- Wed Oct 16 15:17:50 2024
--  /home/pyrrhlin/repo/path/DB.pm 160
SET NAMES 'utf8'

DBI::Log::pre_query(/home/pyrrhlin/repo/path/DBI/Log.pm:197):
197:            if ($sth && $sth->{ParamValues}) {
  DB<2> print 7 if $sth && $sth->{ParamValues}
7
  DB<3> print ref $sth->{ParamValues}
HASH
  DB<4> x $sth->{ParamValues}
0  HASH(0x559dc82a05f8)
   0 => undef
  DB<5> q

So the ParamValues attribute had the true value { 0 => undef }.

However, that data structure was not a "pure-perl" clean hashref, as dereferencing it in list context caused the segfault. Alternative code to copy the hash such as this did not trigger a segfault:

my $h = $sth->{ParamValues}; $values{$_} = $h->{$_} for keys %$h`

Now, reading statement handle docs for DBD::mysql there is no mention of ParamValues attribute, which raises some question whether it is supported at all. The docs do confirm however that:

most attributes are valid only after a successful execute. An undef value will returned otherwise

Testing shows that after execution, the attribute is populated with e.g., { 0 => 42 }, which is almost useful, but non-compliant with the DBI docs (which required the first argument to be under key 1).

So we have these problems:

Other information

Testing performed with system perl on Ubuntu 20.04 LTS :

This is perl 5, version 30, subversion 0 (v5.30.0) built for x86_64-linux-gnu-thread-multi
(with 60 registered patches, see perl -V for more detail)

Apologies I did not have time to try to replicate the failure with a newer version. If I get more time I will try to do that.

Issue #353 also suggests trouble with the xs building the data structures tied behind the statement handle attributes. Perhaps related.

myrrhlin commented 1 day ago

Only a single test references ParamValues at all: rt83494-quotes-comments.t.

If you confirm that we intend to support this attribute, I might try to add some tests, perhaps in some of 4?bindparam.t, or any test file you suggest.