-
Notifications
You must be signed in to change notification settings - Fork 56
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
Cannot set key again if having removed key #26
Comments
I'm unable to reproduce. Are you using the latest version? Release or git master? Do you set & remove the key using DB Browser, shell or C interface? In addition,
|
I was using latest release, and the C interface. ReKey works fine, I only get the issue when having done ReKey that removes the key and then put key again on it. I will play a bit more with it and try not using SetKey again in between and see what happens. Thanks. |
I am having a similar problem using PRAGMA statements. I've stepped through the code and it appears that when sqlite3_rekey_v2 is calling codec_set_to the codec->pagesize is bunk. it contains a value of : -842150451 Any thoughts? |
Caused rekeying to fail after decrypting the whole database.
Thanks for the details @rocketcyberdev I'm still unable to reproduce the actual
445 static int codec_set_to(Codec *codec, Btree *pBt)
446 ...
451 if (!codec->pagesize)
452 codec->pagesize = sqlite3BtreeGetPageSize(pBt); Indeed, Highly appreciated if @einhugur or @rocketcyberdev could test the fix and report back. I also updated the |
Hi @resilar thanks for this. It appears to have solved the problem we reported. |
Great! |
Hi Resliar
Are there any known issues with using sqleet in a multi threaded app?
I’ve got an app that has multiple threads trying to read/write to db. It appears that even when I use the KEY pragma after opening the db, some of the threads will get the not a db error.
Just wondering if it could be some sort of read/write collision between threads. They are not sharing a db handle. Each thread opens the db separately.
From: resilar <[email protected]>
Reply-To: resilar/sqleet <[email protected]>
Date: Tuesday, September 17, 2019 at 9:13 AM
To: resilar/sqleet <[email protected]>
Cc: Developer <[email protected]>, Mention <[email protected]>
Subject: Re: [resilar/sqleet] Cannot set key again if having removed key (#26)
Thanks for the details @rocketcyberdev<https://github.com/rocketcyberdev>
I'm still unable to reproduce the actual sqlite3_rekey_v2() error, but I succeeded to trigger the following (presumably related) valgrind error:
sqlite> PRAGMA rekey='';
sqlite> PRAGMA rekey='GOD';
==55348== Conditional jump or move depends on uninitialised value(s)
==55348== at 0x1D97FE: codec_set_to (sqleet.c:451)
==55348== by 0x1D9E89: sqlite3_rekey_v2 (sqleet.c:613)
==55348== by 0x19303F: sqlite3Pragma (sqlite3.c:123854)
==55348== by 0x1C577F: yy_reduce (sqlite3.c:152913)
==55348== by 0x1C6B5C: sqlite3Parser (sqlite3.c:153461)
==55348== by 0x1C7DCC: sqlite3RunParser (sqlite3.c:154597)
==55348== by 0x194E0B: sqlite3Prepare (sqlite3.c:124831)
==55348== by 0x195161: sqlite3LockAndPrepare (sqlite3.c:124924)
==55348== by 0x19535A: sqlite3_prepare_v2 (sqlite3.c:125008)
==55348== by 0x1EB488: shell_exec (shell.c:11585)
==55348== by 0x1F952B: runOneSqlLine (shell.c:18110)
==55348== by 0x1F9A44: process_input (shell.c:18210)
==55348==
445 static int codec_set_to(Codec *codec, Btree *pBt)
446 ...
451 if (!codec->pagesize)
452 codec->pagesize = sqlite3BtreeGetPageSize(pBt);
Indeed, codec->pagesize is uninitialized since commit ff10548<ff10548> (Implement URI API). Commit 4a6f062<4a6f062> adds the missing initialization of codec->pagesize, likely fixing this rekeying issue. Closing, please reopen if the commit does not resolve this issue.
Highly appreciated if @einhugur<https://github.com/einhugur> or @rocketcyberdev<https://github.com/rocketcyberdev> could test the fix and report back.
…________________________________
I also updated the v0.29.0 release/tag of sqleet to include the fix given the seriousness of the bug. Now thinking about it, this was a little unprofessional because tags should not abruptly change. In future, a better protocol is needed for releasing bugfix releases without "rewriting history".
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#26?email_source=notifications&email_token=AJD55TRNCPAECRK44OYSAPTQKDQWRA5CNFSM4ISEG4AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD64VJTA#issuecomment-532239564>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AJD55TURMVYGBQITVXRCCNLQKDQWRANCNFSM4ISEG4AA>.
|
Prevents issues like #26 in the future. Hopefully.
Interesting. To be honest, I have not tested multi-threaded setup very much. However, there should be no problem since sqleet's Have you tried compiling sqleet explicitly with
This might be the culprit. https://www.sqlite.org/threadsafe.html says: SQLite supports three different threading modes:
Any idea which mode you are using? |
Thanks Resilar,
Yes we have SQLITE_THREADSAFE=1 (Serialized) set at compile time.
I will go back and do some additional testing to make sure that we’re not somehow accidentally opening the db without the key pragma somewhere in the code path or that some runtime db open is overriding the threadsafe setting.
From: resilar <[email protected]>
Reply-To: resilar/sqleet <[email protected]>
Date: Thursday, September 19, 2019 at 2:42 AM
To: resilar/sqleet <[email protected]>
Cc: Developer <[email protected]>, Mention <[email protected]>
Subject: Re: [resilar/sqleet] Cannot set key again if having removed key (#26)
Interesting. To be honest, I have not tested multi-threaded setup very much. However, there should be no problem since sqleet's sqlite3_key_v2 & sqlite3_rekey_v2 are protected by the database mutex. SQLITE_NOTADB (assuming it's not coming from sqlite3 code) is returned by verify_page1() which is called holding the database mutex in addition to the pager shared lock, so there should be no synchronization issues.
Have you tried compiling sqleet explicitly with -DSQLITE_THREADSAFE=1 or -DSQLITE_THREADSAFE=2? -DSQLITE_THREADSAFE=0 disables mutexes and makes concurrent use of SQLite3 (and sqleet) unsafe.
They are not sharing a db handle. Each thread opens the db separately.
This might be the culprit. https://www.sqlite.org/threadsafe.html says:
…________________________________
SQLite supports three different threading modes:
* Single-thread. In this mode, all mutexes are disabled and SQLite is unsafe to use in more than a single thread at once.
* Multi-thread. In this mode, SQLite can be safely used by multiple threads provided that no single database connection is used simultaneously in two or more threads.
* Serialized. In serialized mode, SQLite can be safely used by multiple threads with no restriction.
________________________________
Any idea which mode you are using?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#26?email_source=notifications&email_token=AJD55TX4AIVZ2Y32JP2ORNDQKMUPBA5CNFSM4ISEG4AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7CRIIY#issuecomment-533009443>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AJD55TSUHRYEIFWJHZ33J3LQKMUPBANCNFSM4ISEG4AA>.
|
Now thinking about it, a shared pager lock in I'll look into this and write some concurrency tests when I have time. |
Sounds good thanks.
…Sent from my iPhone
On Sep 20, 2019, at 2:16 AM, resilar <[email protected]<mailto:[email protected]>> wrote:
Now thinking about it, a shared pager lock in verify_page1() might not be enough because the function clears the page cache to force reading the first page from the disk. sqlite3PagerExclusiveLock(pager) is maybe worth trying.
I'll look into this and write some concurrency tests when I have time.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#26?email_source=notifications&email_token=AJD55TTB5PCSVDO3SBUMII3QKR2FJA5CNFSM4ISEG4AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD7F2ETI#issuecomment-533439053>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AJD55TXCVZC2AY4RQYLHKMTQKR2FJANCNFSM4ISEG4AA>.
|
So far I have written a multi-threaded concurrency smoke test program that continuously (re)opens and reads an encrypted sqleet database from dozens of threads. This has not reproduced the |
@rocketcyberdev Finally some progress! Adding concurrent writers did the trick and caused Concurrent writes to a non-WAL-journaled SQLite3 database can block readers in addition to other writers. As a result, opening an encrypted sqleet database can fail because the password/key is verified in Whew, sorry for the crazy amount of details. The take home message is that sqleet ignores There are quite a few approaches to choose from:
Anyway, give me a week or two to address this issue in sqleet. The issue is not critical since it only causes Ping @utelle just in case you are interested or have some ideas. I have not yet checked if wxSQLite3 handles |
Awesome. Thanks for all the details and your efforts. We do use the busy_timeout to have sql retry automatically when a busy error occurs. Right now I think we set at 1000ms. So option 2 sounds like it would work out of the box for us.
…________________________________
From: resilar <[email protected]>
Sent: Tuesday, October 1, 2019 8:51:47 AM
To: resilar/sqleet <[email protected]>
Cc: Developer <[email protected]>; Mention <[email protected]>
Subject: Re: [resilar/sqleet] Cannot set key again if having removed key (#26)
@rocketcyberdev<https://github.com/rocketcyberdev> Finally some progress! Adding concurrent writers did the trick and caused sqlite3_key() (as well as sqlite3_open() with key embedded in URI) to fail in my test setup - hopefully for the same reason you are experiencing in your app.
Concurrent writes to a non-WAL-journaled SQLite3 database can block readers in addition to other writers. As a result, opening an encrypted sqleet database can fail because the password/key is verified in verify_page1() by reading the first page of the database. Now, if a writer blocks the read, then sqlite3PagerSharedLock() on sqleet.c:371<https://github.com/resilar/sqleet/blob/06e922a71dda1ab7ee5c16c8f9e0fa2924b433ac/sqleet.c#L371> fails with error code SQLITE_BUSY. The error is unhandled by sqleet, causing sqlite3PagerPagecount() to return 0 as the database page count. As a result, sqleet thinks the database is empty and generates a new random salt that makes the key derivation function to return wrong encryption key. This ultimately explains the SQLITE_NOTADB error.
Whew, sorry for the crazy amount of details. The take home message is that sqleet ignores SQLITE_BUSY error in verify_page1() which should be returned to the caller or handled appropriately. I still need to do further research to determine the expected sqleet behavior in this situation, but my current understanding is that SQLite3 generally requires the user to take care of SQLITE_BUSY and SQLITE_LOCKEDerrors themselves by implementing retrying logic, or using the built-in SQLite3 busy_handler<https://www.sqlite.org/c3ref/busy_handler.html> & busy_timeout<https://www.sqlite.org/c3ref/busy_timeout.html> mechanisms to do that.
There are quite a few approaches to choose from:
1. Return SQLITE_BUSY directly to the user.
2. Implement retrying logic (obeying busy_timeout and busy_handler?) and return SQLITE_BUSY if retries fail.
3. Do not call verify_page1() when an encryption key is set in sqlite3_open() or sqlite3_key(). I do not like this approach because a wrong key is then not immediately detected (a database read or write is required to see if that fails due to an invalid encryption key). However, I kinda like the idea of optionally skipping the verification, because it could be useful in some (multi-threaded) use-cases where the key is known to be correct beforehand; thus, allowing skipping the heavy key derivation process.
4. Something else that I cannot think of at the moment.
Anyway, give me a week or two to address this issue in sqleet. The issue is not critical since it only causes sqlite3_open() or sqlite3_key() to fail in a multi-threaded setup with a low probability. Retrying the function call can be used as a temporary workaround until then.
________________________________
Ping @utelle<https://github.com/utelle> just in case you are interested or have some ideas. I have not yet checked if wxSQLite3 handles SQLITE_BUSY errors, but if not, you might want to do so if my analysis is correct. I can release my pthreads-based concurrency test program if you are unsure about the multi-threaded usage of wxSQLite3
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#26?email_source=notifications&email_token=AJD55TWHDYLRSNORUHXALC3QMNIXHA5CNFSM4ISEG4AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEABKZCQ#issuecomment-537046154>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AJD55TTHQ4MRCB4JVVSPIWTQMNIXHANCNFSM4ISEG4AA>.
|
This is definitely an interesting issue. At the moment I don't know yet if and how wxSQLite3 is affected by this problem. However, wxSQLite3 does not perform a check like |
@rocketcyberdev Preliminary fix 7dc8126 merged to master. sqleet now handles Indeed, wxSQLite3 does not seem to have In my opinion, immediate feedback from Private gist of my test program. Unfinished and ugly, but better than nothing (polished final version will hopefully be merged into master in the future). In addition, SQLite3 source's |
Thanks will give it a try and report back.
…________________________________
From: resilar <[email protected]>
Sent: Thursday, October 3, 2019 5:50:33 PM
To: resilar/sqleet <[email protected]>
Cc: Developer <[email protected]>; Mention <[email protected]>
Subject: Re: [resilar/sqleet] Cannot set key again if having removed key (#26)
@rocketcyberdev<https://github.com/rocketcyberdev> Preliminary fix 7dc8126<7dc8126> merged to master. sqleet now handles SQLITE_BUSY appropriately by invoking the handler set by sqlite3_busy_handler() or sqlite3_busy_timeout(). Highly appreciated if you could test the fix and tell whether it solves your issue. If not, try also disabling SQLite3 shared-cache mode<https://www.sqlite.org/sharedcache.html> (disabled by default).
________________________________
Indeed, wxSQLite3 does not seem to have verify_page1()-style verification of the encryption key when setting a codec. This is probably common behavior as SQLCipher neither automatically verifies the key according to documentation SQLCipher API: Testing the key<https://www.zetetic.net/sqlcipher/sqlcipher-api/#testing-the-key>. Issue #30<#30> tracks optional key verification skipping in sqleet.
In my opinion, immediate feedback from sqlite3_key_v2() about the correctness of the passed-in key is worth the struggle to read the 1st database page for key verification. However, reading the page 1 from the database file on disk has turned out to be a surprisingly difficult task because of B-trees, page caches and other layers of abstractions on top of the disk storage. I just noticed that the current version of sqleet still does not get the synchronization right with shared-cache mode<https://www.sqlite.org/sharedcache.html> enabled (issue #31<#31>).
Private gist of my test program<https://gist.github.com/resilar/20cbe17b6a7a0a4c4eece6dd823d5180>. Unfinished and ugly, but better than nothing (polished final version will hopefully be merged into master in the future). In addition, SQLite3 source's test/threadtest[1-4].c are also useful! The official thread tests, however, do not test encryption codecs at all. Basic encryption functionality (such as key() and rekey()) can be somewhat tested with small code patches, though.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub<#26?email_source=notifications&email_token=AJD55TRYWX7NS2C6PYDMXF3QMZZLTA5CNFSM4ISEG4AKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAJ2OZI#issuecomment-538158949>, or mute the thread<https://github.com/notifications/unsubscribe-auth/AJD55TQGPKPT2CJ6MO3OX4LQMZZLTANCNFSM4ISEG4AA>.
|
Thanks for providing your thread test code.
It is true that neither wxSQLite3 nor SQLCipher (nor any other encryption extension I tested, except sqleet) perform an immediate verification of the encryption key. However, this follows the general behaviour of SQLite itself, as for example SQLite itself does not check the validity of a database file until an SQL command is executed that actually requires to read from (or write to) the database file. That is, a call to One could argue that on opening an existing file SQLite should check immediately whether the file is really a valid database file, but the SQLite developers chose a different approach. And for the time being wxSQLite3 will stick to this "official" approach. |
Very good points! I'll consider making the immediate codec verification optional (instead of the other way around), especially if reading the page 1 becomes too much hassle with shared cache mode support. Breaking I do not really know if multi-thread shared-cache with encryption is reasonable (or secure) in the first place. SQLCipher segfaults in It looks like the page 1 verification of sqleet might actually not be the cause of the failing shared-cache tests (performing the verification later as in SQLCipher/wxSQLite3 does not make the tests pass). More research is needed... |
That is certainly a good approach. For sure there are scenarios where verifying the codec immediately is preferable, and your approach allows to do that without forcing the "typical" sqleet user to modify build options.
I have to admit that I haven't spent much time yet on analyzing this kind of use case. However, I don't see a largely increased security risk in using shared-cache, since it is restricted to threads belonging to a single process.
I will try to dedicate some time to further analyze this shared cache issue. |
Highly appreciated if you could take a look! To quickly reproduce, use my patched version of // threadtest3.c:519
static void opendb_x(
Error *pErr, /* IN/OUT: Error code */
Sqlite *pDb, /* OUT: Database handle */
const char *zFile, /* Database file name */
int bDelete /* True to delete db file before opening */
){
if (pErr->rc == SQLITE_OK) {
int rc;
int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI
#if 1
| SQLITE_OPEN_SHAREDCACHE
#else
| SQLITE_OPEN_PRIVATECACHE
#endif
;
if (bDelete) unlink(zFile);
if ((rc = sqlite3_open_v2(zFile, &pDb->db, flags, 0)) != SQLITE_OK) {
sqlite_error(pErr, pDb, "open");
goto fail;
}
sqlite3_busy_handler(pDb->db, busyhandler, 0);
#if defined(SQLITE_HAS_CODEC)
if ((rc = sqlite3_key(pDb->db, "test", 4)) != SQLITE_OK) {
printf("[ERROR] sqlite3_key('%s', 'test'): %s (%s) %d\n",
zFile, sqlite3_errmsg(pDb->db), sqlite3_errstr(rc), rc);
sqlite_error(pErr, pDb, "key");
goto fail;
}
// Validate key (SQLCipher/wxSQLite3)
rc = sqlite3_exec(pDb->db, "SELECT count(*) FROM sqlite_master;",
NULL, NULL, NULL);
if (rc != SQLITE_OK) {
printf("[ERROR] sqlite3_verify('%s', 'test'): %s (%s) %d\n",
zFile, sqlite3_errmsg(pDb->db), sqlite3_errstr(rc), rc);
sqlite_error(pErr, pDb, "verify");
goto fail;
}
#endif
sqlite3_create_function(
pDb->db, "md5sum", -1, SQLITE_UTF8, 0, 0, md5step, md5finalize
);
sqlite3_exec(pDb->db, "PRAGMA synchronous=OFF", 0, 0, 0);
}
return;
fail:
sqlite3_close(pDb->db);
pDb->db = 0;
return;
} |
In the meantime I used your variant of If option
If option
and then crashes due to an access violation on trying to encrypt page 2 of the main database. Following the call stack:
The cause of the problem is that the codec structure holds a pointer to a SQLite3 database structure and a pointer to a SQLite3 btree structure. Both structures do not contain valid data, but seem to be uninitialized. My guess is that special measures would be required to determine the database and btree associated with a codec structure in shared cache mode. However, at the moment I have no idea which steps exactly would be required to accomplish proper operation in shared cache mode. |
Thanks for your input. I have not yet taken a deep dive into the shared cache concurrency stuff, so please take my misguided thoughts (in this and earlier comments) as a grain of salt. Feel free goto TLDR. The verbosity level of this post is over 9000. Sorry, personal(ity) issues. I just noticed that the official SQLite3 source code does not pass Therefore, my The primary focus should be on threadtest3 tests run with neither With neither cache flag force-enabled by
In my test environment, sqleet as well as SQLite3 (without codecs) finishes The @utelle Nevertheless, I'd suggest investigating further that walthread5 "invalid argument" error because it is observed only with wxSQLite3. However, the sample size here is only 2 so the error may very well be caused by differences in our test setups rather than an actual bug in wxSQLite3 code.
The official SQLite3 code (without encryption) also frequently crashes during the In comparison, sqleet in shared cache mode always crashes during TLDR:
Sorry for the wall of text. Writing helps me to think about complex topics, which sometimes results in insane posts like this. I'll try to keep the verbosity level within sane limits in the future. |
I will have to digest all the information you gave. However, I really appreciate that you share your thoughts in full. 😄 Therefore, just a quick comments about the current status of my tests. More to come later.
For reliable results we will need a test application that works at least for standard SQLite3 without errors. Otherwise it will be very hard to verify which issues are caused by the encryption extension and which not. |
Standard SQLite3 without encryption passes threadtest3 with 0 errors when using unmodified Remove the setting of |
With my modified wxSQLite3 version (referencing
That is, I got 1 error (and 2 warnings) from |
Regarding the error from That is, the file I don't see where wxSQLite3 would be to blame as these are C library functions producing the error. |
After inspecting the database file at the position where the I modified the code of Note: my test environment is Windows 10 with Visual Studio. |
Okay, that explains the invalid argument error. Good work! Nasty bug though.
That is an exceptionally good result in comparison to the "36 errors out of 14 tests" result by sqleet. However, I managed to reduce the number of errors from 36 down to 0 by skipping the immediate I'm starting to feel much more hopeful now that the root cause of shared-cache issues has been identified somewhat accurately. :) Well, at least in sqleet. wxSQLite3 also seems to be getting very close to a stable concurrent shared-cache mode with encryption (I do not know if the few remaining errors/warnings are just false positivies or non-issues in practice).
IIRC I have seen this error also with the standard SQLite3 (no encryption). However, I cannot reproduce it now successfully, so I might be recalling incorrectly. Better to investigate the error thoroughly, just in case. I'll look into it if the error starts occurring in sqleet after fixing the more severe bugs in the database locking & access. |
In the meantime I somewhat cleaned up my modifications of the wxSQLite3 encryption extension and committed the code to the wxSQLite3 GitHub repository. This code seems to work now sufficiently well in shared cache mode - no errors, only warnings in stress1 test of threadtest3. Additionally, I adjusted the test applications threadtest2 and threadtest4 to use encrypted databases. Both run without errors for wxSQLite3. I have not yet tested sqleet with those new tests. |
Congratulations! Late reply because I had to suddenly drop this project for a couple months due to being busy with other things in life. However, today I further investigated sqleet's 33 I was unable to determine the exact problem accurately, but Anyway, sqleet with robust (shared-cache) concurrency support is still coming. The current "fixed" development version needs refactoring and preferably more research to find better solutions than using Issue #30 tracks the implementation of optional "lazy key verification" feature. It might become the default if key verification immediately in |
I am unable to set key again if I Set Key then remove key. Then open the DB again with no key, then neither SetKey or ReKey will work again. (Key is successfully removed as DB Browser for Sqlite can open it at that time)
The text was updated successfully, but these errors were encountered: