blob: 6e7bae9e3ebcd70b4731a5e38f3dd7525b1877a5 [file] [log] [blame]
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "utility.hpp"
#include <flashupdate/flash/mock.hpp>
#include <flashupdate/info.hpp>
#include <flashupdate/ops.hpp>
#include <flashupdate/validator.hpp>
#include <flashupdate/validator/mock.hpp>
#include <format>
#include <fstream>
#include <sstream>
#include <string>
#include <gtest/gtest.h>
using ::testing::_;
using ::testing::Return;
namespace flashupdate
{
using DescriptorHash = validator::Validator::DescriptorHash;
TEST_F(OperationTest, InvalidValidator)
{
Args args;
args.file.emplace(createTestBin());
args.skipValidation = false;
args.validatorHelper = nullptr;
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(), "invalid Validator Helper");
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WriteInvalidNextImage)
{
Args args;
args.primary = true;
std::string testBin = createTestBin();
args.file.emplace(testBin);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(false));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(),
std::format("failed to validate the CR51 descriptor "
"for the next image: {}",
testBin)
.c_str());
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WriteInvalidFlash)
{
Args args;
args.primary = true;
args.file.emplace(createTestBin());
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true));
EXPECT_CALL(flashMockHelper, getFlash(_, _)).WillOnce(Return(std::nullopt));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(), "failed to find Flash partition");
throw;
},
std::runtime_error);
}
// Prod to dev is not enabled.
TEST_F(OperationTest, WriteInvalidProdToDev)
{
resetInfo();
Args args;
args.primary = true;
args.file.emplace(createTestBin());
args.config.cr51->prodToDev = false;
args.stagingIndex = 0;
std::string filename =
CaseTmpDir() + "/write_invalid_current_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true));
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}));
EXPECT_CALL(validatorMockHelper, prodImage())
.WillOnce(Return(false)); // Next image is dev
EXPECT_CALL(validatorMockHelper, isProdImage(_, _, _))
.WillOnce(Return(true)); // The current image it prod image
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
EXPECT_THROW(
try { ops::write(args); } catch (const std::logic_error& e) {
EXPECT_STREQ(e.what(), "Prod to dev update is not enabled.");
throw;
},
std::logic_error);
}
TEST_F(OperationTest, WriteSecondaryInvalidImageAfter)
{
resetInfo();
Args args;
args.primary = false;
std::string testBin = createTestBin();
args.file.emplace(testBin);
std::string filename = CaseTmpDir() + "/write_secondary_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}));
EXPECT_CALL(validatorMockHelper, prodImage()).WillOnce(Return(true));
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(
e.what(),
std::format(
"failed to validate the CR51 descriptor for the image "
"in the flash after overwriting it: {}",
testBin)
.c_str());
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WritePrimaryInvalidStagingIndex)
{
resetInfo();
updateInfo.stagingIndex = 0;
Args args;
args.config.cr51 = Config::Cr51();
args.primary = true;
args.file.emplace(createTestBin());
args.stagingIndex = 0;
std::string filename = CaseTmpDir() + "/write_secondary_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true));
// The expected hash is all zero right now,
// Setting it to non-zero to trigger an error.
auto expectedHash = std::vector<uint8_t>(SHA256_DIGEST_LENGTH, 1);
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{expectedHash, 0, 0}));
EXPECT_CALL(validatorMockHelper, prodImage()).WillOnce(Return(true));
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(),
"SHA256 of the staged image in the cache "
"does not match the image in the staged partition");
throw;
},
std::logic_error);
}
TEST_F(OperationTest, WritePrimaryInvalidImageAfter)
{
resetInfo();
updateInfo.stagingIndex = 0;
Args args;
args.primary = true;
std::string testBin = createTestBin();
args.file.emplace(testBin);
args.stagingIndex = 0;
std::string filename = CaseTmpDir() + "/write_secondary_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}));
EXPECT_CALL(validatorMockHelper, prodImage()).WillOnce(Return(true));
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(
e.what(),
std::format(
"failed to validate the CR51 descriptor for the image "
"in the flash after overwriting it: {}",
testBin)
.c_str());
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WriteSecondaryPass)
{
resetInfo();
Args args;
args.primary = false;
args.file.emplace(createTestBin());
args.stagingIndex = 1;
std::string filename = CaseTmpDir() + "/write_secondary_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true))
.WillOnce(Return(true))
// Duplicate for the write with no staging index update.
.WillOnce(Return(true))
.WillOnce(Return(true));
uint32_t expectedOffset = 101;
uint32_t expectedSize = 102;
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}))
.WillOnce(
Return(DescriptorHash{defaultHash, expectedOffset, expectedSize}))
// Duplicate for the write with no staging index update.
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}))
.WillOnce(
Return(DescriptorHash{defaultHash, expectedOffset, expectedSize}));
EXPECT_CALL(validatorMockHelper, prodImage())
.WillOnce(Return(true))
// Duplicate for the write with no staging index update.
.WillOnce(Return(true));
EXPECT_CALL(validatorMockHelper, imageVersion())
.WillOnce(Return("10.10.10.10"))
// Duplicate for the write with no staging index update.
.WillOnce(Return("10.10.10.10"));
auto testDev = createTestDev();
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(testDev, inputData.size())))
// Duplicate for the write with no staging index update.
.WillOnce(Return(std::make_pair(testDev, inputData.size())));
ops::write(args);
// Check Stage Descriptor Hash Info
auto info = ops::fetchInfo(args);
EXPECT_EQ(info.stagingIndex, args.stagingIndex);
EXPECT_EQ(info.stageDescriptor.offset, expectedOffset);
EXPECT_EQ(info.stageDescriptor.size, expectedSize);
// Write to partition 2, and skip updateing staging index
args.stagingIndex = 2;
args.updateStagingIndex = false;
ops::write(args);
// Check Stage Descriptor Hash Info
info = ops::fetchInfo(args);
EXPECT_NE(info.stagingIndex, args.stagingIndex);
}
TEST_F(OperationTest, WriteInvalidVersion)
{
resetInfo();
updateInfo.stagingIndex = 0;
std::string minVersion = "20.0.0.0";
std::string maxVersion = "30.0.0.0";
Args args;
args.primary = true;
args.config.supportedVersion = std::vector<Config::SupportedVersion>{
Config::SupportedVersion{std::nullopt, version::Version(maxVersion)},
Config::SupportedVersion{version::Version(minVersion), std::nullopt},
Config::SupportedVersion{version::Version(minVersion),
version::Version(maxVersion)}};
args.file.emplace(createTestBin());
args.stagingIndex = 0;
std::string filename =
CaseTmpDir() + "/write_primary_invalid_version_metadata-XXXXXX";
createFakeEeprom(args, filename);
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(true));
EXPECT_CALL(validatorMockHelper, imageVersion())
.WillOnce(Return("10.10.10.10"))
.WillOnce(Return("40.40.40.40"))
.WillOnce(Return("30.0.0.0"));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(
e.what(),
std::format("Next image's version is not supported for {}",
"10.10.10.10")
.c_str());
throw;
},
std::runtime_error);
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(
e.what(),
std::format("Next image's version is not supported for {}",
"40.40.40.40")
.c_str());
throw;
},
std::runtime_error);
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(
e.what(),
std::format("Next image's version is not supported for {}",
"30.0.0.0")
.c_str());
throw;
},
std::runtime_error);
}
// Write Primary flash with invalid image in the flash
// Test the whole Update workflow plus the following
// - unsigned to dev signed install.
// - version restrictions.
TEST_F(OperationTest, WritePrimaryPass)
{
resetInfo();
updateInfo.stagingIndex = 0;
std::string version0 = "10.10.10.10";
std::string version1 = "10.10.10.11";
Args args;
args.primary = true;
args.config.supportedVersion = std::vector<Config::SupportedVersion>{
Config::SupportedVersion{version::Version(version0),
version::Version(version1)}};
args.file.emplace(createTestBin());
args.stagingIndex = 0;
std::string filename = CaseTmpDir() + "/write_primary_metadata-XXXXXX";
createFakeEeprom(args, filename);
// Allow unsigned image to dev signed installs
args.config.cr51 = Config::Cr51();
args.config.cr51->unsignedToDev = true;
validator::Mock validatorMockHelper;
args.setValidatorHelper(&validatorMockHelper);
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(validatorMockHelper, validateImage(_, _))
.WillOnce(Return(true)) // Installing Image
.WillOnce(Return(true)); // Image on flash after installing.
EXPECT_CALL(validatorMockHelper, descriptorHash())
.WillOnce(Return(DescriptorHash{defaultHash, 0, 0}));
// Install dev image
EXPECT_CALL(validatorMockHelper, prodImage()).WillOnce(Return(false));
EXPECT_CALL(validatorMockHelper, imageVersion())
.WillOnce(Return(version0))
.WillOnce(Return(version0));
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
ops::write(args);
}
TEST_F(OperationTest, WriteOnlyPrimary)
{
Args args;
args.primary = true;
args.skipValidation = true;
args.updateStagingIndex = false;
args.file.emplace(createTestBin());
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(),
"Cannot skip validation on primary partition");
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WriteOnlySecondaryInvalidateFlash)
{
Args args;
args.primary = false;
args.skipValidation = true;
args.updateStagingIndex = false;
args.file.emplace(createTestBin());
args.stagingIndex = 0;
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(flashMockHelper, getFlash(_, _)).WillOnce(Return(std::nullopt));
EXPECT_THROW(
try { ops::write(args); } catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(), "failed to find secondary Flash partition");
throw;
},
std::runtime_error);
}
TEST_F(OperationTest, WriteOnlySecondaryPass)
{
Args args;
args.primary = false;
args.skipValidation = true;
args.updateStagingIndex = false;
args.file.emplace(createTestBin());
args.stagingIndex = 0;
flash::Mock flashMockHelper;
args.setFlashHelper(&flashMockHelper);
EXPECT_CALL(flashMockHelper, getFlash(_, _))
.WillOnce(Return(std::make_pair(createTestDev(), inputData.size())));
ops::write(args);
}
} // namespace flashupdate