Page MenuHomePhabricator

Introduce and implement 'clearSensitiveData' method in DetabaseQueryExecutor and SQLiteQueryExecutor
ClosedPublic

Authored by marcin on Aug 3 2022, 5:42 AM.
Tags
None
Referenced Files
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:08 PM
Unknown Object (File)
Thu, Apr 18, 2:07 PM
Unknown Object (File)
Thu, Apr 18, 2:00 PM

Details

Summary

This differential introduces method to clean Secure Store and SQLite to 'DatabaseQueryExecutor' API and implements it in its derived class 'SQLiteQueryExecutor'. This method firstly attempts to delete database (if it exists), iverwrite encryption key in secure store and finally run migrate' method to create new database. The last step is essential. This method is expected to be called from react component, so it is important that it leaves application in a state that other components can use. In particular core-data-provider` component tries to do some database queries even if the user is logged out.

Test Plan

This function is not expected to be used outside of react-component so it might be hard to provide test plan prior to introducing diffs that implement actual components. But for the purpose of unit testing, someone might: 1) place this function somwhere in AppDelegate 2) open the app without any user logged in 3) execute this function setp by step in the debugger and monitor database file size.

Diff Detail

Repository
rCOMM Comm
Lint
No Lint Coverage
Unit
No Test Coverage

Event Timeline

marcin requested review of this revision.Aug 3 2022, 5:55 AM

Update diff description to include diff link

tomek added inline comments.
native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
785–790

This small code fragment is duplicated. What do you think about creating a new function that handles generating and setting the new encryption key?

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
785–790

I think it is a good practice that duplicated code gets extracted into a function.

atul requested changes to this revision.Aug 4 2022, 3:58 PM

Couple of questions inline. The one that really matters is making sure we're handling the return code of std::remove properly in the conditional on line 780.

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
780

I might be mistaken, but on successful deletion doesn't std::remove(...) return 0?

Should we instead explicitly check if the return code is 0? eg

std::remove(SQLiteQueryExecutor::sqliteFilePath.c_str()) == 0

?


As an aside, looking at the std::remove entry in CPPReference (https://en.cppreference.com/w/cpp/io/c/remove) it looks like they check for successful deletion with the following

std::remove("file1.txt"); // delete file
 
bool failed = !std::ifstream("file1.txt");
if(failed) { std::perror("Error opening deleted file"); return 1; }

I haven't looked too closely at it, but I wonder if that's a more "robust" check to ensure the file was properly deleted?

781–782

Elsewhere in the C++ codebase we handle string interpolation/appending with a pattern that looks like:

std::ostringstream stringStream;
stringStream << "Error creating '" << tableName << "' table: " << error;
Logger::log(stringStream.str());

Could we do something like that here?

eg

std::ostringstream errorStream;
errorStream << "Failed to delete database file. Details: " << strerror(errno);
throw std::system_error(errno, std::generic_category(), errorStream.str());

?

785

Can we use braced initialization here?

CommSecureStore commSecureStore{};

Screen Shot 2022-08-04 at 4.29.45 PM.png (1×902 px, 226 KB)

788

If I understand correctly (it's been months since I've looked at C++ so I'm probably rusty)

  1. We instantiate a CommSecureStore object
  2. We use an instance method to set some key-value sort of thing with [CommSecureStoreIOSWrapper sharedInstance]
  3. The CommSecureStore object goes out of scope

Is there a reason we can't use static functions instead and avoid instantiating a CommSecureStore object?

This revision now requires changes to proceed.Aug 4 2022, 3:58 PM
native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
780

Should we instead explicitly check if the return code is 0?

0 in C++ always evaluates to boolean false and any non-zero integer always evaluates to true. That said value returned from std::remove can be directly used in a boolean expression without explicit comparison against 0. This if statement is intended to check for unsuccessful deletion , so we could write:
std::remove(SQLiteQueryExecutor::sqliteFilePath.c_str()) != 0. But as previously said it is not necessary.

std::remove("file1.txt"); // delete file
bool failed = !std::ifstream("file1.txt");
if(failed) { std::perror("Error opening deleted file"); return 1; }

I think authors of this documentation simply wanted some example of code that shows that std::remove indeed removes the file, so that you can not open it with ifstream again. Documentation for POSIX remove (called by std::remove) explicitly states it will return non-zero integer upon failure. I am not sure why this information shouldn't be enough to detect unsuccessful file deletion.

781–782

Ok, I will stick to the convention and do the refactor.

785

Sure, I will introduce it.

788

CommSecureStore is our cross-platform interface that has platform-specific implementations that call native (Java or Objective-C) code depending on platform. Its method are defined as non-static, so currently we are forced to instantiate it. However it's implementation on both platforms is stateless so there is no reason not to make them static. But it would require a dozen of changes that are out of the scope of this differential. Taking into account the fact that this differential solves high priority Linear issue we probably do not want to delay it by introducing additional diffs into the stack. I think it would be best to create Linear task to make CommSecureStore methods static and to update relevant places in the codebase. Probably it would be a good starter task since it is relatively easy and quick so one of our interns could pick it up.

Refactor: curly brackets for CommSecureStore intiantiation, common logic extraction into a method, using string stream to concantenate strings instead of addition.

atul added inline comments.
native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
780

Ah okay sorry completely disregard what I said, I totally misread the conditional.

I still think explicitly writing std::remove(SQLiteQueryExecutor::sqliteFilePath.c_str()) != 0 might be a little clearer, but up to you

788

Yeah that makes complete sense to me

This revision is now accepted and ready to land.Aug 5 2022, 8:54 AM

Temporarily attach thread to JVM to be able to call Java native methods

Newest change for this diff relies on the following docs: https://github.com/facebookincubator/fbjni/blob/main/cxx/fbjni/detail/Environment.h#L95. SQLiteQueryExecutor::clearSensitiveData() is expected to be called in CommCoreModule on databaseThread. In order to access CommSecureStore on Android from databaseThread we need to attach databaseThread to JVM for the time when this function is executed. This differential introduces this functionality so entire function can be scheduled on databaseThread like in D4729. It must be noted that facebook::jni::ThreadScope::WithClassLoader could also be used directly in D4729. Both approaches are acceptable. I decided to do so in SQLiteQueryExecutor since it seems for me to be more low-level class while CommCoreModule is high-level and platform agnostic. But I am open to change it and use facebook::jni::ThreadScope::WithClassLoader in CommCoreModule just before call to clearSensitiveData is scheduled on databaseThread.

Use run_with_native_accessible in clearSensitiveData

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.cpp
518 ↗(On Diff #16322)

Unclear why we use snake case here