diff --git a/omaha/enterprise/installer/custom_actions/msi_tag_extractor.cc b/omaha/enterprise/installer/custom_actions/msi_tag_extractor.cc index c333b0355..a19766c72 100644 --- a/omaha/enterprise/installer/custom_actions/msi_tag_extractor.cc +++ b/omaha/enterprise/installer/custom_actions/msi_tag_extractor.cc @@ -15,8 +15,8 @@ #include "omaha/enterprise/installer/custom_actions/msi_tag_extractor.h" -#include #include +#include #include @@ -24,23 +24,24 @@ namespace { // A fixed string serves as the magic number to identify whether it's the start // of a valid tag. -const char kTagMagicNumber[] = "Gact"; +const char kTagMagicNumber[] = "Gact2.0Omaha"; const size_t kMagicNumberLength = arraysize(kTagMagicNumber) - 1; const char kStringMapDelimeter = '&'; const char kKeyValueDelimeter = '='; -const DWORD kMaxTagStringLengthAllowed = 4096; +const DWORD kMaxTagStringLengthAllowed = 81920; // 80K -// The smallest meaningful tag is 'Gact00', indicating a zero-length payload. -const DWORD kMinTagLengthAllowed = static_cast( - kMagicNumberLength + sizeof(uint16)); // NOLINT +// The smallest meaningful tag is 'Gact2.0Omaha\0\0', indicating a zero-length +// payload. +const DWORD kMinTagLengthAllowed = + static_cast(kMagicNumberLength + sizeof(uint16)); const DWORD kMaxTagLength = kMaxTagStringLengthAllowed + kMinTagLengthAllowed; // Read a value from the given buffer. The layout is assumed to be // big-endian. Caller must make sure the buffer has enough length. -template +template T GetValueFromBuffer(const void* buffer) { const uint8* value_buffer = static_cast(buffer); T value = T(); @@ -52,9 +53,7 @@ T GetValueFromBuffer(const void* buffer) { return value; } -bool is_not_alnum(char c) { - return !isalnum(c); -} +bool is_not_alnum(char c) { return !isalnum(c); } bool IsValidTagKey(const std::string& str) { return find_if(str.begin(), str.end(), is_not_alnum) == str.end(); @@ -76,27 +75,26 @@ bool IsValidTagValue(const std::string& str) { namespace custom_action { -MsiTagExtractor::MsiTagExtractor() { -} +MsiTagExtractor::MsiTagExtractor() {} bool MsiTagExtractor::ReadTagFromFile(const wchar_t* filename) { HANDLE file_handle = NULL; file_handle = ::CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (file_handle == INVALID_HANDLE_VALUE) { return false; } std::vector tag_buffer; - bool result = ReadTagToBuffer(file_handle, &tag_buffer) && - ParseTagBuffer(tag_buffer); + bool result = + ReadTagToBuffer(file_handle, &tag_buffer) && ParseTagBuffer(tag_buffer); ::CloseHandle(file_handle); return result; } -// Load last 4K bytes from file, search for magic number 'Gact' in the +// Load last 80K bytes from file, search for magic number 'Gact2.0Omaha' in the // buffer. If found, assume that's the start of tag and continue parsing. bool MsiTagExtractor::ReadTagToBuffer(HANDLE file_handle, std::vector* tag) const { @@ -124,27 +122,24 @@ bool MsiTagExtractor::ReadTagToBuffer(HANDLE file_handle, // Add one extra zero at the end to prevent out-of-bounds reads. std::vector buffer(bytes_to_read + 1); DWORD num_bytes_read = 0; - if (!::ReadFile(file_handle, - &buffer[0], - bytes_to_read, - &num_bytes_read, + if (!::ReadFile(file_handle, &buffer[0], bytes_to_read, &num_bytes_read, NULL) || num_bytes_read != bytes_to_read) { return false; } - // Search for the magic number in the loaded buffer. - char* magic_number_pos = std::search(&buffer[0], - &buffer[bytes_to_read], - kTagMagicNumber, - kTagMagicNumber + kMagicNumberLength); + // Search for the last occurrence of the magic number in the loaded buffer. + auto magic_number_pos = + std::find_end(buffer.begin(), buffer.end(), kTagMagicNumber, + kTagMagicNumber + kMagicNumberLength); + // Make sure we found the magic number, and the buffer after it is no less // than the required minimum tag size (kMinTagLengthAllowed). - if (&buffer[bytes_to_read] - magic_number_pos < kMinTagLengthAllowed) { + if (std::distance(buffer.end(), magic_number_pos) < kMinTagLengthAllowed) { return false; } - tag->assign(magic_number_pos, &buffer[bytes_to_read]); + tag->assign(magic_number_pos, buffer.end()); return true; } @@ -167,21 +162,20 @@ bool MsiTagExtractor::ParseTagBuffer(const std::vector& tag_buffer) { return true; } -bool MsiTagExtractor::ParseMagicNumber( - const std::vector& tag_buffer, size_t* parse_position) const { +bool MsiTagExtractor::ParseMagicNumber(const std::vector& tag_buffer, + size_t* parse_position) const { if (tag_buffer.size() <= kMagicNumberLength + *parse_position) { return false; } - bool result = memcmp(&tag_buffer[*parse_position], - kTagMagicNumber, + bool result = memcmp(&tag_buffer[*parse_position], kTagMagicNumber, kMagicNumberLength) == 0; *parse_position += kMagicNumberLength; return result; } -uint16 MsiTagExtractor::ParseTagLength( - const std::vector& tag_buffer, size_t* parse_position) const { +uint16 MsiTagExtractor::ParseTagLength(const std::vector& tag_buffer, + size_t* parse_position) const { uint16 tag_length = GetValueFromBuffer(&tag_buffer[*parse_position]); *parse_position += sizeof(tag_length); return tag_length; @@ -192,8 +186,7 @@ uint16 MsiTagExtractor::ParseTagLength( // 2. All key/value must be alpha-numeric strings, while value can be empty. // Unrecognized tag will be ignored. void MsiTagExtractor::ParseSimpleAsciiStringMap( - const std::vector& tag_buffer, - size_t* parse_position, + const std::vector& tag_buffer, size_t* parse_position, size_t tag_length) { // Construct a string with at most |tag_length| characters, but stop at the // first '\0' if it comes before that. @@ -235,4 +228,3 @@ bool MsiTagExtractor::GetValue(const char* key, std::string* value) const { } } // namespace custom_action - diff --git a/omaha/enterprise/installer/custom_actions/msi_tag_extractor.h b/omaha/enterprise/installer/custom_actions/msi_tag_extractor.h index 6ac550ce1..6bdf21903 100644 --- a/omaha/enterprise/installer/custom_actions/msi_tag_extractor.h +++ b/omaha/enterprise/installer/custom_actions/msi_tag_extractor.h @@ -31,7 +31,7 @@ namespace custom_action { // // Tag specification: // - When reading multi-byte numbers, it's in big endian. -// - Tag area begins with magic number 'Gact'. +// - Tag area begins with magic number 'Gact2.0Omaha'. // - The next 2-bytes is tag string length. // - Then follows the real tag string. The string is in format of: // "key1=value1&key2=value2". Both the key and the value must be @@ -41,17 +41,21 @@ namespace custom_action { // +-------------------------------------+ // ~ .............................. ~ // | .............................. | -// | End of MSI file | End of raw MSI. +// | Other parts of MSI file | // +-------------------------------------+ -// | Magic number 'Gact' | Tag starts -// | Tag string length | +// | Start of the dummy certficate | +// ~ .............................. ~ +// ~ .............................. ~ +// | Magic number 'Gact2.0Omaha' | Tag starts +// | Tag length (2 bytes in big-endian)) | // | tag string | // +-------------------------------------+ // -// A real example (MSI file tagged with 'brand=CDCD&key2=Test'): +// A real example (an MSI file tagged with 'brand=CDCD&key2=Test'): // +-----------------------------------------------------------------+ -// | G a c t \0 024 b r a n d = C D C D | -// | & k e y 2 = T e s t | +// | G a c t 2 . 0 O m a h a 0x0 0x14 b r | +// | a n d = C D C D & k e y 2 = T e | +// | s t | // +-----------------------------------------------------------------+ class MsiTagExtractor { public: diff --git a/omaha/enterprise/installer/custom_actions/msi_tag_extractor_test.cc b/omaha/enterprise/installer/custom_actions/msi_tag_extractor_test.cc index 926b3ec9d..9e205d5fa 100644 --- a/omaha/enterprise/installer/custom_actions/msi_tag_extractor_test.cc +++ b/omaha/enterprise/installer/custom_actions/msi_tag_extractor_test.cc @@ -13,89 +13,44 @@ // limitations under the License. // ======================================================================== +#include "omaha/enterprise/installer/custom_actions/msi_tag_extractor.h" + #include #include + #include #include + #include "omaha/base/app_util.h" #include "omaha/base/file.h" #include "omaha/base/utils.h" #include "omaha/testing/unit_test.h" -#include "omaha/enterprise/installer/custom_actions/msi_tag_extractor.h" - namespace omaha { -namespace { - -const TCHAR kSourceMsi[] = _T("GoogleUpdateHelper.msi"); -const TCHAR kTaggedMsi[] = _T("tagged_GoogleUpdateHelper.msi"); - -} // namesapce - class MsiTagExtractorTest : public testing::Test { protected: virtual void SetUp() { unittest_file_path_ = app_util::GetModuleDirectory(NULL); EXPECT_TRUE(::PathAppend(CStrBuf(unittest_file_path_, MAX_PATH), - _T("..\\staging\\unittest_support"))); + _T("..\\staging\\unittest_support\\tagged_msi"))); EXPECT_TRUE(File::Exists(unittest_file_path_)); } - virtual void TearDown() { - File::Remove(GetTaggedMsiPath()); - } - - CString GetSourceMsiPath() const { - CString source_msi_path(unittest_file_path_); - EXPECT_TRUE(::PathAppend(CStrBuf(source_msi_path, MAX_PATH), kSourceMsi)); - EXPECT_TRUE(File::Exists(source_msi_path)); - return source_msi_path; - } - - CString GetTaggedMsiPath() const { + CString GetMsiFilePath(const CString& file_name) const { CString tagged_msi_path(unittest_file_path_); - EXPECT_TRUE(::PathAppend(CStrBuf(tagged_msi_path, MAX_PATH), kTaggedMsi)); + EXPECT_TRUE(::PathAppend(CStrBuf(tagged_msi_path, MAX_PATH), file_name)); + EXPECT_TRUE(File::Exists(tagged_msi_path)); return tagged_msi_path; } - HRESULT CreateMsiFileWithTag(const uint8* tag_buffer, - size_t size) const { - const CString& source_msi(GetSourceMsiPath()); - const CString& output_msi(GetTaggedMsiPath()); - std::ifstream in; - std::ofstream out; - - in.open(source_msi, std::ifstream::in | std::ifstream::binary); - out.open(output_msi, std::ofstream::out | std::ofstream::binary); - if (!in.is_open() || !out.is_open()) { - return E_FAIL; - } - - // Copy MSI package first. - out << in.rdbuf(); - - if (tag_buffer) { - out.write(reinterpret_cast(tag_buffer), - static_cast(size)); - } - return S_OK; - } - private: CString unittest_file_path_; }; TEST_F(MsiTagExtractorTest, ValidTag) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 10, // Tag string length. - 'b', 'r', 'a', 'n', 'd', '=', 'Q', 'A', 'Q', 'A', // BRAND=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-brand-only.msi's tag:BRAND=QAQA + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-brand-only.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); @@ -110,15 +65,8 @@ TEST_F(MsiTagExtractorTest, ValidTag) { } TEST_F(MsiTagExtractorTest, ValidTag_AmpersandAtEnd) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 11, // Tag string length. - 'b', 'r', 'a', 'n', 'd', '=', 'Q', 'A', 'Q', 'A', '&', 0, // brand=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-ampersand-ending.msi's tag: BRAND=QAQA& + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-ampersand-ending.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); @@ -128,54 +76,13 @@ TEST_F(MsiTagExtractorTest, ValidTag_AmpersandAtEnd) { } TEST_F(MsiTagExtractorTest, MultiValidTags) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 206, // Tag string length. - - // appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}& - 'a', 'p', 'p', 'g', 'u', 'i', 'd', '=', - '{', '8', 'A', '6', '9', 'D', '3', '4', '5', '-', - 'D', '5', '6', '4', '-', '4', '6', '3', 'C', '-', - 'A', 'F', 'F', '1', '-', - 'A', '6', '9', 'D', '9', 'E', '5', '3', '0', 'F', '9', '6', '}', '&', - - // iid={2D8C18E9-8D3A-4EFC-6D61-AE23E3530EA2}& - 'i', 'i', 'd', '=', - '{', '2', 'D', '8', 'C', '1', '8', 'E', '9', '-', - '8', 'D', '3', 'A', '-', '4', 'E', 'F', 'C', '-', - '6', 'D', '6', '1', '-', - 'A', 'E', '2', '3', 'E', '3', '5', '3', '0', 'E', 'A', '2', '}', '&', - - // lang=en& - 'l', 'a', 'n', 'g', '=', 'e', 'n', '&', - - // browser=4& - 'b', 'r', 'o', 'w', 's', 'e', 'r', '=', '4', '&', - - // usagestats=0& - 'u', 's', 'a', 'g', 'e', 's', 't', 'a', 't', 's', '=', '0', '&', - - // appname=Google%20Chrome& - 'a', 'p', 'p', 'n', 'a', 'm', 'e', '=', 'G', 'o', 'o', 'g', 'l', 'e', - '%', '2', '0', 'C', 'h', 'r', 'o', 'm', 'e', '&', - - // needsadmin=prefers& - 'n', 'e', 'e', 'd', 's', 'a', 'd', 'm', 'i', 'n', '=', - 'p', 'r', 'e', 'f', 'e', 'r', 's', '&', - - // brand=CHMB& - 'b', 'r', 'a', 'n', 'd', '=', 'C', 'H', 'M', 'B', '&', - - // installdataindex=defaultbrowser - 'i', 'n', 's', 't', 'a', 'l', 'l', 'd', 'a', 't', 'a', 'i', 'n', 'd', 'e', - 'x', '=', 'd', 'e', 'f', 'a', 'u', 'l', 't', 'b', 'r', 'o', 'w', 's', 'e', - 'r', - - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // File GUH-multiple.msi has tag: + // appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}& + // iid={2D8C18E9-8D3A-4EFC-6D61-AE23E3530EA2}& + // lang=en&browser=4&usagestats=0&appname=Google%20Chrome& + // needsadmin=prefers&brand=CHMB& + // installdataindex=defaultbrowser + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-multiple.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); @@ -209,188 +116,92 @@ TEST_F(MsiTagExtractorTest, MultiValidTags) { } TEST_F(MsiTagExtractorTest, EmptyKey) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 18, // Tag string length. - '=', 'V', 'a', 'l', 'u', 'e', '&', // =Value - 'B', 'R', 'A', 'N', 'D', '=', 'Q', 'A', 'Q', 'A', 0, // BRAND=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-empty-key.msi's tag: =value&BRAND=QAQA + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-empty-key.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_TRUE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_TRUE(tag_extractor.GetValue("brand", &brand_code)); EXPECT_EQ(brand_code.compare("QAQA"), 0); } TEST_F(MsiTagExtractorTest, EmptyValue) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 7, // Tag string length. - 'B', 'R', 'A', 'N', 'D', '=', 0, // BRAND=. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-empty-value.msi's tag: BRAND= + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-empty-value.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_TRUE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_TRUE(tag_extractor.GetValue("brand", &brand_code)); EXPECT_TRUE(brand_code.empty()); } TEST_F(MsiTagExtractorTest, NoTagString) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 0, // Tag string length. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-empty-tag.msi's tag:(empty string) + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-empty-tag.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_FALSE(tag_extractor.GetValue("brand", &brand_code)); } TEST_F(MsiTagExtractorTest, NoTag) { - EXPECT_SUCCEEDED(CreateMsiFileWithTag(NULL, 0)); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // The original MSI is in parent folder. + std::wstring tagged_msi(GetMsiFilePath(_T("..\\GoogleUpdateHelper.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_FALSE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); } TEST_F(MsiTagExtractorTest, InvalidMagicNumber) { - const uint8 tag[] = { - '_', 'a', 'c', 't', // Invalid magic number. - 0, 10, // Tag string length. - 'B', 'R', 'A', 'N', 'D', '=', 'Q', 'A', 'Q', 'A', // BRAND=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-invalid-marker.msi's has invalid magic number "Gact2.0Foo". + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-invalid-marker.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_FALSE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); - std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_FALSE(tag_extractor.GetValue("brand", &brand_code)); } -TEST_F(MsiTagExtractorTest, InvalidTagLength) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 88, // Invalid tag length. - 'B', 'R', 'A', 'N', 'D', '=', 'Q', 'A', 'Q', 'A', 0, // BRAND=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); - custom_action::MsiTagExtractor tag_extractor; - EXPECT_FALSE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); - - std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); -} - TEST_F(MsiTagExtractorTest, InvalidTagCharacterInKey) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 10, // Tag string length. - 'B', 'R', '*', 'N', 'D', '=', 'Q', 'A', 'Q', 'A', // BR*ND=QAQA. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-invalid-key.msi's has invalid charaters in the tag key. + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-invalid-key.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BR*ND", &brand_code)); + EXPECT_FALSE(tag_extractor.GetValue("br*nd", &brand_code)); } TEST_F(MsiTagExtractorTest, InvalidTagCharacterInValue) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 10, // Tag string length. - 'B', 'R', 'A', 'N', 'D', '=', 'Q', 'A', '*', 'A', // BRAND=QA*A. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-invalid-value.msi's has invalid charaters in the tag value. + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-invalid-value.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_FALSE(tag_extractor.GetValue("brand", &brand_code)); } TEST_F(MsiTagExtractorTest, InvalidTagFormat) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 5, // Tag string length. - 'B', 'R', 'A', 'N', 'D', // No '='. - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-bad-format.msi's has invalid tag format. + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-bad-format.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); + EXPECT_FALSE(tag_extractor.GetValue("brand", &brand_code)); } TEST_F(MsiTagExtractorTest, InvalidTagFormat2) { - const uint8 tag[] = { - 'G', 'a', 'c', 't', // magic number. - 0, 24, // Tag string length. - '=', '=', '=', '=', '=', '=', '=', '&', - '=', '=', '=', '=', '=', '=', '=', '&', - '&', '&', '=', '&', '&', '&', '&', '0', - 0, 0, 0, 0, // 4 bytes of 0s. - }; - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - - std::wstring tagged_msi(GetTaggedMsiPath()); + // GUH-bad-format2.msi's has invalid tag format. + std::wstring tagged_msi(GetMsiFilePath(_T("GUH-bad-format.msi"))); custom_action::MsiTagExtractor tag_extractor; EXPECT_TRUE(tag_extractor.ReadTagFromFile(tagged_msi.c_str())); std::string brand_code; - EXPECT_FALSE(tag_extractor.GetValue("BRAND", &brand_code)); -} - -// Verifies that tag extractor won't crash with garbage tag. -TEST_F(MsiTagExtractorTest, RandomTag) { - const int kNumPassesToRun = 10; - srand(static_cast(time(NULL))); - - uint8 tag[1024]; - for (int i = 0; i < kNumPassesToRun; ++i) { - // Fill tag with random data. - for (int j = 0; j < arraysize(tag); ++j) { - tag[j] = static_cast(rand() % 0x100); // NOLINT - } - - EXPECT_SUCCEEDED(CreateMsiFileWithTag(tag, arraysize(tag))); - std::wstring tagged_msi(GetTaggedMsiPath()); - custom_action::MsiTagExtractor tag_extractor; - tag_extractor.ReadTagFromFile(tagged_msi.c_str()); - } + EXPECT_FALSE(tag_extractor.GetValue("brand", &brand_code)); } } // namespace omaha diff --git a/omaha/installers/build_metainstaller.py b/omaha/installers/build_metainstaller.py index d9b507406..53f7391a2 100644 --- a/omaha/installers/build_metainstaller.py +++ b/omaha/installers/build_metainstaller.py @@ -166,8 +166,7 @@ def BuildMetaInstaller( merged_output = env.Command( target='unsigned_' + target_name, source=[empty_metainstaller_path, dll_output_name], - action= '%s --copyappend $SOURCES $TARGET' % resmerge_path - ) + action='%s --copyappend $SOURCES $TARGET' % resmerge_path) authenticode_signed_target_prefix = 'authenticode_' authenticode_signed_exe = env.DualSignedBinary( @@ -175,7 +174,7 @@ def BuildMetaInstaller( source=merged_output, ) - ready_for_tagging_exe = env.OmahaCertificateTagExe( + ready_for_tagging_exe = env.OmahaCertificateTag( target=target_name, source=authenticode_signed_exe, ) diff --git a/omaha/site_scons/site_tools/omaha_builders.py b/omaha/site_scons/site_tools/omaha_builders.py index 5ad732cfa..db8b83c17 100644 --- a/omaha/site_scons/site_tools/omaha_builders.py +++ b/omaha/site_scons/site_tools/omaha_builders.py @@ -17,9 +17,11 @@ """Omaha builders tool for SCons.""" +import binascii from copy import copy from copy import deepcopy import os.path +import struct import SCons.Action import SCons.Builder import SCons.Tool @@ -27,9 +29,12 @@ import omaha_version_utils -def OmahaCertificateTagExe(env, target, source): - """Adds a superfluous certificate with a magic signature to an EXE. The file - must be signed with Authenticode in order for Certificate Tagging to succeed. + +def OmahaCertificateTag(env, target, source): + """Adds a superfluous certificate with a magic signature to an EXE or MSI. + + The file must be signed with Authenticode in order for Certificate Tagging to + succeed. Args: env: The environment. @@ -41,15 +46,66 @@ def OmahaCertificateTagExe(env, target, source): """ certificate_tag = ('"' + env['ENV']['GOROOT'] + '/bin/go.exe' + '"' + - ' run $MAIN_DIR/../common/certificate_tag/certificate_tag.go') + ' run ' + + '$MAIN_DIR/../common/certificate_tag/certificate_tag.go') magic_bytes = 'Gact2.0Omaha' padded_length = len(magic_bytes) + 2 + 8192 certificate_tag_cmd = env.Command( target=target, source=source, - action=certificate_tag + - ' -set-superfluous-cert-tag=' + magic_bytes + - ' -padded-length=' + str(padded_length) + ' -out $TARGET $SOURCE', + action=certificate_tag + ' -set-superfluous-cert-tag=' + magic_bytes + + ' -padded-length=' + str(padded_length) + ' -out $TARGET $SOURCE', + ) + + return certificate_tag_cmd + + +def OmahaCertificateTagForTesting(env, + target, + source, + magic_bytes=None, + tag='', + tag_length=None): + """Adds a superfluous certificate with a magic signature to an EXE or MSI. + + The file must be signed with Authenticode in order for Certificate Tagging to + succeed. + This function allows caller to overwrite some parts of the tag with invalid + values for testing purpose. + + Args: + env: The environment. + target: Name of the certificate-tagged file. + source: Name of the file to be certificate-tagged. + magic_bytes: Optional customized magic bytes. + tag: Optional tag value. + tag_length: Optional tag length (only last two bytes are accountable). + + Returns: + Output node list from env.Command(). + """ + + certificate_tag = ('"' + env['ENV']['GOROOT'] + '/bin/go.exe' + '"' + + ' run ' + + '$MAIN_DIR/../common/certificate_tag/certificate_tag.go') + if magic_bytes is None: + magic_bytes = 'Gact2.0Omaha' + if tag_length is None: + tag_length = len(tag) + if tag_length > 0xFFFF: + raise ValueError('Input tag is too long') + + bin_tag = bytearray(binascii.hexlify(magic_bytes.encode())) + bin_tag.extend(binascii.hexlify(struct.pack('>H', tag_length))) + bin_tag.extend(binascii.hexlify(tag.encode())) + full_tag_encoded = '0x' + bin_tag.decode() + padded_length = len(bin_tag) + 8192 + certificate_tag_cmd = env.Command( + target=target, + source=source, + action=certificate_tag + ' -set-superfluous-cert-tag=' + + full_tag_encoded + ' -padded-length=' + str(padded_length) + + ' -out $TARGET $SOURCE', ) return certificate_tag_cmd @@ -385,9 +441,10 @@ def CompileProtoBuf(env, input_proto_files): # NOTE: SCons requires the use of this name, which fails gpylint. -def generate(env): # pylint: disable-msg=C6409 +def generate(env): # pylint: disable=C6409 """SCons entry point for this tool.""" - env.AddMethod(OmahaCertificateTagExe) + env.AddMethod(OmahaCertificateTag) + env.AddMethod(OmahaCertificateTagForTesting) env.AddMethod(OmahaTagExe) env.AddMethod(IsBuildingModule) env.AddMethod(GetAllInOneUnittestSources) diff --git a/omaha/testing/build.scons b/omaha/testing/build.scons index aca151da1..0152919d7 100644 --- a/omaha/testing/build.scons +++ b/omaha/testing/build.scons @@ -153,6 +153,34 @@ unittest_support += env.Replicate( '$STAGING_DIR/unittest_support/' + loc_guid, 'unittest_support/%s/gears-win32-opt.msi' % loc_guid) +# Create tagged MSI files for testing. +tags = { + 'brand-only': (None, 'brand=QAQA', None), + 'ampersand-ending': (None, 'brand=QAQA&', None), + 'multiple': (None, + ('appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&' + 'iid={2D8C18E9-8D3A-4EFC-6D61-AE23E3530EA2}&' + 'lang=en&browser=4&usagestats=0&appname=Google%20Chrome&' + 'needsadmin=prefers&brand=CHMB&' + 'installdataindex=defaultbrowser'), None), + 'empty-key': (None, '=value&brand=QAQA', None), + 'empty-value': (None, 'brand=', None), + 'empty-tag': (None, '', None), + 'invalid-marker': ('Gact2.0Foo', 'brand=QAQA', None), + 'invalid-length': (None, 'brand=QAQA', 3000), + 'invalid-key': (None, 'br*nd=QAQA', None), + 'invalid-value': (None, 'brand=QA*A', None), + 'bad-format': (None, 'brand', None), + 'bad-format2': (None, '=======&=======&&&=&&&&0', None), +} +for tag_name, tag_option in tags.items(): + unittest_support += env.OmahaCertificateTagForTesting( + target = '$STAGING_DIR/unittest_support/tagged_msi/GUH-%s.msi' % tag_name, + source = 'unittest_support/GoogleUpdateHelper.msi', + magic_bytes = tag_option[0], + tag = tag_option[1], + tag_length = tag_option[2]) + #=============General Unit Test Dependencies=================================== # Many unit tests rely on string resources. omaha_unittest.cc loads them but # assumes they are in the same directory as the tests.