Skip to content

sqldb: guard native-SQL migration path against silent downgrades#10964

Open
kpshukla wants to merge 2 commits into
lightningnetwork:masterfrom
kpshukla:fix/sqldb-downgrade-guard
Open

sqldb: guard native-SQL migration path against silent downgrades#10964
kpshukla wants to merge 2 commits into
lightningnetwork:masterfrom
kpshukla:fix/sqldb-downgrade-guard

Conversation

@kpshukla

Copy link
Copy Markdown

Change Description

Fixes #10781.

sqldb.ApplyMigrations reads the migration version tracked in the
migration_tracker table and skips every configured migration whose
Version is <= currentVersion. There is no check for the reverse case:
if currentVersion is higher than anything the running binary's
migrationConfig knows about, every migration is silently skipped and
lnd proceeds to operate against a schema it doesn't understand.

This can happen if a user upgrades to a newer lnd release (which
advances migration_tracker forward), then accidentally relaunches an
older binary against the same database. The older binary has no way of
knowing it's behind, and unlike channeldb's kvdb meta store (which
already guards against this via ErrDBReversion), the native-SQL store
had no equivalent protection.

Note: sqldb/v2/migrations.go (an in-progress, not-yet-wired-in
package) already implements the same kind of guard for schema-level
downgrades (ErrMigrationDowngrade). This PR ports that idiom to the
currently-in-use sqldb package's migration_tracker-level version
check, which is what the linked issue is specifically about.

Fix

Added ErrDBDowngrade and a check in ApplyMigrations that compares
currentVersion against the highest version the binary's
migrationConfig contains (len(migrations), which is safe because the
function already validates that migration versions are numbered
contiguously from 1). If the database is ahead, ApplyMigrations now
fails loudly instead of silently skipping every migration.

Scope note

The issue also proposed a second, complementary change: bumping
channeldb's mandatory kvdb version with a no-op entry, so that even a
pre-fix binary refuses to start against a database a newer release
already touched (closing the gap for binaries that predate this PR).
I intentionally left that out of this PR — it affects every lnd node
on upgrade (not just native-SQL-store users) and "spends" a kvdb version
slot permanently, which felt like a call best left to a maintainer
rather than bundled unilaterally into a bug-fix PR. Happy to follow up
with that change if maintainers want it.

Testing

  • go build ./... and go vet ./... clean in the sqldb module.
  • Added TestApplyMigrationsRefusesDowngrade: migrates a fresh database
    forward using a 3-entry migration list (simulating a "newer" binary),
    then reopens the same database file/instance and calls
    ApplyAllMigrations with only a 2-entry list (simulating an "older"
    binary). Asserts ErrDBDowngrade is returned and that the tracked
    migration version is left untouched. Covered for both SQLite and
    Postgres backends.
  • Ran the full existing migration suite (TestMigrations,
    TestCustomMigration, TestMigrationSucceedsAfterDirtyStateMigrationFailure19RC1,
    TestMigrationConfigConsistency) plus the new test under go test -race. All SQLite-backed and non-DB cases pass. Postgres-backed cases
    in this suite require a local Docker daemon, which wasn't available in
    my dev environment (docker info confirms), so those specific
    subtests couldn't be locally exercised — this is a pre-existing
    environment limitation, not something introduced by this change, and
    CI should cover the Postgres path.
  • gofmt -l clean; all added lines respect the repo's 80-column
    convention.

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

kpshukla added a commit to kpshukla/lnd that referenced this pull request Jul 11, 2026
@github-actions github-actions Bot added the severity-critical Requires expert review - security/consensus critical label Jul 11, 2026
@github-actions

Copy link
Copy Markdown

🔴 PR Severity: CRITICAL

gh pr view | 3 files | 161 lines changed

🔴 Critical (1 file)
  • sqldb/migrations.go - Database migration file; per policy, changes under sqldb/* (and channeldb/migration*, wtdb/*) are always classified CRITICAL regardless of size, since they affect on-disk schema/state for all nodes.
🟢 Low (2 files)
  • sqldb/migrations_test.go - Test-only file (*_test.go), excluded from severity-driving classification and from the file/line bump count.
  • docs/release-notes/release-notes-0.22.0.md - Documentation/release notes.

Analysis

The only non-test, non-doc file touched is sqldb/migrations.go, which adds a new migration (31 lines). Database migrations are explicitly always CRITICAL per policy, since they run against every node's on-disk database and are difficult to safely revert if something goes wrong. The accompanying migrations_test.go changes (121 lines) are test-only and don't affect the severity determination, and the release-notes update is informational.

Total non-test/non-doc lines changed (40) and file count (1) are both well below the bump thresholds (>500 lines / >20 files), so no additional severity bump applies — the CRITICAL classification here comes solely from the migration-file rule, not from size.

Given this touches migration logic, recommend review from someone familiar with the sqldb migration framework and testing on both fresh and upgraded databases.


To override, add a severity-override-{critical,high,medium,low} label.

kpshukla added 2 commits July 11, 2026 14:13
ApplyMigrations skips any migration whose Version is <= the database's
tracked migration_tracker version. If that tracked version is higher
than anything the running binary's migrationConfig knows about (e.g.
an older lnd binary started against a database already migrated
forward by a newer release), every migration in the loop gets skipped
silently and lnd proceeds to operate against a schema it doesn't
understand, risking data corruption on writes.

Add an explicit guard that compares the tracked version against the
highest version known to the binary and fails with the new
ErrDBDowngrade sentinel error instead of silently skipping.

Fixes lightningnetwork#10781

Tested: go build/vet clean; go test ./... in the sqldb module passes
for all SQLite-backed cases, including the new
TestApplyMigrationsRefusesDowngrade (Postgres-backed cases in the
same suite require a local Docker daemon, unavailable in this
environment, and are unaffected by this change).
@kpshukla kpshukla force-pushed the fix/sqldb-downgrade-guard branch from 648e878 to cf328f9 Compare July 11, 2026 21:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

severity-critical Requires expert review - security/consensus critical

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sqldb: native-SQL store version is not guarded against downgrades

1 participant