Skip to content

Unhandled initialisation failure in mysql_dr_connect() can cause SIGSEGV #488

@ndrwbrwn

Description

@ndrwbrwn

DBD::mysql version

4.052

MySQL client version

8.0.45

Server version

8.0.45-0ubuntu0.22.04.1

Operating system version

Ubuntu 24.04.4 LTS

What happened?

We run a reasonably popular website with Apache+mod_perl & MySQL. Since upgrading a few things[1], we've been experiencing segfaults in apache processes several times a day: 35 yesterday, 12 the day before, 24 the day before that... Analysing core dumps showed the culprit being a function pointer call at the bottom of MySQL's mysql_real_connect() function.

The function pointer (and indeed much of the rest of the MYSQL struct) was uninitialised, hence the segfault. Looking at where that info is supposed to come from, it should be set in mysql_init(). However, it's possible (in cases of specific memory pressure) for mysql_init() to fail, in which case it will return nullptr.

This error case isn't checked by DBD::mysql, so if this occurs, DBD::mysql will continue freely setting options (which works, because the MYSQL struct itself is allocated, it's just not totally filled-out). Eventually it ends up in mysql_real_connect(), where the first actual access to an uninitialised field is the culprit function pointer call, and there's our segfault.

Fixing this is just a matter of ensuring that DBD::mysql checks for mysql_init() failures and reasonably exits, rather than continuing until something explodes.

I've written a patch on my local machine, but haven't really tested it, as running manually-compiled DBD will require compiling MySQL & Perl, which will require compiling other modules, and so on ad infinitum. The patch is pretty trivial though, and it compiles OK, so I'm hoping it just works.

diff --git a/dbdimp.c b/dbdimp.c
index 39db033..7a00e94 100644
--- a/dbdimp.c
+++ b/dbdimp.c
@@ -1207,7 +1207,8 @@ MYSQL *mysql_dr_connect(
     client_flag = CLIENT_FOUND_ROWS;
 #endif
     mysql_library_init(0, NULL, NULL);
-    mysql_init(sock);
+    if (!mysql_init(sock))
+      return NULL;

     if (imp_dbh)
     {

[1] I suspect the upgrade just caused the specific memory pressure pattern for this problem to start occuring, rather than it being a flaw in the particular versions of things that we upgraded to.

Other information

No response

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions