wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it #28774

pull vasild wants to merge 1 commits into bitcoin:master from vasild:avoid_returning_reference_to_mutex_guarded_member changing 4 files +20 −8
  1. vasild commented at 1:01 pm on November 2, 2023: contributor

    CWallet::GetEncryptionKey() would return a reference to the internal CWallet::vMasterKey, guarded by CWallet::cs_wallet, which is unsafe.

    Returning a copy would be a shorter solution, but could have security implications of the master key remaining somewhere in the memory even after CWallet::Lock() (the current calls to CWallet::GetEncryptionKey() are safe, but that is not future proof).

    So, instead of EncryptSecret(m_storage.GetEncryptionKey(), ...) change the GetEncryptionKey() method to provide the encryption key to a given callback: m_storage.WithEncryptionKey([](const CKeyingMaterial& k) { EncryptSecret(k, ...); })

    This silences the following (clang 18):

    0wallet/wallet.cpp:3520:12: error: returning variable 'vMasterKey' by reference requires holding mutex 'cs_wallet' [-Werror,-Wthread-safety-reference-return]
    1 3520 |     return vMasterKey;
    2      |            ^
    

    Previously this PR modified both ArgsManager and wallet code. But the ArgsManager commit https://github.com/bitcoin/bitcoin/commit/856c88776f8486446602476a1c9e133ac0cff510 was merged in #29040 so now this only affects wallet code. The previous PR description was:

    Avoid this unsafe pattern from ArgsManager and CWallet:

    0class A
    1{
    2    Mutex mutex;
    3    Foo member GUARDED_BY(mutex);
    4    const Foo& Get()
    5    {
    6        LOCK(mutex);
    7        return member;
    8    } // callers of `Get()` will have access to `member` without owning the mutex.
    
  2. DrahtBot commented at 1:01 pm on November 2, 2023: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ryanofsky, furszy, achow101
    Stale ACK jonatack

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

    Conflicts

    No conflicts as of last run.

  3. DrahtBot added the label CI failed on Nov 2, 2023
  4. fanquake commented at 1:55 pm on November 2, 2023: member

    https://cirrus-ci.com/task/6687992417353728:

    0clang-tidy-17 -p=/ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu -quiet -load=/tidy-build/libbitcoin-tidy.so /ci_container_base/ci/scratch/build/bitcoin-x86_64-pc-linux-gnu/src/common/args.cpp
    1common/args.cpp:281:1: error: return type 'const fs::path' is 'const'-qualified at the top level, which may reduce code readability without improving const correctness [readability-const-return-type,-warnings-as-errors]
    2  281 | const fs::path ArgsManager::GetBlocksDirPath() const
    3      | ^~~~~
    
  5. vasild force-pushed on Nov 2, 2023
  6. vasild commented at 2:14 pm on November 2, 2023: contributor
    e13401084f...93cd65566a: fix #28774 (comment), thanks!
  7. maflcko commented at 2:37 pm on November 2, 2023: member
    I don’t think a mutex is required for read-only access to a const blob of memory?
  8. DrahtBot removed the label CI failed on Nov 2, 2023
  9. vasild commented at 8:00 am on November 3, 2023: contributor
    @maflcko, a mutex is required by readers to avoid writers modifying the blob while the read is happening, resulting in a torn read. The underlying variable is not readonly (ie declared as const), I explored converting the variables to const and only setting them at the constructor, but that didn’t seem viable.
  10. maflcko commented at 8:24 am on November 3, 2023: member

    No write will happen to the datadir cache after the first write, and no reader will have the reference before the first write, no? I am trying to say that the current code is fine. Otherwise, it would be good to add steps to reproduce UB.

    No objection to changing the code, but if this is worth it to change, it should be done for all places in the whole codebase, not just some places.

  11. vasild force-pushed on Nov 3, 2023
  12. vasild commented at 11:31 am on November 3, 2023: contributor

    No write will happen to the datadir cache after the first write, and no reader will have the reference before the first write, no?

    That is not obvious to me. m_cached_blocks_path can be modified by repeated calls to ArgsManager::GetBlocksDirPath() which end up executing:

    https://github.com/bitcoin/bitcoin/blob/9b68c9b85efebfa23daec6471b87e9cbb514a006/src/common/args.cpp#L293

    ArgsManager::GetBlocksDirPath() is called by init.cpp and by the GUI, so it is not immediately obvious that it cannot be called concurrently. There is also ArgsManager::ClearPathCache() which modifies the variable.

    I am trying to say that the current code is fine. Otherwise, it would be good to add steps to reproduce UB.

    Maybe the current code is fine. I did not dive to the bottom of it to say with certainty whether it is fine or not because what matters is that it is certainly unsafe. Unsafe wrt future changes in the same way as if we remove some GUARDED_BY() then the current code is fine now, but is unsafe wrt future changes.

    No objection to changing the code, but if this is worth it to change, it should be done for all places in the whole codebase, not just some places.

    These are all reported (by clang 18). I see ArgsManager::GetDataDir() has the same issue. Maybe it was not detected because of the ?: operator. I have extended the first commit to cover that too.

  13. vasild commented at 7:39 am on November 27, 2023: contributor
    There is the argument to not bother silencing compiler warnings from unreleased/development versions of compilers. I somewhat agree with that if it is being done for the sake of silencing a warning. In this case however, the problem is real and the warning is just the messenger. I think this deserves to be fixed even if the warning did not exist.
  14. jonatack approved
  15. jonatack commented at 4:09 pm on November 27, 2023: contributor
    ACK d4ef7004050c7ee8ac75ae895511334b62890f1b
  16. maflcko commented at 12:29 pm on December 9, 2023: member

    No objection to changing the code, but if this is worth it to change, it should be done for all places in the whole codebase, not just some places.

    These are all reported (by clang 18). I see ArgsManager::GetDataDir() has the same issue. Maybe it was not detected because of the ?: operator. I have extended the first commit to cover that too.

    Thanks, I’ve taken the commit and put it in #29040, because it fits into the other fs::path changes. Let me know if I should drop it and you prefer to keep it here.

    The fs::path should be fine to do, even if it is not needed. (Creating a copy here should be harmless and not cause performance issues).

    I haven’t looked at the wallet commit.

  17. vasild commented at 10:17 am on December 11, 2023: contributor

    Let me know if I should drop it and you prefer to keep it here

    It is ok. I don’t have a preference whether it gets merged via #29040 of via this PR. Just that it makes it to master ;-) Thanks!

  18. jonatack commented at 8:40 pm on January 5, 2024: contributor

    Let me know if I should drop it and you prefer to keep it here

    It is ok. I don’t have a preference whether it gets merged via #29040 of via this PR. Just that it makes it to master ;-) Thanks!

    Need rebase, following merge of first commit cba94d151757a4e69b6eb684ae09bc6a4ea530d5 in #29040?

  19. vasild force-pushed on Jan 6, 2024
  20. vasild commented at 4:02 pm on January 6, 2024: contributor
    d4ef700405...7a8e5310e7: rebase as pointed about above by @jonatack. Thanks!
  21. vasild commented at 2:55 pm on January 16, 2024: contributor
    @achow101, this is now solely in the wallet. Maybe you want to take a look?
  22. DrahtBot added the label CI failed on Jan 16, 2024
  23. DrahtBot added the label Needs rebase on Jan 17, 2024
  24. in src/wallet/scriptpubkeyman.h:50 in 7a8e5310e7 outdated
    45@@ -46,7 +46,8 @@ class WalletStorage
    46     virtual void UnsetBlankWalletFlag(WalletBatch&) = 0;
    47     virtual bool CanSupportFeature(enum WalletFeature) const = 0;
    48     virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr) = 0;
    49-    virtual const CKeyingMaterial& GetEncryptionKey() const = 0;
    50+    virtual bool EncryptSecret(const CKeyingMaterial& plaintext, const uint256& iv, std::vector<unsigned char>& ciphertext) const = 0;
    51+    virtual bool DecryptKey(const std::vector<unsigned char>& crypted_secret, const CPubKey& pub_key, CKey& key) const = 0;
    


    furszy commented at 9:10 pm on January 17, 2024:
    These methods doesn’t seems to fit inside the WalletStorage interface. What do you think about creating a new interface for encrypting/decrypting data, then provide it to each spkm?

    vasild commented at 5:23 pm on January 18, 2024:
    Right, those methods were not very natural for the WalletStorage interface which contained a method to retrieve the encryption key. Adding a new interface seems a bit too complicated for this. Instead, I changed it to give the key to a callback function instead of returning the key to the caller.
  25. wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it
    `CWallet::GetEncryptionKey()` would return a reference to the internal
    `CWallet::vMasterKey`, guarded by `CWallet::cs_wallet`, which is unsafe.
    
    Returning a copy would be a shorter solution, but could have security
    implications of the master key remaining somewhere in the memory even
    after `CWallet::Lock()` (the current calls to
    `CWallet::GetEncryptionKey()` are safe, but that is not future proof).
    
    So, instead of `EncryptSecret(m_storage.GetEncryptionKey(), ...)`
    change the `GetEncryptionKey()` method to provide the encryption
    key to a given callback:
    `m_storage.WithEncryptionKey([](const CKeyingMaterial& k) { EncryptSecret(k, ...); })`
    
    This silences the following (clang 18):
    
    ```
    wallet/wallet.cpp:3520:12: error: returning variable 'vMasterKey' by reference requires holding mutex 'cs_wallet' [-Werror,-Wthread-safety-reference-return]
     3520 |     return vMasterKey;
          |            ^
    ```
    32a9f13cb8
  26. vasild force-pushed on Jan 18, 2024
  27. vasild commented at 5:19 pm on January 18, 2024: contributor
    7a8e5310e7...32a9f13cb8: rebase due to conflicts and change the approach to use a callback, see #28774 (review).
  28. DrahtBot removed the label Needs rebase on Jan 18, 2024
  29. DrahtBot removed the label CI failed on Jan 18, 2024
  30. ryanofsky approved
  31. ryanofsky commented at 8:48 pm on January 18, 2024: contributor

    Code review ACK 32a9f13cb805ecf6aebb5cf4e5d92b8a47c4548b. This seems like a potentially real race condition, and the fix here is pretty simple.

    I do think the PR title and description are confusing and probably causing this PR to get overlooked.

    Would suggest replacing PR title and description with the title and description from the 32a9f13cb805ecf6aebb5cf4e5d92b8a47c4548b commit message which are much clearer. To make the PR comment history legible, you could include the original description below the new description maybe separated with a horizontal line and a comment like “Previously this PR modified both ArgsManager and wallet code. But the ArgsManager commit 856c88776f8486446602476a1c9e133ac0cff510 was merged in #29040 so now this only affects wallet code. The previous PR description was: …”

  32. DrahtBot requested review from jonatack on Jan 18, 2024
  33. in src/wallet/wallet.cpp:3519 in 32a9f13cb8
    3512@@ -3513,9 +3513,10 @@ void CWallet::SetupLegacyScriptPubKeyMan()
    3513     AddScriptPubKeyMan(id, std::move(spk_manager));
    3514 }
    3515 
    3516-const CKeyingMaterial& CWallet::GetEncryptionKey() const
    3517+bool CWallet::WithEncryptionKey(std::function<bool (const CKeyingMaterial&)> cb) const
    3518 {
    3519-    return vMasterKey;
    3520+    LOCK(cs_wallet);
    3521+    return cb(vMasterKey);
    


    furszy commented at 9:08 pm on January 18, 2024:

    nit: Could inline it

    0return WITH_LOCK(cs_wallet, return cb(vMasterKey));
    

    Also, could pass cb as a const ref.


    vasild commented at 9:09 am on January 19, 2024:
    Will do if I retouch.
  34. furszy commented at 9:12 pm on January 18, 2024: member
    ACK 32a9f13c
  35. vasild renamed this:
    Avoid returning references to mutex guarded members
    wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it
    on Jan 19, 2024
  36. DrahtBot added the label Wallet on Jan 19, 2024
  37. vasild commented at 8:45 am on January 19, 2024: contributor

    Would suggest replacing PR title and description with…

    Done. Thanks for the suggestion!

  38. ryanofsky requested review from achow101 on Jan 19, 2024
  39. achow101 commented at 7:50 pm on January 23, 2024: member
    ACK 32a9f13cb805ecf6aebb5cf4e5d92b8a47c4548b
  40. DrahtBot removed review request from achow101 on Jan 23, 2024
  41. achow101 merged this on Jan 23, 2024
  42. achow101 closed this on Jan 23, 2024

  43. vasild deleted the branch on Jan 24, 2024

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2024-09-28 22:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me