blob: 91405030a2ca11f2d9a551bbcf971fb8e9703628 [file] [log] [blame]
// Copyright 2020 The Chromium 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 "chrome/updater/win/tag_extractor.h"
#include <stddef.h>
#include <stdint.h>
#include <windows.h>
#include <algorithm>
#include <limits>
#include <string>
#include <vector>
#include "base/numerics/checked_math.h"
#include "base/win/pe_image_reader.h"
#include "build/build_config.h"
#include "chrome/updater/win/tag_extractor_impl.h"
namespace updater {
namespace {
// Magic strings used to identify the tag in the binary.
constexpr uint8_t kTagStartMagicUtf8[] = {'G', 'a', 'c', 't', '2', '.',
'0', 'O', 'm', 'a', 'h', 'a'};
constexpr uint8_t kTagStartMagicUtf16[] = {0, 'G', 0, 'a', 0, 'c', 0, 't',
0, '2', 0, '.', 0, '0', 0, 'O',
0, 'm', 0, 'a', 0, 'h', 0, 'a'};
constexpr uint8_t kTagEndMagicUtf16[] = {0, 'a', 0, 'h', 0, 'a', 0, 'm',
0, 'O', 0, '0', 0, '.', 0, '2',
0, 't', 0, 'c', 0, 'a', 0, 'G'};
// Converts a big-endian 2-byte value to little-endian and returns it
// as a uint16_t.
uint16_t BigEndianReadU16(BinaryConstIt it) {
static_assert(ARCH_CPU_LITTLE_ENDIAN, "Machine should be little-endian.");
return (uint16_t{*it} << 8) + (uint16_t{*(it + 1)});
}
std::string ReadTagUtf8(BinaryConstIt cert_begin, BinaryConstIt cert_end) {
const uint8_t* magic_begin = std::begin(kTagStartMagicUtf8);
const uint8_t* magic_end = std::end(kTagStartMagicUtf8);
BinaryConstIt magic_str =
std::search(cert_begin, cert_end, magic_begin, magic_end);
if (magic_str == cert_end)
return std::string();
BinaryConstIt taglen_buf =
AdvanceIt(magic_str, magic_end - magic_begin, cert_end);
// Checks that the stored tag length is found within the binary.
if (!CheckRange(taglen_buf, sizeof(uint16_t), cert_end))
return std::string();
// Tag length is stored as a big-endian uint16_t.
const uint16_t tag_len = BigEndianReadU16(taglen_buf);
BinaryConstIt tag_buf = AdvanceIt(taglen_buf, sizeof(uint16_t), cert_end);
if (tag_buf == cert_end)
return std::string();
// Checks that the specified tag is found within the binary.
if (!CheckRange(tag_buf, tag_len, cert_end))
return std::string();
return std::string(tag_buf, tag_buf + tag_len);
}
std::string ReadTagUtf16(BinaryConstIt cert_begin, BinaryConstIt cert_end) {
const uint8_t* magic_begin = std::begin(kTagStartMagicUtf16);
const uint8_t* magic_end = std::end(kTagStartMagicUtf16);
BinaryConstIt magic_str =
std::search(cert_begin, cert_end, magic_begin, magic_end);
if (magic_str == cert_end)
return std::string();
BinaryConstIt tag_buf =
AdvanceIt(magic_str, magic_end - magic_begin, cert_end);
BinaryConstIt tag_buf_end =
std::search(tag_buf, cert_end, std::begin(kTagEndMagicUtf16),
std::end(kTagEndMagicUtf16));
if (tag_buf_end == cert_end)
return std::string();
// UTF-16 strings can only have an even number of bytes since each
// character occupies two bytes.
if ((tag_buf_end - tag_buf) % 2 != 0)
return std::string();
std::wstring tag_utf16;
tag_utf16.resize((tag_buf_end - tag_buf) / sizeof(uint16_t));
// Converts the UTF-16 tag from big-endian to little-endian.
size_t tag_utf16_idx = 0;
for (auto it = tag_buf; it < tag_buf_end; it += sizeof(uint16_t)) {
tag_utf16[tag_utf16_idx] = std::wstring::value_type{BigEndianReadU16(it)};
++tag_utf16_idx;
}
// Converts the tag from UTF-16 to UTF-8.
const int str_size =
::WideCharToMultiByte(CP_UTF8, 0, &tag_utf16[0], tag_utf16.size(),
nullptr, 0, nullptr, nullptr);
std::string tag;
tag.resize(str_size);
::WideCharToMultiByte(CP_UTF8, 0, &tag_utf16[0], tag_utf16.size(), &tag[0],
str_size, nullptr, nullptr);
return tag;
}
struct CallbackContext {
TagEncoding encoding = TagEncoding::kUtf8;
BinaryConstIt binary_begin;
std::string tag;
};
// Callback used by PeImageReader::EnumCertificates(). If we find the tag,
// the function returns false to stop searching subsequent certificates.
bool SearchCertForTag(uint16_t revision,
uint16_t certificate_type,
const uint8_t* certificate_data,
size_t certificate_data_size,
void* context) {
CallbackContext* callback_context =
reinterpret_cast<CallbackContext*>(context);
BinaryConstIt cert_it = callback_context->binary_begin +
(certificate_data - &*callback_context->binary_begin);
switch (callback_context->encoding) {
case TagEncoding::kUtf8:
callback_context->tag =
ReadTagUtf8(cert_it, cert_it + certificate_data_size);
break;
case TagEncoding::kUtf16:
callback_context->tag =
ReadTagUtf16(cert_it, cert_it + certificate_data_size);
break;
}
return callback_context->tag.empty();
}
} // namespace
BinaryConstIt AdvanceIt(BinaryConstIt it, size_t distance, BinaryConstIt end) {
if (it >= end)
return end;
ptrdiff_t dist_to_end = 0;
if (!base::CheckedNumeric<ptrdiff_t>(end - it).AssignIfValid(&dist_to_end))
return end;
return it + std::min(distance, static_cast<size_t>(dist_to_end));
}
bool CheckRange(BinaryConstIt it, size_t size, BinaryConstIt end) {
if (it >= end || size == 0)
return false;
ptrdiff_t dist_to_end = 0;
if (!base::CheckedNumeric<ptrdiff_t>(end - it).AssignIfValid(&dist_to_end))
return false;
return size <= static_cast<size_t>(dist_to_end);
}
std::string ExtractTagFromBuffer(const std::vector<uint8_t>& binary,
TagEncoding encoding) {
base::win::PeImageReader pe_image_reader;
if (!pe_image_reader.Initialize(&binary[0], binary.size()))
return std::string();
CallbackContext context;
context.encoding = encoding;
context.binary_begin = binary.begin();
pe_image_reader.EnumCertificates(SearchCertForTag, &context);
return context.tag;
}
std::string ExtractTagFromFile(const std::wstring& path, TagEncoding encoding) {
HANDLE file_handle =
::CreateFile(path.c_str(), FILE_READ_ATTRIBUTES | FILE_READ_DATA,
FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (file_handle == INVALID_HANDLE_VALUE)
return std::string();
LARGE_INTEGER file_size = {};
if (!::GetFileSizeEx(file_handle, &file_size)) {
::CloseHandle(file_handle);
return std::string();
}
if (file_size.QuadPart > std::numeric_limits<DWORD>::max()) {
::CloseHandle(file_handle);
return std::string();
}
std::vector<uint8_t> binary(file_size.QuadPart);
DWORD bytes_read = 0;
if (!::ReadFile(file_handle, &binary[0], static_cast<DWORD>(binary.size()),
&bytes_read, nullptr)) {
::CloseHandle(file_handle);
return std::string();
}
::CloseHandle(file_handle);
if (bytes_read < binary.size())
return std::string();
return ExtractTagFromBuffer(binary, encoding);
}
} // namespace updater