Skip to content

Conversation

@vasild
Copy link
Contributor

@vasild vasild commented Nov 2, 2023

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;
      |            ^

Previously this PR modified both ArgsManager and wallet code. But the ArgsManager commit 856c887 was merged in #29040 so now this only affects wallet code. The previous PR description was:

Avoid this unsafe pattern from ArgsManager and CWallet:

class A
{
    Mutex mutex;
    Foo member GUARDED_BY(mutex);
    const Foo& Get()
    {
        LOCK(mutex);
        return member;
    } // callers of `Get()` will have access to `member` without owning the mutex.

@DrahtBot
Copy link
Contributor

DrahtBot commented Nov 2, 2023

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.

@fanquake
Copy link
Member

fanquake commented Nov 2, 2023

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

clang-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
common/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]
  281 | const fs::path ArgsManager::GetBlocksDirPath() const
      | ^~~~~

@vasild vasild force-pushed the avoid_returning_reference_to_mutex_guarded_member branch from e134010 to 93cd655 Compare November 2, 2023 14:14
@vasild
Copy link
Contributor Author

vasild commented Nov 2, 2023

e13401084f...93cd65566a: fix #28774 (comment), thanks!

@maflcko
Copy link
Member

maflcko commented Nov 2, 2023

I don't think a mutex is required for read-only access to a const blob of memory?

@vasild
Copy link
Contributor Author

vasild commented Nov 3, 2023

@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.

@maflcko
Copy link
Member

maflcko commented Nov 3, 2023

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.

@vasild vasild force-pushed the avoid_returning_reference_to_mutex_guarded_member branch from 93cd655 to d4ef700 Compare November 3, 2023 11:27
@vasild
Copy link
Contributor Author

vasild commented Nov 3, 2023

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:

path = "";

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.

@vasild
Copy link
Contributor Author

vasild commented Nov 27, 2023

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.

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

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

ACK d4ef7004050c7ee8ac75ae895511334b62890f1b

@maflcko
Copy link
Member

maflcko commented Dec 9, 2023

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.

@vasild
Copy link
Contributor Author

vasild commented Dec 11, 2023

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!

@jonatack
Copy link
Member

jonatack commented Jan 5, 2024

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?

@vasild vasild force-pushed the avoid_returning_reference_to_mutex_guarded_member branch from d4ef700 to 7a8e531 Compare January 6, 2024 16:01
@vasild
Copy link
Contributor Author

vasild commented Jan 6, 2024

d4ef700405...7a8e5310e7: rebase as pointed about above by @jonatack. Thanks!

@vasild
Copy link
Contributor Author

vasild commented Jan 16, 2024

@achow101, this is now solely in the wallet. Maybe you want to take a look?

Comment on lines 49 to 50
Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

… 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;
      |            ^
