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

Aria encryption silently persists metadata after cryptographic RNG failures

    XMLWordPrintable

Details

    • Bug
    • Status: Open (View Workflow)
    • Minor
    • Resolution: Unresolved
    • 12.3.3
    • 11.4, 11.8, 12.3
    • Encryption
    • None

    Description

      When an encrypted Aria table is created, MariaDB requests a 16-byte IV and a
      4-byte space value through `my_random_bytes()`. Both return values are ignored.
      If OpenSSL `RAND_bytes()` fails for both requests, `CREATE TABLE` still reports
      success and the generated encryption metadata is written to the `.MAI` file.
      The table remains readable after `FLUSH TABLES` forces it to be reopened.

      The current `my_random_bytes()` error path fills the output from a process-local
      `std::mt19937` fallback and returns an error. This report's product test proves
      that the real Aria caller ignores those errors and persists the resulting
      metadata; it does not claim a natural OpenSSL failure or a practical attack.

      Steps to reproduce

      Run only on a disposable Docker host. This uses a small `LD_PRELOAD` shim that
      fails exactly one 16-byte and one 4-byte `RAND_bytes()` call after explicit arm
      files are created. All other RNG calls are passed through to OpenSSL.

      set -eu
      IMAGE='mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979'
      C='mdev-aria-rng-failure-repro'
      OWNER="edbf-report-$C-$$"
      D=$(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 -r "$D" >/dev/null 2>&1 || true
      }
      trap cleanup EXIT
       
      cat >"$D/rand_fail_once.c" <<'EOF'
      #define _GNU_SOURCE
      #include <dlfcn.h>
      #include <fcntl.h>
      #include <stdio.h>
      #include <unistd.h>
      typedef int (*rand_fn)(unsigned char *, int);
      int RAND_bytes(unsigned char *buf, int num) {
        static rand_fn real_fn;
        if (!real_fn) real_fn=(rand_fn)dlsym(RTLD_NEXT,"RAND_bytes");
        const char *arm=num==16 ? "/tmp/fail-rand-16" :
                        num==4  ? "/tmp/fail-rand-4"  : 0;
        if (arm && unlink(arm)==0) {
          int fd=open("/tmp/rand-fail.log",O_WRONLY|O_CREAT|O_APPEND,0600);
          if (fd>=0) {
            char line[64];
            int n=snprintf(line,sizeof(line),"RAND_bytes num=%d result=0\n",num);
            if (n>0) { ssize_t written=write(fd,line,(size_t)n); (void)written; }
            close(fd);
          }
          return 0;
        }
        return real_fn ? real_fn(buf,num) : 0;
      }
      EOF
      gcc -shared -fPIC -O2 -Wall -Wextra -o "$D/rand_fail_once.so" \
        "$D/rand_fail_once.c" -ldl
      printf '%s\n' '1;0123456789ABCDEF0123456789ABCDEF' >"$D/keys.txt"
      chmod 0755 "$D"
      chmod 0644 "$D/rand_fail_once.so" "$D/keys.txt"
       
      docker run --detach --name "$C" \
        --label "io.encryptiondbfuzz.owner=$OWNER" \
        --network none --read-only --cap-drop ALL \
        --security-opt no-new-privileges=true --user 999:999 \
        --tmpfs /var/lib/mysql:rw,nosuid,nodev,size=640m,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="$D",dst=/edbf,readonly \
        --env MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
        --env LD_PRELOAD=/edbf/rand_fail_once.so \
        "$IMAGE" \
        --plugin-load-add=file_key_management.so \
        --file-key-management-filename=/edbf/keys.txt \
        --aria-encrypt-tables=ON
       
      READY=0
      for i in $(seq 1 90); do
        if docker exec --env LD_PRELOAD= "$C" mariadb --protocol=socket -uroot -NBe \
             "SELECT @@port" 2>/dev/null | grep -qx 3306; then
          READY=1
          break
        fi
        sleep 1
      done
      test "$READY" -eq 1
      docker exec --env LD_PRELOAD= "$C" mariadb --protocol=socket -uroot -NBe \
        "SELECT PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS
          WHERE PLUGIN_NAME='file_key_management'"
       
      # No-fault control: create and reopen the same Aria table shape before arming
      # the two failures. The failure log must not exist yet.
      docker exec --env LD_PRELOAD= "$C" mariadb --protocol=socket -uroot -e \
        "CREATE DATABASE edbf; USE edbf;
         CREATE TABLE t_control(id INT PRIMARY KEY,pad VARCHAR(200))
           ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1;
         INSERT INTO t_control SELECT seq,REPEAT('x',80) FROM seq_1_to_32;
         FLUSH TABLES t_control;
         SELECT COUNT(*) AS control_rows_after_reopen FROM t_control;"
      docker exec --env LD_PRELOAD= "$C" test ! -e /tmp/rand-fail.log
       
      # The client process must not inherit the shim, or it could consume an arm file.
      docker exec --env LD_PRELOAD= "$C" touch \
        /tmp/fail-rand-16 /tmp/fail-rand-4
      docker exec --env LD_PRELOAD= "$C" mariadb --protocol=socket -uroot -e \
        "USE edbf;
         CREATE TABLE t_trigger(id INT PRIMARY KEY,pad VARCHAR(200))
           ENGINE=Aria ROW_FORMAT=PAGE TRANSACTIONAL=1;
         INSERT INTO t_trigger SELECT seq,REPEAT('x',80) FROM seq_1_to_32;
         FLUSH TABLES t_trigger;"
       
      docker exec --env LD_PRELOAD= "$C" cat /tmp/rand-fail.log
      BEFORE=$(docker exec --env LD_PRELOAD= "$C" sha256sum \
        /var/lib/mysql/edbf/t_trigger.MAI | awk '{print $1}')
      docker exec --env LD_PRELOAD= "$C" mariadb --protocol=socket -uroot -e \
        "FLUSH TABLES edbf.t_trigger;
         SELECT COUNT(*) AS rows_after_reopen FROM edbf.t_trigger;"
      AFTER=$(docker exec --env LD_PRELOAD= "$C" sha256sum \
        /var/lib/mysql/edbf/t_trigger.MAI | awk '{print $1}')
      printf 'mai_hash_before=%s\nmai_hash_after=%s\n' "$BEFORE" "$AFTER"
      test "$BEFORE" = "$AFTER"
      

      Actual result

      The plugin was `ACTIVE`. The two exact OpenSSL failures were delivered in the
      server thread:

      RAND_bytes num=16 result=0
      RAND_bytes num=4 result=0
      

      `CREATE TABLE` and the insert returned success. The real table query after
      `FLUSH TABLES` returned:

      rows_after_reopen
      32
      

      The script prints the `.MAI` SHA-256 before and after the close/reopen sequence;
      the two values are identical.

      Before the failures were armed, the no-fault control printed
      `control_rows_after_reopen=32`; `/tmp/rand-fail.log` did not exist. Thus the
      same table shape also works normally, while only the trigger records the two
      injected RNG failures.

      Expected result

      If either mandatory encryption-metadata RNG request fails, Aria table creation
      should return an error and avoid persisting metadata derived from a
      non-cryptographic fallback. The return value of `my_random_bytes()` should not
      be silently discarded.

      Attachments

        Activity

          People

            serg Sergei Golubchik
            csfuzz csfuzz
            Votes:
            0 Vote for this issue
            Watchers:
            2 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.