| // Copyright 2019 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 <memory> |
| #include <string_view> |
| |
| #include <zxtest/zxtest.h> |
| |
| #include "lib/cmdline.h" |
| |
| namespace { |
| |
| // Print the command line in hex (for debugging test failures). |
| void PrintHex(const char* data, size_t size) { |
| for (size_t i = 0; i < size; ++i) { |
| printf("%02hhx ", data[i]); |
| } |
| } |
| |
| // "Base case" for EqualsOffset parameter pack template. |
| // |
| // See Equals template below. |
| bool EqualsOffset(const char* data, size_t size, size_t offset, const char* value) { |
| if (offset >= size) { |
| return false; |
| } |
| if (strcmp(data + offset, value)) { |
| return false; |
| } |
| return true; |
| } |
| |
| // See Equals template below. |
| template <typename... Rest> |
| bool EqualsOffset(const char* data, size_t size, size_t offset, const char* value, Rest... rest) { |
| if (!EqualsOffset(data, size, offset, value)) { |
| return false; |
| } |
| // Step over the value and the '\0'. |
| return EqualsOffset(data, size, offset + strlen(value) + 1, rest...); |
| } |
| |
| // Compares |data|, a sequence of \0-terminated strings followed by a final \0, with the |rest| |
| // args. |
| // |
| // Example: |
| // assert(Equals(c, "k1=v1", "k2=v2", "k3=v3")); |
| template <typename... Rest> |
| bool Equals(Cmdline* c, Rest... rest) { |
| if (!EqualsOffset(c->data(), c->size(), 0, rest...)) { |
| printf("Cmdline contains: [ "); |
| PrintHex(c->data(), c->size()); |
| printf("]\n"); |
| return false; |
| } |
| return true; |
| } |
| |
| TEST(KernelCmdlineTest, InitialState) { |
| auto c = std::make_unique<Cmdline>(); |
| ASSERT_EQ(1u, c->size()); |
| ASSERT_EQ('\0', c->data()[0]); |
| } |
| |
| TEST(KernelCmdlineTest, AppendBasic) { |
| // nullptr |
| auto c = std::make_unique<Cmdline>(); |
| c->Append(nullptr); |
| EXPECT_TRUE(Equals(c.get(), "")); |
| EXPECT_EQ(1u, c->size()); |
| |
| // empty string |
| c = std::make_unique<Cmdline>(); |
| c->Append(""); |
| EXPECT_TRUE(Equals(c.get(), "")); |
| EXPECT_EQ(1u, c->size()); |
| |
| // single whitespace |
| c = std::make_unique<Cmdline>(); |
| c->Append(" "); |
| EXPECT_TRUE(Equals(c.get(), "")); |
| EXPECT_EQ(1u, c->size()); |
| |
| // multiple whitespace |
| c = std::make_unique<Cmdline>(); |
| c->Append(" "); |
| EXPECT_TRUE(Equals(c.get(), "")); |
| EXPECT_EQ(1u, c->size()); |
| |
| // key only |
| c = std::make_unique<Cmdline>(); |
| c->Append("k"); |
| EXPECT_TRUE(Equals(c.get(), "k=")); |
| EXPECT_EQ(2 + strlen(c->data()), c->size()); |
| |
| // whitespace before key |
| c = std::make_unique<Cmdline>(); |
| c->Append(" k"); |
| EXPECT_TRUE(Equals(c.get(), "k=")); |
| EXPECT_EQ(2 + strlen(c->data()), c->size()); |
| |
| // key equals |
| c = std::make_unique<Cmdline>(); |
| c->Append("k="); |
| EXPECT_TRUE(Equals(c.get(), "k=")); |
| EXPECT_EQ(2 + strlen(c->data()), c->size()); |
| |
| // two keys |
| c = std::make_unique<Cmdline>(); |
| c->Append("k1 k2"); |
| EXPECT_TRUE(Equals(c.get(), "k1=", "k2=")); |
| |
| // white space collapsing |
| c = std::make_unique<Cmdline>(); |
| c->Append(" k1 k2 "); |
| EXPECT_TRUE(Equals(c.get(), "k1=", "k2=")); |
| |
| // key equals value |
| c = std::make_unique<Cmdline>(); |
| c->Append(" k1=hello k2=world "); |
| EXPECT_TRUE(Equals(c.get(), "k1=hello", "k2=world")); |
| |
| // illegal chars become dot |
| c = std::make_unique<Cmdline>(); |
| c->Append( |
| " k1=foo k2=red" |
| "\xf8" |
| "\x07" |
| "blue"); |
| EXPECT_TRUE(Equals(c.get(), "k1=foo", "k2=red..blue")); |
| } |
| |
| TEST(KernelCmdlineTest, OverflowByALot) { |
| auto c = std::make_unique<Cmdline>(); |
| ASSERT_DEATH(([&c]() { |
| constexpr char kPattern[] = "abcdefg"; |
| for (size_t j = 0; j < Cmdline::kCmdlineMax; ++j) { |
| c->Append(kPattern); |
| } |
| })); |
| } |
| |
| TEST(KernelCmdlineTest, OverflowExact) { |
| // Maximum is 'aaaaa...aaaaa' followed by '=\0\0'. So the longest string that |
| // can be added is 3 less than the max. Allocate a buffer 2 less than the max, |
| // so that we can \0 terminate this string, that has a strlen() of 3 less than |
| // the max. |
| auto c = std::make_unique<Cmdline>(); |
| char data[Cmdline::kCmdlineMax - 2]; |
| memset(data, 'a', Cmdline::kCmdlineMax - 3); |
| data[Cmdline::kCmdlineMax - 3] = 0; |
| EXPECT_EQ(strlen(data), Cmdline::kCmdlineMax - 3); |
| c->Append(data); |
| EXPECT_EQ(c->size(), Cmdline::kCmdlineMax); |
| |
| // Adding anything now should abort. |
| ASSERT_DEATH(([&c]() { c->Append("b"); })); |
| |
| // However, adding "b" actually adds "b=\0" to the total length, so test 2 |
| // fewer than above as well for the "full" starting amount. |
| auto c2 = std::make_unique<Cmdline>(); |
| memset(data, 'a', Cmdline::kCmdlineMax - 5); |
| data[Cmdline::kCmdlineMax - 5] = 0; |
| EXPECT_EQ(strlen(data), Cmdline::kCmdlineMax - 5); |
| c2->Append(data); |
| EXPECT_EQ(c2->size(), Cmdline::kCmdlineMax - 2); |
| |
| ASSERT_DEATH(([&c2]() { c2->Append("b"); })); |
| |
| // Finally, confirm that one fewer doesn't fail. |
| auto c3 = std::make_unique<Cmdline>(); |
| memset(data, 'a', Cmdline::kCmdlineMax - 6); |
| data[Cmdline::kCmdlineMax - 6] = 0; |
| EXPECT_EQ(strlen(data), Cmdline::kCmdlineMax - 6); |
| c3->Append(data); |
| EXPECT_EQ(c3->size(), Cmdline::kCmdlineMax - 3); |
| |
| // Shouldn't crash, cmdline is now full. |
| c3->Append("b"); |
| EXPECT_EQ(c3->size(), Cmdline::kCmdlineMax); |
| } |
| |
| TEST(KernelCmdlineTest, GetString) { |
| auto c = std::make_unique<Cmdline>(); |
| EXPECT_EQ(nullptr, c->GetString("k1")); |
| EXPECT_EQ(nullptr, c->GetString("")); |
| EXPECT_EQ(c->data(), c->GetString(nullptr)); |
| |
| c->Append("k1=red k2=blue k1=green"); |
| EXPECT_TRUE(!strcmp(c->GetString("k1"), "green")); |
| EXPECT_TRUE(!strcmp(c->GetString("k2"), "blue")); |
| EXPECT_EQ(nullptr, c->GetString("")); |
| EXPECT_EQ(c->data(), c->GetString(nullptr)); |
| } |
| |
| TEST(KernelCmdlineTest, GetBool) { |
| auto c = std::make_unique<Cmdline>(); |
| // not found, default is returned |
| EXPECT_FALSE(c->GetBool("k0", false)); |
| EXPECT_TRUE(c->GetBool("k0", true)); |
| |
| c->Append("k1=red k2 k3=0 k4=false k5=off k6=01 k7=falseish k8=offset"); |
| |
| // not found, default is returned |
| EXPECT_FALSE(c->GetBool("k0", false)); |
| EXPECT_TRUE(c->GetBool("k0", true)); |
| |
| // values that don't "look like" false are true |
| EXPECT_TRUE(c->GetBool("k1", false)); |
| EXPECT_TRUE(c->GetBool("k2", false)); |
| |
| // values that "look like" false are false |
| EXPECT_FALSE(c->GetBool("k3", true)); |
| EXPECT_FALSE(c->GetBool("k4", true)); |
| EXPECT_FALSE(c->GetBool("k5", true)); |
| |
| // almost false, but not quite |
| EXPECT_TRUE(c->GetBool("k6", false)); |
| EXPECT_TRUE(c->GetBool("k7", false)); |
| EXPECT_TRUE(c->GetBool("k8", false)); |
| } |
| |
| TEST(KernelCmdlineTest, GetUInt32) { |
| auto c = std::make_unique<Cmdline>(); |
| EXPECT_EQ(99u, c->GetUInt32("k1", 99u)); |
| |
| c->Append("k1 k2= k3=42 k4=0 k5=4294967295"); |
| EXPECT_EQ(99u, c->GetUInt32("k1", 99u)); |
| EXPECT_EQ(99u, c->GetUInt32("k2", 99u)); |
| EXPECT_EQ(42u, c->GetUInt32("k3", 99u)); |
| EXPECT_EQ(0u, c->GetUInt32("k4", 99u)); |
| EXPECT_EQ(UINT32_MAX, c->GetUInt32("k5", 99u)); |
| } |
| |
| TEST(KernelCmdlineTest, GetUInt64) { |
| auto c = std::make_unique<Cmdline>(); |
| EXPECT_EQ(99u, c->GetUInt64("k1", 99u)); |
| |
| c->Append("k1 k2= k3=42 k4=0 k5=9223372036854775807 k6=18446744073709551615"); |
| EXPECT_EQ(99u, c->GetUInt64("k1", 99u)); |
| EXPECT_EQ(99u, c->GetUInt64("k2", 99u)); |
| EXPECT_EQ(42u, c->GetUInt64("k3", 99u)); |
| EXPECT_EQ(0u, c->GetUInt64("k4", 99u)); |
| |
| // |GetUInt64| is limited to parsing up to INT64_MAX. Anything higher is saturated to INT64_MAX. |
| EXPECT_EQ(static_cast<uint64_t>(INT64_MAX), c->GetUInt64("k5", 99u)); |
| EXPECT_EQ(static_cast<uint64_t>(INT64_MAX), c->GetUInt64("k6", 99u)); |
| } |
| |
| TEST(KernelCmdlineTest, LaterOverride) { |
| auto c = std::make_unique<Cmdline>(); |
| c->Append("k1 k2= k1=42"); |
| EXPECT_TRUE(strcmp(c->GetString("k1"), "42") == 0); |
| EXPECT_TRUE(strcmp(c->GetString("k2"), "") == 0); |
| |
| c->Append("k1=stuff"); |
| EXPECT_TRUE(strcmp(c->GetString("k1"), "stuff") == 0); |
| |
| c->Append("k1=zip k1=zap"); |
| EXPECT_TRUE(strcmp(c->GetString("k1"), "zap") == 0); |
| |
| c->Append("k1"); |
| EXPECT_TRUE(strcmp(c->GetString("k1"), "") == 0); |
| } |
| |
| TEST(KernelCmdlineTest, Short) { |
| auto c = std::make_unique<Cmdline>(); |
| c->Append("a=1"); |
| EXPECT_EQ(c->GetUInt32("a", 0), 1); |
| } |
| |
| TEST(KernelCmdlineTest, AlmostMaximumExpansion) { |
| auto c = std::make_unique<Cmdline>(); |
| |
| static_assert(Cmdline::kCmdlineMax == 4096, "1365 below needs to be updated"); |
| |
| // Appending "a " turns into "a=\0" in the buffer, for 4095 bytes, leaving one |
| // byte for the additional terminator. |
| for (size_t i = 0; i < 1365; ++i) { |
| c->Append("a "); |
| } |
| |
| // One more should panic though. |
| ASSERT_DEATH(([&c]() { c->Append("a "); })); |
| } |
| |
| TEST(KernelCmdlineTest, MaximumExpansion) { |
| auto c = std::make_unique<Cmdline>(); |
| |
| static_assert(Cmdline::kCmdlineMax == 4096, "1364 below needs to be updated"); |
| |
| // AlmostMaximumExpansion was the first attempt at finding the correct maximal |
| // input, but, as a fuzzer found, there's a larger case. See fxbug.dev/47006 |
| // for repro. |
| // |
| // Consider if the limit were 10 (rather than 4096), then an analogous |
| // 'longest' input would be "a a a " which would expand to "a=0a=0a=00" (using |
| // 0 instead of \0 for readability), so 6->10 (including terminator). |
| // |
| // But, because the end of the string also counts as the end of a variable, |
| // these 6 characters as input instead "a a aa" result in "a=0a=0aa=00" for an |
| // expansion of 6->11. (This is sort of like getting a bonus 7th character in |
| // the input due to null termination, rather than needing to have the trailing |
| // space to end the variable.) |
| // |
| // Attempting to use this "trick" twice results in fewer \0 being inserted, so |
| // doesn't further lengthen the string. |
| // |
| // (The specifics aren't particularly important as the kernel is already going |
| // to panic if the limit is exceeded, but it's necessary to get the edges |
| // right for this test and the fuzzer test.) |
| |
| for (size_t i = 0; i < 1364; ++i) { |
| c->Append("a "); |
| } |
| |
| // One more should panic though. |
| ASSERT_DEATH(([&c]() { c->Append("aa"); })); |
| } |
| |
| TEST(KernelCmdlineTest, ProcessRamReservations) { |
| auto c = std::make_unique<Cmdline>(); |
| static constexpr const char* kTestVector = |
| "kernel.Ram.reserve.t0=0x2000,0xXXXXXXXXXXXXXXXX " // Wrong argument name |
| "kernel.ram.reserve.t1=0x20000xXXXXXXXXXXXXXXXX " // Missing ',' |
| "kernel.ram.reserve.t2= " // Missing args |
| "kernel.ram.reserve.t3=0x300G,0xXXXXXXXXXXXXXXXX " // Bad size |
| "kernel.ram.reserve.t4=0x3000,0xXXXXXXXxXXXXXXXX " // Bad dynamic allocation placeholder |
| "kernel.ram.reserve.t5=0x3000,0xXXXXXXXXXXXXXXX " // Short dynamic allocation placeholder |
| "kernel.ram.reserve.t6=0x3000,0xXXXXXXXXXXXXXXXXX " // Long dynamic allocation placeholder |
| "kernel.ram.reserve.t7=0x1000,0xXXXXXXXXXXXXXXXX " // Good dynamic allocation |
| "kernel.ram.reserve.t8=0x2000,0xXXXXXXXXXXXXXXXX " // Duplicate name, should be erased |
| "kernel.ram.reserve.t8=0x3000,0xXXXXXXXXXXXXXXXX " // Duplicate name, should be erased |
| "kernel.ram.reserve.t8=0x5000,0xXXXXXXXXXXXXXXXX " // This is the real 't8' |
| "kernel.ram.reserve.t9=0x8000,0xXXXXXXXXXXXXXXXX "; // Denied reservation |
| |
| struct ExpectedCallback { |
| size_t size; |
| const char* name; |
| std::optional<uintptr_t> to_return; |
| bool visited = false; |
| }; |
| |
| std::array expected = { |
| ExpectedCallback{0x1000, "t7", 0x5000}, |
| ExpectedCallback{0x5000, "t8", 0xA000}, |
| ExpectedCallback{0x8000, "t9", std::nullopt}, |
| }; |
| |
| // Populate the command line instance with our test vector. |
| c->Append(kTestVector); |
| |
| // Now process our data an make sure we get the callbacks we expect. |
| c->ProcessRamReservations( |
| [&expected](size_t size, std::string_view name) -> std::optional<uintptr_t> { |
| // Find our expected results |
| auto e = std::find_if(expected.begin(), expected.end(), [&name](const ExpectedCallback& e) { |
| return !strncmp(e.name, name.data(), name.size()); |
| }); |
| |
| // If we don't find any, then this callback should not have been made. |
| if (e == expected.end()) { |
| EXPECT_TRUE(false); |
| return std::nullopt; |
| } |
| |
| // Check to make sure the size we received in the callback |
| // matches what we expect. |
| EXPECT_EQ(e->size, size); |
| |
| // Make sure we have not already visited this expected result, then mark it |
| // as visited. |
| EXPECT_FALSE(e->visited); |
| e->visited = true; |
| |
| // Finally, return the value the test wants us to. |
| return e->to_return; |
| }); |
| |
| // Make sure that we visited all of the nodes we expected to visit |
| for (const auto& e : expected) { |
| EXPECT_TRUE(e.visited, "callback not made for \"%s\"", e.name); |
| } |
| |
| // Finally, make sure that the transformed data in the command line buffer matches what we expect. |
| static constexpr const char kExpectedBuffer[] = |
| "kernel.Ram.reserve.t0=0x2000,0xXXXXXXXXXXXXXXXX\0" // Wrong argument name |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Missing ',' |
| "xxxxxxxxxxxxxxxxxxxxxx\0" // Missing args |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Bad size |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Bad dynamic allocation placeholder |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Short dynamic allocation placeholder |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Long dynamic allocation placeholder |
| "kernel.ram.reserve.t7=0x1000,0x0000000000005000\0" // Good dynamic allocation |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Duplicate name, should be erased |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0" // Duplicate name, should be erased |
| "kernel.ram.reserve.t8=0x5000,0x000000000000a000\0" // This is the real 't8' |
| "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\0"; // Denied reservation |
| |
| ASSERT_EQ(sizeof(kExpectedBuffer), c->size()); |
| EXPECT_BYTES_EQ(kExpectedBuffer, c->data(), c->size()); |
| } |
| |
| } // namespace |