Skip to content

segfault using ParamValues from $sth #447

@myrrhlin

Description

@myrrhlin

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:

  • under no circumstance should accessing the parameter value, regardless of context, cause a segfault.

  • before execution, if we can't provide the values yet, the attribute value should be undef.

  • after execution, it should probably be using integer keys starting with 1, e.g. { 1 => 42 }

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.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions