chromium/chrome/chrome_elf/third_party_dlls/logs_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/chrome_elf/third_party_dlls/logs.h"

#include <windows.h>

#include <memory>
#include <string>

#include "base/containers/heap_array.h"
#include "base/synchronization/waitable_event.h"
#include "base/time/time.h"
#include "chrome/chrome_elf/sha1/sha1.h"
#include "chrome/chrome_elf/third_party_dlls/public_api.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace third_party_dlls {
namespace {

enum { kWaitTimeoutMs = 3000 };

// Argument for NotificationHandler().
struct NotificationHandlerArguments {
  uint32_t logs_expected;
  std::unique_ptr<base::WaitableEvent> notification_event;
};

struct TestEntry {
  uint32_t image_size;
  uint32_t time_date_stamp;
};

// Sample TestEntries
TestEntry kTestLogs[] = {
    {0x9901, 0x12345678}, {0x9902, 0x12345678}, {0x9903, 0x12345678},
    {0x9904, 0x12345678}, {0x9905, 0x12345678}, {0x9906, 0x12345678},
};

// Be sure to test the padding/alignment issues well here.
const char* const kTestPaths[] = {
    "1", "123", "1234", "12345", "123456", "1234567",
};

static_assert(
    std::size(kTestLogs) == std::size(kTestPaths),
    "Some tests currently expect these two arrays to be the same size.");

// Ensure |buffer_size| passed in is the actual bytes written by DrainLog().
void VerifyBuffer(uint8_t* buffer, uint32_t buffer_size) {
  uint32_t total_logs = 0;
  size_t index = 0;
  size_t array_size = std::size(kTestLogs);

  // Verify against kTestLogs/kTestPaths.  Expect 2 * std::size(kTestLogs)
  // entries: first half are "blocked", second half are "allowed".
  LogEntry* entry = nullptr;
  uint8_t* tracker = buffer;
  while (tracker < buffer + buffer_size) {
    entry = reinterpret_cast<LogEntry*>(tracker);

    EXPECT_EQ(entry->module_size, kTestLogs[index].image_size);
    EXPECT_EQ(entry->time_date_stamp, kTestLogs[index].time_date_stamp);

    if (entry->path_len)
      EXPECT_STREQ(entry->path, kTestPaths[index]);

    ++total_logs;
    tracker += GetLogEntrySize(entry->path_len);

    // Roll index back to 0 for second run through kTestLogs, else increment.
    index = (index == array_size - 1) ? 0 : index + 1;
  }
  EXPECT_EQ(total_logs, array_size * 2);
}

// Helper function to count the number of LogEntries in a buffer returned from
// DrainLog().
uint32_t GetLogCount(uint8_t* buffer, uint32_t bytes_written) {
  LogEntry* entry = nullptr;
  uint8_t* tracker = buffer;
  uint32_t counter = 0;

  while (tracker < buffer + bytes_written) {
    entry = reinterpret_cast<LogEntry*>(tracker);
    ++counter;
    tracker += GetLogEntrySize(entry->path_len);
  }

  return counter;
}

// A thread function used to test log notifications.
// - |parameter| should be a NotificationHandlerArguments*.
// - Returns 0 for successful retrieval of expected number of LogEntries.
DWORD WINAPI NotificationHandler(LPVOID parameter) {
  NotificationHandlerArguments* args =
      reinterpret_cast<NotificationHandlerArguments*>(parameter);
  uint32_t log_counter = 0;

  // Make a buffer big enough for any possible DrainLog call.
  uint32_t buffer_size = args->logs_expected * GetLogEntrySize(0);
  auto buffer = base::HeapArray<uint8_t>::Uninit(buffer_size);
  uint32_t bytes_written = 0;

  do {
    if (!args->notification_event->TimedWait(
            base::Milliseconds(kWaitTimeoutMs)))
      break;

    bytes_written = DrainLog(buffer.data(), buffer_size, nullptr);
    log_counter += GetLogCount(buffer.data(), bytes_written);
  } while (log_counter < args->logs_expected);

  return (log_counter == args->logs_expected) ? 0 : 1;
}

//------------------------------------------------------------------------------
// Third-party log tests
//------------------------------------------------------------------------------

// Test successful initialization and module lookup.
TEST(ThirdParty, Logs) {
  // Init.
  ASSERT_EQ(InitLogs(), ThirdPartyStatus::kSuccess);

  for (size_t i = 0; i < std::size(kTestLogs); ++i) {
    // Add some blocked entries.
    LogLoadAttempt(LogType::kBlocked, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, std::string());

    // Add some allowed entries.
    LogLoadAttempt(LogType::kAllowed, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, kTestPaths[i]);
  }

  uint32_t initial_log = 0;
  DrainLog(nullptr, 0, &initial_log);
  ASSERT_TRUE(initial_log);

  auto buffer = base::HeapArray<uint8_t>::Uninit(initial_log);
  uint32_t remaining_log = 0;
  uint32_t bytes_written = DrainLog(buffer.data(), initial_log, &remaining_log);
  EXPECT_EQ(bytes_written, initial_log);
  EXPECT_EQ(remaining_log, uint32_t{0});

  VerifyBuffer(buffer.data(), bytes_written);

  DeinitLogs();
}

// Test notifications.
TEST(ThirdParty, LogNotifications) {
  // Init.
  ASSERT_EQ(InitLogs(), ThirdPartyStatus::kSuccess);

  uint32_t initial_log = 0;
  DrainLog(nullptr, 0, &initial_log);
  EXPECT_EQ(initial_log, uint32_t{0});

  // Set up the required arguments for the test thread.
  NotificationHandlerArguments handler_data;
  handler_data.logs_expected = std::size(kTestLogs);
  handler_data.notification_event = std::make_unique<base::WaitableEvent>(
      base::WaitableEvent::ResetPolicy::AUTOMATIC,
      base::WaitableEvent::InitialState::NOT_SIGNALED);

  // Register the event.
  ASSERT_TRUE(
      RegisterLogNotification(handler_data.notification_event->handle()));

  // Fire off a thread to handle the notifications.
  base::win::ScopedHandle thread(::CreateThread(
      nullptr, 0, &NotificationHandler, &handler_data, 0, nullptr));

  for (size_t i = 0; i < handler_data.logs_expected; ++i) {
    // Add blocked entries - type doesn't matter in this test.
    LogLoadAttempt(LogType::kBlocked, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, std::string());
  }

  EXPECT_EQ(::WaitForSingleObject(thread.Get(), kWaitTimeoutMs * 2),
            WAIT_OBJECT_0);
  DWORD exit_code = 1;
  EXPECT_TRUE(::GetExitCodeThread(thread.Get(), &exit_code));
  EXPECT_EQ(exit_code, DWORD{0});

  DeinitLogs();
}

// Test that "spam", duplicate block logs are handled as expected across drains.
TEST(ThirdParty, BlockedLogDuplicates) {
  // Init.
  ASSERT_EQ(InitLogs(), ThirdPartyStatus::kSuccess);

  for (size_t i = 0; i < std::size(kTestLogs); ++i) {
    // Add some blocked entries.
    LogLoadAttempt(LogType::kBlocked, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, kTestPaths[i]);

    // Add some allowed entries.
    LogLoadAttempt(LogType::kAllowed, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, kTestPaths[i]);
  }

  uint32_t initial_log = 0;
  DrainLog(nullptr, 0, &initial_log);
  ASSERT_TRUE(initial_log);

  auto buffer = base::HeapArray<uint8_t>::Uninit(initial_log);
  uint32_t remaining_log = 0;
  uint32_t bytes_written = DrainLog(buffer.data(), initial_log, &remaining_log);
  EXPECT_EQ(bytes_written, initial_log);
  EXPECT_EQ(remaining_log, uint32_t{0});

  // Validate that all of the logs have been drained.
  EXPECT_EQ(GetLogCount(buffer.data(), bytes_written),
            std::size(kTestLogs) * 2);

  // Now the real test.  Add the same log entries again, and expect that the
  // blocked logs will NOT be re-added and drained this time.
  for (size_t i = 0; i < std::size(kTestLogs); ++i) {
    // Add some blocked entries.
    LogLoadAttempt(LogType::kBlocked, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, kTestPaths[i]);

    // Add some allowed entries.
    LogLoadAttempt(LogType::kAllowed, kTestLogs[i].image_size,
                   kTestLogs[i].time_date_stamp, kTestPaths[i]);
  }

  initial_log = 0;
  DrainLog(nullptr, 0, &initial_log);
  ASSERT_TRUE(initial_log);

  buffer = base::HeapArray<uint8_t>::Uninit(initial_log);
  remaining_log = 0;
  bytes_written = DrainLog(buffer.data(), initial_log, &remaining_log);
  EXPECT_EQ(bytes_written, initial_log);
  EXPECT_EQ(remaining_log, uint32_t{0});

  // Validate that only half of the logs have been drained.
  EXPECT_EQ(GetLogCount(buffer.data(), bytes_written), std::size(kTestLogs));

  DeinitLogs();
}

}  // namespace
}  // namespace third_party_dlls