blob: 97a4689d38f6d85d2625ef70fb2f76aafeed0241 [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
Kansho Nishida630cc7a2019-10-23 17:37:41 +09009#include <fcntl.h>
Miriam Zimmerman36042862019-11-14 20:01:29 -080010#include <limits>
Satoru Takabayashi9a587522018-10-29 09:36:27 +090011#include <memory>
Tim Zheng11a665e2019-06-26 17:44:01 -070012#include <sys/mman.h>
Satoru Takabayashi9a587522018-10-29 09:36:27 +090013
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090014#include <base/files/file_util.h>
15#include <base/files/scoped_temp_dir.h>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070016#include <base/rand_util.h>
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -070017#include <base/test/simple_test_clock.h>
Kansho Nishida630cc7a2019-10-23 17:37:41 +090018#include <base/time/time.h>
Simon Glass2b1da092020-05-21 12:24:16 -060019#include <brillo/process/process.h>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070020#include <brillo/streams/memory_stream.h>
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090021#include <gtest/gtest.h>
22
23#include "crash-reporter/crash_sender_paths.h"
24#include "crash-reporter/paths.h"
25#include "crash-reporter/test_util.h"
26
Tim Zheng11a665e2019-06-26 17:44:01 -070027// The QEMU emulator we use to run unit tests on simulated ARM boards does not
28// support memfd_create. (https://bugs.launchpad.net/qemu/+bug/1734792) Skip
29// tests that rely on memfd_create on ARM boards.
30#if defined(ARCH_CPU_ARM_FAMILY)
31#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) DISABLED_##test_name
32#else
33#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) test_name
34#endif
35
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090036namespace util {
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090037namespace {
38
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070039constexpr char kLsbReleaseContents[] =
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090040 "CHROMEOS_RELEASE_BOARD=bob\n"
41 "CHROMEOS_RELEASE_NAME=Chromium OS\n"
42 "CHROMEOS_RELEASE_VERSION=10964.0.2018_08_13_1405\n";
43
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -070044constexpr char kHwClassContents[] = "fake_hwclass";
45
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070046constexpr char kGzipPath[] = "/bin/gzip";
47
48constexpr char kSemiRandomData[] =
49 "ABJCI239AJSDLKJ;kalkjkjsd98723;KJHASD87;kqw3p088ad;lKJASDP823;KJ";
50constexpr int kRandomDataMinLength = 32768; // 32kB
51constexpr int kRandomDataMaxLength = 262144; // 256kB
52
Kansho Nishida630cc7a2019-10-23 17:37:41 +090053constexpr char kReadFdToStreamContents[] = "1234567890";
54
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070055// Verifies that |raw_file| corresponds to the gzip'd version of
56// |compressed_file| by decompressing it and comparing the contents. Returns
57// true if they match, false otherwise. This will overwrite the contents of
58// |compressed_file| in the process of doing this.
59bool VerifyCompression(const base::FilePath& raw_file,
60 const base::FilePath& compressed_file) {
61 if (!base::PathExists(raw_file)) {
62 LOG(ERROR) << "raw_file doesn't exist for verifying compression: "
63 << raw_file.value();
64 return false;
65 }
66 if (!base::PathExists(compressed_file)) {
67 LOG(ERROR) << "compressed_file doesn't exist for verifying compression: "
68 << compressed_file.value();
69 return false;
70 }
71 brillo::ProcessImpl proc;
72 proc.AddArg(kGzipPath);
73 proc.AddArg("-d"); // decompress
74 proc.AddArg(compressed_file.value());
75 std::string error;
76 const int res = util::RunAndCaptureOutput(&proc, STDERR_FILENO, &error);
77 if (res < 0) {
78 PLOG(ERROR) << "Failed to execute gzip";
79 return false;
80 }
81 if (res != 0) {
82 LOG(ERROR) << "Failed to un-gzip " << compressed_file.value();
83 util::LogMultilineError(error);
84 return false;
85 }
86 base::FilePath uncompressed_file = compressed_file.RemoveFinalExtension();
87 std::string raw_contents;
88 std::string uncompressed_contents;
89 if (!base::ReadFileToString(raw_file, &raw_contents)) {
90 LOG(ERROR) << "Failed reading in raw_file " << raw_file.value();
91 return false;
92 }
93 if (!base::ReadFileToString(uncompressed_file, &uncompressed_contents)) {
94 LOG(ERROR) << "Failed reading in uncompressed_file "
95 << uncompressed_file.value();
96 return false;
97 }
98 return raw_contents == uncompressed_contents;
99}
100
101// We use a somewhat random string of ASCII data to better reflect the data we
102// would be compressing for real. We also shouldn't use something like
103// base::RandBytesAsString() because that will generate uniformly random data
104// which does not compress.
105std::string CreateSemiRandomString(size_t size) {
106 std::string result;
107 result.reserve(size);
108 while (result.length() < size) {
109 int rem = size - result.length();
110 if (rem > sizeof(kSemiRandomData) - 1)
111 rem = sizeof(kSemiRandomData) - 1;
112 int rand_start = base::RandInt(0, rem - 1);
113 int rand_end = base::RandInt(rand_start + 1, rem);
114 result.append(&kSemiRandomData[rand_start], rand_end - rand_start);
115 }
116 return result;
117}
118
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900119} // namespace
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900120
121class CrashCommonUtilTest : public testing::Test {
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700122 protected:
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900123 void SetUp() override {
124 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
125 test_dir_ = scoped_temp_dir_.GetPath();
126 paths::SetPrefixForTesting(test_dir_);
Kansho Nishida630cc7a2019-10-23 17:37:41 +0900127 base::FilePath file = scoped_temp_dir_.GetPath().Append("tmpfile");
128 ASSERT_TRUE(test_util::CreateFile(file, kReadFdToStreamContents));
129 fd_ = open(file.value().c_str(), O_RDONLY);
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900130 }
131
132 void TearDown() override { paths::SetPrefixForTesting(base::FilePath()); }
133
134 base::FilePath test_dir_;
135 base::ScopedTempDir scoped_temp_dir_;
Kansho Nishida630cc7a2019-10-23 17:37:41 +0900136 unsigned int fd_;
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900137};
138
139TEST_F(CrashCommonUtilTest, IsCrashTestInProgress) {
140 EXPECT_FALSE(IsCrashTestInProgress());
141 ASSERT_TRUE(
142 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
143 paths::kCrashTestInProgress),
144 ""));
145 EXPECT_TRUE(IsCrashTestInProgress());
146}
147
Satoru Takabayashi2d728042018-12-10 09:19:00 +0900148TEST_F(CrashCommonUtilTest, IsDeviceCoredumpUploadAllowed) {
149 EXPECT_FALSE(IsDeviceCoredumpUploadAllowed());
150 ASSERT_TRUE(
151 test_util::CreateFile(paths::GetAt(paths::kCrashReporterStateDirectory,
152 paths::kDeviceCoredumpUploadAllowed),
153 ""));
154 EXPECT_TRUE(IsDeviceCoredumpUploadAllowed());
155}
156
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900157TEST_F(CrashCommonUtilTest, IsDeveloperImage) {
158 EXPECT_FALSE(IsDeveloperImage());
159
160 ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kLeaveCoreFile), ""));
161 EXPECT_TRUE(IsDeveloperImage());
162
163 ASSERT_TRUE(
164 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
165 paths::kCrashTestInProgress),
166 ""));
167 EXPECT_FALSE(IsDeveloperImage());
168}
169
Satoru Takabayashif6a36802018-08-14 16:23:05 +0900170TEST_F(CrashCommonUtilTest, IsTestImage) {
171 EXPECT_FALSE(IsTestImage());
172
173 // Should return false because the channel is stable.
174 ASSERT_TRUE(test_util::CreateFile(
175 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
176 "CHROMEOS_RELEASE_TRACK=stable-channel"));
177 EXPECT_FALSE(IsTestImage());
178
179 // Should return true because the channel is testimage.
180 ASSERT_TRUE(test_util::CreateFile(
181 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
182 "CHROMEOS_RELEASE_TRACK=testimage-channel"));
183 EXPECT_TRUE(IsTestImage());
184
185 // Should return false if kCrashTestInProgress is present.
186 ASSERT_TRUE(
187 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
188 paths::kCrashTestInProgress),
189 ""));
190 EXPECT_FALSE(IsTestImage());
191}
192
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900193TEST_F(CrashCommonUtilTest, IsOfficialImage) {
194 EXPECT_FALSE(IsOfficialImage());
195
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900196 // Check if lsb-release is handled correctly.
197 ASSERT_TRUE(test_util::CreateFile(
198 paths::Get("/etc/lsb-release"),
199 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Test Build) developer-build"));
200 EXPECT_FALSE(IsOfficialImage());
201
202 ASSERT_TRUE(test_util::CreateFile(
203 paths::Get("/etc/lsb-release"),
204 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Official Build) canary-channel"));
205 EXPECT_TRUE(IsOfficialImage());
206}
207
Ian Barkley-Yeungb12d6cc2020-02-12 14:00:54 -0800208TEST_F(CrashCommonUtilTest, HasMockConsent) {
Miriam Zimmerman3c951162020-03-18 09:39:21 -0700209 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
210 "CHROMEOS_RELEASE_TRACK=testimage-channel\n"
211 "CHROMEOS_RELEASE_DESCRIPTION=12985.0.0 "
212 "(Official Build) dev-channel asuka test"));
Ian Barkley-Yeungb12d6cc2020-02-12 14:00:54 -0800213 EXPECT_FALSE(HasMockConsent());
214 ASSERT_TRUE(test_util::CreateFile(
215 paths::GetAt(paths::kSystemRunStateDirectory, paths::kMockConsent), ""));
216 EXPECT_TRUE(HasMockConsent());
217}
218
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700219TEST_F(CrashCommonUtilTest, GetOsTimestamp) {
220 // If we can't read /etc/lsb-release then we should be returning the null
221 // time.
222 EXPECT_TRUE(util::GetOsTimestamp().is_null());
223
224 base::FilePath lsb_file_path = paths::Get("/etc/lsb-release");
225 ASSERT_TRUE(test_util::CreateFile(lsb_file_path, "foo=bar"));
226 base::Time old_time = base::Time::Now() - base::TimeDelta::FromDays(366);
227 ASSERT_TRUE(base::TouchFile(lsb_file_path, old_time, old_time));
228 // ext2/ext3 seem to have a timestamp granularity of 1s.
229 EXPECT_EQ(util::GetOsTimestamp().ToTimeVal().tv_sec,
230 old_time.ToTimeVal().tv_sec);
231}
232
233TEST_F(CrashCommonUtilTest, IsOsTimestampTooOldForUploads) {
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700234 base::SimpleTestClock clock;
235 const base::Time now = test_util::GetDefaultTime();
236 clock.SetNow(now);
237
238 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(base::Time(), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700239 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700240 now - base::TimeDelta::FromDays(179), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700241 EXPECT_TRUE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700242 now - base::TimeDelta::FromDays(181), &clock));
Miriam Zimmerman36042862019-11-14 20:01:29 -0800243
244 // Crashes with invalid timestamps should upload.
245 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
246 now + base::TimeDelta::FromDays(1), &clock));
247 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
248 base::Time::FromTimeT(std::numeric_limits<time_t>::min()), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700249}
250
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700251TEST_F(CrashCommonUtilTest, GetHardwareClass) {
252 EXPECT_EQ("undefined", GetHardwareClass());
253
254 ASSERT_TRUE(test_util::CreateFile(
255 paths::Get("/sys/devices/platform/chromeos_acpi/HWID"),
256 kHwClassContents));
257 EXPECT_EQ(kHwClassContents, GetHardwareClass());
258}
259
260TEST_F(CrashCommonUtilTest, GetBootModeString) {
261 EXPECT_EQ("missing-crossystem", GetBootModeString());
262
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700263 ASSERT_TRUE(
264 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
265 paths::kCrashTestInProgress),
266 ""));
267 EXPECT_EQ("", GetBootModeString());
268}
269
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900270TEST_F(CrashCommonUtilTest, GetCachedKeyValue) {
271 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
272 kLsbReleaseContents));
273 ASSERT_TRUE(test_util::CreateFile(paths::Get("/empty/lsb-release"), ""));
274
275 std::string value;
276 // No directories are specified.
277 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
278 "CHROMEOS_RELEASE_VERSION", {}, &value));
279 // A non-existent directory is specified.
280 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
281 "CHROMEOS_RELEASE_VERSION",
282 {paths::Get("/non-existent")}, &value));
283
284 // A non-existent base name is specified.
285 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("non-existent"),
286 "CHROMEOS_RELEASE_VERSION",
287 {paths::Get("/etc")}, &value));
288
289 // A wrong key is specified.
290 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"), "WRONG_KEY",
291 {paths::Get("/etc")}, &value));
292
293 // This should succeed.
294 EXPECT_TRUE(GetCachedKeyValue(base::FilePath("lsb-release"),
295 "CHROMEOS_RELEASE_VERSION",
296 {paths::Get("/etc")}, &value));
297 EXPECT_EQ("10964.0.2018_08_13_1405", value);
298
299 // A non-existent directory is included, but this should still succeed.
300 EXPECT_TRUE(GetCachedKeyValue(
301 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
302 {paths::Get("/non-existent"), paths::Get("/etc")}, &value));
303 EXPECT_EQ("10964.0.2018_08_13_1405", value);
304
305 // A empty file is included, but this should still succeed.
306 EXPECT_TRUE(GetCachedKeyValue(
307 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
308 {paths::Get("/empty"), paths::Get("/etc")}, &value));
309 EXPECT_EQ("10964.0.2018_08_13_1405", value);
310}
311
312TEST_F(CrashCommonUtilTest, GetCachedKeyValueDefault) {
313 std::string value;
314 EXPECT_FALSE(
315 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
316
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700317 // kEtcDirectory is the second candidate directory.
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900318 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700319 paths::GetAt(paths::kEtcDirectory, "test.txt"), "FOO=2\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900320 EXPECT_TRUE(
321 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
322 EXPECT_EQ("2", value);
323
324 // kCrashReporterStateDirectory is the first candidate directory.
325 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700326 paths::GetAt(paths::kCrashReporterStateDirectory, "test.txt"),
327 "FOO=1\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900328 EXPECT_TRUE(
329 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
330 EXPECT_EQ("1", value);
331}
332
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900333TEST_F(CrashCommonUtilTest, GetUserCrashDirectories) {
334 auto mock =
335 std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
336
337 std::vector<base::FilePath> directories;
338
339 test_util::SetActiveSessions(mock.get(), {});
340 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
341 EXPECT_TRUE(directories.empty());
342
343 test_util::SetActiveSessions(mock.get(),
344 {{"user1", "hash1"}, {"user2", "hash2"}});
345 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
346 EXPECT_EQ(2, directories.size());
Satoru Takabayashib09f7052018-10-01 15:26:29 +0900347 EXPECT_EQ(paths::Get("/home/user/hash1/crash").value(),
348 directories[0].value());
349 EXPECT_EQ(paths::Get("/home/user/hash2/crash").value(),
350 directories[1].value());
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900351}
352
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700353TEST_F(CrashCommonUtilTest, GzipStream) {
354 std::string content = CreateSemiRandomString(
355 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700356 std::vector<unsigned char> compressed_content =
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700357 util::GzipStream(brillo::MemoryStream::OpenCopyOf(
358 content.c_str(), content.length(), nullptr));
359 EXPECT_FALSE(compressed_content.empty());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700360 EXPECT_LT(compressed_content.size(), content.size())
361 << "Didn't actually compress";
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700362 base::FilePath raw_file;
363 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &raw_file));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700364 base::FilePath compressed_file_name;
365 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &compressed_file_name));
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700366 // Remove the file we will decompress to or gzip will fail on decompression.
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700367 ASSERT_TRUE(base::DeleteFile(compressed_file_name, false));
368 compressed_file_name = compressed_file_name.AddExtension(".gz");
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700369 ASSERT_EQ(base::WriteFile(raw_file, content.c_str(), content.length()),
370 content.length());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700371 {
372 base::File compressed_file(
373 compressed_file_name, base::File::FLAG_WRITE | base::File::FLAG_CREATE);
374 ASSERT_TRUE(compressed_file.IsValid());
375 ssize_t write_result = HANDLE_EINTR(write(compressed_file.GetPlatformFile(),
376 compressed_content.data(),
377 compressed_content.size()));
378 ASSERT_EQ(write_result, compressed_content.size());
379 }
380 EXPECT_TRUE(VerifyCompression(raw_file, compressed_file_name))
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700381 << "Random input data: " << content;
382}
383
Tim Zheng11a665e2019-06-26 17:44:01 -0700384TEST_F(CrashCommonUtilTest,
385 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringEmpty)) {
386 int memfd = memfd_create("test_memfd", 0);
387 std::string read_outs;
388 EXPECT_FALSE(ReadMemfdToString(memfd, &read_outs));
389}
390
391TEST_F(CrashCommonUtilTest,
392 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringSuccess)) {
393 int memfd = memfd_create("test_memfd", 0);
394 const std::string write_ins = "Test data to write into memfd";
395 ASSERT_EQ(write(memfd, write_ins.c_str(), strlen(write_ins.c_str())),
396 strlen(write_ins.c_str()));
397 std::string read_outs;
398 EXPECT_TRUE(ReadMemfdToString(memfd, &read_outs));
399 EXPECT_EQ(read_outs, write_ins);
400}
401
Kansho Nishida630cc7a2019-10-23 17:37:41 +0900402TEST_F(CrashCommonUtilTest, ReadFdToStream) {
403 std::stringstream stream;
404 EXPECT_TRUE(ReadFdToStream(fd_, &stream));
405 EXPECT_EQ(kReadFdToStreamContents, stream.str());
406}
407
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900408} // namespace util