Skip to content

Conversation

uhliksk
Copy link

@uhliksk uhliksk commented Feb 7, 2020

Crashes:
If wallet is checking existence of wallet.dat in directory where access is denied then fs::exists() without ec parameter will cause wallet crash.

Reproduction steps on Linux platform:

  1. Type: mkdir ~/.bitcoin/crash
  2. Type: sudo chown root:root ~/.bitcoin/crash
  3. Run bitcoin-qt
  4. Go to menu "File" -> "Open Wallet", wallet will crash immediately

It's fixed by adding "ec" parameter into fs::exists() function to handle any error correctly.

Infinite loop:
If iterator is not able to increment because of access denied to next level of recursive scan it will stay on the same position indefinitely causing wallet to stop responding.

Reproduction steps on Windows platform:

  1. Create new folder into bitcoin data folder (for example: %Appdata%\Roaming\Bitcoin\Loop)
  2. Deny access to newly created folder for current user
  3. Run bitcoin-qt.exe
  4. Go to menu "File" -> "Open Wallet", wallet will hang indefinitely

It's fixed by disabling recursion if iterator increment failed. This way iterator will continue to next record in current recursion level not trying to dive into inaccessible directory structure.

@uhliksk uhliksk requested a review from promag February 7, 2020 21:12
@DrahtBot DrahtBot added the Wallet label Feb 7, 2020
@meshcollider
Copy link
Contributor

Nice catch and fix, thanks! Great first time contribution. Looks good to me, but it would be great to have a test for this too if possible :)

@hebasto
Copy link
Member

hebasto commented Feb 9, 2020

Concept ACK.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

Concept ACK. Maybe the same check should be done in VerifyWallets?

@uhliksk
Copy link
Author

uhliksk commented Feb 10, 2020

Nice catch and fix, thanks! Great first time contribution. Looks good to me, but it would be great to have a test for this too if possible :)

I'll try to create test for all functions where error_code is necessary. Thank you for advice.

@laanwj laanwj added the Bug label Feb 10, 2020
@uhliksk
Copy link
Author

uhliksk commented Feb 11, 2020

I added test to check all boost filesystem functions and added error code handlers where necessary.

@uhliksk
Copy link
Author

uhliksk commented Feb 11, 2020

I squashed all relative commits together to make it much cleaner now.

@uhliksk uhliksk force-pushed the master branch 2 times, most recently from e0eebb2 to 87a4de7 Compare February 11, 2020 03:05
@uhliksk uhliksk requested a review from promag February 11, 2020 03:07
@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 11, 2020

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

Conflicts

Reviewers, this pull request conflicts with the following ones:

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@uhliksk
Copy link
Author

uhliksk commented Feb 11, 2020

There is an issue with wallet backup after changes in error handling. I'll check today.

@uhliksk
Copy link
Author

uhliksk commented Feb 11, 2020

Ok, there was an unnecessary error handler. Routine is just checking if provided path is file or directory. It should not fail regardless of result. Fixed.

@hebasto
Copy link
Member

hebasto commented Feb 13, 2020

@uhliksk
I guess with the words "... it would be great to have a test for this too if possible" @meshcollider had in mind a new functional test, not a linter. The latter seems do more harm than good.

I think no need to force replacement existing boost filesystem library function calls that throw an exception with ones that set an error_code.

@uhliksk
Copy link
Author

uhliksk commented Feb 14, 2020

I think no need to force replacement existing boost filesystem library function calls that throw an exception with ones that set an error_code.

Ok, I can revert all changes back to ListWalletDir() fix only but I still think it's better to handle all function calls properly instead of random wallet crashes. With "try...catch" method you never know what actually caused exception - boost function or your own code. It's harder to debug those "try...catch" blocks because all exceptions are catched. With error_code handled you know if boost function failed you have error code returned and if your own code failed you have an exception.

@uhliksk
Copy link
Author

uhliksk commented Feb 14, 2020

@hebasto I'm little bit busy today but I can try to add functional test tomorrow. Thank you for suggestion.

