Details
-
Bug
-
Status: Confirmed (View Workflow)
-
Major
-
Resolution: Unresolved
-
10.11, 11.4, 11.8, 12.3, 13.1, 12.3.3
-
None
-
Linux x86_64; Docker; GCC; official image
`mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979`;
server reports `12.3.3-MariaDB-ubu2404`, source revision
`83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e`
Description
`auth_pam` declares `pid_t proc_id` without initialization. If `posix_spawn()`
fails, control jumps to the common cleanup block, which repeatedly calls
`waitpid(proc_id, ...)` and can later call `kill(proc_id, SIGKILL)`.
The product-level reproducer makes only the `auth_pam_tool` spawn return
`EAGAIN`, writes a fixed sentinel into the output PID, and safely suppresses
`waitpid`/`kill` for that sentinel. A real PAM login then causes repeated
`waitpid(0x414141, ...)` calls and `kill(0x414141, SIGKILL)`. All calls for
other PIDs pass through unchanged.
Steps to reproduce
Run only on a disposable host. The shim intentionally prevents the invalid
signal from reaching the kernel.
#!/usr/bin/env bash
|
set -euo pipefail
|
IMAGE='mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979'
|
C="mdev-mcf2s08-$$"; OWNER="mdev-mcf2s08-$$"; TMP=$(mktemp -d)
|
cleanup() {
|
if docker inspect --format '{{ index .Config.Labels "io.encryptiondbfuzz.owner" }}' \
|
"$C" 2>/dev/null | grep -Fqx "$OWNER"; then
|
docker rm -f "$C" >/dev/null 2>&1 || true
|
fi
|
rm -rf -- "$TMP"
|
}
|
trap cleanup EXIT
|
 |
cat >"$TMP/fail.c" <<'EOF_C'
|
#define _GNU_SOURCE
|
#include <dlfcn.h>
|
#include <errno.h>
|
#include <signal.h>
|
#include <spawn.h>
|
#include <stdio.h>
|
#include <string.h>
|
#include <sys/types.h>
|
#include <sys/wait.h>
|
#include <unistd.h>
|
static const pid_t marker=(pid_t)0x414141;
|
static void note(const char *op,pid_t pid,int value){
|
FILE *f=fopen("/tmp/pam-spawn.log","a");
|
if(f){fprintf(f,"%s pid=%ld value=%d\n",op,(long)pid,value);fclose(f);}
|
}
|
int posix_spawn(pid_t *pid,const char *path,const posix_spawn_file_actions_t *a,
|
const posix_spawnattr_t *attr,char *const argv[],char *const envp[]){
|
static int (*real_fn)(pid_t*,const char*,const posix_spawn_file_actions_t*,
|
const posix_spawnattr_t*,char *const[],char *const[]);
|
if(strstr(path,"auth_pam_tool")){if(pid)*pid=marker;note("posix_spawn_fail",marker,EAGAIN);return EAGAIN;}
|
if(!real_fn)real_fn=dlsym(RTLD_NEXT,"posix_spawn");
|
return real_fn(pid,path,a,attr,argv,envp);
|
}
|
pid_t waitpid(pid_t pid,int *status,int options){
|
static pid_t (*real_fn)(pid_t,int*,int);
|
if(pid==marker){(void)status;(void)options;note("waitpid_after_spawn_failure",pid,ECHILD);errno=ECHILD;return -1;}
|
if(!real_fn)real_fn=dlsym(RTLD_NEXT,"waitpid");
|
return real_fn(pid,status,options);
|
}
|
int kill(pid_t pid,int sig){
|
static int (*real_fn)(pid_t,int);
|
if(pid==marker){note("kill_after_spawn_failure",pid,sig);errno=ESRCH;return -1;}
|
if(!real_fn)real_fn=dlsym(RTLD_NEXT,"kill");
|
return real_fn(pid,sig);
|
}
|
EOF_C
|
gcc -shared -fPIC -O2 -Wall -Wextra -Werror -o "$TMP/fail.so" "$TMP/fail.c" -ldl
|
chmod 755 "$TMP"; chmod 444 "$TMP/fail.so"
|
 |
docker run -d --name "$C" --label "io.encryptiondbfuzz.owner=$OWNER" \
|
--network none --read-only --cap-drop ALL \
|
--security-opt no-new-privileges=true --pids-limit 160 --memory 1100m \
|
--cpus 1 --user 999:999 \
|
--tmpfs /var/lib/mysql:rw,nosuid,nodev,size=512m,uid=999,gid=999 \
|
--tmpfs /run/mysqld:rw,nosuid,nodev,size=16m,uid=999,gid=999 \
|
--tmpfs /tmp:rw,nosuid,nodev,size=64m,uid=999,gid=999 \
|
--mount "type=bind,src=$TMP,dst=/edbf,readonly" \
|
--env LD_PRELOAD=/edbf/fail.so --env MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
|
"$IMAGE" >/dev/null
|
 |
READY=0
|
for I in $(seq 1 90); do
|
if docker exec --user 999:999 --env LD_PRELOAD= "$C" mariadb --protocol=socket \
|
--socket=/run/mysqld/mysqld.sock -uroot -NBe 'SELECT VERSION()' 2>/dev/null; then READY=1; break; fi
|
sleep 1
|
done
|
test "$READY" -eq 1
|
SQL=(docker exec --user 999:999 --env LD_PRELOAD= "$C" mariadb --protocol=socket --socket=/run/mysqld/mysqld.sock -uroot --batch --skip-column-names)
|
"${SQL[@]}" -e "INSTALL SONAME 'auth_pam'; CREATE USER pam_probe@localhost IDENTIFIED VIA pam USING 'mariadb'"
|
set +e
|
docker exec --user 999:999 --env LD_PRELOAD= "$C" timeout 10 mariadb \
|
--protocol=socket --socket=/run/mysqld/mysqld.sock -upam_probe -pwrong \
|
--batch --skip-column-names -e 'SELECT 1'
|
RC=$?
|
set -e
|
echo "login_rc=$RC"
|
docker exec --user 999:999 "$C" cat /tmp/pam-spawn.log
|
"${SQL[@]}" -e "SELECT CONCAT('server_health=',1)"
|
Actual result
ERROR 1045 (28000): Access denied for user 'pam_probe'@'localhost' (...)
|
login_rc=1
|
posix_spawn_fail pid=4276545 value=11
|
waitpid_after_spawn_failure pid=4276545 value=10
|
... repeated waitpid calls ...
|
kill_after_spawn_failure pid=4276545 value=9
|
waitpid_after_spawn_failure pid=4276545 value=10
|
server_health=1
|
The fixed sentinel is decimal 4276545 (`0x414141`). The shim suppressed only
cleanup operations for that value, so the test did not signal any real process.
The server remained healthy and did not crash.
Expected result
If `posix_spawn()` fails, the PAM plugin should close its local resources and
return the authentication error without waiting for or signaling a child that
was never created. `proc_id` must only be used after a successful spawn.
Attachments
Issue Links
- relates to
-
MDEV-10890 plugins.pam fails in buildbot with valgrind
-
- Closed
-