Skip to content

Fix MySQL statement metrics crash on embedded null character - #25281

Draft
joeykelroy wants to merge 2 commits into
masterfrom
joey.kelroy/SDBM-2979-mysql-null-byte-obfuscation
Draft

joeykelroy wants to merge 2 commits into
masterfrom
joey.kelroy/SDBM-2979-mysql-null-byte-obfuscation

Conversation

@joeykelroy

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes a crash loop in the MySQL statement-metrics job when a query text contains an embedded null character (\x00).

_normalize_queries failed in two stages:

  1. obfuscate_sql_with_metadata raised ValueError: embedded null character on the row.
  2. The except handler logged the same raw text, and because AgentLogHandler.emit passes the formatted message into the Go binding, that log call raised the identical ValueError again.

The second exception escaped the handler and crashed the whole job loop, so the job restarted on every check run and crashed again rather than skipping the one bad row. No query metrics were collected at all.

This PR:

  • Passes replace_null_character=True, so the row is collected with the null character removed instead of being dropped. This reuses the existing base-check option added for SQL Server in [DBMON-3495] Replace embedded null characters in query text #16742.
  • Uses repr() for the offending text in the warning, so the error handler cannot itself raise.
  • Adds a regression test, and corrects the obfuscate_sql_with_metadata docstring, which described embedded null characters as a SQL Server trait.

Motivation

Reported in SDBM-2979: a customer's statement-metrics job crash-looped every 15s (344 crashes in the flare window) on MySQL 8.0.30, with complete loss of query metrics. Activity samples, query samples and metadata collection were all unaffected.

The null character does not come from events_statements_summary_by_digest. It cannot: literals are normalized to ?, comments are stripped, MySQL forbids U+0000 in identifiers, and unparseable statements get no digest row. I verified each of those.

It comes from performance_schema.prepared_statements_instances.sql_text, which #21425 unioned into the same query as AS digest_text. That column is raw, un-normalized statement text, so a null character in a literal survives. The alias meant unsafe data began flowing through a variable whose name implied the database had already normalized it, and _normalize_queries was not revisited.

This is also why only statement-metrics crashed: statement_samples.py and activity.py gate their raw-text log behind log_unobfuscated_queries (default off), so they take the safe debug branch. statements.py has no such gate.

Verified against a local reproduction (MySQL 8.0.30 + Agent 7.83.1, a null-bearing prepared statement created via COM_STMT_PREPARE and held open):

  • Shipped check 15.20.0: reproduces the customer's traceback exactly, crashing every 15s.
  • This branch, via ddev env start --dev: check [OK], statement-metrics job started once and never crashed, 378 query-metric submissions and climbing, with the null-bearing statement live and executing throughout.

The regression test fails without the fix ('abc\x00def' vs 'abcdef') and passes with it. Postgres is not affected: its wire protocol null-terminates strings, so a null character truncates the query into a syntax error and chr(0) is rejected outright, meaning it can never reach pg_stat_statements.query.

Review checklist (to be filled by reviewers)

  • Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
  • Add qa/required if this PR needs QA validation, or qa/skip-qa if it does not. Exactly one of the two is required.
  • If you need to backport this PR to another branch, you can add the backport/<branch-name> label to the PR and it will automatically open a backport PR once this one is merged

Rows unioned in from performance_schema.prepared_statements_instances carry raw
sql_text, which may contain an embedded null character. Obfuscation raised
ValueError on it, and the except handler then logged the same raw text, which
raised again inside the log handler and crashed the statement-metrics job loop.
The job restarted every check run and crashed again, so no query metrics were
collected at all.

Pass replace_null_character=True so the row is collected instead of dropped, and
use repr() when logging the offending text so the error handler cannot raise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@joeykelroy joeykelroy added the qa/skip-qa Automatically skip this PR for the next QA label Sep 18, 2026
@joeykelroy joeykelroy added qa/required QA is required for this PR and will generate a QA card and removed qa/skip-qa Automatically skip this PR for the next QA labels Sep 18, 2026
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dd-octo-sts

dd-octo-sts Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

❌ Dispatcher tests · failed

Dispatcher beta: informational only

Dispatcher is running alongside existing CI while we validate it. You can ignore this report and its statuses. Existing CI remains the merge signal.

Caution

Dispatcher tests failed. See the failures below.

  853/853 jobs
