| // Copyright 2021 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "dlp/dlp_adaptor.h" |
| |
| #include <map> |
| #include <memory> |
| #include <poll.h> |
| #include <string> |
| #include <utility> |
| |
| #include <base/containers/contains.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_file.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/functional/callback.h> |
| #include <base/memory/scoped_refptr.h> |
| #include <base/process/process_handle.h> |
| #include <base/run_loop.h> |
| #include "base/time/time.h" |
| #include <brillo/dbus/mock_dbus_method_response.h> |
| #include <brillo/files/file_util.h> |
| #include <gtest/gtest.h> |
| |
| #include "dlp/dlp_adaptor_test_helper.h" |
| #include "dlp/file_id.h" |
| |
| using testing::_; |
| using testing::ElementsAre; |
| using testing::Invoke; |
| using testing::Return; |
| |
| namespace dlp { |
| namespace { |
| |
| // Some arbitrary D-Bus message serial number. Required for mocking D-Bus calls. |
| constexpr int kDBusSerial = 123; |
| constexpr int kPid = 1234; |
| constexpr int kIoTaskId = 12345; |
| |
| class FileOpenRequestResultWaiter { |
| public: |
| FileOpenRequestResultWaiter() = default; |
| ~FileOpenRequestResultWaiter() = default; |
| FileOpenRequestResultWaiter(const FileOpenRequestResultWaiter&) = delete; |
| FileOpenRequestResultWaiter& operator=(const FileOpenRequestResultWaiter&) = |
| delete; |
| |
| // Waits until the result is available and returns it. |
| bool GetResult() { |
| run_loop_.Run(); |
| return result_; |
| } |
| |
| // Returns the callback which should be passed to |
| // DlpAdaptor::ProcessFileOpenRequest. |
| base::OnceCallback<void(bool)> GetCallback() { |
| return base::BindOnce(&FileOpenRequestResultWaiter::OnResult, |
| base::Unretained(this)); |
| } |
| |
| private: |
| // Invoked when a result is available. |
| void OnResult(bool result) { |
| result_ = result; |
| run_loop_.Quit(); |
| } |
| |
| base::RunLoop run_loop_; |
| |
| // Not initialized before run loop is quit. |
| bool result_; |
| }; |
| |
| bool IsFdClosed(int fd) { |
| struct pollfd pfd = { |
| .fd = fd, |
| .events = POLLERR, |
| }; |
| if (poll(&pfd, 1, 1) < 0) |
| return false; |
| return pfd.revents & POLLERR; |
| } |
| |
| // Parses a response message from a byte array. |
| template <typename TResponse> |
| TResponse ParseResponse(const std::vector<uint8_t>& response_blob) { |
| TResponse response; |
| EXPECT_TRUE( |
| response.ParseFromArray(response_blob.data(), response_blob.size())); |
| return response; |
| } |
| |
| // Pseudo-random proto made of determined 10 bytes. |
| std::vector<uint8_t> RandomProtoBlob() { |
| const size_t random_proto_size = 10; |
| std::vector<uint8_t> proto_blob(random_proto_size); |
| for (uint8_t i = 0; i < random_proto_size; ++i) { |
| proto_blob.push_back(i); |
| } |
| return proto_blob; |
| } |
| |
| } // namespace |
| |
| class DlpAdaptorTest : public ::testing::Test { |
| public: |
| DlpAdaptorTest() { |
| // By passing true to SetFanotifyWatcherStartedForTesting, |
| // DlpAdaptor won't try to start Fanotify. And given that these tests are |
| // meant to test DlpAdaptor and don't depend on Fanotify, so Fanotify |
| // initialisation isn't needed anyway. |
| GetDlpAdaptor()->SetFanotifyWatcherStartedForTesting(true); |
| } |
| |
| ~DlpAdaptorTest() override = default; |
| |
| DlpAdaptorTest(const DlpAdaptorTest&) = delete; |
| DlpAdaptorTest& operator=(const DlpAdaptorTest&) = delete; |
| |
| DlpAdaptor* GetDlpAdaptor() { return helper_.adaptor(); } |
| scoped_refptr<dbus::MockObjectProxy> GetMockDlpFilesPolicyServiceProxy() { |
| return helper_.mock_dlp_files_policy_service_proxy(); |
| } |
| |
| AddFileRequest CreateAddFileRequest(const base::FilePath& path, |
| const std::string& source, |
| const std::string& referrer) { |
| AddFileRequest request; |
| request.set_file_path(path.value()); |
| request.set_source_url(source); |
| request.set_referrer_url(referrer); |
| return request; |
| } |
| |
| std::vector<uint8_t> CreateSerializedAddFilesRequest( |
| std::vector<AddFileRequest> add_file_requests) { |
| AddFilesRequest request; |
| *request.mutable_add_file_requests() = {add_file_requests.begin(), |
| add_file_requests.end()}; |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| std::vector<uint8_t> CreateSerializedRequestFileAccessRequest( |
| std::vector<std::string> files_paths, |
| int pid, |
| const std::string& destination) { |
| RequestFileAccessRequest request; |
| *request.mutable_files_paths() = {files_paths.begin(), files_paths.end()}; |
| request.set_process_id(pid); |
| request.set_destination_url(destination); |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| std::vector<uint8_t> CreateSerializedRequestFileAccessRequest( |
| std::vector<std::string> files_paths, int pid, DlpComponent component) { |
| RequestFileAccessRequest request; |
| *request.mutable_files_paths() = {files_paths.begin(), files_paths.end()}; |
| request.set_process_id(pid); |
| request.set_destination_component(component); |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| std::vector<uint8_t> CreateSerializedCheckFilesTransferRequest( |
| std::vector<std::string> files_paths, const std::string& destination) { |
| CheckFilesTransferRequest request; |
| *request.mutable_files_paths() = {files_paths.begin(), files_paths.end()}; |
| request.set_destination_url(destination); |
| request.set_file_action(FileAction::TRANSFER); |
| request.set_io_task_id(kIoTaskId); |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| std::vector<uint8_t> CreateSerializedCheckFilesTransferRequest( |
| std::vector<std::string> files_paths, DlpComponent destination) { |
| CheckFilesTransferRequest request; |
| *request.mutable_files_paths() = {files_paths.begin(), files_paths.end()}; |
| request.set_destination_component(destination); |
| request.set_file_action(FileAction::TRANSFER); |
| request.set_io_task_id(kIoTaskId); |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| std::vector<uint8_t> CreateSerializedGetFilesSourcesRequest( |
| std::vector<std::string> paths) { |
| GetFilesSourcesRequest request; |
| *request.mutable_files_paths() = {paths.begin(), paths.end()}; |
| |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| return proto_blob; |
| } |
| |
| void StubIsDlpPolicyMatched( |
| dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::MockObjectProxy::ResponseCallback* response_callback, |
| dbus::MockObjectProxy::ErrorCallback* error_callback) { |
| method_call->SetSerial(kDBusSerial); |
| auto response = dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| |
| IsDlpPolicyMatchedResponse response_proto; |
| response_proto.set_restricted(is_file_policy_restricted_); |
| |
| writer.AppendProtoAsArrayOfBytes(response_proto); |
| std::move(*response_callback).Run(response.get()); |
| } |
| |
| void StubReplyWithError( |
| dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::MockObjectProxy::ResponseCallback* response_callback, |
| dbus::MockObjectProxy::ErrorCallback* error_callback) { |
| method_call->SetSerial(kDBusSerial); |
| auto error_response = dbus::ErrorResponse::FromMethodCall( |
| method_call, "dlp.Error", "error message"); |
| std::move(*error_callback).Run(error_response.get()); |
| } |
| |
| void StubReplyBadProto( |
| dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::MockObjectProxy::ResponseCallback* response_callback, |
| dbus::MockObjectProxy::ErrorCallback* error_callback) { |
| method_call->SetSerial(kDBusSerial); |
| auto response = dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| |
| writer.AppendArrayOfBytes(RandomProtoBlob()); |
| std::move(*response_callback).Run(response.get()); |
| } |
| |
| void StubIsFilesTransferRestricted( |
| dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::MockObjectProxy::ResponseCallback* response_callback, |
| dbus::MockObjectProxy::ErrorCallback* error_callback) { |
| dbus::MessageReader reader(method_call); |
| IsFilesTransferRestrictedRequest request; |
| EXPECT_TRUE(reader.PopArrayOfBytesAsProto(&request)); |
| |
| method_call->SetSerial(kDBusSerial); |
| auto response = dbus::Response::FromMethodCall(method_call); |
| dbus::MessageWriter writer(response.get()); |
| |
| IsFilesTransferRestrictedResponse response_proto; |
| for (const auto& file : request.transferred_files()) { |
| for (const auto& [file_metadata, restriction_level] : |
| files_restrictions_) { |
| if (file.inode() == file_metadata.inode()) { |
| FileRestriction* file_restriction = |
| response_proto.add_files_restrictions(); |
| *file_restriction->mutable_file_metadata() = file_metadata; |
| file_restriction->set_restriction_level(restriction_level); |
| break; |
| } |
| } |
| } |
| |
| writer.AppendProtoAsArrayOfBytes(response_proto); |
| std::move(*response_callback).Run(response.get()); |
| } |
| |
| void AddFilesAndCheck(const std::vector<AddFileRequest>& add_file_requests, |
| bool expected_result) { |
| bool success; |
| std::unique_ptr< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>> |
| response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| base::RunLoop run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* success, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| AddFilesResponse response = |
| ParseResponse<AddFilesResponse>(proto_blob); |
| *success = response.error_message().empty(); |
| run_loop->Quit(); |
| }, |
| &success, &run_loop)); |
| GetDlpAdaptor()->AddFiles( |
| std::move(response), |
| CreateSerializedAddFilesRequest(add_file_requests)); |
| run_loop.Run(); |
| EXPECT_EQ(expected_result, success); |
| } |
| |
| GetFilesSourcesResponse GetFilesSources(std::vector<std::string> paths) { |
| GetFilesSourcesResponse result; |
| std::unique_ptr< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>> |
| response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| base::RunLoop run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](GetFilesSourcesResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<GetFilesSourcesResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| |
| GetDlpAdaptor()->GetFilesSources( |
| std::move(response), CreateSerializedGetFilesSourcesRequest(paths)); |
| run_loop.Run(); |
| return result; |
| } |
| |
| GetDatabaseEntriesResponse GetDatabaseEntries() { |
| GetDatabaseEntriesResponse result; |
| std::unique_ptr< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>> |
| response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| base::RunLoop run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](GetDatabaseEntriesResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<GetDatabaseEntriesResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| |
| GetDlpAdaptor()->GetDatabaseEntries(std::move(response)); |
| run_loop.Run(); |
| return result; |
| } |
| |
| void InitDatabase() { |
| database_directory_ = std::make_unique<base::ScopedTempDir>(); |
| ASSERT_TRUE(database_directory_->CreateUniqueTempDir()); |
| base::RunLoop run_loop; |
| GetDlpAdaptor()->InitDatabase(database_directory_->GetPath(), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| void SetRulesAndInitFanotify() { |
| GetDlpAdaptor()->SetFanotifyWatcherStartedForTesting(false); |
| SetDlpFilesPolicyRequest request; |
| ::dlp::DlpFilesRule* rule = request.add_rules(); |
| rule->add_source_urls("example.com"); |
| rule->add_destination_urls("*"); |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_TRUE(helper_.IsFanotifyWatcherActive()); |
| } |
| |
| protected: |
| bool is_file_policy_restricted_; |
| std::vector<std::pair<FileMetadata, RestrictionLevel>> files_restrictions_; |
| std::unique_ptr<base::ScopedTempDir> database_directory_; |
| |
| DlpAdaptorTestHelper helper_; |
| }; |
| |
| TEST_F(DlpAdaptorTest, AllowedWithoutDatabase) { |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest({/*inode=*/1, /*crtime=*/0}, kPid, |
| waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kDatabaseNotReadyError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, AllowedWithDatabase) { |
| InitDatabase(); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest({/*inode=*/1, /*crtime=*/0}, kPid, |
| waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, NotRestrictedFileAddedAndAllowed) { |
| InitDatabase(); |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| FileId id = GetFileId(file_path.value()); |
| |
| is_file_policy_restricted_ = false; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsDlpPolicyMatched)); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, NotRestrictedFileAddedAndDlpPolicyMatched_BadProto) { |
| InitDatabase(); |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| FileId id = GetFileId(file_path.value()); |
| |
| is_file_policy_restricted_ = false; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyBadProto)); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_FALSE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, |
| NotRestrictedFileAddedAndDlpPolicyMatched_ResponseError) { |
| InitDatabase(); |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| FileId id = GetFileId(file_path.value()); |
| |
| is_file_policy_restricted_ = false; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyWithError)); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_FALSE(waiter.GetResult()); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kRestrictionDetectionError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndNotAllowed) { |
| InitDatabase(); |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| FileId id = GetFileId(file_path.value()); |
| |
| is_file_policy_restricted_ = true; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsDlpPolicyMatched)); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_FALSE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAllowedForItself) { |
| InitDatabase(); |
| |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| FileId id = GetFileId(file_path.value()); |
| |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, base::GetCurrentProcId(), |
| waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| // |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedAllowed) { |
| // Create database. |
| base::ScopedTempDir database_directory; |
| ASSERT_TRUE(database_directory.CreateUniqueTempDir()); |
| base::RunLoop run_loop; |
| GetDlpAdaptor()->InitDatabase(database_directory.GetPath(), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path1; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, "source", "referrer"), |
| CreateAddFileRequest(file_path2, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), |
| CreateSerializedRequestFileAccessRequest( |
| {file_path1.value(), file_path2.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_FALSE(IsFdClosed(lifeline_fd.get())); |
| |
| // Access the first file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id1, kPid, waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| |
| // Second request still allowed. |
| FileOpenRequestResultWaiter waiter2; |
| helper_.ProcessFileOpenRequest(id1, kPid, waiter2.GetCallback()); |
| |
| EXPECT_TRUE(waiter2.GetResult()); |
| |
| // Access the second file. |
| FileOpenRequestResultWaiter waiter3; |
| helper_.ProcessFileOpenRequest(id2, kPid, waiter3.GetCallback()); |
| |
| EXPECT_TRUE(waiter3.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| // Cached allow response had no access grant attached to its ScopedFD. |
| // This test makes sure this doesn't happen anymore. |
| // http://b/281497666 |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedCachedAllowed) { |
| InitDatabase(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| FileMetadata file_metadata; |
| file_metadata.set_inode(id.first); |
| file_metadata.set_crtime(id.second); |
| file_metadata.set_path(file_path.value()); |
| files_restrictions_.push_back( |
| {std::move(file_metadata), RestrictionLevel::LEVEL_ALLOW}); |
| ON_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillByDefault( |
| Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| // Called for the first RequestFileAccess and both ProcessFileOpen after the |
| // closed ScopedFD. The second RequestFileAccess is cached. |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .Times(3); |
| |
| // Second loop run with cached results |
| for (int i = 0; i < 2; ++i) { |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, DlpComponent::USB)); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_FALSE(IsFdClosed(lifeline_fd.get())); |
| |
| // Access the file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| EXPECT_TRUE(waiter.GetResult()); |
| |
| // Cancel access to the file. |
| lifeline_fd.reset(); |
| |
| // Let DlpAdaptor process that lifeline_fd is closed. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Second request: still allowed |
| FileOpenRequestResultWaiter waiter2; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter2.GetCallback()); |
| EXPECT_TRUE(waiter2.GetResult()); |
| } |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileSystemRequestedAllowed) { |
| InitDatabase(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| FileMetadata file_metadata; |
| file_metadata.set_inode(id.first); |
| file_metadata.set_crtime(id.second); |
| file_metadata.set_path(file_path.value()); |
| files_restrictions_.push_back( |
| {std::move(file_metadata), RestrictionLevel::LEVEL_BLOCK}); |
| ON_CALL(*GetMockDlpFilesPolicyServiceProxy(), DoCallMethodWithErrorCallback) |
| .WillByDefault( |
| Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| // Called for both ProcessFileOpen after the closed ScopedFD. |
| // RequestFileAccess is allowed with only checking component. |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback) |
| .Times(2); |
| |
| // Both runs should be answered without getting the cache involved. |
| for (int i = 0; i < 2; ++i) { |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), |
| CreateSerializedRequestFileAccessRequest({file_path.value()}, kPid, |
| DlpComponent::SYSTEM)); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_FALSE(IsFdClosed(lifeline_fd.get())); |
| |
| // Access the file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| EXPECT_TRUE(waiter.GetResult()); |
| |
| // Cancel access to the file. |
| lifeline_fd.reset(); |
| |
| // Let DlpAdaptor process that lifeline_fd is closed. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Second request: still allowed |
| FileOpenRequestResultWaiter waiter2; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter2.GetCallback()); |
| EXPECT_TRUE(waiter2.GetResult()); |
| } |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedButBadProto) { |
| InitDatabase(); |
| |
| // Create file to request access by ids. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyBadProto)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, DlpComponent::USB)); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_FALSE(allowed); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedButErrorResponse) { |
| InitDatabase(); |
| |
| // Create file to request access by inodes. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyWithError)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, DlpComponent::USB)); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_FALSE(allowed); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kRestrictionDetectionError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedCachedNotAllowed) { |
| InitDatabase(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| FileMetadata file_metadata; |
| file_metadata.set_inode(id.first); |
| file_metadata.set_crtime(id.second); |
| file_metadata.set_path(file_path.value()); |
| files_restrictions_.push_back( |
| {std::move(file_metadata), RestrictionLevel::LEVEL_BLOCK}); |
| |
| // Second loop run with cached results, third with another destination. |
| for (int i = 0; i < 3; ++i) { |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| // The first and third call needs to query the proxy - the second call is |
| // answered from the cache |
| if (i == 0 || i == 2) { |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce( |
| Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| } |
| const std::string destination = i < 2 ? "destination" : "destination2"; |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, destination)); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_FALSE(allowed); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| |
| is_file_policy_restricted_ = true; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsDlpPolicyMatched)); |
| |
| // Access the file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| EXPECT_FALSE(waiter.GetResult()); |
| } |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFilesNotAddedAndRequestedAllowed) { |
| InitDatabase(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path1; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| // Add only first file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), |
| CreateSerializedRequestFileAccessRequest( |
| {file_path1.value(), file_path2.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_FALSE(IsFdClosed(lifeline_fd.get())); |
| |
| // Access the first file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id1, kPid, waiter.GetCallback()); |
| |
| EXPECT_TRUE(waiter.GetResult()); |
| |
| // Access the second file. |
| FileOpenRequestResultWaiter waiter2; |
| helper_.ProcessFileOpenRequest(id2, kPid, waiter2.GetCallback()); |
| |
| EXPECT_TRUE(waiter2.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileNotAddedAndImmediatelyAllowed) { |
| InitDatabase(); |
| |
| // Create files to request access by ids. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Access already allowed. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| EXPECT_TRUE(waiter.GetResult()); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .Times(0); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| |
| // Access still allowed. |
| FileOpenRequestResultWaiter waiter2; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter2.GetCallback()); |
| |
| EXPECT_TRUE(waiter2.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedAndRequestedNotAllowed) { |
| InitDatabase(); |
| |
| // Create file to request access by id. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| FileMetadata file_metadata; |
| file_metadata.set_path(file_path.value()); |
| file_metadata.set_inode(id.first); |
| file_metadata.set_crtime(id.second); |
| files_restrictions_.push_back( |
| {std::move(file_metadata), RestrictionLevel::LEVEL_BLOCK}); |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_FALSE(allowed); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| |
| // Setup callback for DlpFilesPolicyService::IsDlpPolicyMatched() |
| is_file_policy_restricted_ = true; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsDlpPolicyMatched)); |
| |
| // Request access to the file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_FALSE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, RestrictedFileAddedRequestedAndCancelledNotAllowed) { |
| InitDatabase(); |
| |
| // Create file to request access by id. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| const FileId id = GetFileId(file_path.value()); |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::ScopedFD* lifeline_fd, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| lifeline_fd->reset(dup((fd.get()))); |
| run_loop->Quit(); |
| }, |
| &allowed, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_FALSE(IsFdClosed(lifeline_fd.get())); |
| |
| // Cancel access to the file. |
| lifeline_fd.reset(); |
| |
| // Let DlpAdaptor process that lifeline_fd is closed. |
| base::RunLoop().RunUntilIdle(); |
| |
| // Setup callback for DlpFilesPolicyService::IsDlpPolicyMatched() |
| is_file_policy_restricted_ = true; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubIsDlpPolicyMatched)); |
| |
| // Request access to the file. |
| FileOpenRequestResultWaiter waiter; |
| helper_.ProcessFileOpenRequest(id, kPid, waiter.GetCallback()); |
| |
| EXPECT_FALSE(waiter.GetResult()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| // DlpAdaptor::RequestFileAccess crashes if file access is requested while the |
| // database isn't created yet. This test makes sure this doesn't happen anymore. |
| // https://crbug.com/1267295. |
| TEST_F(DlpAdaptorTest, RequestAllowedWithoutDatabase) { |
| // Create file to request access by id. |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| |
| // Request access to the file. |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| bool allowed; |
| base::RunLoop request_file_access_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* allowed, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob, const base::ScopedFD& fd) { |
| RequestFileAccessResponse response = |
| ParseResponse<RequestFileAccessResponse>(proto_blob); |
| *allowed = response.allowed(); |
| run_loop->Quit(); |
| }, |
| &allowed, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), CreateSerializedRequestFileAccessRequest( |
| {file_path.value()}, kPid, "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(allowed); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSources) { |
| InitDatabase(); |
| |
| // Create files to request sources. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| |
| const std::string source1 = "source1"; |
| const std::string referrer1 = "referrer1"; |
| |
| // Add one of the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1)}, |
| /*expected_result=*/true); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value(), "/bad/path"}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 1u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.path(), file_path1.value()); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesDatabaseMigrated) { |
| // Opening the database with the new table. |
| InitDatabase(); |
| |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add first file to the legacy database. |
| helper_.AddFileToLegacyDb(id1, source1, referrer1); |
| |
| // Add the second file to the new database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value(), "/bad/path"}); |
| |
| // Only the second file is expected as database was not migrated. |
| ASSERT_EQ(response.files_metadata_size(), 1u); |
| |
| FileMetadata file_metadata = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata.inode(), id2.first); |
| EXPECT_EQ(file_metadata.crtime(), id2.second); |
| EXPECT_EQ(file_metadata.source_url(), source2); |
| EXPECT_EQ(file_metadata.referrer_url(), referrer2); |
| |
| // Reinitialize database to run migration. Do it twice to ensure that second |
| // migration is not kicked out or doesn't change anything. |
| for (int i = 0; i < 2; i++) { |
| GetDlpAdaptor()->CloseDatabaseForTesting(); |
| base::RunLoop run_loop; |
| GetDlpAdaptor()->InitDatabase(database_directory_->GetPath(), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| |
| response = |
| GetFilesSources({file_path1.value(), file_path2.value(), "/bad/path"}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 2u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| |
| FileMetadata file_metadata2 = response.files_metadata()[1]; |
| EXPECT_EQ(file_metadata2.inode(), id2.first); |
| EXPECT_EQ(file_metadata2.crtime(), id2.second); |
| EXPECT_EQ(file_metadata2.source_url(), source2); |
| EXPECT_EQ(file_metadata2.referrer_url(), referrer2); |
| } |
| // There are 4 histogram entries - on the first run when tables were created |
| // (false), after data to migrate was added (true), after it was migrated |
| // (false) and on the next init indicating that no migration is needed anymore |
| // (false). |
| EXPECT_THAT(helper_.GetMetrics(kDlpDatabaseMigrationNeededHistogram), |
| ElementsAre(static_cast<int>(false), static_cast<int>(true), |
| static_cast<int>(false), static_cast<int>(false))); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesWithoutDatabase) { |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add the files to the database. The addition will be pending, so success |
| // is returned. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| EXPECT_EQ(response.files_metadata_size(), 0u); |
| |
| // Create database and add pending files. |
| InitDatabase(); |
| |
| // Check that the pending entries were added. |
| response = GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 2u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| |
| FileMetadata file_metadata2 = response.files_metadata()[1]; |
| EXPECT_EQ(file_metadata2.inode(), id2.first); |
| EXPECT_EQ(file_metadata2.crtime(), id2.second); |
| EXPECT_EQ(file_metadata2.source_url(), source2); |
| EXPECT_EQ(file_metadata2.referrer_url(), referrer2); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kDatabaseNotReadyError), |
| static_cast<int>(AdaptorError::kDatabaseNotReadyError))); |
| } |
| |
| // TODO(b/290389988): Flaky test |
| TEST_F(DlpAdaptorTest, DISABLED_GetFilesSourcesWithoutDatabaseNotAdded) { |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| |
| // Add the files to the database. The addition will be pending, so success |
| // is returned. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, "referrer1"), |
| CreateAddFileRequest(file_path2, source2, "referrer2")}, |
| /*expected_result=*/true); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| EXPECT_EQ(response.files_metadata_size(), 0u); |
| |
| helper_.ReCreateAdaptor(); |
| |
| // Create database and add pending files. |
| InitDatabase(); |
| |
| // Check that the pending entries were not added. |
| response = GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| EXPECT_EQ(response.files_metadata_size(), 0u); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesFileDeletedDBReopenedWithCleanup) { |
| // Enable feature. |
| helper_.SetDatabaseCleanupFeatureEnabled(true); |
| |
| // Create database. |
| InitDatabase(); |
| |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| // Delete one of the files. |
| brillo::DeleteFile(file_path2); |
| // Reinitialize database. |
| GetDlpAdaptor()->CloseDatabaseForTesting(); |
| base::RunLoop run_loop2; |
| GetDlpAdaptor()->InitDatabase(database_directory_->GetPath(), |
| run_loop2.QuitClosure()); |
| run_loop2.Run(); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 1u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesFileDeletedDBReopenedWithoutCleanup) { |
| // Disabled feature. |
| helper_.SetDatabaseCleanupFeatureEnabled(false); |
| |
| // Create database. |
| InitDatabase(); |
| |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| // Delete one of the files. |
| brillo::DeleteFile(file_path2); |
| // Reinitialize database. |
| GetDlpAdaptor()->CloseDatabaseForTesting(); |
| base::RunLoop run_loop2; |
| GetDlpAdaptor()->InitDatabase(database_directory_->GetPath(), |
| run_loop2.QuitClosure()); |
| run_loop2.Run(); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 1u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesFileDeletedInFlight) { |
| SetRulesAndInitFanotify(); |
| // Create database. |
| InitDatabase(); |
| |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| // Delete one of the files. |
| brillo::DeleteFile(file_path2); |
| // Notify that file was deleted. |
| helper_.OnFileDeleted(id2); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 1u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| |
| // Reinitialize database. |
| GetDlpAdaptor()->CloseDatabaseForTesting(); |
| InitDatabase(); |
| |
| // Delete the other file. |
| brillo::DeleteFile(file_path1); |
| // Notify that file was deleted. |
| helper_.OnFileDeleted(id1); |
| |
| response = GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 0u); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSourcesOverwrite) { |
| // Create database. |
| InitDatabase(); |
| |
| // Create files to request sources by ids. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| GetFilesSourcesResponse response = |
| GetFilesSources({file_path1.value(), file_path2.value()}); |
| |
| ASSERT_EQ(response.files_metadata_size(), 2u); |
| |
| FileMetadata file_metadata1 = response.files_metadata()[0]; |
| EXPECT_EQ(file_metadata1.inode(), id1.first); |
| EXPECT_EQ(file_metadata1.crtime(), id1.second); |
| EXPECT_EQ(file_metadata1.source_url(), source1); |
| EXPECT_EQ(file_metadata1.referrer_url(), referrer1); |
| |
| FileMetadata file_metadata2 = response.files_metadata()[1]; |
| EXPECT_EQ(file_metadata2.inode(), id2.first); |
| EXPECT_EQ(file_metadata2.crtime(), id2.second); |
| EXPECT_EQ(file_metadata2.source_url(), source2); |
| EXPECT_EQ(file_metadata2.referrer_url(), referrer2); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, SetDlpFilesPolicy_EmptyProto) { |
| SetDlpFilesPolicyRequest request; |
| request.add_rules(); |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_FALSE(helper_.IsFanotifyWatcherActive()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, SetDlpFilesPolicy_BadProto) { |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(RandomProtoBlob()); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_TRUE(response.has_error_message()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, SetDlpFilesPolicy_EnableDisable) { |
| // Testing fanotify watcher start for real. |
| GetDlpAdaptor()->SetFanotifyWatcherStartedForTesting(false); |
| |
| // Send non-empty policy -> enabled. |
| { |
| SetDlpFilesPolicyRequest request; |
| ::dlp::DlpFilesRule* rule = request.add_rules(); |
| rule->add_source_urls("example.com"); |
| rule->add_destination_urls("*"); |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_TRUE(helper_.IsFanotifyWatcherActive()); |
| } |
| |
| // Send another non-empty policy -> enabled. |
| { |
| SetDlpFilesPolicyRequest request; |
| ::dlp::DlpFilesRule* rule = request.add_rules(); |
| rule->add_source_urls("example.com"); |
| rule->add_destination_urls("google.com"); |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_TRUE(helper_.IsFanotifyWatcherActive()); |
| } |
| |
| // Send same non-empty policy -> enabled. |
| { |
| SetDlpFilesPolicyRequest request; |
| ::dlp::DlpFilesRule* rule = request.add_rules(); |
| rule->add_source_urls("example.com"); |
| rule->add_destination_urls("google.com"); |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_TRUE(helper_.IsFanotifyWatcherActive()); |
| } |
| |
| // Send empty policy -> disabled. |
| { |
| SetDlpFilesPolicyRequest request; |
| std::vector<uint8_t> proto_blob(request.ByteSizeLong()); |
| request.SerializeToArray(proto_blob.data(), proto_blob.size()); |
| |
| std::vector<uint8_t> response_blob = |
| GetDlpAdaptor()->SetDlpFilesPolicy(proto_blob); |
| |
| SetDlpFilesPolicyResponse response = |
| ParseResponse<SetDlpFilesPolicyResponse>(response_blob); |
| |
| EXPECT_FALSE(response.has_error_message()); |
| EXPECT_FALSE(helper_.IsFanotifyWatcherActive()); |
| } |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, AddZeroFilesToTheDaemon) { |
| AddFilesAndCheck({}, /*expected_result=*/true); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, AddFiles_BadProto) { |
| bool success; |
| std::unique_ptr< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>> |
| response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| base::RunLoop run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](bool* success, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| AddFilesResponse response = ParseResponse<AddFilesResponse>(proto_blob); |
| *success = response.error_message().empty(); |
| run_loop->Quit(); |
| }, |
| &success, &run_loop)); |
| GetDlpAdaptor()->AddFiles(std::move(response), RandomProtoBlob()); |
| run_loop.Run(); |
| EXPECT_FALSE(success); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kAddFileError), |
| static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, AddFiles_NonExistentFile) { |
| AddFilesAndCheck( |
| {CreateAddFileRequest(base::FilePath("/tmp/non-existent-file"), "source", |
| "referrer")}, |
| /*expected_result=*/false); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kAddFileError), |
| static_cast<int>(AdaptorError::kInodeRetrievalError))); |
| } |
| |
| // TODO(b/304574852): Using TaskEnvironment causes DlpAdaptorTest to hang. |
| // We need to find another way to fake time or rewrite the test. |
| TEST_F(DlpAdaptorTest, DISABLED_AddFiles_OldFile) { |
| base::FilePath file_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &file_path); |
| // const FileId id = GetFileId(file_path.value()); |
| //// Set to file creation time. |
| // task_environment_.FastForwardBy(base::Time::FromTimeT(id.second) - |
| // base::Time::Now()); |
| //// Advance by a minute. |
| // task_environment_.FastForwardBy(base::Minutes(1)); |
| AddFilesAndCheck({CreateAddFileRequest(file_path, "source", "referrer")}, |
| /*expected_result=*/false); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kAddFileError), |
| static_cast<int>(AdaptorError::kAddedFileIsTooOld))); |
| } |
| |
| TEST_F(DlpAdaptorTest, AddFiles_Symlink) { |
| base::FilePath file_path; |
| base::CreateTemporaryFile(&file_path); |
| base::FilePath symlink_path; |
| base::CreateTemporaryFileInDir(helper_.home_path(), &symlink_path); |
| brillo::DeleteFile(symlink_path); |
| base::CreateSymbolicLink(file_path, symlink_path); |
| |
| AddFilesAndCheck({CreateAddFileRequest(symlink_path, "source", "referrer")}, |
| /*expected_result=*/false); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kAddFileError), |
| static_cast<int>(AdaptorError::kAddedFileIsNotOnUserHome))); |
| } |
| |
| TEST_F(DlpAdaptorTest, RequestFileAccess_BadProto) { |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| RequestFileAccessResponse request_file_response; |
| response->set_return_callback(base::BindOnce( |
| [](RequestFileAccessResponse* response, base::ScopedFD* lifeline_fd, |
| base::RunLoop* run_loop, const std::vector<uint8_t>& proto_blob, |
| const base::ScopedFD& fd) { |
| *response = ParseResponse<RequestFileAccessResponse>(proto_blob); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &request_file_response, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess(std::move(response), RandomProtoBlob()); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_FALSE(request_file_response.allowed()); |
| EXPECT_FALSE(request_file_response.error_message().empty()); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, RequestFileAccess_NonExistentFile) { |
| InitDatabase(); |
| |
| auto response = std::make_unique<brillo::dbus_utils::MockDBusMethodResponse< |
| std::vector<uint8_t>, base::ScopedFD>>(nullptr); |
| base::ScopedFD lifeline_fd; |
| base::RunLoop request_file_access_run_loop; |
| RequestFileAccessResponse request_file_response; |
| response->set_return_callback(base::BindOnce( |
| [](RequestFileAccessResponse* response, base::ScopedFD* lifeline_fd, |
| base::RunLoop* run_loop, const std::vector<uint8_t>& proto_blob, |
| const base::ScopedFD& fd) { |
| *response = ParseResponse<RequestFileAccessResponse>(proto_blob); |
| lifeline_fd->reset(dup(fd.get())); |
| run_loop->Quit(); |
| }, |
| &request_file_response, &lifeline_fd, &request_file_access_run_loop)); |
| GetDlpAdaptor()->RequestFileAccess( |
| std::move(response), |
| CreateSerializedRequestFileAccessRequest({"/tmp/non-existent-file"}, kPid, |
| "destination")); |
| request_file_access_run_loop.Run(); |
| |
| EXPECT_TRUE(request_file_response.allowed()); |
| EXPECT_TRUE(request_file_response.error_message().empty()); |
| EXPECT_TRUE(IsFdClosed(lifeline_fd.get())); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetFilesSources_BadProto) { |
| GetFilesSourcesResponse result; |
| std::unique_ptr< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>> |
| response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| base::RunLoop run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](GetFilesSourcesResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<GetFilesSourcesResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| |
| GetDlpAdaptor()->GetFilesSources(std::move(response), RandomProtoBlob()); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(result.error_message().empty()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, CheckFilesTransfer_BadProto) { |
| InitDatabase(); |
| |
| CheckFilesTransferResponse result; |
| base::RunLoop run_loop; |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| response->set_return_callback(base::BindOnce( |
| [](CheckFilesTransferResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<CheckFilesTransferResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| GetDlpAdaptor()->CheckFilesTransfer(std::move(response), RandomProtoBlob()); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(result.error_message().empty()); |
| EXPECT_TRUE(result.files_paths().empty()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, CheckFilesTransfer_DbNotInitialized) { |
| CheckFilesTransferResponse result; |
| base::RunLoop run_loop; |
| |
| // Create files. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| |
| // Add the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, "referrer1"), |
| CreateAddFileRequest(file_path2, source2, "referrer2")}, |
| /*expected_result=*/true); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| response->set_return_callback(base::BindOnce( |
| [](CheckFilesTransferResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<CheckFilesTransferResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| GetDlpAdaptor()->CheckFilesTransfer( |
| std::move(response), |
| CreateSerializedCheckFilesTransferRequest( |
| {file_path1.value(), file_path2.value()}, DlpComponent::USB)); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(result.error_message().empty()); |
| EXPECT_TRUE(result.files_paths().empty()); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kDatabaseNotReadyError), |
| static_cast<int>(AdaptorError::kDatabaseNotReadyError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, CheckFilesTransfer_IsFilesTransferRestrictedBadProto) { |
| // Create database. |
| InitDatabase(); |
| |
| CheckFilesTransferResponse result; |
| base::RunLoop run_loop; |
| |
| // Create file. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| |
| const std::string source1 = "source1"; |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, "referrer1")}, |
| /*expected_result=*/true); |
| |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyBadProto)); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| response->set_return_callback(base::BindOnce( |
| [](CheckFilesTransferResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<CheckFilesTransferResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| GetDlpAdaptor()->CheckFilesTransfer( |
| std::move(response), CreateSerializedCheckFilesTransferRequest( |
| {file_path1.value()}, DlpComponent::USB)); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(result.error_message().empty()); |
| EXPECT_TRUE(result.files_paths().empty()); |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kInvalidProtoError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, |
| CheckFilesTransfer_IsFilesTransferRestrictedResponseError) { |
| // Create database. |
| InitDatabase(); |
| |
| CheckFilesTransferResponse result; |
| base::RunLoop run_loop; |
| |
| // Create file. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| |
| const std::string source1 = "source1"; |
| |
| // Add the file to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, "referrer1")}, |
| /*expected_result=*/true); |
| |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .WillOnce(Invoke(this, &DlpAdaptorTest::StubReplyWithError)); |
| |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| response->set_return_callback(base::BindOnce( |
| [](CheckFilesTransferResponse* result, base::RunLoop* run_loop, |
| const std::vector<uint8_t>& proto_blob) { |
| *result = ParseResponse<CheckFilesTransferResponse>(proto_blob); |
| run_loop->Quit(); |
| }, |
| &result, &run_loop)); |
| GetDlpAdaptor()->CheckFilesTransfer( |
| std::move(response), CreateSerializedCheckFilesTransferRequest( |
| {file_path1.value()}, DlpComponent::USB)); |
| run_loop.Run(); |
| |
| EXPECT_FALSE(result.error_message().empty()); |
| EXPECT_TRUE(result.files_paths().empty()); |
| EXPECT_THAT( |
| helper_.GetMetrics(kDlpAdaptorErrorHistogram), |
| ElementsAre(static_cast<int>(AdaptorError::kRestrictionDetectionError))); |
| } |
| |
| TEST_F(DlpAdaptorTest, GetDatabaseEntries) { |
| InitDatabase(); |
| |
| // Create files. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add two of the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| GetDatabaseEntriesResponse response = GetDatabaseEntries(); |
| |
| ASSERT_EQ(response.files_entries_size(), 2u); |
| |
| std::map<FileId, FileMetadata> files_entries_map; |
| for (const auto& file_entry : response.files_entries()) { |
| files_entries_map[FileId(file_entry.inode(), file_entry.crtime())] = |
| file_entry; |
| } |
| |
| ASSERT_TRUE(base::Contains(files_entries_map, id1)); |
| EXPECT_EQ(files_entries_map.at(id1).source_url(), source1); |
| EXPECT_EQ(files_entries_map.at(id1).referrer_url(), referrer1); |
| |
| ASSERT_TRUE(base::Contains(files_entries_map, id2)); |
| EXPECT_EQ(files_entries_map.at(id2).source_url(), source2); |
| EXPECT_EQ(files_entries_map.at(id2).referrer_url(), referrer2); |
| } |
| |
| class DlpAdaptorCheckFilesTransferTest |
| : public DlpAdaptorTest, |
| public ::testing::WithParamInterface<RestrictionLevel> { |
| public: |
| DlpAdaptorCheckFilesTransferTest(const DlpAdaptorCheckFilesTransferTest&) = |
| delete; |
| DlpAdaptorCheckFilesTransferTest& operator=( |
| const DlpAdaptorCheckFilesTransferTest&) = delete; |
| |
| protected: |
| DlpAdaptorCheckFilesTransferTest() = default; |
| ~DlpAdaptorCheckFilesTransferTest() override = default; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(DlpAdaptor, |
| DlpAdaptorCheckFilesTransferTest, |
| ::testing::Values(RestrictionLevel::LEVEL_UNSPECIFIED, |
| RestrictionLevel::LEVEL_ALLOW, |
| RestrictionLevel::LEVEL_REPORT, |
| RestrictionLevel::LEVEL_WARN_PROCEED, |
| RestrictionLevel::LEVEL_WARN_CANCEL, |
| RestrictionLevel::LEVEL_BLOCK)); |
| |
| TEST_P(DlpAdaptorCheckFilesTransferTest, Run) { |
| // Create database. |
| InitDatabase(); |
| |
| // Create files. |
| base::FilePath file_path1; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path1)); |
| const FileId id1 = GetFileId(file_path1.value()); |
| base::FilePath file_path2; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path2)); |
| const FileId id2 = GetFileId(file_path2.value()); |
| base::FilePath file_path3; |
| ASSERT_TRUE(base::CreateTemporaryFileInDir(helper_.home_path(), &file_path3)); |
| |
| const std::string source1 = "source1"; |
| const std::string source2 = "source2"; |
| const std::string referrer1 = "referrer1"; |
| const std::string referrer2 = "referrer2"; |
| |
| // Add two of the files to the database. |
| AddFilesAndCheck({CreateAddFileRequest(file_path1, source1, referrer1), |
| CreateAddFileRequest(file_path2, source2, referrer2)}, |
| /*expected_result=*/true); |
| |
| // Setup callback for DlpFilesPolicyService::IsFilesTransferRestricted() |
| files_restrictions_.clear(); |
| FileMetadata file1_metadata; |
| file1_metadata.set_inode(id1.first); |
| file1_metadata.set_crtime(id1.second); |
| file1_metadata.set_path(file_path1.value()); |
| FileMetadata file2_metadata; |
| file2_metadata.set_path(file_path2.value()); |
| file2_metadata.set_inode(id2.first); |
| file2_metadata.set_crtime(id2.second); |
| files_restrictions_.push_back( |
| {std::move(file1_metadata), RestrictionLevel::LEVEL_BLOCK}); |
| files_restrictions_.push_back({std::move(file2_metadata), GetParam()}); |
| const int is_files_transfer_restricted_calls = |
| (GetParam() == LEVEL_UNSPECIFIED || GetParam() == LEVEL_WARN_CANCEL) ? 2 |
| : 1; |
| EXPECT_CALL(*GetMockDlpFilesPolicyServiceProxy(), |
| DoCallMethodWithErrorCallback(_, _, _, _)) |
| .Times(is_files_transfer_restricted_calls) |
| .WillRepeatedly( |
| Invoke(this, &DlpAdaptorTest::StubIsFilesTransferRestricted)); |
| |
| // Do 2 times for cache. |
| for (int i = 0; i < 2; i++) { |
| // Request access to the file. |
| auto response = std::make_unique< |
| brillo::dbus_utils::MockDBusMethodResponse<std::vector<uint8_t>>>( |
| nullptr); |
| |
| std::vector<std::string> restricted_files_paths; |
| base::RunLoop check_files_transfer_run_loop; |
| response->set_return_callback(base::BindOnce( |
| [](std::vector<std::string>* restricted_files_paths, |
| base::RunLoop* run_loop, const std::vector<uint8_t>& proto_blob) { |
| CheckFilesTransferResponse response = |
| ParseResponse<CheckFilesTransferResponse>(proto_blob); |
| restricted_files_paths->insert(restricted_files_paths->begin(), |
| response.files_paths().begin(), |
| response.files_paths().end()); |
| run_loop->Quit(); |
| }, |
| &restricted_files_paths, &check_files_transfer_run_loop)); |
| GetDlpAdaptor()->CheckFilesTransfer( |
| std::move(response), |
| CreateSerializedCheckFilesTransferRequest( |
| {file_path1.value(), file_path2.value(), file_path3.value()}, |
| "destination")); |
| check_files_transfer_run_loop.Run(); |
| |
| if (GetParam() == RestrictionLevel::LEVEL_BLOCK || |
| GetParam() == RestrictionLevel::LEVEL_WARN_CANCEL) { |
| EXPECT_EQ(restricted_files_paths.size(), 2u); |
| } else { |
| EXPECT_EQ(restricted_files_paths.size(), 1u); |
| EXPECT_EQ(restricted_files_paths[0], file_path1.value()); |
| } |
| } |
| EXPECT_THAT(helper_.GetMetrics(kDlpAdaptorErrorHistogram), ElementsAre()); |
| } |
| |
| } // namespace dlp |