blob: c2b9876e9f5adc66a3ce2de1e40fd0a8844026a2 [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>
15#include <brillo/process.h>
16#include <brillo/streams/memory_stream.h>
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090017#include <gtest/gtest.h>
18
19#include "crash-reporter/crash_sender_paths.h"
20#include "crash-reporter/paths.h"
21#include "crash-reporter/test_util.h"
22
Tim Zheng11a665e2019-06-26 17:44:01 -070023// The QEMU emulator we use to run unit tests on simulated ARM boards does not
24// support memfd_create. (https://bugs.launchpad.net/qemu/+bug/1734792) Skip
25// tests that rely on memfd_create on ARM boards.
26#if defined(ARCH_CPU_ARM_FAMILY)
27#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) DISABLED_##test_name
28#else
29#define DISABLED_ON_QEMU_FOR_MEMFD_CREATE(test_name) test_name
30#endif
31
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +090032namespace util {
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090033namespace {
34
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070035constexpr char kLsbReleaseContents[] =
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +090036 "CHROMEOS_RELEASE_BOARD=bob\n"
37 "CHROMEOS_RELEASE_NAME=Chromium OS\n"
38 "CHROMEOS_RELEASE_VERSION=10964.0.2018_08_13_1405\n";
39
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -070040constexpr char kHwClassContents[] = "fake_hwclass";
41
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -070042constexpr char kGzipPath[] = "/bin/gzip";
43
44constexpr char kSemiRandomData[] =
45 "ABJCI239AJSDLKJ;kalkjkjsd98723;KJHASD87;kqw3p088ad;lKJASDP823;KJ";
46constexpr int kRandomDataMinLength = 32768; // 32kB
47constexpr int kRandomDataMaxLength = 262144; // 256kB
48
49// Verifies that |raw_file| corresponds to the gzip'd version of
50// |compressed_file| by decompressing it and comparing the contents. Returns
51// true if they match, false otherwise. This will overwrite the contents of
52// |compressed_file| in the process of doing this.
53bool VerifyCompression(const base::FilePath& raw_file,
54 const base::FilePath& compressed_file) {
55 if (!base::PathExists(raw_file)) {
56 LOG(ERROR) << "raw_file doesn't exist for verifying compression: "
57 << raw_file.value();
58 return false;
59 }
60 if (!base::PathExists(compressed_file)) {
61 LOG(ERROR) << "compressed_file doesn't exist for verifying compression: "
62 << compressed_file.value();
63 return false;
64 }
65 brillo::ProcessImpl proc;
66 proc.AddArg(kGzipPath);
67 proc.AddArg("-d"); // decompress
68 proc.AddArg(compressed_file.value());
69 std::string error;
70 const int res = util::RunAndCaptureOutput(&proc, STDERR_FILENO, &error);
71 if (res < 0) {
72 PLOG(ERROR) << "Failed to execute gzip";
73 return false;
74 }
75 if (res != 0) {
76 LOG(ERROR) << "Failed to un-gzip " << compressed_file.value();
77 util::LogMultilineError(error);
78 return false;
79 }
80 base::FilePath uncompressed_file = compressed_file.RemoveFinalExtension();
81 std::string raw_contents;
82 std::string uncompressed_contents;
83 if (!base::ReadFileToString(raw_file, &raw_contents)) {
84 LOG(ERROR) << "Failed reading in raw_file " << raw_file.value();
85 return false;
86 }
87 if (!base::ReadFileToString(uncompressed_file, &uncompressed_contents)) {
88 LOG(ERROR) << "Failed reading in uncompressed_file "
89 << uncompressed_file.value();
90 return false;
91 }
92 return raw_contents == uncompressed_contents;
93}
94
95// We use a somewhat random string of ASCII data to better reflect the data we
96// would be compressing for real. We also shouldn't use something like
97// base::RandBytesAsString() because that will generate uniformly random data
98// which does not compress.
99std::string CreateSemiRandomString(size_t size) {
100 std::string result;
101 result.reserve(size);
102 while (result.length() < size) {
103 int rem = size - result.length();
104 if (rem > sizeof(kSemiRandomData) - 1)
105 rem = sizeof(kSemiRandomData) - 1;
106 int rand_start = base::RandInt(0, rem - 1);
107 int rand_end = base::RandInt(rand_start + 1, rem);
108 result.append(&kSemiRandomData[rand_start], rand_end - rand_start);
109 }
110 return result;
111}
112
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900113} // namespace
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900114
115class CrashCommonUtilTest : public testing::Test {
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700116 protected:
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900117 void SetUp() override {
118 ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
119 test_dir_ = scoped_temp_dir_.GetPath();
120 paths::SetPrefixForTesting(test_dir_);
121 }
122
123 void TearDown() override { paths::SetPrefixForTesting(base::FilePath()); }
124
125 base::FilePath test_dir_;
126 base::ScopedTempDir scoped_temp_dir_;
127};
128
129TEST_F(CrashCommonUtilTest, IsCrashTestInProgress) {
130 EXPECT_FALSE(IsCrashTestInProgress());
131 ASSERT_TRUE(
132 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
133 paths::kCrashTestInProgress),
134 ""));
135 EXPECT_TRUE(IsCrashTestInProgress());
136}
137
Satoru Takabayashi2d728042018-12-10 09:19:00 +0900138TEST_F(CrashCommonUtilTest, IsDeviceCoredumpUploadAllowed) {
139 EXPECT_FALSE(IsDeviceCoredumpUploadAllowed());
140 ASSERT_TRUE(
141 test_util::CreateFile(paths::GetAt(paths::kCrashReporterStateDirectory,
142 paths::kDeviceCoredumpUploadAllowed),
143 ""));
144 EXPECT_TRUE(IsDeviceCoredumpUploadAllowed());
145}
146
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900147TEST_F(CrashCommonUtilTest, IsDeveloperImage) {
148 EXPECT_FALSE(IsDeveloperImage());
149
150 ASSERT_TRUE(test_util::CreateFile(paths::Get(paths::kLeaveCoreFile), ""));
151 EXPECT_TRUE(IsDeveloperImage());
152
153 ASSERT_TRUE(
154 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
155 paths::kCrashTestInProgress),
156 ""));
157 EXPECT_FALSE(IsDeveloperImage());
158}
159
Satoru Takabayashif6a36802018-08-14 16:23:05 +0900160TEST_F(CrashCommonUtilTest, IsTestImage) {
161 EXPECT_FALSE(IsTestImage());
162
163 // Should return false because the channel is stable.
164 ASSERT_TRUE(test_util::CreateFile(
165 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
166 "CHROMEOS_RELEASE_TRACK=stable-channel"));
167 EXPECT_FALSE(IsTestImage());
168
169 // Should return true because the channel is testimage.
170 ASSERT_TRUE(test_util::CreateFile(
171 paths::GetAt(paths::kEtcDirectory, paths::kLsbRelease),
172 "CHROMEOS_RELEASE_TRACK=testimage-channel"));
173 EXPECT_TRUE(IsTestImage());
174
175 // Should return false if kCrashTestInProgress is present.
176 ASSERT_TRUE(
177 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
178 paths::kCrashTestInProgress),
179 ""));
180 EXPECT_FALSE(IsTestImage());
181}
182
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900183TEST_F(CrashCommonUtilTest, IsOfficialImage) {
184 EXPECT_FALSE(IsOfficialImage());
185
Satoru Takabayashi9a587522018-10-29 09:36:27 +0900186 // Check if lsb-release is handled correctly.
187 ASSERT_TRUE(test_util::CreateFile(
188 paths::Get("/etc/lsb-release"),
189 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Test Build) developer-build"));
190 EXPECT_FALSE(IsOfficialImage());
191
192 ASSERT_TRUE(test_util::CreateFile(
193 paths::Get("/etc/lsb-release"),
194 "CHROMEOS_RELEASE_DESCRIPTION=10964.0 (Official Build) canary-channel"));
195 EXPECT_TRUE(IsOfficialImage());
196}
197
Jeffrey Kardatzkee3fb8fb2019-05-13 13:59:12 -0700198TEST_F(CrashCommonUtilTest, GetOsTimestamp) {
199 // If we can't read /etc/lsb-release then we should be returning the null
200 // time.
201 EXPECT_TRUE(util::GetOsTimestamp().is_null());
202
203 base::FilePath lsb_file_path = paths::Get("/etc/lsb-release");
204 ASSERT_TRUE(test_util::CreateFile(lsb_file_path, "foo=bar"));
205 base::Time old_time = base::Time::Now() - base::TimeDelta::FromDays(366);
206 ASSERT_TRUE(base::TouchFile(lsb_file_path, old_time, old_time));
207 // ext2/ext3 seem to have a timestamp granularity of 1s.
208 EXPECT_EQ(util::GetOsTimestamp().ToTimeVal().tv_sec,
209 old_time.ToTimeVal().tv_sec);
210}
211
212TEST_F(CrashCommonUtilTest, IsOsTimestampTooOldForUploads) {
213 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(base::Time()));
214 EXPECT_FALSE(util::IsOsTimestampTooOldForUploads(
215 base::Time::Now() - base::TimeDelta::FromDays(179)));
216 EXPECT_TRUE(util::IsOsTimestampTooOldForUploads(
217 base::Time::Now() - base::TimeDelta::FromDays(181)));
218}
219
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700220TEST_F(CrashCommonUtilTest, GetHardwareClass) {
221 EXPECT_EQ("undefined", GetHardwareClass());
222
223 ASSERT_TRUE(test_util::CreateFile(
224 paths::Get("/sys/devices/platform/chromeos_acpi/HWID"),
225 kHwClassContents));
226 EXPECT_EQ(kHwClassContents, GetHardwareClass());
227}
228
229TEST_F(CrashCommonUtilTest, GetBootModeString) {
230 EXPECT_EQ("missing-crossystem", GetBootModeString());
231
Jeffrey Kardatzkeea333932019-04-12 10:17:51 -0700232 ASSERT_TRUE(
233 test_util::CreateFile(paths::GetAt(paths::kSystemRunStateDirectory,
234 paths::kCrashTestInProgress),
235 ""));
236 EXPECT_EQ("", GetBootModeString());
237}
238
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900239TEST_F(CrashCommonUtilTest, GetCachedKeyValue) {
240 ASSERT_TRUE(test_util::CreateFile(paths::Get("/etc/lsb-release"),
241 kLsbReleaseContents));
242 ASSERT_TRUE(test_util::CreateFile(paths::Get("/empty/lsb-release"), ""));
243
244 std::string value;
245 // No directories are specified.
246 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
247 "CHROMEOS_RELEASE_VERSION", {}, &value));
248 // A non-existent directory is specified.
249 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"),
250 "CHROMEOS_RELEASE_VERSION",
251 {paths::Get("/non-existent")}, &value));
252
253 // A non-existent base name is specified.
254 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("non-existent"),
255 "CHROMEOS_RELEASE_VERSION",
256 {paths::Get("/etc")}, &value));
257
258 // A wrong key is specified.
259 EXPECT_FALSE(GetCachedKeyValue(base::FilePath("lsb-release"), "WRONG_KEY",
260 {paths::Get("/etc")}, &value));
261
262 // This should succeed.
263 EXPECT_TRUE(GetCachedKeyValue(base::FilePath("lsb-release"),
264 "CHROMEOS_RELEASE_VERSION",
265 {paths::Get("/etc")}, &value));
266 EXPECT_EQ("10964.0.2018_08_13_1405", value);
267
268 // A non-existent directory is included, but this should still succeed.
269 EXPECT_TRUE(GetCachedKeyValue(
270 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
271 {paths::Get("/non-existent"), paths::Get("/etc")}, &value));
272 EXPECT_EQ("10964.0.2018_08_13_1405", value);
273
274 // A empty file is included, but this should still succeed.
275 EXPECT_TRUE(GetCachedKeyValue(
276 base::FilePath("lsb-release"), "CHROMEOS_RELEASE_VERSION",
277 {paths::Get("/empty"), paths::Get("/etc")}, &value));
278 EXPECT_EQ("10964.0.2018_08_13_1405", value);
279}
280
281TEST_F(CrashCommonUtilTest, GetCachedKeyValueDefault) {
282 std::string value;
283 EXPECT_FALSE(
284 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
285
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700286 // kEtcDirectory is the second candidate directory.
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900287 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700288 paths::GetAt(paths::kEtcDirectory, "test.txt"), "FOO=2\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900289 EXPECT_TRUE(
290 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
291 EXPECT_EQ("2", value);
292
293 // kCrashReporterStateDirectory is the first candidate directory.
294 ASSERT_TRUE(test_util::CreateFile(
Jeffrey Kardatzke8ed15d12019-03-21 16:57:20 -0700295 paths::GetAt(paths::kCrashReporterStateDirectory, "test.txt"),
296 "FOO=1\n"));
Satoru Takabayashib2ca40d2018-08-09 14:02:04 +0900297 EXPECT_TRUE(
298 GetCachedKeyValueDefault(base::FilePath("test.txt"), "FOO", &value));
299 EXPECT_EQ("1", value);
300}
301
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900302TEST_F(CrashCommonUtilTest, GetUserCrashDirectories) {
303 auto mock =
304 std::make_unique<org::chromium::SessionManagerInterfaceProxyMock>();
305
306 std::vector<base::FilePath> directories;
307
308 test_util::SetActiveSessions(mock.get(), {});
309 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
310 EXPECT_TRUE(directories.empty());
311
312 test_util::SetActiveSessions(mock.get(),
313 {{"user1", "hash1"}, {"user2", "hash2"}});
314 EXPECT_TRUE(GetUserCrashDirectories(mock.get(), &directories));
315 EXPECT_EQ(2, directories.size());
Satoru Takabayashib09f7052018-10-01 15:26:29 +0900316 EXPECT_EQ(paths::Get("/home/user/hash1/crash").value(),
317 directories[0].value());
318 EXPECT_EQ(paths::Get("/home/user/hash2/crash").value(),
319 directories[1].value());
Satoru Takabayashi8ce6db82018-08-17 15:18:41 +0900320}
321
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700322TEST_F(CrashCommonUtilTest, GzipStream) {
323 std::string content = CreateSemiRandomString(
324 base::RandInt(kRandomDataMinLength, kRandomDataMaxLength));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700325 std::vector<unsigned char> compressed_content =
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700326 util::GzipStream(brillo::MemoryStream::OpenCopyOf(
327 content.c_str(), content.length(), nullptr));
328 EXPECT_FALSE(compressed_content.empty());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700329 EXPECT_LT(compressed_content.size(), content.size())
330 << "Didn't actually compress";
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700331 base::FilePath raw_file;
332 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &raw_file));
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700333 base::FilePath compressed_file_name;
334 ASSERT_TRUE(base::CreateTemporaryFileInDir(test_dir_, &compressed_file_name));
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700335 // Remove the file we will decompress to or gzip will fail on decompression.
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700336 ASSERT_TRUE(base::DeleteFile(compressed_file_name, false));
337 compressed_file_name = compressed_file_name.AddExtension(".gz");
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700338 ASSERT_EQ(base::WriteFile(raw_file, content.c_str(), content.length()),
339 content.length());
Ian Barkley-Yeunge87b4f42019-05-22 15:05:13 -0700340 {
341 base::File compressed_file(
342 compressed_file_name, base::File::FLAG_WRITE | base::File::FLAG_CREATE);
343 ASSERT_TRUE(compressed_file.IsValid());
344 ssize_t write_result = HANDLE_EINTR(write(compressed_file.GetPlatformFile(),
345 compressed_content.data(),
346 compressed_content.size()));
347 ASSERT_EQ(write_result, compressed_content.size());
348 }
349 EXPECT_TRUE(VerifyCompression(raw_file, compressed_file_name))
Jeffrey Kardatzke437fa922019-05-09 11:34:32 -0700350 << "Random input data: " << content;
351}
352
Tim Zheng11a665e2019-06-26 17:44:01 -0700353TEST_F(CrashCommonUtilTest,
354 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringEmpty)) {
355 int memfd = memfd_create("test_memfd", 0);
356 std::string read_outs;
357 EXPECT_FALSE(ReadMemfdToString(memfd, &read_outs));
358}
359
360TEST_F(CrashCommonUtilTest,
361 DISABLED_ON_QEMU_FOR_MEMFD_CREATE(ReadMemfdToStringSuccess)) {
362 int memfd = memfd_create("test_memfd", 0);
363 const std::string write_ins = "Test data to write into memfd";
364 ASSERT_EQ(write(memfd, write_ins.c_str(), strlen(write_ins.c_str())),
365 strlen(write_ins.c_str()));
366 std::string read_outs;
367 EXPECT_TRUE(ReadMemfdToString(memfd, &read_outs));
368 EXPECT_EQ(read_outs, write_ins);
369}
370
Satoru Takabayashie7f6d2a2018-08-08 17:06:29 +0900371} // namespace util