Repository navigation
mysqlnd: segfault on connect with an empty password to a MariaDB PAM account #24293
Description
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():
php-src/ext/mysqlnd/mysqlnd_auth.c
Lines 620 to 627 in 8db886e
| 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:
php-src/ext/mysqlnd/mysqlnd_wireprotocol.c
Lines 867 to 868 in 8db886e
| 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
Add test for phpGH-24293
Send a scramble with the auth switch in the phpGH-24293 test
Fix phpGH-24293: Segfault with an empty password in MariaDB PAM (php#…
Merge branch 'PHP-8.4' into PHP-8.5
Merge branch 'PHP-8.5' into PHP-8.6
Merge branch 'PHP-8.6'
Merge remote-tracking branch 'up/PHP-8.4' into fix-phpgh-20503