✅ 852 passed · ❌ 1 failed

Batches

BatchStateJobsWorkflow
batch-01✅ passed240/240run 35391301915
batch-02✅ passed215/215run 35391302145
batch-03✅ passed220/220run 35391302073
batch-04❌ failed178/178run 35391301883

❌ Failures

sqlserver / py3.13-linux-FreeTDS-2022-ha / linux   view job
No test-level failure was reported for this job.

⚠️ Unavailable results

  • sqlserver / py3.13-linux-FreeTDS-2022-ha / linux — no artifacts were downloaded for this job
Dispatcher finished on 22b7ed1GitHub Run.

@cit-pr-commenter-54b7da

cit-pr-commenter-54b7da Bot commented Sep 18, 2026

Copy link
Copy Markdown

evalya-impact-summary

evalya impact analysis
Impact analysis: 0 selected, 0 skipped (of 0 test tasks)
Publish tasks:   2 (always emitted)
Diff (5 files):
  datadog_checks_base/changelog.d/25281.fixed
  datadog_checks_base/datadog_checks/base/utils/db/utils.py
  mysql/changelog.d/25281.fixed
  mysql/datadog_checks/mysql/statements.py
  mysql/tests/test_statements.py

Debug a specific task: evalya plan impact --path <path> --task <task>

Learn more about CI impact filtering

@dd-octo-sts

dd-octo-sts Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Validation Report

All 21 validations passed.

Show details
Validation Description Status
agent-reqs Verify check versions match the Agent requirements file
ci Validate CI configuration and code coverage settings
codeowners Validate every integration has a CODEOWNERS entry
config Validate default configuration files against spec.yaml
dep Verify dependency pins are consistent and Agent-compatible
http Validate integrations use the HTTP wrapper correctly
imports Validate check imports do not use deprecated modules
integration-style Validate check code style conventions
jmx-metrics Validate JMX metrics definition files and config
labeler Validate PR labeler config matches integration directories
legacy-signature Validate no integration uses the legacy Agent check signature
license-headers Validate Python files have proper license headers
licenses Validate third-party license attribution list
metadata Validate metadata.csv metric definitions
models Validate configuration data models match spec.yaml
openmetrics Validate OpenMetrics integrations disable the metric limit
package Validate Python package metadata and naming
qa-label Validate the pull request declares whether it needs QA for the next Agent release
readmes Validate README files have required sections
saved-views Validate saved view JSON file structure and fields
version Validate version consistency between package and changelog

View full run

@datadog-datadog-prod-us1

datadog-datadog-prod-us1 Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Tests  Code Coverage

All CI checks and tests passed. Datadog automation helped this PR pass.

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🔄 Datadog retried 1 test - 1 passed on retry View in Datadog

🎯 Code Coverage (details)
Patch Coverage: 88.89%
Overall Coverage: 89.27% (-0.88%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 755d454 | Docs | View more details | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Disk usage change

Commit 982e710 compared against 0952d3d.

Uncompressed

linux-aarch64 linux-x86_64 macos-aarch64 macos-x86_64 windows-x86_64
🔺 +737 B 🔺 +737 B 🔺 +737 B 🔺 +737 B 🔺 +737 B
Details
Name Version Type linux-aarch64 linux-x86_64 macos-aarch64 macos-x86_64 windows-x86_64
mysql 15.21.0 Integration 🔺 +617 B 🔺 +617 B 🔺 +617 B 🔺 +617 B 🔺 +617 B
datadog_checks_base 38.3.2 Integration 🔺 +120 B 🔺 +120 B 🔺 +120 B 🔺 +120 B 🔺 +120 B

Compressed

linux-aarch64 linux-x86_64 macos-aarch64 macos-x86_64 windows-x86_64
🔺 +290 B 🔺 +290 B 🔺 +290 B 🔺 +290 B 🔺 +290 B
Details
Name Version Type linux-aarch64 linux-x86_64 macos-aarch64 macos-x86_64 windows-x86_64
mysql 15.21.0 Integration 🔺 +232 B 🔺 +232 B 🔺 +232 B 🔺 +232 B 🔺 +232 B
datadog_checks_base 38.3.2 Integration 🔺 +58 B 🔺 +58 B 🔺 +58 B 🔺 +58 B 🔺 +58 B

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant