Skip to content

mysqlnd: segfault on connect with an empty password to a MariaDB PAM account #24293

Closed

Description

@clamy54

Description

Description

Connecting with mysqli to a MariaDB account that authenticates through PAM with
pam_use_cleartext_plugin=ON crashes the PHP process (SIGSEGV) when the
password is empty. The server sends an auth switch request to
mysql_clear_password; with an empty password mysqlnd dereferences a NULL
buffer instead of sending a single NUL byte. With a non-empty password the same
connection works.

It showed up with Adminer 6, which reconnects with an empty password right
after login. Every MySQL login ended with the php-fpm worker exiting on
signal 11 and nginx answering 502.

Reproducer:

<?php
mysqli_report(MYSQLI_REPORT_OFF);
$m = mysqli_init();
$r = @$m->real_connect('db', 'pamuser', '', null, 3306);
echo $r ? "connected\n" : "error: {$m->connect_error}\n";

Server side (MariaDB official image, tested with pam_permit):

# /etc/mysql/conf.d/pam.cnf
[mariadbd]
plugin-load-add = auth_pam.so
pam_use_cleartext_plugin = ON

# /etc/pam.d/mariadb
auth required pam_permit.so
account required pam_permit.so

CREATE USER 'pamuser'@'%' IDENTIFIED VIA pam USING 'mariadb';

Resulted in this output:

(no output, exit code 139)

Program received signal SIGSEGV, Segmentation fault.
#0  0x00007ffff746185a in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1-#7  ?? () in the php binary (no symbols)
#8  mysqlnd_connection_connect ()
#9  mysqli_common_connect () from .../mysqli.so

But I expected this output instead:

error: Access denied for user 'pamuser'@'...' (using password: NO)

Code

mysql_clear_password goes through mysqlnd_pam_auth_get_auth_data():

if (passwd && passwd_len) {
ret = (zend_uchar*) zend_strndup(passwd, passwd_len);
}
/*
Trailing null required. bug#78680
https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_authentication_methods_clear_text_password.html
*/
*auth_data_len = passwd_len + 1;

if (passwd && passwd_len) {
	ret = (zend_uchar*) zend_strndup(passwd, passwd_len);
}
*auth_data_len = passwd_len + 1;

Empty password: ret stays NULL, auth_data_len is 1.

On the write side, php_mysqlnd_change_auth_response_write() copies
auth_data_len bytes from auth_data:

if (packet->auth_data_len) {
p = zend_mempcpy(p, packet->auth_data, packet->auth_data_len);

For comparison, the sha256 clear text path (L903-L906, changed in GH-11440)
allocates the buffer whatever the password length.

PHP Version

PHP 8.3.35, 8.4.26 and 8.5.11 (official docker images php:8.x-cli and php:8.3-fpm-bookworm)
MariaDB 10.11.19 and 11.4.13

Operating System

Debian (official PHP docker images)

Activity

  1. added a commit that references this issue on Oct 13, 2026
    3c1a072
  2. added 3 commits that reference this issue on Oct 13, 2026
    b04cd86
    bd53a71
    3331b9d
  3. added a commit that references this issue on Oct 15, 2026
    57ad95f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions