Skip to content

Don't restore cached backup password (bkpw) from backup file#653

Open
dmonakhov wants to merge 1 commit into
Coldcard:masterfrom
dmonakhov:bkpw-backup-restore-fix
Open

Don't restore cached backup password (bkpw) from backup file#653
dmonakhov wants to merge 1 commit into
Coldcard:masterfrom
dmonakhov:bkpw-backup-restore-fix

Conversation

@dmonakhov
Copy link
Copy Markdown
Contributor

Summary

render_backup_contents() deliberately strips the cached backup password
bkpw from generated backups, but restore_from_dict_ll() has no matching
filter and re-imports any setting.bkpw line from a backup file. A crafted or
tampered backup with setting.bkpw = "<known words>" thus plants an attacker-
known backup password on restore. bkpw is a MASTER_FIELD, so it persists,
and make_complete_backup then offers it as the default next-backup password
(quiz skipped, only a first/last-word hint shown). Result: all FUTURE backups
are silently encrypted with a password the attacker knows. Password fixation;
bypasses an existing precaution.

Affected

AFAIU all versions are affected, at least

Impact

Tampering a backup only needs its decryption password, which already exposes
the master secret inside - so the master seed is not what this bug leaks. But now days cold card state is more than just master seed.

The fixated, attacker-known password is reused for all FUTURE backups, leaking
secrets added AFTER restore that the attacker did not know at craft time like

  • SeedVaults ( affected models: MK4,5 Q1)
  • SecretNotes( only Q1 affected) ,

Fix

Mirror the write-side strip in restore_from_dict_ll, beside sd2fa/ccc:

        if k == 'bkpw':
            # never import a cached backup password from a backup file;
            # a present value means a tampered file fixating future backups.
            continue

Legitimate backups never contain bkpw, so this only drops injected values.
Tests already assert bkpw absent after restore (test_backup.py:377,456), so
no breakage.

Reproduction (simulator)

  1. Add setting.bkpw = [12 known words] before # EOF in a decrypted backup.
  2. Restore (start simulatore w/o seed ./simulator.py --q1 -l, Import Existing -> Restore Backup
  3. After reboot settings.get('bkpw') returns the injected value; a new Backup
    defaults to those words. With the fix: bkpw absent, fresh random password.

@dmonakhov dmonakhov force-pushed the bkpw-backup-restore-fix branch from c03d892 to 16903b9 Compare June 2, 2026 08:49
@scgbckbone
Copy link
Copy Markdown
Collaborator

this requires attacker to know backup password - which is game over anyways.

I do not mind to merge it, but remove the test - it is hacky...

Restore mirrored the write-side strip of bkpw: a crafted backup could inject
setting.bkpw and fixate the password used for future backups. Drop it on restore
@dmonakhov dmonakhov closed this Jun 2, 2026
@dmonakhov dmonakhov force-pushed the bkpw-backup-restore-fix branch from 16903b9 to ca06dfd Compare June 2, 2026 16:14
@dmonakhov dmonakhov reopened this Jun 2, 2026
@dmonakhov
Copy link
Copy Markdown
Contributor Author

Ack. Drop regression test.

this requires attacker to know backup password - which is game over anyways.

Agree that knowing backup password is already a critical, but situation can be worse.
In fact CC-Q opens wide range of use-cases where master key may not be that important as in normal case
One of killer features for me is avaryday airgap password storage (via secure notes), In this case storing important private key on such device is not reasonable, so it contains just a dummy master key

@doc-hex
Copy link
Copy Markdown
Contributor

doc-hex commented Jun 2, 2026

add a line to Next-Changelog.md linking to your github handle, and I will merge.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants