Details
-
Bug
-
Status: Open (View Workflow)
-
Minor
-
Resolution: Unresolved
-
12.3.3
-
None
-
Environment: Linux x86_64 host with GCC; official image
`mariadb@sha256:dd9b303aed4f4890ed09f766d8ca9ddfd176c0c6f6267feff53b3192ec65a979`;
server reports `12.3.3-MariaDB-ubu2404`, source revision
`83e909fc2a0dbc394b4b683fb3fa2d7dcf26cc5e`.
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.