blob: 5603c5d3f5b58c8ccd5629b39d0c3973da46eef5 [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>
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070019#include <brillo/process.h>
20#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
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700208TEST_F(CrashCommonUtilTest, GetOsTimestamp) {
209 // If we can't read /etc/lsb-release then we should be returning the null
210 // time.
211 EXPECT_TRUE(util::GetOsTimestamp().is_null());
212
213 base::FilePath lsb_file_path = paths::Get("/etc/lsb-release");
214 ASSERT_TRUE(test_util::CreateFile(lsb_file_path, "foo=bar"));
215 base::Time old_time = base::Time::Now() - base::TimeDelta::FromDays(366);
216 ASSERT_TRUE(base::TouchFile(lsb_file_path, old_time, old_time));
217 // ext2/ext3 seem to have a timestamp granularity of 1s.
218 EXPECT_EQ(util::GetOsTimestamp().ToTimeVal().tv_sec,
219 old_time.ToTimeVal().tv_sec);
220}
221
222TEST_F(CrashCommonUtilTest, IsOsTimestampTooOldForUploads) {
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700223 base::SimpleTestClock clock;
224 const base::Time now = test_util::GetDefaultTime();
225 clock.SetNow(now);
226
227 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(base::Time(), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700228 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700229 now - base::TimeDelta::FromDays(179), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700230 EXPECT_TRUE(util::IsOsTimestampTooOldForUploads(
Ian Barkley-Yeungc377b092019-10-09 19:23:53 -0700231 now - base::TimeDelta::FromDays(181), &clock));
Miriam Zimmerman36042862019-11-14 20:01:29 -0800232
233 // Crashes with invalid timestamps should upload.
234 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
235 now + base::TimeDelta::FromDays(1), &clock));
236 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
237 base::Time::FromTimeT(std::numeric_limits<time_t>::min()), &clock));
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700238}
239
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700240TEST_F(CrashCommonUtilTest, GetHardwareClass) {
241 EXPECT_EQ("undefined", GetHardwareClass());
242
243 ASSERT_TRUE(test_util::CreateFile(
244 paths::Get("/sys/devices/platform/chromeos_acpi/HWID"),
245 kHwClassContents));
246 EXPECT_EQ(kHwClassContents, GetHardwareClass());
247}
248
249TEST_F(CrashCommonUtilTest, GetBootModeString) {
250 EXPECT_EQ("missing-crossystem", GetBootModeString());
251
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700252 ASSERT_TRUE(
253 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
254 paths::kCrashTestInProgress),
255 ""));
256 EXPECT_EQ("", GetBootModeString());
257}
258
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900259TEST_F(CrashCommonUtilTest, GetCachedKeyValue) {
260 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
261 kLsbReleaseContents));
262 ASSERT_TRUE(test_util::CreateFile(paths::Get("/empty/lsb-release"), ""));
263
264 std::string value;
265 // No directories are specified.
266 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
267 "CHROMEOS_RELEASE_VERSION", {}, &value));
268 // A non-existent directory is specified.
269 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
270 "CHROMEOS_RELEASE_VERSION",
271 {paths::Get("/non-existent")}, &value));
272
273 // A non-existent base name is specified.
274 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("non-existent"),
275 "CHROMEOS_RELEASE_VERSION",
276 {paths::Get("/etc")}, &value));
277
278 // A wrong key is specified.
279 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"), "WRONG_KEY",
280 {paths::Get("/etc")}, &value));
281
282 // This should succeed.
283 EXPECT_TRUE(GetCachedKeyValue(base::FilePath("lsb-release"),
284 "CHROMEOS_RELEASE_VERSION",
285 {paths::Get("/etc")}, &value));
286 EXPECT_EQ("10964.0.2018_08_13_1405", value);
287
288 // A non-existent directory is included, but this should still succeed.
289 EXPECT_TRUE(GetCachedKeyValue(
290 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
291 {paths::Get("/non-existent"), paths::Get("/etc")}, &value));
292 EXPECT_EQ("10964.0.2018_08_13_1405", value);
293
294 // A empty file is included, but this should still succeed.
295 EXPECT_TRUE(GetCachedKeyValue(
296 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
297 {paths::Get("/empty"), paths::Get("/etc")}, &value));
298 EXPECT_EQ("10964.0.2018_08_13_1405", value);
299}
300
301TEST_F(CrashCommonUtilTest, GetCachedKeyValueDefault) {
302 std::string value;
303 EXPECT_FALSE(
304 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
305
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700306 // kEtcDirectory is the second candidate directory.
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900307 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700308 paths::GetAt(paths::kEtcDirectory, "test.txt"), "FOO=2\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900309 EXPECT_TRUE(
310 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
311 EXPECT_EQ("2", value);
312
313 // kCrashReporterStateDirectory is the first candidate directory.
314 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700315 paths::GetAt(paths::kCrashReporterStateDirectory, "test.txt"),
316 "FOO=1\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900317 EXPECT_TRUE(
318 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
319 EXPECT_EQ("1", value);
320}
321
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900322TEST_F(CrashCommonUtilTest, GetUserCrashDirectories) {
323 auto mock =
324 std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
325
326 std::vector<base::FilePath> directories;
327
328 test_util::SetActiveSessions(mock.get(), {});
329 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
330 EXPECT_TRUE(directories.empty());
331
332 test_util::SetActiveSessions(mock.get(),
333 {{"user1", "hash1"}, {"user2", "hash2"}});
334 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
335 EXPECT_EQ(2, directories.size());
Satoru Takabayashib09f7052018-10-01 15:26:29 +0900336 EXPECT_EQ(paths::Get("/home/user/hash1/crash").value(),
337 directories[0].value());
338 EXPECT_EQ(paths::Get("/home/user/hash2/crash").value(),
339 directories[1].value());
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900340}
341
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700342TEST_F(CrashCommonUtilTest, GzipStream) {
343 std::string content = CreateSemiRandomString(
344 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700345 std::vector<unsigned char> compressed_content =
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700346 util::GzipStream(brillo::MemoryStream::OpenCopyOf(
347 content.c_str(), content.length(), nullptr));
348 EXPECT_FALSE(compressed_content.empty());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700349 EXPECT_LT(compressed_content.size(), content.size())
350 << "Didn't actually compress";
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700351 base::FilePath raw_file;
352 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &raw_file));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700353 base::FilePath compressed_file_name;
354 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &compressed_file_name));
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700355 // Remove the file we will decompress to or gzip will fail on decompression.
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700356 ASSERT_TRUE(base::DeleteFile(compressed_file_name, false));
357 compressed_file_name = compressed_file_name.AddExtension(".gz");
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700358 ASSERT_EQ(base::WriteFile(raw_file, content.c_str(), content.length()),
359 content.length());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700360 {
361 base::File compressed_file(
362 compressed_file_name, base::File::FLAG_WRITE | base::File::FLAG_CREATE);
363 ASSERT_TRUE(compressed_file.IsValid());
364 ssize_t write_result = HANDLE_EINTR(write(compressed_file.GetPlatformFile(),
365 compressed_content.data(),
366 compressed_content.size()));
367 ASSERT_EQ(write_result, compressed_content.size());
368 }
369 EXPECT_TRUE(VerifyCompression(raw_file, compressed_file_name))
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700370 << "Random input data: " << content;
371}
372
Tim Zheng11a665e2019-06-26 17:44:01 -0700373TEST_F(CrashCommonUtilTest,
374 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringEmpty)) {
375 int memfd = memfd_create("test_memfd", 0);
376 std::string read_outs;
377 EXPECT_FALSE(ReadMemfdToString(memfd, &read_outs));
378}
379
380TEST_F(CrashCommonUtilTest,
381 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringSuccess)) {
382 int memfd = memfd_create("test_memfd", 0);
383 const std::string write_ins = "Test data to write into memfd";
384 ASSERT_EQ(write(memfd, write_ins.c_str(), strlen(write_ins.c_str())),
385 strlen(write_ins.c_str()));
386 std::string read_outs;
387 EXPECT_TRUE(ReadMemfdToString(memfd, &read_outs));
388 EXPECT_EQ(read_outs, write_ins);
389}
390
Kansho Nishida630cc7a2019-10-23 17:37:41 +0900391TEST_F(CrashCommonUtilTest, ReadFdToStream) {
392 std::stringstream stream;
393 EXPECT_TRUE(ReadFdToStream(fd_, &stream));
394 EXPECT_EQ(kReadFdToStreamContents, stream.str());
395}
396
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900397} // namespace util