Skip to content

Validate data loaded from SQLite to prevent crashes on corrupted databases - #22679

Merged
stelfrag merged 9 commits into
netdata:masterfrom
stelfrag:fix-sqlite-load-validation
Jun 15, 2026
Merged

Validate data loaded from SQLite to prevent crashes on corrupted databases#22679
stelfrag merged 9 commits into
netdata:masterfrom
stelfrag:fix-sqlite-load-validation

Conversation

@stelfrag

@stelfrag stelfrag commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator
Summary

SQLite enforces schema constraints (NOT NULL, types, lengths) only at write time. When reading a database damaged by storage failures or modified outside the agent, any column can return SQL NULL, an unexpected type, or a blob of any length.

An audit of every sqlite3_column_* read site found seven places that dereference such values unvalidated, crashing the agent — three of them during startup, turning one bad row into a permanent boot loop.

All fixes route through validation helpers that already exist in the codebase.
Behavior for valid data is unchanged; malformed rows are skipped instead of crashing.

  • Host system info — a single bad row in the host_info table crashed the agent on every startup. Bad rows are now ignored.
  • Chart type — a context entry with a missing chart type crashed startup; it now falls back to the default (line).
  • Alert push to Cloud — alert log entries with a missing chart or alert name crashed the agent each time it tried to send them to Netdata Cloud; they are now sent with empty values instead.
  • remove-stale-node CLI command — a host entry with a missing or truncated host ID crashed the command; such entries are now skipped.
  • sqlite-alert-cleanup CLI command — same problem; the lookup was also reworked so the host ID can be fully validated before use.
  • Database schema upgrades — corrupted table names in the database catalog crashed the upgrade that runs at startup; they are now skipped.
  • Context loading — context entries saved without a version could leave the agent reading freed memory later; they are now ignored at load.

Summary by cubic

Validate and sanitize SQLite reads to prevent crashes on corrupted or externally modified databases. Invalid rows are skipped or defaulted, fixing startup loops and runtime failures without changing behavior for valid data; also simplifies migration code for safer upgrades.

  • Bug Fixes

    • Host system info: ignore rows with null key/value; fix startup crash.
    • Chart type: default to line when missing; fix startup crash.
    • Context loading: skip entries without a version to avoid dangling pointers.
    • Alert push to Cloud: allow missing chart/name by sending empty strings.
    • remove-stale-node CLI: skip invalid/truncated host_id UUIDs.
    • sqlite-alert-cleanup CLI: skip hosts with invalid host_id; log and continue.
    • Migrations: skip null/invalid table and index names during schema upgrades.
  • Refactors

    • Replaced sqlite3_exec callback in alert cleanup with prepared statements for clearer control flow and errors.
    • Centralized use of validation helpers for SQLite reads (e.g., safe UUID/text extraction).
    • Alert cleanup: log non-SQLITE_DONE results for clearer failures.
    • Migrations: extracted reusable helpers to alter/analyze/drop across matching tables/indexes, reducing duplication and improving safety.

Written for commit 6f61354. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Confidence score: 3/5

  • In src/database/sqlite/sqlite_health.c, sql_alert_cleanup() stops checking sqlite3_step return codes, so failed cleanup queries can be reported as successful; that can hide real database health problems and mislead operators after merge — restore explicit sqlite3_step error handling (and failure logging/propagation) before merging.
Architecture diagram
sequenceDiagram
    participant Agent as Agent (Startup/Runtime)
    participant SQLite as SQLite Database
    participant Valid as Validation Helpers
    participant CLI as CLI Commands
    participant Cloud as Netdata Cloud

    Agent->>SQLite: Read host_info table
    SQLite-->>Agent: (key, value) row
    alt Invalid row (NULL key/value)
        Agent->>Agent: NEW: Skip row (ignore corrupted data)
    else Valid row
        Agent->>Agent: Process system info
    end

    Agent->>SQLite: Read chart type from context
    SQLite-->>Agent: chart_type string (possibly NULL)
    alt Missing chart type
        Agent->>Agent: NEW: Default to RRDSET_TYPE_LINE
    else Valid type
        Agent->>Agent: Use provided chart type
    end

    Agent->>SQLite: Load context entries (rrdcontext_load_context_callback)
    SQLite-->>Agent: VERSIONED_CONTEXT_DATA with hub.version
    alt Version not set (0)
        Agent->>Agent: NEW: Skip entry (avoid dangling pointers)
    else Version set
        Agent->>Agent: Process context normally
    end

    Agent->>SQLite: Read schema migration table names/indexes
    SQLite-->>Agent: table_name (possibly NULL)
    alt NULL table name
        Agent->>Agent: NEW: Skip corrupted entry
    else Valid name
        Agent->>Agent: Apply schema upgrade
    end

    Agent->>SQLite: Read alert log entries for Cloud push
    SQLite-->>Agent: chart, name columns (possibly NULL)
    alt Missing chart or name
        Agent->>Agent: NEW: strdupz("") for empty values
        Agent->>Cloud: Send alert with empty fields
    else Valid chart/name
        Agent->>Cloud: Send alert normally
    end

    CLI->>SQLite: remove-stale-node: Read host entries
    SQLite-->>CLI: host_id UUID blob
    alt Invalid or truncated UUID
        CLI->>CLI: NEW: Skip entry (continue loop)
    else Valid UUID
        CLI->>Agent: Process host removal
    end

    CLI->>SQLite: sqlite-alert-cleanup: Read host entries
    SQLite-->>CLI: host_id, hostname columns
    alt Invalid host_id (uuid_copy fails)
        CLI->>CLI: NEW: Log "skipping host", continue
        CLI->>CLI: Loop to next row
    else Valid host_id
        CLI->>CLI: Process alert cleanup for host
    end

    Note over Agent,SQLite: All reads now use safe validation helpers<br/>(sqlite3_column_uuid_copy, sqlite3_text_strdupz_empty,<br/>null checks) before dereferencing
