blob: 6fa2d6dce9654ecbe495d0b41a77814e10fffe03 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "biod/biod_storage.h"
#include <sys/resource.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <unordered_set>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/check.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <testing/gtest/include/gtest/gtest.h>
#include <base/strings/string_util.h>
#include <base/files/important_file_writer.h>
#include <base/json/json_string_value_serializer.h>
namespace biod {
using Record = BiodStorageInterface::Record;
using RecordMetadata = BiodStorageInterface::RecordMetadata;
namespace {
const char kBiometricsManagerName[] = "BiometricsManager";
const base::FilePath kFilePath("TestFile");
const char kRecordId1[] = "00000000_0000_0000_0000_000000000001";
const char kUserId1[] = "0000000000000000000000000000000000000001";
const char kLabel1[] = "record1";
const std::vector<uint8_t> kValidationVal1 = {0x00, 0x01};
const char kData1[] = "Hello, world1!";
const char kRecordId2[] = "00000000_0000_0000_0000_000000000002";
const char kUserId2[] = "0000000000000000000000000000000000000002";
const char kLabel2[] = "record2";
const std::vector<uint8_t> kValidationVal2 = {0x00, 0x02};
const char kData2[] = "Hello, world2!";
const char kRecordId3[] = "00000000_0000_0000_0000_000000000003";
const char kLabel3[] = "record3";
const std::vector<uint8_t> kValidationVal3 = {0x00, 0x03};
const char kData3[] = "Hello, world3!";
constexpr int kPermissions600 =
base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER;
constexpr int kPermissions700 = base::FILE_PERMISSION_USER_MASK;
const char kInvalidUTF8[] = "\xed\xa0\x80\xed\xbf\xbf";
constexpr int kFpc1145TemplateSizeBytes = 47616;
constexpr int kFpc1025TemplateSizeBytes = 5156;
constexpr int kElan80TemplateSizeBytes = 41024;
constexpr int kElan515TemplateSizeBytes = 67064;
/**
* "Max locked memory" value from reading /proc/<PID>/limits on DUT
*
* This matches the default value in the kernel:
* https://chromium.googlesource.com/chromiumos/third_party/kernel/+/a5746cdefaed35de0a85ede48a47e9a340a6f7e6/include/uapi/linux/resource.h#72
*
* The default can be overridden in /etc/security/limits.conf:
* https://access.redhat.com/solutions/61334
*
* or in the upstart script http://upstart.ubuntu.com/cookbook/#limit:
*
* limit memlock <soft> <hard>
*/
constexpr int kRlimitMemlockBytes = 65536;
struct MemlockTestParams {
int rlimit_bytes;
int template_size_bytes;
};
} // namespace
class BiodStorageBaseTest : public ::testing::Test {
public:
BiodStorageBaseTest() {
CHECK(temp_dir_.CreateUniqueTempDir());
root_path_ = temp_dir_.GetPath().AppendASCII("biod_storage_unittest_root");
biod_storage_ = std::make_unique<BiodStorage>(kBiometricsManagerName);
// Since there is no session manager, allow accesses by default.
biod_storage_->set_allow_access(true);
biod_storage_->SetRootPathForTesting(root_path_);
}
BiodStorageBaseTest(const BiodStorageBaseTest&) = delete;
BiodStorageBaseTest& operator=(const BiodStorageBaseTest&) = delete;
~BiodStorageBaseTest() override {
EXPECT_TRUE(base::DeletePathRecursively(temp_dir_.GetPath()));
}
base::Value CreateRecordDictionary(
const std::vector<uint8_t>& validation_val) {
base::Value record_dictionary(base::Value::Type::DICTIONARY);
std::string validation_value_str(validation_val.begin(),
validation_val.end());
base::Base64Encode(validation_value_str, &validation_value_str);
record_dictionary.SetStringKey("match_validation_value",
validation_value_str);
return record_dictionary;
}
protected:
base::ScopedTempDir temp_dir_;
base::FilePath root_path_;
std::unique_ptr<BiodStorage> biod_storage_;
};
TEST_F(BiodStorageBaseTest, WriteAndReadRecords) {
const std::vector<uint8_t> kEmpty;
const std::vector<Record> kRecords(
{{{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1},
{{kRecordFormatVersion, kRecordId2, kUserId2, kLabel2, kValidationVal2},
kData2},
{{kRecordFormatVersion, kRecordId3, kUserId2, kLabel3, kValidationVal3},
kData3}});
// Write the record.
for (auto const& record : kRecords) {
EXPECT_TRUE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
// Read the record.
std::unordered_set<std::string> user_ids({kUserId1, kUserId2});
auto read_result = biod_storage_->ReadRecords(user_ids);
EXPECT_EQ(read_result.size(), kRecords.size());
// Check if all records returned by ReadRecords have "valid" flag set.
EXPECT_TRUE(std::all_of(read_result.begin(), read_result.end(),
[](auto const& record) { return record.valid; }));
EXPECT_TRUE(std::is_permutation(kRecords.begin(), kRecords.end(),
read_result.begin()));
}
TEST_F(BiodStorageBaseTest, WriteAndReadSingleRecord) {
const std::vector<Record> kRecords(
{{{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1},
{{kRecordFormatVersion, kRecordId2, kUserId2, kLabel2, kValidationVal2},
kData2},
{{kRecordFormatVersion, kRecordId3, kUserId2, kLabel3, kValidationVal3},
kData3}});
// Write the record.
for (auto const& record : kRecords) {
EXPECT_TRUE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
// Read the records.
EXPECT_EQ(kRecords[0],
*biod_storage_->ReadSingleRecord(kUserId1, kRecordId1));
EXPECT_EQ(kRecords[1],
*biod_storage_->ReadSingleRecord(kUserId2, kRecordId2));
EXPECT_EQ(kRecords[2],
*biod_storage_->ReadSingleRecord(kUserId2, kRecordId3));
}
TEST_F(BiodStorageBaseTest, ReadSingleRecord) {
const std::vector<Record> kRecords(
{{{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1},
{{kRecordFormatVersion, kRecordId2, kUserId2, kLabel2, kValidationVal2},
kData2}});
// Write the record.
for (auto const& record : kRecords) {
EXPECT_TRUE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
// Check if ReadSingleRecord returns empty std::optional on RecordId,
// UserId mismatch.
EXPECT_FALSE(biod_storage_->ReadSingleRecord(kUserId1, kRecordId2));
EXPECT_FALSE(biod_storage_->ReadSingleRecord(kUserId2, kRecordId1));
}
TEST_F(BiodStorageBaseTest, WriteRecord_InvalidAbsolutePath) {
Record record = {{kRecordFormatVersion, kRecordId1, "/absolutepath", kLabel1,
kValidationVal1},
kData1};
EXPECT_FALSE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
TEST_F(BiodStorageBaseTest, WriteRecord_RecordIdNotUTF8) {
EXPECT_FALSE(base::IsStringUTF8(kInvalidUTF8));
Record record = {
{kRecordFormatVersion, kInvalidUTF8, kUserId1, kLabel1, kValidationVal1},
kData1};
EXPECT_FALSE(record.metadata.IsValidUTF8());
EXPECT_FALSE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
TEST_F(BiodStorageBaseTest, WriteRecord_UserIdNotUTF8) {
EXPECT_FALSE(base::IsStringUTF8(kInvalidUTF8));
Record record = {{kRecordFormatVersion, kRecordId1, kInvalidUTF8, kLabel1,
kValidationVal1},
kData1};
EXPECT_FALSE(record.metadata.IsValidUTF8());
EXPECT_FALSE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
TEST_F(BiodStorageBaseTest, WriteRecord_LabelNotUTF8) {
EXPECT_FALSE(base::IsStringUTF8(kInvalidUTF8));
Record record = {{kRecordFormatVersion, kRecordId1, kUserId1, kInvalidUTF8,
kValidationVal1},
kData1};
EXPECT_FALSE(record.metadata.IsValidUTF8());
EXPECT_FALSE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
}
TEST_F(BiodStorageBaseTest, WriteRecord_CheckUmask) {
Record record = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
const base::FilePath kRecordStorageFilename =
root_path_.Append("biod")
.Append(record.metadata.user_id)
.Append(kBiometricsManagerName)
.Append("Record" + record.metadata.record_id);
ASSERT_FALSE(base::PathExists(kRecordStorageFilename));
ASSERT_FALSE(base::PathExists(kRecordStorageFilename.DirName()));
EXPECT_TRUE(
biod_storage_->WriteRecord(record.metadata, base::Value(record.data)));
// Check permissions of directory
int actual_permissions;
EXPECT_TRUE(base::GetPosixFilePermissions(kRecordStorageFilename.DirName(),
&actual_permissions));
EXPECT_EQ(kPermissions700, actual_permissions);
// Check permissions of record
EXPECT_TRUE(base::GetPosixFilePermissions(kRecordStorageFilename,
&actual_permissions));
EXPECT_EQ(kPermissions600, actual_permissions);
}
TEST_F(BiodStorageBaseTest, DeleteRecord) {
const std::vector<uint8_t> kEmpty;
const Record kRecord = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
// Delete a non-existent record.
EXPECT_TRUE(biod_storage_->DeleteRecord(kUserId1, kRecordId1));
EXPECT_TRUE(
biod_storage_->WriteRecord(kRecord.metadata, base::Value(kRecord.data)));
// Check this record is properly written.
std::unordered_set<std::string> user_ids({kUserId1});
auto read_result = biod_storage_->ReadRecords(user_ids);
EXPECT_EQ(read_result.size(), 1);
const auto& record = read_result[0];
EXPECT_TRUE(record.valid);
EXPECT_EQ(record, kRecord);
EXPECT_TRUE(biod_storage_->DeleteRecord(kUserId1, kRecordId1));
// Check this record is properly deleted.
read_result = biod_storage_->ReadRecords(user_ids);
EXPECT_TRUE(read_result.empty());
}
TEST_F(BiodStorageBaseTest, GenerateNewRecordId) {
// Check the two record ids are different.
std::string record_id1(BiodStorage::GenerateNewRecordId());
std::string record_id2(BiodStorage::GenerateNewRecordId());
EXPECT_NE(record_id1, record_id2);
}
TEST_F(BiodStorageBaseTest, TestEqualOperator) {
Record record = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
Record record_eq = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
Record record_eq2 = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1,
/* valid= */ false};
Record record_ne = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal2},
kData1};
EXPECT_EQ(record, record_eq);
EXPECT_EQ(record, record_eq2);
EXPECT_NE(record, record_ne);
}
TEST_F(BiodStorageBaseTest, TestReadValidationValueFromRecord) {
auto record_dictionary = CreateRecordDictionary(kValidationVal1);
auto ret = biod_storage_->ReadValidationValueFromRecord(record_dictionary,
kFilePath);
EXPECT_TRUE(ret != nullptr);
EXPECT_EQ(*ret, kValidationVal1);
}
/**
* Tests for invalid records. In general records will be correctly formatted
* since we follow a specific format when writing them, but we should be able
* to handle invalid records from bugs, disk corruption, etc.
*/
class BiodStorageInvalidRecordTest : public ::testing::Test {
public:
BiodStorageInvalidRecordTest() {
CHECK(temp_dir_.CreateUniqueTempDir());
root_path_ = temp_dir_.GetPath().AppendASCII(
"biod_storage_invalid_record_test_root");
biod_storage_ = std::make_unique<BiodStorage>(kBiometricsManagerName);
// Since there is no session manager, allow accesses by default.
biod_storage_->set_allow_access(true);
biod_storage_->SetRootPathForTesting(root_path_);
Record record = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
record_name_ = biod_storage_->GetRecordFilename(record.metadata);
EXPECT_FALSE(record_name_.empty());
EXPECT_TRUE(base::CreateDirectory(record_name_.DirName()));
}
protected:
base::ScopedTempDir temp_dir_;
base::FilePath root_path_;
base::FilePath record_name_;
std::unique_ptr<BiodStorageInterface> biod_storage_;
};
TEST_F(BiodStorageInvalidRecordTest, InvalidJSON) {
EXPECT_TRUE(base::ImportantFileWriter::WriteFileAtomically(record_name_,
"this is not "
"JSON"));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, MissingFormatVersion) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"data": "some_data",
"match_validation_value": "4567"
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, InvalidFormatVersion) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"data": "some_data",
"match_validation_value": "4567",
"version": -1
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, MissingRecordId) {
auto record = R"({
"label": "some_label",
"data": "some_data",
"match_validation_value": "4567",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, MissingRecordLabel) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"data": "some_data",
"match_validation_value": "4567",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, MissingRecordData) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"match_validation_value": "4567",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, MissingValidationValue) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"data": "some_data",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, ValidationValueNotBase64) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"data": "some_data",
"match_validation_value": "not valid base64",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
TEST_F(BiodStorageInvalidRecordTest, VersionOneWithoutValidationValueIsValid) {
auto record = R"({
"record_id": "00000000_0000_0000_0000_000000000001",
"label": "some_label",
"data": "some_data",
"version": 1
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_TRUE(read_result[0].valid);
EXPECT_TRUE(read_result[0].metadata.validation_val.empty());
EXPECT_EQ(read_result[0].metadata.record_format_version,
kRecordFormatVersionNoValidationValue);
}
TEST_F(BiodStorageInvalidRecordTest, RecordIdMismatch) {
auto record = R"({
"record_id": "5678",
"label": "some_label",
"data": "some_data",
"match_validation_value": "4567",
"version": 2
})";
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_FALSE(read_result[0].valid);
}
/**
* Tests to make sure we do not crash due to hitting the RLIMIT_MEMLOCK limit.
* See http://b/181281782, http://b/175158241, and http://b/173655013.
*/
class BiodStorageMemlockTest
: public testing::TestWithParam<MemlockTestParams> {
public:
BiodStorageMemlockTest() : params_(GetParam()) {
CHECK(temp_dir_.CreateUniqueTempDir());
root_path_ =
temp_dir_.GetPath().AppendASCII("biod_storage_memlock_test_root");
biod_storage_ = std::make_unique<BiodStorage>(kBiometricsManagerName);
// Since there is no session manager, allow accesses by default.
biod_storage_->set_allow_access(true);
biod_storage_->SetRootPathForTesting(root_path_);
Record record = {
{kRecordFormatVersion, kRecordId1, kUserId1, kLabel1, kValidationVal1},
kData1};
record_name_ = biod_storage_->GetRecordFilename(record.metadata);
EXPECT_FALSE(record_name_.empty());
EXPECT_TRUE(base::CreateDirectory(record_name_.DirName()));
struct rlimit limit;
EXPECT_EQ(getrlimit(RLIMIT_MEMLOCK, &limit), 0);
orig_limit_ = limit;
limit.rlim_cur = params_.rlimit_bytes;
EXPECT_LT(limit.rlim_cur, limit.rlim_max);
EXPECT_EQ(setrlimit(RLIMIT_MEMLOCK, &limit), 0);
EXPECT_EQ(getrlimit(RLIMIT_MEMLOCK, &limit), 0);
EXPECT_EQ(limit.rlim_cur, params_.rlimit_bytes);
}
~BiodStorageMemlockTest() override {
// Restore original limits.
EXPECT_EQ(setrlimit(RLIMIT_MEMLOCK, &orig_limit_), 0);
}
protected:
const MemlockTestParams params_;
base::ScopedTempDir temp_dir_;
base::FilePath root_path_;
base::FilePath record_name_;
std::unique_ptr<BiodStorageInterface> biod_storage_;
struct rlimit orig_limit_;
};
TEST_P(BiodStorageMemlockTest, ReadReadRecords) {
base::Value record_value(base::Value::Type::DICTIONARY);
record_value.SetStringKey("record_id", kRecordId1);
record_value.SetStringKey("label", "some_label");
record_value.SetStringKey("match_validation_value", "4567");
record_value.SetIntKey("version", 2);
std::vector<uint8_t> data(params_.template_size_bytes, 'a');
record_value.SetStringKey("data", base::Base64Encode(data));
std::string record;
JSONStringValueSerializer json_serializer(&record);
EXPECT_TRUE(json_serializer.Serialize(record_value));
EXPECT_TRUE(
base::ImportantFileWriter::WriteFileAtomically(record_name_, record));
auto read_result = biod_storage_->ReadRecordsForSingleUser(kUserId1);
EXPECT_EQ(read_result.size(), 1);
EXPECT_TRUE(read_result[0].valid);
}
INSTANTIATE_TEST_SUITE_P(
BiodStorageMemlock,
BiodStorageMemlockTest,
testing::Values(
MemlockTestParams{
.rlimit_bytes = kRlimitMemlockBytes,
.template_size_bytes = kElan515TemplateSizeBytes,
},
MemlockTestParams{
.rlimit_bytes = kRlimitMemlockBytes,
.template_size_bytes = kElan80TemplateSizeBytes,
},
MemlockTestParams{
.rlimit_bytes = kRlimitMemlockBytes,
.template_size_bytes = kFpc1145TemplateSizeBytes,
},
MemlockTestParams{
.rlimit_bytes = kRlimitMemlockBytes,
.template_size_bytes = kFpc1025TemplateSizeBytes,
}));
} // namespace biod