
#!/usr/bin/env bash
set -euo pipefail

# =====================================================================
# MDEV-41233: auth_pam uses an uninitialized child PID in waitpid()
# and kill() after posix_spawn() fails
#
# Confirmed reproducing on: 10.11.16, 11.8.8, 13.1.0
#
# WARNING: Only run this on a disposable host/VM. The shim logs (but
# does not suppress) the waitpid()/kill() calls against the garbage
# PID, so a real, unrelated process with that PID could be signaled.
#
# Usage:
#   ./mdev-41233.sh /path/to/mariadb/install/root
#   or
#   MARIADB_ROOT=/path/to/mariadb/install/root ./mdev-41233.sh
#
# MARIADB_ROOT must be the top-level install/build directory containing
# bin/mariadbd, bin/mariadb, lib/plugin/auth_pam.so, and (usually under
# scripts/ or bin/) mariadb-install-db.
# =====================================================================

MARIADB_ROOT="${1:-${MARIADB_ROOT:-}}"

if [ -z "$MARIADB_ROOT" ]; then
  echo "ERROR: MARIADB_ROOT is not set." >&2
  echo "" >&2
  echo "Usage:" >&2
  echo "  $0 /path/to/mariadb/install/root" >&2
  echo "  or" >&2
  echo "  MARIADB_ROOT=/path/to/mariadb/install/root $0" >&2
  exit 1
fi

if [ ! -x "$MARIADB_ROOT/bin/mariadbd" ]; then
  echo "ERROR: '$MARIADB_ROOT/bin/mariadbd' not found or not executable." >&2
  echo "Check that MARIADB_ROOT points at a valid MariaDB install/build root." >&2
  exit 1
fi

if [ ! -f "$MARIADB_ROOT/lib/plugin/auth_pam.so" ]; then
  echo "ERROR: '$MARIADB_ROOT/lib/plugin/auth_pam.so' not found." >&2
  echo "This MariaDB build was likely compiled without PAM support." >&2
  echo "Try an official binary distribution instead (these always ship auth_pam.so)," >&2
  echo "e.g. one of your ~/Downloads/mariadb-*-linux-systemd-x86_64 directories." >&2
  exit 1
fi

WORKDIR=$(mktemp -d)
DATADIR="$WORKDIR/data"
SOCKET="$WORKDIR/pam_repro.sock"
PORT=$(( (RANDOM % 10000) + 20000 ))
PIDFILE="$WORKDIR/mariadbd.pid"
PLUGIN_DIR="$MARIADB_ROOT/lib/plugin"

cleanup() {
  if [ -f "$PIDFILE" ]; then
    kill "$(cat "$PIDFILE")" 2>/dev/null || true
    wait "$(cat "$PIDFILE")" 2>/dev/null || true
  fi
  rm -rf -- "$WORKDIR"
}
trap cleanup EXIT

echo "=== Using MARIADB_ROOT=$MARIADB_ROOT ==="
echo "=== Using port=$PORT, socket=$SOCKET, plugin_dir=$PLUGIN_DIR ==="

echo "=== Building LD_PRELOAD shim ==="
cat > "$WORKDIR/fail.c" <<'EOF_C'
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <spawn.h>
#include <stdio.h>

static const pid_t marker = (pid_t)0x414141;

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[]) {
    if (strstr(path, "auth_pam_tool")) {
        if (pid) *pid = marker;
        return EAGAIN;
    }
    static int (*real_fn)(pid_t*, const char*, const posix_spawn_file_actions_t*,
                          const posix_spawnattr_t*, char *const[], char *const[]);
    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) {
        fprintf(stderr, "[REPRO] waitpid() called on never-spawned PID %ld\n", (long)pid);
    }
    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) {
        fprintf(stderr, "[REPRO] kill(%ld, %d) called on never-spawned PID -- DANGEROUS if this PID exists!\n", (long)pid, sig);
    }
    if (!real_fn) real_fn = dlsym(RTLD_NEXT, "kill");
    return real_fn(pid, sig);
}
EOF_C
gcc -shared -fPIC -O2 -o "$WORKDIR/fail.so" "$WORKDIR/fail.c" -ldl

echo "=== Initializing datadir ==="
mkdir -p "$DATADIR"
INSTALL_DB=$(find "$MARIADB_ROOT" -maxdepth 3 -name "mariadb-install-db" | head -1)
if [ -z "$INSTALL_DB" ]; then
  echo "ERROR: could not find mariadb-install-db under $MARIADB_ROOT" >&2
  exit 1
fi
"$INSTALL_DB" --no-defaults --datadir="$DATADIR" --auth-root-authentication-method=normal >/dev/null

echo "=== Starting server with shim preloaded ==="
LD_PRELOAD="$WORKDIR/fail.so" "$MARIADB_ROOT/bin/mariadbd" \
  --no-defaults \
  --datadir="$DATADIR" --socket="$SOCKET" --port="$PORT" \
  --plugin-dir="$PLUGIN_DIR" \
  --pid-file="$PIDFILE" &

READY=0
for i in $(seq 1 30); do
  if "$MARIADB_ROOT/bin/mariadb" --socket="$SOCKET" -uroot -e "SELECT 1" >/dev/null 2>&1; then
    READY=1
    break
  fi
  sleep 1
done

if [ "$READY" -ne 1 ]; then
  echo "ERROR: server did not become ready within 30 seconds. Check for port/socket conflicts or startup errors above." >&2
  exit 1
fi

echo "=== Setting up PAM auth ==="
"$MARIADB_ROOT/bin/mariadb" --socket="$SOCKET" -uroot -e "
  INSTALL SONAME 'auth_pam';
  CREATE USER pam_probe@localhost IDENTIFIED VIA pam USING 'mariadb';
"

echo "=== Triggering the bug: attempted PAM login ==="
"$MARIADB_ROOT/bin/mariadb" --socket="$SOCKET" -upam_probe -pwrong -e 'SELECT 1' || true

echo "=== Done. Look for [REPRO] lines above confirming waitpid()/kill() on the never-spawned PID ==="