Loading

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/database/sqlite/sqlite_health.c Outdated
@sonarqubecloud

Copy link
Copy Markdown

@stelfrag
stelfrag marked this pull request as ready for review June 12, 2026 08:18
@stelfrag

Copy link
Copy Markdown
Collaborator Author

@cubic-dev-ai review PR

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai review PR

@stelfrag I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 8 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.
Architecture diagram
sequenceDiagram
    participant Agent as Netdata Agent
    participant SQLite as SQLite Database
    participant FS as File System
    participant Cloud as Netdata Cloud

    Note over Agent,Cloud: READ FLOW: Corrupted SQLite rows handled safely

    Agent->>SQLite: SELECT system_key, system_value FROM host_info
    SQLite-->>Agent: Row with NULL key/value (corrupted)
    alt Valid row (key AND value NOT NULL)
        Agent->>Agent: rrdhost_system_info_set_by_name(key, value)
    else NEW: NULL key or value
        Agent->>Agent: Skip bad row → no crash
    end

    Agent->>SQLite: SELECT type FROM context_entry
    SQLite-->>Agent: NULL type (corrupted)
    alt Valid type
        Agent->>Agent: rrdset_type_id(type)
    else NEW: NULL type
        Agent->>Agent: Return RRDSET_TYPE_LINE (default)
    end

    Agent->>SQLite: SELECT version, id, ... FROM context_data
    SQLite-->>Agent: Row with version = 0 (unset)
    alt version > 0
        Agent->>Agent: rrdcontext_load_context_callback() — copy strings
    else NEW: version == 0
        Agent->>Agent: Skip row → no dangling pointer
    end

    Agent->>SQLite: SELECT chart, name, ... FROM alert_log
    SQLite-->>Agent: Row with NULL chart or name
    alt Valid chart/name
        Agent->>Agent: strdupz(chart), strdupz(name)
    else CHANGED: NULL chart or name
        Agent->>Agent: sqlite3_text_strdupz_empty() → empty string
        Agent->>Cloud: Send alert with empty chart/name
    end

    Note over Agent,SQLite: READ FLOW: CLI commands safe against corrupted host_id

    Agent->>SQLite: SELECT host_id, hostname FROM host
    SQLite-->>Agent: Row with invalid/truncated host_id
    alt Valid UUID
        Agent->>Agent: uuid_unparse_lower() → process host
    else NEW: Invalid UUID
        Agent->>Agent: Skip host → log warning, no crash
    end

    Note over Agent,SQLite: MIGRATION FLOW: Schema upgrades skip corrupted metadata

    SQLite->>FS: Read sqlite_schema for table/index names
    alt Valid name (non-NULL text)
        Agent->>SQLite: ALTER TABLE/ANALYZE/DROP INDEX
    else NEW: NULL name (corrupted catalog)
        Agent->>Agent: Skip bad table/index → migration continues
    end
Loading

Re-trigger cubic

@thiagoftsm thiagoftsm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issue found during runtime. LGTM!

@stelfrag
stelfrag merged commit fbf1845 into netdata:master Jun 15, 2026
157 checks passed
@stelfrag
stelfrag deleted the fix-sqlite-load-validation branch June 15, 2026 18:32
@stelfrag stelfrag mentioned this pull request Jun 22, 2026
Ferroin pushed a commit that referenced this pull request Jul 15, 2026
…bases (#22679)

* Add null checks for `name` and `value` in `rrdhost_system_info_set_by_name` and update `SELECT_HOST_INFO` to exclude null keys/values

* Add null check for `name` in `rrdset_type_id` to prevent undefined behavior

* Replace `strdupz` logic with `sqlite3_text_strdupz_empty` for safer handling of null database values

* Add UUID parsing safeguard to skip invalid rows in ephemeral host cleanup loop

* Refactor alert cleanup to remove `sqlite3_exec` callback and use prepared statements for better error handling

* Add null checks for `name` in SQLite migration loops to prevent undefined behavior

* Add safeguard to skip SQLite insert callback for rows without version set

* Add error handling for alert cleanup by logging non-SQLITE_DONE results in SQLite loops

* Refactor SQLite migration logic to centralize repetitive table and index operations into reusable helpers

(cherry picked from commit fbf1845)
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.

2 participants