Copy link
Contributor

@promag promag left a comment

Choose a reason for hiding this comment

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

Maybe we should wrap these boost::filesystem?

Also, maybe extract that refactor to a different PR and revert this one to just fix the original issue?

@uhliksk
Copy link
Author

uhliksk commented Feb 21, 2020

Yes, I was also thinking about that today. I'll revert back this PR to original issue and create another one for other changes.

@promag
Copy link
Contributor

promag commented Mar 2, 2020

ACK 6307dfa.

@laanwj
Copy link
Member

laanwj commented Mar 27, 2020

Concept ACK but

It's fixed by adding "ec" parameter into fs::exists() function to handle any error correctly.

It's not actually checking the ec output (calls file_size right after, overwriting it). I don't think this is correct. The normal boost::fs::exists will throw an exception in case of an error, the ec one will not raise an exception but return the error in the parameter. This means that it must not be ignored.

@uhliksk
Copy link
Author

uhliksk commented Mar 28, 2020

Concept ACK but

It's fixed by adding "ec" parameter into fs::exists() function to handle any error correctly.

It's not actually checking the ec output (calls file_size right after, overwriting it). I don't think this is correct. The normal boost::fs::exists will throw an exception in case of an error, the ec one will not raise an exception but return the error in the parameter. This means that it must not be ignored.

It's not important to check the ec output because boost::fs::exists return true if the given path or filestatus corresponds to an existing file or directory. If it fail for any reason it will return false. In our use case it's not important to know what actually caused an error.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

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

Approach ACK 24625c8, tested on Linux Mint 20 (x86_64).
Should be squashed into a single commit before merging.

I can reproduce the initial error with denied access to a directory:

$ ls -l ~/bitcoin-data-dir/wallets/
total 1448
drwx------ 3 hebasto hebasto    4096 Oct  3 11:16 20191109-main
drwx------ 2 root    root       4096 Oct  4 18:25 crash
-rw-r--r-- 1 hebasto hebasto       0 Mar  5  2020 db.log
-rw-r----- 1 hebasto hebasto 1474560 Sep 12 16:08 wallet.dat
$ src/bitcoin-cli listwalletdir
error code: -1
error message:
boost::filesystem::status: Permission denied: "/home/hebasto/.bitcoin/wallets/crash/wallet.dat"

While fixing it with the following patch:

--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -31,11 +31,12 @@ fs::path GetWalletDir()
 
 bool IsBerkeleyBtree(const fs::path& path)
 {
-    if (!fs::exists(path)) return false;
-
     // A Berkeley DB Btree file has at least 4K.
     // This check also prevents opening lock files.
     boost::system::error_code ec;
+
+    if (!fs::exists(path, ec)) return false;
+
     auto size = fs::file_size(path, ec);
     if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
     if (size < 4096) return false;

I encounter the infinite loop that is also fixed in this PR.

With this PR I can see in the log:

2020-10-04T16:08:35Z [httpworker.0] ListWalletDir: increment: Permission denied /home/hebasto/.bitcoin/wallets/crash

and src/bitcoin-cli listwalletdir returns correct output.


With "try...catch" method you never know what actually caused exception - boost function or your own code.

This is not true. My own code does not throw exceptions of filesystem_error type :)

@uhliksk uhliksk force-pushed the master branch 2 times, most recently from 0f70f05 to ece61bb Compare October 4, 2020 22:52
return paths;
}

