Bug #121600 Auto-generated TLS certificate/key files are written non-atomically and a truncated file is trusted on restart
Submitted: 25 Oct 15:08
Reporter: Wwwwing Wwwwing Email Updates:
Status: Open Impact on me:
None 
Category:MySQL Server: Connection Handling Severity:S3 (Non-critical)
Version:MySQL 8.4.11 official image OS:Any
Assigned to: CPU Architecture:Any

[25 Oct 15:08] Wwwwing Wwwwing
Description:
When `--auto-generate-certs` (default ON) creates `ca.pem`, `server-cert.pem` and
`server-key.pem`, the X.509 path writes directly to the final filenames. The
private-key file is created before the RSA key is generated, and the
certificate file is written in place with no temporary file, fsync or rename.
A crash, kill or ENOSPC inside that window leaves a zero-byte or truncated
certificate/key file at the default path.

On the next start, `auto_detect_ssl()` only calls `my_stat()` on the three
default files. If all three exist, it reports `SSL_ARTIFACTS_AUTO_DETECTED`,
and `do_auto_cert_generation()` returns success without parsing, validating or
regenerating them. The later `vio_set_cert_stuff()` call then fails to parse the
truncated file, so TLS is not configured.

How to repeat:
Use the official image and a private data directory (all commands below are
text-only; no source build needed):

```bash
IMG=mysql@sha256:85b9bf2e29cf836ecb8c2a15a935d4ba0c606631dff1dd79531a11983c638f2a
DATA=$(mktemp -d); chown 999:999 "$DATA"
docker run -d --name my51 --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 \
  --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 the server is ready, then stop it
docker exec my51 mysqladmin -uroot --socket=/var/run/mysqld/mysqld.sock ping
docker stop my51

# simulate an interrupted generation: truncate the already-present certificate
truncate -s 120 "$DATA/server-cert.pem"

docker start my51
sleep 20
docker exec my51 mysqladmin -uroot --socket=/var/run/mysqld/mysqld.sock ping
tail -40 "$DATA/error.log"
```

## Actual result

The server starts and answers `mysqladmin ping`, but TLS initialization fails
and the service continues without usable TLS:

```text
[Note] [MY-010182] Found ca.pem, server-cert.pem and server-key.pem in data directory. Trying to enable SSL support using them.
[Note] [MY-010304] Skipping generation of SSL certificates as certificate files are present in data directory.
[ERROR] [MY-000059] SSL error: Unable to get certificate from 'server-cert.pem'.
[Warning] [MY-010069] Failed to set up SSL because of the following SSL library error: Unable to get certificate
[Warning] [MY-013595] Failed to initialize TLS for channel: mysql_main.
[Note] [MY-011242] Plugin mysqlx reported: 'Neither MySQL Server nor Mysqlx Plugin has valid SSL configuration'
```

## Expected result

The server should not trust an unparsable certificate file. It should either
regenerate the auto-generated artifacts or fail startup with a clear error, and
the generation itself should be crash-safe (temporary files + fsync + rename).

## Impact

Availability and TLS integrity after a crash/power loss during first-start
certificate generation. The same truncated files are reused on every subsequent
start, so TLS stays unavailable (or startup fails when
`tls_certificates_enforced_validation=ON`) until an administrator deletes and
regenerates the files. No key disclosure: the files are 0600 and truncation does
not expose new material. Not attacker-triggered by a remote user.

## Source analysis

- `sql/auth/sql_authentication.cc:5475` creates the key file on the final path
  before `rsa_gen()` runs; `:5561` writes the certificate on the final path via
  `File_IO::operator<<` (write+sync+close) with no temp file or rename.
- The same file's `create_RSA_key_pair()` (`:5611`, `:5689-5692`) does use
  `.temp` names plus rename, so the X.509 path is an unpatched sibling.
- `sql/ssl_init_callback.cc:434-446` detects artifacts only with `my_stat()`;
  `sql/auth/sql_authentication.cc:5765-5770` accepts AUTO_DETECTED without
  parsing them (the fix for the related parsing bug only touched
  `vio/viosslfactories.cc` and tests).
- `sql/ssl_acceptor_context_data.cc:319` then calls `vio_set_cert_stuff()`;
  the parse failure leaves `ssl_acceptor_fd_` null.

Suggested fix:

Use the same temporary-file + `my_sync` + rename + cleanup path for the X.509
certificate and key generation that `create_RSA_key_pair()` already uses; on
`SSL_ARTIFACTS_AUTO_DETECTED`, parse the files (or run a validity check) before
declaring the certificates usable.