```
@vasild vasild force-pushed the avoid_returning_reference_to_mutex_guarded_member branch from 7a8e531 to 32a9f13 Compare January 18, 2024 17:18
@vasild
Copy link
Contributor Author

vasild commented Jan 18, 2024

7a8e5310e7...32a9f13cb8: rebase due to conflicts and change the approach to use a callback, see #28774 (comment).

Copy link
Contributor

@ryanofsky ryanofsky left a comment

Choose a reason for hiding this comment

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

Code review ACK 32a9f13. 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 32a9f13 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 856c887 was merged in #29040 so now this only affects wallet code. The previous PR description was: ..."

@DrahtBot DrahtBot requested a review from jonatack January 18, 2024 20:49
Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

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

ACK 32a9f13

Comment on lines +3518 to +3519
LOCK(cs_wallet);
return cb(vMasterKey);
Copy link
Member

Choose a reason for hiding this comment

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

nit:
Could inline it

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

Also, could pass cb as a const ref.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do if I retouch.

@vasild vasild changed the title Avoid returning references to mutex guarded members wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it Jan 19, 2024
@vasild
Copy link
Contributor Author

vasild commented Jan 19, 2024

Would suggest replacing PR title and description with...

Done. Thanks for the suggestion!

@ryanofsky ryanofsky requested a review from achow101 January 19, 2024 10:34
@achow101
Copy link
Member

ACK 32a9f13

@DrahtBot DrahtBot removed the request for review from achow101 January 23, 2024 19:50
@achow101 achow101 merged commit 6f732ff into bitcoin:master Jan 23, 2024
@vasild vasild deleted the avoid_returning_reference_to_mutex_guarded_member branch January 24, 2024 09:12
knst pushed a commit to knst/dash that referenced this pull request Oct 12, 2024
…y after releasing the mutex that guards it

32a9f13 wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it (Vasil Dimov)

Pull request description:

  `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;
        |            ^
  ```

  ---
  _Previously this PR modified both ArgsManager and wallet code. But the ArgsManager commit bitcoin@856c887 was merged in bitcoin#29040 so now this only affects wallet code. The previous PR description was:_

  Avoid this unsafe pattern from `ArgsManager` and `CWallet`:

  ```cpp
  class A
  {
      Mutex mutex;
      Foo member GUARDED_BY(mutex);
      const Foo& Get()
      {
          LOCK(mutex);
          return member;
      } // callers of `Get()` will have access to `member` without owning the mutex.
  ```

ACKs for top commit:
  achow101:
    ACK 32a9f13
  ryanofsky:
    Code review ACK 32a9f13. This seems like a potentially real race condition, and the fix here is pretty simple.
  furszy:
    ACK 32a9f13

Tree-SHA512: 133da84691642afc1a73cf14ad004a7266cb4be1a6a3ec634d131dca5dbcdef52522c1d5eb04f5b6c4e06e1fc3e6ac57315f8fe1e207b464ca025c2b4edefdc1
PastaPastaPasta added a commit to dashpay/dash that referenced this pull request Oct 21, 2024
…itcoin#28774 - lock annotations

1db9e6a Merge bitcoin#19979: Replace LockAssertion with AssertLockHeld, remove LockAssertion (MarcoFalke)
d0a4198 Merge bitcoin#21598: refactor: Remove negative lock annotations from globals (MarcoFalke)
55114a6 Merge bitcoin#19668: Do not hide compile-time thread safety warnings (MarcoFalke)
898282d Merge bitcoin#28774: wallet: avoid returning a reference to vMasterKey after releasing the mutex that guards it (Ava Chow)
6d45284 fix: add missing lock annotation for ContextualCheckBlock and related TODO (Konstantin Akimov)
846ebab fix: fixes for orphange's locks and cs_main (Konstantin Akimov)
002580c fix: add ignoring safety-annotation for llmq/signing_shares due to template lambdas (Konstantin Akimov)
502d6ae fix: add multiple missing annotation about locks for dash specific code (Konstantin Akimov)
a219a8b partial Merge bitcoin#29040: refactor: Remove pre-C++20 code, fs::path cleanup (Konstantin Akimov)
042e8a4 fix: drop unneded lock assert in net.cpp for m_nodes_mutex (Konstantin Akimov)

Pull request description:

  ## What was done?
  These 4 backports improve noticeable implementation of thread-safety analysis by moving many possible warnings to compilation level.
  There's a lot of fixes for annotations after that!

  ## How Has This Been Tested?
  Build with clang:
  ```
  CC=clang CXX=clang++ ./configure --prefix=$(pwd)/depends/x86_64-pc-linux-gnu --enable-suppress-external-warnings   --enable-debug  --enable-stacktraces --enable-werror --enable-crash-hooks --enable-maintainer-mode
  ```

  ## Breaking Changes
  n/a

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [x] I have commented my code, particularly in hard-to-understand areas
  - [x] I have added or updated relevant unit/integration/functional/e2e tests
  - [x] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone

ACKs for top commit:
  UdjinM6:
    utACK 1db9e6a
  PastaPastaPasta:
    utACK 1db9e6a

Tree-SHA512: 42a73e423f311689babc366b58a92f024556cf63af93357cfa0715c471efc1491d02ed9e7ba19b23efcdf423ab5bd051a24da2ad64c32e4b6db132a05f6669c1
Fabcien pushed a commit to Bitcoin-ABC/bitcoin-abc that referenced this pull request Oct 26, 2024
… mutex that guards it

Summary:
```
`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;
      |            ^
```

Backport of [[bitcoin/bitcoin#28774 | core#28774]].

Test Plan:
With clang 18:
  ninja all check-all

Reviewers: #bitcoin_abc, PiRK

Reviewed By: #bitcoin_abc, PiRK

Differential Revision: https://reviews.bitcoinabc.org/D17013
roqqit pushed a commit to doged-io/doged that referenced this pull request Nov 20, 2024
… mutex that guards it

Summary:
```
`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;
      |            ^
```

Backport of [[bitcoin/bitcoin#28774 | core#28774]].

Test Plan:
With clang 18:
  ninja all check-all

Reviewers: #bitcoin_abc, PiRK

Reviewed By: #bitcoin_abc, PiRK

Differential Revision: https://reviews.bitcoinabc.org/D17013
@bitcoin bitcoin locked and limited conversation to collaborators Jan 23, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants