| // Copyright 2021 The Chromium Authors | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "ash/system/diagnostics/async_log.h" | 
 |  | 
 | #include <memory> | 
 |  | 
 | #include "ash/system/diagnostics/log_test_helpers.h" | 
 | #include "base/files/file_path.h" | 
 | #include "base/files/file_util.h" | 
 | #include "base/files/scoped_temp_dir.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/test/task_environment.h" | 
 | #include "base/test/test_simple_task_runner.h" | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace ash { | 
 | namespace diagnostics { | 
 | namespace { | 
 |  | 
 | const char kLogFileName[] = "test_async_log"; | 
 |  | 
 | }  // namespace | 
 |  | 
 | class AsyncLogTest : public testing::Test { | 
 |  public: | 
 |   AsyncLogTest() : task_runner_(new base::TestSimpleTaskRunner) { | 
 |     EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); | 
 |     log_path_ = temp_dir_.GetPath().AppendASCII(kLogFileName); | 
 |   } | 
 |  | 
 |   ~AsyncLogTest() override { base::RunLoop().RunUntilIdle(); } | 
 |  | 
 |  protected: | 
 |   base::test::TaskEnvironment task_environment_{ | 
 |       base::test::TaskEnvironment::TimeSource::MOCK_TIME}; | 
 |   scoped_refptr<base::TestSimpleTaskRunner> task_runner_; | 
 |  | 
 |   base::ScopedTempDir temp_dir_; | 
 |   base::FilePath log_path_; | 
 | }; | 
 |  | 
 | TEST_F(AsyncLogTest, NoWriteEmpty) { | 
 |   AsyncLog log(log_path_); | 
 |   log.SetTaskRunnerForTesting(task_runner_); | 
 |  | 
 |   // The file won't until it is written to. | 
 |   EXPECT_FALSE(base::PathExists(log_path_)); | 
 |  | 
 |   // The log is empty. | 
 |   EXPECT_TRUE(log.GetContents().empty()); | 
 | } | 
 |  | 
 | TEST_F(AsyncLogTest, WriteEmpty) { | 
 |   AsyncLog log(log_path_); | 
 |   log.SetTaskRunnerForTesting(task_runner_); | 
 |  | 
 |   // Append empty string to the log. | 
 |   log.Append(""); | 
 |  | 
 |   EXPECT_TRUE(task_runner_->HasPendingTask()); | 
 |   // Ensure pending tasks complete. | 
 |   task_runner_->RunUntilIdle(); | 
 |   EXPECT_FALSE(task_runner_->HasPendingTask()); | 
 |  | 
 |   // The file exists. | 
 |   EXPECT_TRUE(base::PathExists(log_path_)); | 
 |  | 
 |   // But log is still empty. | 
 |   EXPECT_TRUE(log.GetContents().empty()); | 
 | } | 
 |  | 
 | TEST_F(AsyncLogTest, WriteOneLine) { | 
 |   AsyncLog log(log_path_); | 
 |   log.SetTaskRunnerForTesting(task_runner_); | 
 |  | 
 |   const std::string line = "Hello"; | 
 |  | 
 |   // Append `line` to the log. | 
 |   log.Append(line); | 
 |  | 
 |   // Ensure pending tasks complete. | 
 |   task_runner_->RunUntilIdle(); | 
 |  | 
 |   // Log contains `line`. | 
 |   EXPECT_EQ(line, log.GetContents()); | 
 | } | 
 |  | 
 | TEST_F(AsyncLogTest, WriteMultipleLines) { | 
 |   AsyncLog log(log_path_); | 
 |   log.SetTaskRunnerForTesting(task_runner_); | 
 |  | 
 |   const std::vector<std::string> lines = { | 
 |       "Line 1", | 
 |       "Line 2", | 
 |       "Line 3", | 
 |   }; | 
 |  | 
 |   // Append all the `lines` with a new line to the log. | 
 |   for (auto line : lines) { | 
 |     log.Append(line + "\n"); | 
 |   } | 
 |  | 
 |   // Ensure pending tasks complete. | 
 |   task_runner_->RunUntilIdle(); | 
 |  | 
 |   // Read back the log and split the lines. | 
 |   EXPECT_EQ(lines, GetLogLines(log.GetContents())); | 
 | } | 
 |  | 
 | // This case is to test against a UAF security issue, | 
 | // https://crbug.com/286210532. If it has to be disabled, for example for | 
 | // flakiness reasons, please be sure to leave a comment in | 
 | // https://crbug.com/286210532 to alert the test owners. | 
 | // | 
 | // More on the UAF, before CL https://crrev.com/c/4583920, AsyncLog::Append() | 
 | // would post a task that binds the WeakPtr to an AsyncLog object and its | 
 | // AsyncLog::AppendImpl() member function to call. In certain situations, as is | 
 | // documented in https://crbug.com/286210532, the task would have started | 
 | // running, while the AsyncLog object is destroyed mid-execution. This would | 
 | // lead to UAF as operations to access member variables of AsyncLog used to be | 
 | // performed inside AsyncLog::AppendImpl(), before CL | 
 | // https://crrev.com/c/4583920 was landed. | 
 | // | 
 | // CL https://crrev.com/c/4583920 fixed this issue by changing AppendImpl() and | 
 | // CreateFile() to free functions in an anonymous namespace, which prevented | 
 | // access to member variables from an async task. | 
 | // | 
 | // This test was once disabled by chromium gardeners due to flakiness caused by | 
 | // a minor flaw. It used to be that all 10 AsyncLog objects inside the for loop | 
 | // was trying to write to the same file path asynchronously all at once. A | 
 | // DCHECK() failure would quickly emerge inside AsyncLog::CreateFile(), as | 
 | // AsyncLog was designed in a way that a unique file path was supposed to be | 
 | // handled by a unique AsyncLog object. Multiple AsyncLog objects handling the | 
 | // same file path was not expected. A follow-up CL fixed this flakiness, | 
 | // https://crrev.com/c/6550156. | 
 | TEST_F(AsyncLogTest, NoUseAfterFreeCrash) { | 
 |   const std::string new_line = "Line\n"; | 
 |  | 
 |   // Simulate race conditions between the destruction of AsyncLog and the | 
 |   // execution of AppendImpl. | 
 |   for (size_t i = 0; i < 10; ++i) { | 
 |     auto unique_file_path = temp_dir_.GetPath().AppendASCII( | 
 |         std::string(kLogFileName) + "_" + base::NumberToString(i)); | 
 |     auto log = std::make_unique<AsyncLog>(unique_file_path); | 
 |     log->Append(new_line); | 
 |   } | 
 |  | 
 |   // This should finish without crash. | 
 |   task_environment_.RunUntilIdle(); | 
 | } | 
 |  | 
 | }  // namespace diagnostics | 
 | }  // namespace ash |