-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Fix crashes and infinite loop in ListWalletDir() #18095
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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 :) |
Concept ACK. |
There was a problem hiding this 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
?
I'll try to create test for all functions where error_code is necessary. Thank you for advice. |
I added test to check all boost filesystem functions and added error code handlers where necessary. |
I squashed all relative commits together to make it much cleaner now. |
e0eebb2
to
87a4de7
Compare
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, 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. |
There is an issue with wallet backup after changes in error handling. I'll check today. |
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. |
@uhliksk I think no need to force replacement existing boost filesystem library function calls that throw an exception with ones that set an |
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 |
@hebasto I'm little bit busy today but I can try to add functional test tomorrow. Thank you for suggestion. |
There was a problem hiding this 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?
Yes, I was also thinking about that today. I'll revert back this PR to original issue and create another one for other changes. |
ACK 6307dfa. |
Concept ACK but
It's not actually checking the |
It's not important to check the |
There was a problem hiding this 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 :)
0f70f05
to
ece61bb
Compare
src/wallet/walletutil.cpp
Outdated
return paths; | ||
} | ||
|
||
for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for (it; it != fs::recursive_directory_iterator(); it.increment(ec)) { | |
for (; it != fs::recursive_directory_iterator(); it.increment(ec)) { |
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; | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this 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
There was a problem hiding this 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.
Yes, sorry, my mistake. I forgot to clean it up. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 2f06e9d
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 |
Bug has been introduced even into |
I should get back on #18189 where I have lint test to prevent this kind of bugs in future. |
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). |
Closing now that #19502 has been merged. |
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:
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:
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.