Bug #121602 component_keyring_file restores an unvalidated .backup file and destroys the primary keyring
Submitted: 25 Oct 15:10
Reporter: Wwwwing Wwwwing Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Components / Services Severity:S2 (Serious)
Version:MySQL 8.4.11 official image OS:Any
Assigned to: CPU Architecture:Any

[25 Oct 15:10] Wwwwing Wwwwing
Description:
`component_keyring_file` writes key material in two steps: first the new content
to `<keyring>.backup`, then the same content to the live keyring file, then it
removes the backup. The low-level writer uses `std::ofstream` without
`fflush`/`fsync`/atomic rename and does not check the close result, so an
interrupted write or ENOSPC can leave a non-empty but truncated `.backup`.

On the next start, the data-file reader treats any non-empty `.backup` as an
in-progress write and copies it over the primary file *before* any JSON or
completeness validation. A truncated backup therefore replaces a good primary
keyring, and the subsequent JSON validation fails, leaving the keyring component
disabled. Encrypted tables can then become inaccessible unless a separate copy
of the master key exists.

How to repeat:
The two-phase start avoids loading the component into the entrypoint's temporary
initialization server. In the second phase the component manifest and
configuration are mounted read-only, and a syntactically valid seeded keyring
file is placed in the data directory.

```bash
IMG=mysql@sha256:85b9bf2e29cf836ecb8c2a15a935d4ba0c606631dff1dd79531a11983c638f2a
DATA=$(mktemp -d); chown 999:999 "$DATA"
mkdir -p "$WORK"; printf '%s\n' '{"components":"file://component_keyring_file"}' > "$WORK/mysqld.my"
printf '%s\n' '{"path":"/var/lib/mysql/keyring","read_only":false}' > "$WORK/component_keyring_file.cnf"
chmod 644 "$WORK"/*.my "$WORK"/*.cnf
printf '%s' '{"version":"2","elements":[{"user":"root","data_id":"dsh_seed","data_type":"AES","data":"00","extension":[]}]}' > "$DATA/keyring"
chown 999:999 "$DATA/keyring"; chmod 600 "$DATA/keyring"

docker run -d --name my56 --network none --read-only --cap-drop ALL \
  --security-opt no-new-privileges=true --pids-limit 512 --memory 2048m --cpus 2 \
  --user 999:999 --mount type=bind,src="$DATA",dst=/var/lib/mysql \
  --mount type=bind,src="$WORK/mysqld.my",dst=/usr/sbin/mysqld.my,readonly \
  --mount type=bind,src="$WORK/component_keyring_file.cnf",dst=/usr/lib64/mysql/plugin/component_keyring_file.cnf,readonly \
  --tmpfs /var/run/mysqld:rw,noexec,nosuid,nodev,size=16m,uid=999,gid=999 \
  -e MYSQL_ALLOW_EMPTY_PASSWORD=yes "$IMG" \
  --skip-name-resolve --log-error=/var/lib/mysql/error.log --log-error-verbosity=3

# wait until ready: component is Active
docker exec my56 mysql -uroot --socket=/var/run/mysqld/mysqld.sock -e \
  "SELECT * FROM performance_schema.keyring_component_status;"
docker stop my56

# simulate the interrupted write: non-empty but truncated backup
printf '%s' '{"Version":2,"data":"partial-unvalidated"' > "$DATA/keyring.backup"
sha256sum "$DATA/keyring" "$DATA/keyring.backup"

docker start my56
sleep 20
sha256sum "$DATA/keyring" "$DATA/keyring.backup"
docker exec my56 mysql -uroot --socket=/var/run/mysqld/mysqld.sock -e \
  "SELECT * FROM performance_schema.keyring_component_status;"
grep -a KEYRING "$DATA/error.log" | tail -5
```

## Actual result

Before the restart the seeded primary keyring is valid and the component is
`Active`. After the restart:

```text
# primary file was overwritten with the truncated backup content
sha256(keyring) == sha256(keyring.backup) == <partial content hash>

Component_status  Disabled
Data_file         <NOT APPLICABLE>

[ERROR] [MY-015108] Component component_keyring_file reported:
  'The keyring file content is not in a valid ...'
```

The server itself still starts, but the keyring component is disabled and any
keyring-dependent operation (including InnoDB encryption) is unavailable.

## Expected result

The reader should validate the backup content (JSON schema and completeness)
before overwriting the primary file, and should prefer the primary file when the
backup is incomplete. The writer should fsync and atomically rename the live
file and remove the backup only after a successful sync.