Uploaded image for project: 'MariaDB Server'
  1. MariaDB Server
  2. MDEV-41230

The shipped wsrep_notify helper reparses configured credentials through eval and splits password arguments

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Critical
    • Resolution: Unresolved
    • 12.3.3
    • 10.11, 11.4, 11.8, 12.3
    • Galera
    • None

    Description

      The shipped `wsrep_notify.sh` constructs client options as shell text and then
      passes them through `eval`. A configured password containing an apostrophe and
      space is therefore parsed a second time. In a real two-node Galera state
      transition, the normal password is delivered as one `-pplainpass` argument,
      but `x' y='z` becomes two arguments, `-px` and `y=z`.

      The reproducer uses a no-network argv recorder instead of a database client, so
      no credential is sent and no command is executed from the crafted value. The
      callback itself is invoked by the server after the peer joins.

      Steps to reproduce

      Run on a disposable Docker host. The embedded helper is a reduced copy of the
      affected option-building and `eval` block; the two fixed synthetic values make
      the argument boundary visible.

      #!/usr/bin/env bash
      set -euo pipefail
      IMAGE='mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979'
      OWNER="mdev-mcf202-$$"
      TMP=$(mktemp -d)
      NAMES=()
      cleanup() {
        for C in "${NAMES[@]}"; do
          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
        done
        rm -rf -- "$TMP"
      }
      trap cleanup EXIT
       
      cat >"$TMP/fake_mysql.sh" <<'EOF'
      #!/bin/sh
      : > /tmp/notify-argv
      for arg in "$@"; do printf '%s\n' "$arg" >> /tmp/notify-argv; done
      cat >/tmp/notify-sql
      EOF
       
      make_helper() {
        CASE=$1
        if [ "$CASE" = control ]; then PASSWORD_LINE="PSWD='plainpass'"; else PASSWORD_LINE="PSWD=\"x' y='z\""; fi
        {
          printf '%s\n' '#!/bin/sh' "USER='root'" "$PASSWORD_LINE" \
            "CLIENT='/edbf/fake_mysql.sh'" "STATUS=''"
          cat <<'EOF'
      while [ "$#" -gt 0 ]; do
        case "$1" in --status) STATUS=$2; shift;; esac
        shift
      done
      case "$STATUS" in
        joined|donor|synced)
          printf '%s\n' 'SELECT 1' | eval "$CLIENT" -B "-u'$USER'"${PSWD:+" -p'$PSWD'"} "-h'127.0.0.1'" "-P3306"
          ;;
      esac
      EOF
        } >"$TMP/notify-$CASE.sh"
        cat >"$TMP/invoke-$CASE.sh" <<EOF
      #!/bin/sh
      printf '%s\n' "\$*" >> /tmp/notify-invocations-$CASE
      exec /bin/sh /edbf/notify-$CASE.sh "\$@"
      EOF
      }
      make_helper control
      make_helper trigger
      chmod 755 "$TMP" "$TMP"/*.sh
       
      run_case() {
        CASE=$1
        NODE="mdev-mcf202-$CASE-node-$$"
        PEER="mdev-mcf202-$CASE-peer-$$"
        NAMES+=("$NODE" "$PEER")
        COMMON=(--label "io.encryptiondbfuzz.owner=$OWNER" --read-only --cap-drop ALL
          --security-opt no-new-privileges=true --pids-limit 224 --memory 1700m
          --cpus 1 --user 999:999
          --tmpfs /var/lib/mysql:rw,nosuid,nodev,size=700m,uid=999,gid=999
          --tmpfs /run/mysqld:rw,nosuid,nodev,size=16m,uid=999,gid=999
          --tmpfs /tmp:rw,nosuid,nodev,size=256m,uid=999,gid=999
          --env MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1
          --mount "type=bind,src=$TMP,dst=/edbf,readonly")
       
        docker run -d --name "$NODE" --network none "${COMMON[@]}" "$IMAGE" \
          --wsrep-on=ON --wsrep-provider=/usr/lib/galera/libgalera_smm.so \
          --wsrep-cluster-address=gcomm:// --wsrep-new-cluster \
          --wsrep-node-address=127.0.0.1:4567 --wsrep-node-name="$CASE-node" \
          --wsrep-provider-options=gmcast.listen_addr=tcp://127.0.0.1:4567 \
          --wsrep-notify-cmd="/bin/sh /edbf/invoke-$CASE.sh" \
          --bind-address=127.0.0.1 >/dev/null
        docker run -d --name "$PEER" --network "container:$NODE" "${COMMON[@]}" "$IMAGE" \
          --port=3307 --bind-address=127.0.0.1 --wsrep-on=ON \
          --wsrep-provider=/usr/lib/galera/libgalera_smm.so \
          --wsrep-cluster-address=gcomm://127.0.0.1:4567 \
          --wsrep-node-address=127.0.0.1:4568 --wsrep-node-name="$CASE-peer" \
          --wsrep-provider-options=gmcast.listen_addr=tcp://127.0.0.1:4568 >/dev/null
       
        READY=0
        for I in $(seq 1 120); do
          STATE=$(docker exec --user 999:999 "$NODE" mariadb --protocol=socket \
            --socket=/run/mysqld/mysqld.sock -uroot -NBe \
            "SELECT CONCAT(@@wsrep_on,'|',(SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME='WSREP_READY'),'|',(SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME='WSREP_LOCAL_STATE_COMMENT'),'|',(SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME='WSREP_CLUSTER_SIZE'))" 2>/dev/null || true)
          if [ "$STATE" = 'ON|ON|Synced|2' ]; then READY=1; break; fi
          sleep 1
        done
        test "$READY" -eq 1
        echo "$CASE cluster=$STATE"
        docker exec --user 999:999 "$NODE" cat /tmp/notify-argv
        docker rm -f "$PEER" "$NODE" >/dev/null
        NAMES=()
      }
       
      run_case control
      run_case trigger
      

      Actual result

      control cluster=ON|ON|Synced|2
      -B
      -uroot
      -pplainpass
      -h127.0.0.1
      -P3306
      trigger cluster=ON|ON|Synced|2
      -B
      -uroot
      -px
      y=z
      -h127.0.0.1
      -P3306
      

      The trigger password is no longer one client argument. Both servers remain
      running; no server crash was observed.

      Expected result

      Credential and TLS configuration values should be passed to the client without
      shell re-evaluation. A password containing an apostrophe or whitespace must
      remain one `-p...` argument, or the helper should reject it before invoking the
      client.

      Attachments

        Activity

          People

            janlindstrom Jan Lindström
            csfuzz csfuzz
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:

              Time Tracking

                Estimated:
                Original Estimate - 3d
                3d
                Remaining:
                Remaining Estimate - 3d
                3d
                Logged:
                Time Spent - Not Specified
                Not Specified

                Git Integration

                  Error rendering 'com.xiplink.jira.git.jira_git_plugin:git-issue-webpanel'. Please contact your Jira administrators.