for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) {
for (; it != fs::recursive_directory_iterator(); it.increment(ec)) {

Comment on lines +65 to +46
auto it = fs::recursive_directory_iterator(wallet_dir, ec);
if (ec) {
LogPrintf("%s: iterator: %s %s\n", __func__, ec.message(), it->path().string());
return paths;
}
Copy link
Member

Choose a reason for hiding this comment

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

Wondering what conditions could cause this error. I've read the relevant Boost docs, and tried different ill filesystem setups but without success.

Copy link
Contributor

@meshcollider meshcollider left a comment

Choose a reason for hiding this comment

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

This looks good to me now, utACK ece61bb

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

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

Approach ACK ece61bb, the only suggestion I remind about, as currently clang emits warning:

wallet/walletutil.cpp:71:10: warning: expression result unused [-Wunused-value]
    for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) {
         ^~
1 warning generated.

@uhliksk
Copy link
Author

uhliksk commented Oct 11, 2020

Approach ACK ece61bb, the only suggestion I remind about, as currently clang emits warning:

wallet/walletutil.cpp:71:10: warning: expression result unused [-Wunused-value]
    for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) {
         ^~
1 warning generated.

Yes, sorry, my mistake. I forgot to clean it up.

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

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

ACK 2f06e9d

@uhliksk
Copy link
Author

uhliksk commented Oct 15, 2020

rebase

Rebased.

@hebasto
Copy link
Member

hebasto commented Oct 16, 2020

rebase

Rebased.

I don't think the latest rebasing is correct, wrt the ac38a87 commit.

@uhliksk
Copy link
Author

uhliksk commented Oct 17, 2020

rebase

Rebased.

I don't think the latest rebasing is correct, wrt the ac38a87 commit.

What exactly do you mean? I checked walletutil.cpp with current master branch and there is nothing different.

@hebasto
Copy link
Member

hebasto commented Oct 17, 2020

rebase

Rebased.

I don't think the latest rebasing is correct, wrt the ac38a87 commit.

What exactly do you mean? I checked walletutil.cpp with current master branch and there is nothing different.

I mean this initial patch:

--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -31,11 +31,12 @@ fs::path GetWalletDir()
 
 bool IsBerkeleyBtree(const fs::path& path)
 {
-    if (!fs::exists(path)) return false;
-
     // A Berkeley DB Btree file has at least 4K.
     // This check also prevents opening lock files.
     boost::system::error_code ec;
+
+    if (!fs::exists(path, ec)) return false;
+
     auto size = fs::file_size(path, ec);
     if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
     if (size < 4096) return false;

Are such changes not required now?

@uhliksk
Copy link
Author

uhliksk commented Oct 17, 2020

rebase

Rebased.

I don't think the latest rebasing is correct, wrt the ac38a87 commit.

What exactly do you mean? I checked walletutil.cpp with current master branch and there is nothing different.

I mean this initial patch:

--- a/src/wallet/walletutil.cpp
+++ b/src/wallet/walletutil.cpp
@@ -31,11 +31,12 @@ fs::path GetWalletDir()
 
 bool IsBerkeleyBtree(const fs::path& path)
 {
-    if (!fs::exists(path)) return false;
-
     // A Berkeley DB Btree file has at least 4K.
     // This check also prevents opening lock files.
     boost::system::error_code ec;
+
+    if (!fs::exists(path, ec)) return false;
+
     auto size = fs::file_size(path, ec);
     if (ec) LogPrintf("%s: %s %s\n", __func__, ec.message(), path.string());
     if (size < 4096) return false;

Are such changes not required now?

Yes, thank you. I missed that one as IsBerkeleyBtree function has been removed from walletutil.cpp and rebase just show changes in this one file for me. I didn't noticed it was moved to bdb.cpp as IsBDBFile.

@uhliksk
Copy link
Author

uhliksk commented Oct 17, 2020

Are such changes not required now?

Bug has been introduced even into IsSQLiteFile. I fixed both right now. Thank you.

@uhliksk
Copy link
Author

uhliksk commented Oct 17, 2020

I should get back on #18189 where I have lint test to prevent this kind of bugs in future.

@hebasto
Copy link
Member

hebasto commented Oct 20, 2020

@uhliksk

I suggest to combine your approach to prevent an infinity loop into #19502 (see #19502 (comment)).

@luke-jr's approach to handle file system errors/exceptions seems more preferable to me (with two types of wallet databases now).

@fanquake
Copy link
Member

Closing now that #19502 has been merged.

@fanquake fanquake closed this Nov 12, 2020
@bitcoin bitcoin locked as resolved and limited conversation to collaborators Feb 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants