diff --git a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp index 2e18ca17..98eae18e 100644 --- a/libwvdrmengine/oemcrypto/test/oec_device_features.cpp +++ b/libwvdrmengine/oemcrypto/test/oec_device_features.cpp @@ -7,8 +7,13 @@ #include "oec_device_features.h" #include -#include -#include + +#ifdef _WIN32 +# include +#else +# include +# include +#endif #include @@ -18,6 +23,43 @@ namespace wvoec { DeviceFeatures global_features; +bool CanChangeTime() { +#ifdef _WIN32 + LUID desired_id; + if (!LookupPrivilegeValue(nullptr, SE_SYSTEMTIME_NAME, &desired_id)) + return false; + HANDLE token; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &token)) + return false; + std::unique_ptr safe_token(token, &CloseHandle); + + // This queries all the permissions given to the token to determine if we can + // change the system time. Note this is subtly different from PrivilegeCheck + // as that only checks "enabled" privileges; even with admin rights, the + // privilege is default disabled, even when granted. + + DWORD size = 0; + // Determine how big we need to allocate first. + GetTokenInformation(token, TokenPrivileges, nullptr, 0, &size); + // Since TOKEN_PRIVILEGES uses a variable-length array, we need to use malloc + std::unique_ptr privileges( + (TOKEN_PRIVILEGES*)malloc(size), &free); + if (privileges && GetTokenInformation(token, TokenPrivileges, + privileges.get(), size, &size)) { + for (int i = 0; i < privileges->PrivilegeCount; i++) { + if (privileges->Privileges[i].Luid.HighPart == desired_id.HighPart && + privileges->Privileges[i].Luid.LowPart == desired_id.LowPart) { + return true; + } + } + } + + return false; +#else + return getuid() == 0; +#endif +} + void DeviceFeatures::Initialize(bool is_cast_receiver, bool force_load_test_keybox) { cast_receiver = is_cast_receiver; @@ -162,7 +204,7 @@ std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { if (api_version < 15) FilterOut(&filter, "*API15*"); // Some tests may require root access. If user is not root, filter these tests // out. - if (getuid()) { + if (!CanChangeTime()) { FilterOut(&filter, "UsageTableTest.TimeRollbackPrevention"); } // Performance tests take a long time. Filter them out if they are not diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index 1f9aa46d..29465eb8 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -13,9 +13,14 @@ #include #include #include -#include #include +#ifdef _WIN32 +# include +#else +# include +#endif + #include #include #include @@ -86,6 +91,37 @@ int GetRandBytes(unsigned char* buf, int num) { // returns 1 on success, -1 if not supported, or 0 if other failure. return RAND_bytes(buf, num); } + +/** @return The Unix time of the given time point. */ +template +uint64_t UnixTime(const std::chrono::time_point& point) { + return point.time_since_epoch() / std::chrono::seconds(1); +} + +#ifdef _WIN32 +using NativeTime = SYSTEMTIME; +#else +using NativeTime = timeval; +#endif + +void AddNativeTime(int64_t delta_seconds, NativeTime* time) { +#ifdef _WIN32 + // See remarks from this for why this series is used. + // https://msdn.microsoft.com/en-us/f77cdf86-0f97-4a89-b565-95b46fa7d65b + FILETIME file_time; + ASSERT_TRUE(SystemTimeToFileTime(time, &file_time)); + uint64_t long_time = static_cast(file_time.dwLowDateTime) | + (static_cast(file_time.dwHighDateTime) << 32); + long_time += delta_seconds * 1e7; // long_time is in 100-nanosecond intervals. + file_time.dwLowDateTime = long_time & ((1ull << 32) - 1); + file_time.dwHighDateTime = long_time >> 32; + ASSERT_TRUE(FileTimeToSystemTime(&file_time, time)); +#else + time->tv_sec += delta_seconds; +#endif +} + } // namespace class OEMCryptoClientTest : public ::testing::Test, public SessionUtil { @@ -4881,6 +4917,22 @@ class UsageTableTest : public GenericCryptoTest { void SetUp() override { GenericCryptoTest::SetUp(); new_mac_keys_ = true; + did_change_system_time_ = false; + test_start_steady_ = steady_clock_.now(); +#ifdef _WIN32 + GetSystemTime(&test_start_wall_); +#else + ASSERT_EQ(0, gettimeofday(&test_start_wall_, nullptr)); +#endif + } + + void TearDown() override { + if (did_change_system_time_) { + const auto delta = steady_clock_.now() - test_start_steady_; + const int64_t delta_sec = delta / std::chrono::seconds(1); + ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta(delta_sec)); + } + GenericCryptoTest::TearDown(); } virtual void ShutDown() { @@ -4925,7 +4977,26 @@ class UsageTableTest : public GenericCryptoTest { } protected: + /** + * Sets the current wall-clock time to a delta based on the start of the + * test. + */ + void SetWallTimeDelta(int64_t delta_seconds) { + did_change_system_time_ = true; + NativeTime time = test_start_wall_; + ASSERT_NO_FATAL_FAILURE(AddNativeTime(delta_seconds, &time)); +#ifdef _WIN32 + ASSERT_TRUE(SetSystemTime(&time)); +#else + ASSERT_EQ(0, settimeofday(&time, nullptr)); +#endif + } + bool new_mac_keys_; + bool did_change_system_time_; + std::chrono::steady_clock steady_clock_; + std::chrono::time_point test_start_steady_; + NativeTime test_start_wall_; }; // Some usage tables we want to check a license either with or without a @@ -6284,23 +6355,19 @@ TEST_F(UsageTableTest, TimeRollbackPrevention) { cout << "This test temporarily rolls back the system time in order to verify " << "that the usage report accounts for the change. It then rolls " << "the time back forward to the absolute time." << endl; - // We use clock_gettime(CLOCK_REALTIME, ...) over time(...) so we can easily - // set the time using clock_settime. - timespec current_time; - ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, ¤t_time)); - time_t loaded = current_time.tv_sec; + std::chrono::system_clock wall_clock; + std::chrono::steady_clock monotonic_clock; + const auto loaded = wall_clock.now(); ASSERT_NO_FATAL_FAILURE(LoadOfflineLicense(s1, pst)); ASSERT_NO_FATAL_FAILURE(s1.open()); ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, ¤t_time)); - time_t first_decrypt = current_time.tv_sec; + const auto first_decrypt = wall_clock.now(); // Monotonic clock can't be changed. We use this since system clock will be // unreliable. - ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, ¤t_time)); - time_t first_decrypt_monotonic = current_time.tv_sec; + const auto first_decrypt_monotonic = monotonic_clock.now(); ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s1.close()); @@ -6316,20 +6383,17 @@ TEST_F(UsageTableTest, TimeRollbackPrevention) { ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s1.close()); - ASSERT_EQ(0, clock_gettime(CLOCK_REALTIME, ¤t_time)); // Rollback the wall clock time. cout << "Rolling the system time back..." << endl; - timeval current_time_of_day = {}; - current_time_of_day.tv_sec = current_time.tv_sec - kLongDuration * 10; - ASSERT_EQ(0, settimeofday(¤t_time_of_day, NULL)); + ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta( + -static_cast(kLongDuration) * 10)); // Try to playback again. ASSERT_NO_FATAL_FAILURE(s1.open()); ASSERT_NO_FATAL_FAILURE(s1.ReloadUsageEntry()); ASSERT_NO_FATAL_FAILURE(InstallTestSessionKeys(&s1)); ASSERT_NO_FATAL_FAILURE(s1.LoadTestKeys(pst, new_mac_keys_)); - ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, ¤t_time)); - time_t third_decrypt = current_time.tv_sec; + const auto third_decrypt_monotonic = monotonic_clock.now(); ASSERT_NO_FATAL_FAILURE(s1.TestDecryptCTR()); ASSERT_NO_FATAL_FAILURE(s1.UpdateUsageEntry(&encrypted_usage_header_)); ASSERT_NO_FATAL_FAILURE(s1.GenerateReport(pst)); @@ -6337,18 +6401,18 @@ TEST_F(UsageTableTest, TimeRollbackPrevention) { // Restore wall clock to its original position to verify that OEMCrypto does // not report negative times. - ASSERT_EQ(0, clock_gettime(CLOCK_MONOTONIC, ¤t_time)); - current_time_of_day.tv_sec = - first_decrypt + current_time.tv_sec - first_decrypt_monotonic; + const auto test_duration = third_decrypt_monotonic - first_decrypt_monotonic; cout << "Rolling the system time forward to the absolute time..." << endl; - ASSERT_EQ(0, settimeofday(¤t_time_of_day, NULL)); + ASSERT_NO_FATAL_FAILURE(SetWallTimeDelta( + test_duration / std::chrono::seconds(1))); // Need to update time created since the verification checks the time of PST // report creation. - expected.time_created = current_time_of_day.tv_sec; + expected.time_created = UnixTime(wall_clock.now()); + const auto end_time = first_decrypt + test_duration; ASSERT_NO_FATAL_FAILURE( - s1.VerifyReport(expected, loaded, first_decrypt, - first_decrypt + third_decrypt - first_decrypt_monotonic)); + s1.VerifyReport(expected, UnixTime(loaded), UnixTime(first_decrypt), + UnixTime(end_time))); ASSERT_NO_FATAL_FAILURE(s1.close()); }