blob: 2c543c94b324869b93d1813d4889a0592a41b828 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/cksum.h>
#include <stdint.h>
#include <zircon/boot/crash-reason.h>
#include <zircon/errors.h>
#include <array>
#include <vector>
#include <ram-crashlog/ram-crashlog.h>
#include <zxtest/zxtest.h>
namespace {
// The buffer we will use as our target for serializing the crashlog during testing.
uint8_t crashlog_buffer[256];
constexpr uint32_t TEST_PAYLOAD_MAX = sizeof(crashlog_buffer) - sizeof(ram_crashlog_t);
constexpr uint8_t TEST_PAYLOAD_FILL = 0xA5;
static_assert(sizeof(crashlog_buffer) > sizeof(ram_crashlog_t),
"Test buffer must be able to hold _some_ payload");
static_assert(TEST_PAYLOAD_MAX >= 2,
"Test buffer must be able to hold a payload of at least 2 bytes");
constexpr const char LONG_PAYLOAD[] =
"Four score and seven years ago our fathers brought forth on this continent, a new nation, "
"conceived in Liberty, and dedicated to the proposition that all men are created equal. Now "
"we are engaged in a great civil war, testing whether that nation, or any nation so conceived "
"and so dedicated, can long endure. We are met on a great battle-field of that war. We have "
"come to dedicate a portion of that field, as a final resting place for those who here gave "
"their lives that that nation might live. It is altogether fitting and proper that we should "
"do this. But, in a larger sense, we can not dedicate—we can not consecrate—we can not "
"hallow—this ground. The brave men, living and dead, who struggled here, have consecrated it, "
"far above our poor power to add or detract. The world will little note, nor long remember "
"what we say here, but it can never forget what they did here. It is for us the living, "
"rather, to be dedicated here to the unfinished work which they who fought here have thus far "
"so nobly advanced. It is rather for us to be here dedicated to the great task remaining "
"before us—that from these honored dead we take increased devotion to that cause for which "
"they gave the last full measure of devotion—that we here highly resolve that these dead shall "
"not have died in vain—that this nation, under God, shall have a new birth of freedom—and that "
"government of the people, by the people, for the people, shall not perish from the earth.";
constexpr uint32_t LONG_PAYLOAD_LEN = sizeof(LONG_PAYLOAD) - 1;
static_assert(LONG_PAYLOAD_LEN > TEST_PAYLOAD_MAX,
"Long payload must be longer than the max test payload");
// A constant test vector we use in several tests. This "crashlog" image
// contains two valid headers, and indicates hdr[0] as being the active header.
// Starting from this template, many types of tests can be constructed.
constexpr ram_crashlog_t TEST_LOG = {
.magic = RAM_CRASHLOG_MAGIC_0,
.hdr =
{
{
.uptime = 0xabcde,
.reason = ZirconCrashReason::Panic,
.payload_len = TEST_PAYLOAD_MAX,
.payload_crc32 = 0xaa0b6321, // CRC for a payload of all 0xA5s
.header_crc32 = 0xd8d492b1,
},
{
.uptime = 0x12345,
.reason = ZirconCrashReason::Unknown,
.payload_len = 0,
.payload_crc32 = 0x0,
.header_crc32 = 0xe082e1b7,
},
},
};
TEST(RamCrashlogTestCase, ValidBufferRequired) {
zx_status_t res = ram_crashlog_stow(nullptr, 0, nullptr, 0, ZirconCrashReason::Unknown, 0);
EXPECT_STATUS(res, ZX_ERR_INVALID_ARGS);
res = ram_crashlog_stow(crashlog_buffer, sizeof(crashlog_buffer), nullptr, 25,
ZirconCrashReason::Unknown, 0);
EXPECT_STATUS(res, ZX_ERR_INVALID_ARGS);
}
TEST(RamCrashlogTestCase, BufferTooSmall) {
// Attempt to stash a crashlog into a location which cannot possibly fit the
// internal header and verify that it informs us that our buffer is too small.
uint8_t tiny_buf[1];
zx_status_t res =
ram_crashlog_stow(tiny_buf, sizeof(tiny_buf), nullptr, 0, ZirconCrashReason::Unknown, 0);
EXPECT_STATUS(res, ZX_ERR_BUFFER_TOO_SMALL);
// Likewise, we cannot recover a crashlog from a user supplied buffer which is too small to
// possibly hold a crashlog.
recovered_ram_crashlog_t rlog;
res = ram_crashlog_recover(tiny_buf, sizeof(tiny_buf), &rlog);
EXPECT_STATUS(res, ZX_ERR_BUFFER_TOO_SMALL);
}
TEST(RamCrashlogTestCase, ValidReasonRequired) {
// When stowing a crashlog, require that the crash reason given is a valid one
// (even if the reason is unknown).
//
struct Reason {
ZirconCrashReason reason;
bool is_valid;
};
constexpr std::array REASONS{
Reason{ZirconCrashReason::Unknown, true},
Reason{ZirconCrashReason::NoCrash, true},
Reason{ZirconCrashReason::Oom, true},
Reason{ZirconCrashReason::Panic, true},
Reason{ZirconCrashReason::SoftwareWatchdog, true},
Reason{ZirconCrashReason::Invalid, false},
Reason{static_cast<ZirconCrashReason>(0xbaadf00d), false},
};
for (const auto& r : REASONS) {
zx_status_t res =
ram_crashlog_stow(crashlog_buffer, sizeof(crashlog_buffer), nullptr, 0, r.reason, 0);
EXPECT_STATUS(res, r.is_valid ? ZX_OK : ZX_ERR_INVALID_ARGS);
}
}
TEST(RamCrashlogTestCase, IntegrityChecks) {
// Start by using our test header template to simulate a crashlog stowed in
// RAM, and make sure that it passes the default integrity checks.
ram_crashlog_t& log = *(reinterpret_cast<ram_crashlog_t*>(crashlog_buffer));
uint8_t* payload = reinterpret_cast<uint8_t*>(&log + 1);
memset(crashlog_buffer, TEST_PAYLOAD_FILL, sizeof(crashlog_buffer));
log = TEST_LOG;
// Recover the log, and make sure that the recovered data matches what we
// expect. Right now, the magic number indicates that the active header is
// hdr[0].
recovered_ram_crashlog_t rlog;
zx_status_t res;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(TEST_LOG.hdr[0].uptime, rlog.uptime);
EXPECT_EQ(TEST_LOG.hdr[0].reason, rlog.reason);
EXPECT_EQ(TEST_LOG.hdr[0].payload_len, rlog.payload_len);
EXPECT_TRUE(rlog.payload_valid);
EXPECT_EQ(payload, rlog.payload);
// Corrupt the payload and verify that the log is still recoverable, but that
// it clearly indicates that the payload portion of the log may have been
// damaged.
payload[0] = ~payload[0];
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(TEST_LOG.hdr[0].uptime, rlog.uptime);
EXPECT_EQ(TEST_LOG.hdr[0].reason, rlog.reason);
EXPECT_EQ(TEST_LOG.hdr[0].payload_len, rlog.payload_len);
EXPECT_FALSE(rlog.payload_valid);
EXPECT_EQ(payload, rlog.payload);
// Fix the damage we just did to the payload, then update the payload length
// length in hdr[0] to be impossibly long (longer than the buffer we provide,
// once the fixed overhead is accounted for). Make sure to update the header
// CRC. Finally, attempt to recover the log. Again, this should succeed, but
// indicate that the payload may be damaged. Additionally, it should report a
// length which is equal to the space we have remaining in the crashlog buffer
// after the log headers.
payload[0] = TEST_PAYLOAD_FILL;
log.hdr[0].payload_len = sizeof(crashlog_buffer);
log.hdr[0].header_crc32 = crc32(0, reinterpret_cast<const uint8_t*>(&log.hdr[0]),
offsetof(ram_crashlog_header_t, header_crc32));
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(TEST_LOG.hdr[0].uptime, rlog.uptime);
EXPECT_EQ(TEST_LOG.hdr[0].reason, rlog.reason);
EXPECT_EQ(TEST_PAYLOAD_MAX, rlog.payload_len);
EXPECT_FALSE(rlog.payload_valid);
EXPECT_EQ(payload, rlog.payload);
// Corrupt the header by restoring the old payload length, but not updating
// the header CRC, then attempt to recover the log. This should simply fail.
// We cannot currently recover the log if the active header is damaged.
log.hdr[0].payload_len = TEST_LOG.hdr[0].payload_len;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
EXPECT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, res);
// Flip the magic number to indicate that the other header is active, and
// re-verify. This should succeed, even though we have corrupted hdr[0].
// This header should indicate a valid but zero-length payload.
log.magic = RAM_CRASHLOG_MAGIC_1;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(TEST_LOG.hdr[1].uptime, rlog.uptime);
EXPECT_EQ(TEST_LOG.hdr[1].reason, rlog.reason);
EXPECT_EQ(TEST_LOG.hdr[1].payload_len, rlog.payload_len);
EXPECT_TRUE(rlog.payload_valid);
EXPECT_NULL(rlog.payload);
// Corrupt the contents of hdr[1] and make sure that it fails to recover.
log.hdr[1].payload_len = 1;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
EXPECT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, res);
// Finally, fix all of the headers by going back to the template image, but
// corrupt the magic number so that we don't know which header (if any) is
// active. This should also produce a DATA_INTEGRITY error.
log = TEST_LOG;
log.magic = 0x0123456789ABCDEF;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
EXPECT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, res);
}
TEST(RamCrashlogTestCase, Stow) {
// Start with an invalid crashlog state (we just use a buffer full of 0s).
// Verify that this fails to recover.
ram_crashlog_t& log = *(reinterpret_cast<ram_crashlog_t*>(crashlog_buffer));
uint8_t* payload = reinterpret_cast<uint8_t*>(&log + 1);
memset(crashlog_buffer, 0, sizeof(crashlog_buffer));
recovered_ram_crashlog_t rlog;
zx_status_t res;
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
EXPECT_STATUS(ZX_ERR_IO_DATA_INTEGRITY, res);
// Now stow a new log with no payload and verify that it holds the values we
// told it to.
res = ram_crashlog_stow(crashlog_buffer, sizeof(crashlog_buffer), nullptr, 0,
ZirconCrashReason::Unknown, 4599);
ASSERT_OK(res);
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(4599, rlog.uptime);
EXPECT_EQ(ZirconCrashReason::Unknown, rlog.reason);
EXPECT_EQ(0, rlog.payload_len);
EXPECT_TRUE(rlog.payload_valid);
EXPECT_NULL(rlog.payload);
// While we do not specific which header the implementation will use when
// replacing an invalid log with a valid log, we _do_ specify that the log
// headers should be double buffered. Now that the implementation has chosen
// a header, we expect the choice of header to toggle each time we stow a new
// log.
uint64_t expected_magic =
(log.magic == RAM_CRASHLOG_MAGIC_0) ? RAM_CRASHLOG_MAGIC_1 : RAM_CRASHLOG_MAGIC_0;
// Stow a new crashlog, but this time stash a payload which fits in our space, but
// does not fill it entirely.
constexpr uint32_t TO_STOW = TEST_PAYLOAD_MAX / 2;
memset(payload, 0, TEST_PAYLOAD_MAX);
res = ram_crashlog_stow(crashlog_buffer, sizeof(crashlog_buffer), LONG_PAYLOAD, TO_STOW,
ZirconCrashReason::Oom, 9945);
ASSERT_OK(res);
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(9945, rlog.uptime);
EXPECT_EQ(ZirconCrashReason::Oom, rlog.reason);
EXPECT_EQ(TO_STOW, rlog.payload_len);
EXPECT_TRUE(rlog.payload_valid);
EXPECT_BYTES_EQ(LONG_PAYLOAD, rlog.payload, rlog.payload_len);
EXPECT_EQ(expected_magic,
log.magic); // Peek under the hood and validate this implementation detail.
// Finally, attempt to stash a log with a payload which does _not_ fit into
// our available space. This should succeed, but the payload (once recovered)
// should be truncated.
expected_magic =
(log.magic == RAM_CRASHLOG_MAGIC_0) ? RAM_CRASHLOG_MAGIC_1 : RAM_CRASHLOG_MAGIC_0;
memset(payload, 0xFF, TEST_PAYLOAD_MAX);
res = ram_crashlog_stow(crashlog_buffer, sizeof(crashlog_buffer), LONG_PAYLOAD, LONG_PAYLOAD_LEN,
ZirconCrashReason::Panic, 314159);
ASSERT_OK(res);
res = ram_crashlog_recover(crashlog_buffer, sizeof(crashlog_buffer), &rlog);
ASSERT_OK(res);
EXPECT_EQ(314159, rlog.uptime);
EXPECT_EQ(ZirconCrashReason::Panic, rlog.reason);
EXPECT_EQ(TEST_PAYLOAD_MAX, rlog.payload_len);
EXPECT_TRUE(rlog.payload_valid);
EXPECT_BYTES_EQ(LONG_PAYLOAD, rlog.payload, rlog.payload_len);
EXPECT_EQ(expected_magic,
log.magic); // Peek under the hood and validate this implementation detail.
}
} // namespace