blob: 405b5798fa0324a8481c08f29b5239c32a28cf94 [file] [log] [blame]
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +09001// Copyright 2018 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "crash-reporter/util.h"
6
Satoru Takabayashi9a587522018-10-29 09:36:27 +09007#include <stdlib.h>
8
9#include <memory>
Tim Zheng11a665e2019-06-26 17:44:01 -070010#include <sys/mman.h>
Satoru Takabayashi9a587522018-10-29 09:36:27 +090011
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090012#include <base/files/file_util.h>
13#include <base/files/scoped_temp_dir.h>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070014#include <base/rand_util.h>
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -070015#include <base/test/simple_test_clock.h>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070016#include <brillo/process.h>
17#include <brillo/streams/memory_stream.h>
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090018#include <gtest/gtest.h>
19
20#include "crash-reporter/crash_sender_paths.h"
21#include "crash-reporter/paths.h"
22#include "crash-reporter/test_util.h"
23
Tim Zheng11a665e2019-06-26 17:44:01 -070024// The QEMU emulator we use to run unit tests on simulated ARM boards does not
25// support memfd_create. (https://bugs.launchpad.net/qemu/+bug/1734792) Skip
26// tests that rely on memfd_create on ARM boards.
27#if defined(ARCH_CPU_ARM_FAMILY)
28#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) DISABLED_##test_name
29#else
30#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) test_name
31#endif
32
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090033namespace util {
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090034namespace {
35
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070036constexpr char kLsbReleaseContents[] =
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090037 "CHROMEOS_RELEASE_BOARD=bob\n"
38 "CHROMEOS_RELEASE_NAME=Chromium OS\n"
39 "CHROMEOS_RELEASE_VERSION=10964.0.2018_08_13_1405\n";
40
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -070041constexpr char kHwClassContents[] = "fake_hwclass";
42
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070043constexpr char kGzipPath[] = "/bin/gzip";
44
45constexpr char kSemiRandomData[] =
46 "ABJCI239AJSDLKJ;kalkjkjsd98723;KJHASD87;kqw3p088ad;lKJASDP823;KJ";
47constexpr int kRandomDataMinLength = 32768; // 32kB
48constexpr int kRandomDataMaxLength = 262144; // 256kB
49
50// Verifies that |raw_file| corresponds to the gzip'd version of
51// |compressed_file| by decompressing it and comparing the contents. Returns
52// true if they match, false otherwise. This will overwrite the contents of
53// |compressed_file| in the process of doing this.
54bool VerifyCompression(const base::FilePath& raw_file,
55 const base::FilePath& compressed_file) {
56 if (!base::PathExists(raw_file)) {
57 LOG(ERROR) << "raw_file doesn't exist for verifying compression: "
58 << raw_file.value();
59 return false;
60 }
61 if (!base::PathExists(compressed_file)) {
62 LOG(ERROR) << "compressed_file doesn't exist for verifying compression: "
63 << compressed_file.value();
64 return false;
65 }
66 brillo::ProcessImpl proc;
67 proc.AddArg(kGzipPath);
68 proc.AddArg("-d"); // decompress
69 proc.AddArg(compressed_file.value());
70 std::string error;
71 const int res = util::RunAndCaptureOutput(&proc, STDERR_FILENO, &error);
72 if (res < 0) {
73 PLOG(ERROR) << "Failed to execute gzip";
74 return false;
75 }
76 if (res != 0) {
77 LOG(ERROR) << "Failed to un-gzip " << compressed_file.value();
78 util::LogMultilineError(error);
79 return false;
80 }
81 base::FilePath uncompressed_file = compressed_file.RemoveFinalExtension();
82 std::string raw_contents;
83 std::string uncompressed_contents;
84 if (!base::ReadFileToString(raw_file, &raw_contents)) {
85 LOG(ERROR) << "Failed reading in raw_file " << raw_file.value();
86 return false;
87 }
88 if (!base::ReadFileToString(uncompressed_file, &uncompressed_contents)) {
89 LOG(ERROR) << "Failed reading in uncompressed_file "
90 << uncompressed_file.value();
91 return false;
92 }
93 return raw_contents == uncompressed_contents;
94}
95
96// We use a somewhat random string of ASCII data to better reflect the data we
97// would be compressing for real. We also shouldn't use something like
98// base::RandBytesAsString() because that will generate uniformly random data
99// which does not compress.
100std::string CreateSemiRandomString(size_t size) {
101 std::string result;
102 result.reserve(size);
103 while (result.length() < size) {
104 int rem = size - result.length();
105 if (rem > sizeof(kSemiRandomData) - 1)
106 rem = sizeof(kSemiRandomData) - 1;
107 int rand_start = base::RandInt(0, rem - 1);
108 int rand_end = base::RandInt(rand_start + 1, rem);
109 result.append(&kSemiRandomData[rand_start], rand_end - rand_start);
110 }
111 return result;
112}
113
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900114} // namespace
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900115
116class CrashCommonUtilTest : public testing::Test {
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700117 protected:
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900118 void SetUp() override {
119 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
120 test_dir_ = scoped_temp_dir_.GetPath();
121 paths::SetPrefixForTesting(test_dir_);
122 }
123
124 void TearDown() override { paths::SetPrefixForTesting(base::FilePath()); }
125
126 base::FilePath test_dir_;
127 base::ScopedTempDir scoped_temp_dir_;
128};
129
130TEST_F(CrashCommonUtilTest, IsCrashTestInProgress) {
131 EXPECT_FALSE(IsCrashTestInProgress());
132 ASSERT_TRUE(
133 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
134 paths::kCrashTestInProgress),
135 ""));
136 EXPECT_TRUE(IsCrashTestInProgress());
137}
138
Satoru Takabayashi2d728042018-12-10 09:19:00 +0900139TEST_F(CrashCommonUtilTest, IsDeviceCoredumpUploadAllowed) {
140 EXPECT_FALSE(IsDeviceCoredumpUploadAllowed());
141 ASSERT_TRUE(
142 test_util::CreateFile(paths::GetAt(paths::kCrashReporterStateDirectory,
143 paths::kDeviceCoredumpUploadAllowed),
144 ""));
145 EXPECT_TRUE(IsDeviceCoredumpUploadAllowed());
146}
147
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900148TEST_F(CrashCommonUtilTest, IsDeveloperImage) {
149 EXPECT_FALSE(IsDeveloperImage());
150
151 ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kLeaveCoreFile), ""));
152 EXPECT_TRUE(IsDeveloperImage());
153
154 ASSERT_TRUE(
155 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
156 paths::kCrashTestInProgress),
157 ""));
158 EXPECT_FALSE(IsDeveloperImage());
159}
160
Satoru Takabayashif6a36802018-08-14 16:23:05 +0900161TEST_F(CrashCommonUtilTest, IsTestImage) {
162 EXPECT_FALSE(IsTestImage());
163
164 // Should return false because the channel is stable.
165 ASSERT_TRUE(test_util::CreateFile(
166 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
167 "CHROMEOS_RELEASE_TRACK=stable-channel"));
168 EXPECT_FALSE(IsTestImage());
169
170 // Should return true because the channel is testimage.
171 ASSERT_TRUE(test_util::CreateFile(
172 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
173 "CHROMEOS_RELEASE_TRACK=testimage-channel"));
174 EXPECT_TRUE(IsTestImage());
175
176 // Should return false if kCrashTestInProgress is present.
177 ASSERT_TRUE(
178 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
179 paths::kCrashTestInProgress),
180 ""));
181 EXPECT_FALSE(IsTestImage());
182}
183
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900184TEST_F(CrashCommonUtilTest, IsOfficialImage) {
185 EXPECT_FALSE(IsOfficialImage());
186
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900187 // Check if lsb-release is handled correctly.
188 ASSERT_TRUE(test_util::CreateFile(
189 paths::Get("/etc/lsb-release"),
190 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Test Build) developer-build"));
191 EXPECT_FALSE(IsOfficialImage());
192
193 ASSERT_TRUE(test_util::CreateFile(
194 paths::Get("/etc/lsb-release"),
195 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Official Build) canary-channel"));
196 EXPECT_TRUE(IsOfficialImage());
197}
198
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700199TEST_F(CrashCommonUtilTest, GetOsTimestamp) {
200 // If we can't read /etc/lsb-release then we should be returning the null
201 // time.
202 EXPECT_TRUE(util::GetOsTimestamp().is_null());
203
204 base::FilePath lsb_file_path = paths::Get("/etc/lsb-release");
205 ASSERT_TRUE(test_util::CreateFile(lsb_file_path, "foo=bar"));
206 base::Time old_time = base::Time::Now() - base::TimeDelta::FromDays(366);
207 ASSERT_TRUE(base::TouchFile(lsb_file_path, old_time, old_time));
208 // ext2/ext3 seem to have a timestamp granularity of 1s.
209 EXPECT_EQ(util::GetOsTimestamp().ToTimeVal().tv_sec,
210 old_time.ToTimeVal().tv_sec);
211}
212
213TEST_F(CrashCommonUtilTest, IsOsTimestampTooOldForUploads) {
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700214 base::SimpleTestClock clock;
215 const base::Time now = test_util::GetDefaultTime();
216 clock.SetNow(now);
217
218 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(base::Time(), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700219 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700220 now - base::TimeDelta::FromDays(179), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700221 EXPECT_TRUE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700222 now - base::TimeDelta::FromDays(181), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700223}
224
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700225TEST_F(CrashCommonUtilTest, GetHardwareClass) {
226 EXPECT_EQ("undefined", GetHardwareClass());
227
228 ASSERT_TRUE(test_util::CreateFile(
229 paths::Get("/sys/devices/platform/chromeos_acpi/HWID"),
230 kHwClassContents));
231 EXPECT_EQ(kHwClassContents, GetHardwareClass());
232}
233
234TEST_F(CrashCommonUtilTest, GetBootModeString) {
235 EXPECT_EQ("missing-crossystem", GetBootModeString());
236
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700237 ASSERT_TRUE(
238 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
239 paths::kCrashTestInProgress),
240 ""));
241 EXPECT_EQ("", GetBootModeString());
242}
243
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900244TEST_F(CrashCommonUtilTest, GetCachedKeyValue) {
245 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
246 kLsbReleaseContents));
247 ASSERT_TRUE(test_util::CreateFile(paths::Get("/empty/lsb-release"), ""));
248
249 std::string value;
250 // No directories are specified.
251 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
252 "CHROMEOS_RELEASE_VERSION", {}, &value));
253 // A non-existent directory is specified.
254 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
255 "CHROMEOS_RELEASE_VERSION",
256 {paths::Get("/non-existent")}, &value));
257
258 // A non-existent base name is specified.
259 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("non-existent"),
260 "CHROMEOS_RELEASE_VERSION",
261 {paths::Get("/etc")}, &value));
262
263 // A wrong key is specified.
264 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"), "WRONG_KEY",
265 {paths::Get("/etc")}, &value));
266
267 // This should succeed.
268 EXPECT_TRUE(GetCachedKeyValue(base::FilePath("lsb-release"),
269 "CHROMEOS_RELEASE_VERSION",
270 {paths::Get("/etc")}, &value));
271 EXPECT_EQ("10964.0.2018_08_13_1405", value);
272
273 // A non-existent directory is included, but this should still succeed.
274 EXPECT_TRUE(GetCachedKeyValue(
275 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
276 {paths::Get("/non-existent"), paths::Get("/etc")}, &value));
277 EXPECT_EQ("10964.0.2018_08_13_1405", value);
278
279 // A empty file is included, but this should still succeed.
280 EXPECT_TRUE(GetCachedKeyValue(
281 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
282 {paths::Get("/empty"), paths::Get("/etc")}, &value));
283 EXPECT_EQ("10964.0.2018_08_13_1405", value);
284}
285
286TEST_F(CrashCommonUtilTest, GetCachedKeyValueDefault) {
287 std::string value;
288 EXPECT_FALSE(
289 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
290
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700291 // kEtcDirectory is the second candidate directory.
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900292 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700293 paths::GetAt(paths::kEtcDirectory, "test.txt"), "FOO=2\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900294 EXPECT_TRUE(
295 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
296 EXPECT_EQ("2", value);
297
298 // kCrashReporterStateDirectory is the first candidate directory.
299 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700300 paths::GetAt(paths::kCrashReporterStateDirectory, "test.txt"),
301 "FOO=1\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900302 EXPECT_TRUE(
303 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
304 EXPECT_EQ("1", value);
305}
306
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900307TEST_F(CrashCommonUtilTest, GetUserCrashDirectories) {
308 auto mock =
309 std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
310
311 std::vector<base::FilePath> directories;
312
313 test_util::SetActiveSessions(mock.get(), {});
314 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
315 EXPECT_TRUE(directories.empty());
316
317 test_util::SetActiveSessions(mock.get(),
318 {{"user1", "hash1"}, {"user2", "hash2"}});
319 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
320 EXPECT_EQ(2, directories.size());
Satoru Takabayashib09f7052018-10-01 15:26:29 +0900321 EXPECT_EQ(paths::Get("/home/user/hash1/crash").value(),
322 directories[0].value());
323 EXPECT_EQ(paths::Get("/home/user/hash2/crash").value(),
324 directories[1].value());
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900325}
326
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700327TEST_F(CrashCommonUtilTest, GzipStream) {
328 std::string content = CreateSemiRandomString(
329 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700330 std::vector<unsigned char> compressed_content =
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700331 util::GzipStream(brillo::MemoryStream::OpenCopyOf(
332 content.c_str(), content.length(), nullptr));
333 EXPECT_FALSE(compressed_content.empty());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700334 EXPECT_LT(compressed_content.size(), content.size())
335 << "Didn't actually compress";
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700336 base::FilePath raw_file;
337 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &raw_file));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700338 base::FilePath compressed_file_name;
339 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &compressed_file_name));
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700340 // Remove the file we will decompress to or gzip will fail on decompression.
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700341 ASSERT_TRUE(base::DeleteFile(compressed_file_name, false));
342 compressed_file_name = compressed_file_name.AddExtension(".gz");
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700343 ASSERT_EQ(base::WriteFile(raw_file, content.c_str(), content.length()),
344 content.length());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700345 {
346 base::File compressed_file(
347 compressed_file_name, base::File::FLAG_WRITE | base::File::FLAG_CREATE);
348 ASSERT_TRUE(compressed_file.IsValid());
349 ssize_t write_result = HANDLE_EINTR(write(compressed_file.GetPlatformFile(),
350 compressed_content.data(),
351 compressed_content.size()));
352 ASSERT_EQ(write_result, compressed_content.size());
353 }
354 EXPECT_TRUE(VerifyCompression(raw_file, compressed_file_name))
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700355 << "Random input data: " << content;
356}
357
Tim Zheng11a665e2019-06-26 17:44:01 -0700358TEST_F(CrashCommonUtilTest,
359 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringEmpty)) {
360 int memfd = memfd_create("test_memfd", 0);
361 std::string read_outs;
362 EXPECT_FALSE(ReadMemfdToString(memfd, &read_outs));
363}
364
365TEST_F(CrashCommonUtilTest,
366 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringSuccess)) {
367 int memfd = memfd_create("test_memfd", 0);
368 const std::string write_ins = "Test data to write into memfd";
369 ASSERT_EQ(write(memfd, write_ins.c_str(), strlen(write_ins.c_str())),
370 strlen(write_ins.c_str()));
371 std::string read_outs;
372 EXPECT_TRUE(ReadMemfdToString(memfd, &read_outs));
373 EXPECT_EQ(read_outs, write_ins);
374}
375
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900376} // namespace util