Merge bitcoin/bitcoin#26594: wallet: Avoid a segfault in migratewallet failure cleanup

5e65a216d1 wallet: Explicitly say migratewallet on encrypted wallets is unsupported (Andrew Chow)
88afc73ae0 tests: Test for migrating encrypted wallets (Andrew Chow)
86ef7b3c7b wallet: Avoid null pointer deref when cleaning up migratewallet (Andrew Chow)

Pull request description:

  When `migratewallet` fails, we do an automatic cleanup in order to reset everything so that the user does not experience any interruptions. However, this apparently has a segfault in it, caused by the the pointers to the watchonly and solvables wallets being nullptr. If those wallets are not created (either not needed, or failed early on), we will accidentally attempt to dereference these nullptrs, which causes a segfault.

  This failure can be easily reached by trying to migrate an encrypted wallet. Currently, we can't migrate encrypted wallets because of how we unload wallets before migrating, and therefore forget the encryption key if the wallet was unlocked. So any encrypted wallets will fail, entering the cleanup, and because watchonly and solvables wallets don't exist yet, the segfault is reached.

  This PR fixes this by not putting those nullptrs in a place that we will end up dereferencing them later. It also adds a test that uses the encrypted wallet issue.

ACKs for top commit:
  S3RK:
    reACK 5e65a216d1
  stickies-v:
    ACK [5e65a21](5e65a216d1)
  furszy:
    diff ACK 5e65a21

Tree-SHA512: f75643797220d4232ad3ab8cb4b46d0f3667f00486e910ca748c9b6d174d446968f1ec4dd7f907da1be9566088849da7edcd8cd8f12de671c3241b513deb8e80
This commit is contained in:
fanquake 2022-12-01 10:16:58 +00:00
commit e334f7a545
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
3 changed files with 15 additions and 3 deletions

View file

@ -730,7 +730,9 @@ static RPCHelpMan migratewallet()
std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request); std::shared_ptr<CWallet> wallet = GetWalletForJSONRPCRequest(request);
if (!wallet) return NullUniValue; if (!wallet) return NullUniValue;
EnsureWalletIsUnlocked(*wallet); if (wallet->IsCrypted()) {
throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: migratewallet on encrypted wallets is currently unsupported.");
}
WalletContext& context = EnsureWalletContext(request.context); WalletContext& context = EnsureWalletContext(request.context);

View file

@ -4182,8 +4182,8 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(std::shared_ptr<CWallet>
// Make list of wallets to cleanup // Make list of wallets to cleanup
std::vector<std::shared_ptr<CWallet>> created_wallets; std::vector<std::shared_ptr<CWallet>> created_wallets;
created_wallets.push_back(std::move(res.watchonly_wallet)); if (res.watchonly_wallet) created_wallets.push_back(std::move(res.watchonly_wallet));
created_wallets.push_back(std::move(res.solvables_wallet)); if (res.solvables_wallet) created_wallets.push_back(std::move(res.solvables_wallet));
// Get the directories to remove after unloading // Get the directories to remove after unloading
for (std::shared_ptr<CWallet>& w : created_wallets) { for (std::shared_ptr<CWallet>& w : created_wallets) {

View file

@ -396,6 +396,15 @@ class WalletMigrationTest(BitcoinTestFramework):
assert_equal(bals, wallet.getbalances()) assert_equal(bals, wallet.getbalances())
def test_encrypted(self):
self.log.info("Test migration of an encrypted wallet")
wallet = self.create_legacy_wallet("encrypted")
wallet.encryptwallet("pass")
assert_raises_rpc_error(-15, "Error: migratewallet on encrypted wallets is currently unsupported.", wallet.migratewallet)
# TODO: Fix migratewallet so that we can actually migrate encrypted wallets
def run_test(self): def run_test(self):
self.generate(self.nodes[0], 101) self.generate(self.nodes[0], 101)
@ -405,6 +414,7 @@ class WalletMigrationTest(BitcoinTestFramework):
self.test_other_watchonly() self.test_other_watchonly()
self.test_no_privkeys() self.test_no_privkeys()
self.test_pk_coinbases() self.test_pk_coinbases()
self.test_encrypted()
if __name__ == '__main__': if __name__ == '__main__':
WalletMigrationTest().main() WalletMigrationTest().main()