/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include <fcntl.h> #include <signal.h> #include <stdint.h> #include <sys/prctl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <chrono> #include <iostream> #include <string> #include <android-base/properties.h> #include <gtest/gtest.h> #include <log/log_time.h> // for MS_PER_SEC and US_PER_SEC #include "llkd.h" using namespace std::chrono; using namespace std::chrono_literals; namespace { milliseconds GetUintProperty(const std::string& key, milliseconds def) { return milliseconds(android::base::GetUintProperty(key, static_cast<uint64_t>(def.count()), static_cast<uint64_t>(def.max().count()))); } seconds GetUintProperty(const std::string& key, seconds def) { return seconds(android::base::GetUintProperty(key, static_cast<uint64_t>(def.count()), static_cast<uint64_t>(def.max().count()))); } // GTEST_LOG_(WARNING) output is fugly, this has much less noise // ToDo: look into fixing googletest to produce output that matches style of // all the other status messages, and can switch off __line__ and // __function__ noise #define GTEST_LOG_WARNING std::cerr << "[ WARNING ] " #define GTEST_LOG_INFO std::cerr << "[ INFO ] " // Properties is _not_ a high performance ABI! void rest() { usleep(200000); } void execute(const char* command) { if (getuid() || system(command)) { system((std::string("su root ") + command).c_str()); } } seconds llkdSleepPeriod(char state) { auto default_eng = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng"; auto default_enable = LLK_ENABLE_DEFAULT; if (!LLK_ENABLE_DEFAULT && default_eng && android::base::GetBoolProperty("ro.debuggable", false)) { default_enable = true; } default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable); if (default_eng) { GTEST_LOG_INFO << LLK_ENABLE_PROPERTY " defaults to \"eng\" thus " << (default_enable ? "true" : "false") << "\n"; } // Hail Mary hope is unconfigured. if ((GetUintProperty(LLK_TIMEOUT_MS_PROPERTY, LLK_TIMEOUT_MS_DEFAULT) != duration_cast<milliseconds>(120s)) || (GetUintProperty(LLK_CHECK_MS_PROPERTY, LLK_TIMEOUT_MS_DEFAULT / LLK_CHECKS_PER_TIMEOUT_DEFAULT) != duration_cast<milliseconds>(10s))) { execute("stop llkd-0"); execute("stop llkd-1"); rest(); std::string setprop("setprop "); // Manually check that SyS_openat is _added_ to the list when restarted execute((setprop + LLK_CHECK_STACK_PROPERTY + " ,SyS_openat").c_str()); rest(); execute((setprop + LLK_ENABLE_WRITEABLE_PROPERTY + " false").c_str()); rest(); execute((setprop + LLK_TIMEOUT_MS_PROPERTY + " 120000").c_str()); rest(); execute((setprop + KHT_TIMEOUT_PROPERTY + " 130").c_str()); rest(); execute((setprop + LLK_CHECK_MS_PROPERTY + " 10000").c_str()); rest(); if (!default_enable) { execute((setprop + LLK_ENABLE_PROPERTY + " true").c_str()); rest(); } execute((setprop + LLK_ENABLE_WRITEABLE_PROPERTY + " true").c_str()); rest(); } default_enable = LLK_ENABLE_DEFAULT; if (!LLK_ENABLE_DEFAULT && (android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng") && android::base::GetBoolProperty("ro.debuggable", false)) { default_enable = true; } default_enable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, default_enable); if (default_enable) { execute("start llkd-1"); rest(); GTEST_LOG_INFO << "llkd enabled\n"; } else { GTEST_LOG_WARNING << "llkd disabled\n"; } /* KISS follows llk_init() */ milliseconds llkTimeoutMs = LLK_TIMEOUT_MS_DEFAULT; seconds khtTimeout = duration_cast<seconds>( llkTimeoutMs * (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT) / LLK_CHECKS_PER_TIMEOUT_DEFAULT); khtTimeout = GetUintProperty(KHT_TIMEOUT_PROPERTY, khtTimeout); llkTimeoutMs = khtTimeout * LLK_CHECKS_PER_TIMEOUT_DEFAULT / (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT); llkTimeoutMs = GetUintProperty(LLK_TIMEOUT_MS_PROPERTY, llkTimeoutMs); if (llkTimeoutMs < LLK_TIMEOUT_MS_MINIMUM) { llkTimeoutMs = LLK_TIMEOUT_MS_MINIMUM; } milliseconds llkCheckMs = llkTimeoutMs / LLK_CHECKS_PER_TIMEOUT_DEFAULT; auto timeout = GetUintProperty((state == 'Z') ? LLK_Z_TIMEOUT_MS_PROPERTY : (state == 'S') ? LLK_STACK_TIMEOUT_MS_PROPERTY : LLK_D_TIMEOUT_MS_PROPERTY, llkTimeoutMs); if (timeout < LLK_TIMEOUT_MS_MINIMUM) { timeout = LLK_TIMEOUT_MS_MINIMUM; } if (llkCheckMs > timeout) { llkCheckMs = timeout; } llkCheckMs = GetUintProperty(LLK_CHECK_MS_PROPERTY, llkCheckMs); timeout += llkCheckMs; auto sec = duration_cast<seconds>(timeout); if (sec == 0s) { ++sec; } else if (sec > 59s) { GTEST_LOG_WARNING << "llkd is configured for about " << duration_cast<minutes>(sec).count() << " minutes to react\n"; } // 33% margin for the test to naturally timeout waiting for llkd to respond return (sec * 4 + 2s) / 3; } inline void waitForPid(pid_t child_pid) { int wstatus; ASSERT_LE(0, waitpid(child_pid, &wstatus, 0)); EXPECT_FALSE(WIFEXITED(wstatus)) << "[ INFO ] exit=" << WEXITSTATUS(wstatus); ASSERT_TRUE(WIFSIGNALED(wstatus)); ASSERT_EQ(WTERMSIG(wstatus), SIGKILL); } bool checkKill(const char* reason) { if (android::base::GetBoolProperty(LLK_KILLTEST_PROPERTY, LLK_KILLTEST_DEFAULT)) { return false; } auto bootreason = android::base::GetProperty("sys.boot.reason", "nothing"); if (bootreason == reason) { GTEST_LOG_INFO << "Expected test result confirmed " << reason << "\n"; return true; } GTEST_LOG_WARNING << "Expected test result is " << reason << "\n"; // apct adjustment if needed (set LLK_KILLTEST_PROPERTY to "off" to allow test) // // if (android::base::GetProperty(LLK_KILLTEST_PROPERTY, "") == "false") { // GTEST_LOG_WARNING << "Bypassing test\n"; // return true; // } return false; } } // namespace // The tests that use this helper are to simulate processes stuck in 'D' // state that are experiencing forward scheduled progress. As such the // expectation is that llkd will _not_ perform any mitigations. The sleepfor // argument helps us set the amount of forward scheduler progress. static void llkd_driver_ABA(const microseconds sleepfor) { const auto period = llkdSleepPeriod('D'); if (period <= sleepfor) { GTEST_LOG_WARNING << "llkd configuration too short for " << duration_cast<milliseconds>(sleepfor).count() << "ms work cycle\n"; return; } auto child_pid = fork(); ASSERT_LE(0, child_pid); int wstatus; if (!child_pid) { auto ratio = period / sleepfor; ASSERT_LT(0, ratio); // vfork() parent is uninterruptable D state waiting for child to exec() while (--ratio > 0) { auto driver_pid = vfork(); ASSERT_LE(0, driver_pid); if (driver_pid) { // parent waitpid(driver_pid, &wstatus, 0); if (!WIFEXITED(wstatus)) { exit(42); } if (WEXITSTATUS(wstatus) != 42) { exit(42); } } else { usleep(sleepfor.count()); exit(42); } } exit(0); } ASSERT_LE(0, waitpid(child_pid, &wstatus, 0)); EXPECT_TRUE(WIFEXITED(wstatus)); if (WIFEXITED(wstatus)) { EXPECT_EQ(0, WEXITSTATUS(wstatus)); } ASSERT_FALSE(WIFSIGNALED(wstatus)) << "[ INFO ] signo=" << WTERMSIG(wstatus); } TEST(llkd, driver_ABA_fast) { llkd_driver_ABA(5ms); } TEST(llkd, driver_ABA_slow) { llkd_driver_ABA(1s); } TEST(llkd, driver_ABA_glacial) { llkd_driver_ABA(1min); } // Following tests must be last in this file to capture possible errant // kernel_panic mitigation failure. // The following tests simulate processes stick in 'Z' or 'D' state with // no forward scheduling progress, but interruptible. As such the expectation // is that llkd will perform kill mitigation and not progress to kernel_panic. TEST(llkd, zombie) { if (checkKill("kernel_panic,sysrq,livelock,zombie")) { return; } const auto period = llkdSleepPeriod('Z'); /* Create a Persistent Zombie Process */ pid_t child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { auto zombie_pid = fork(); ASSERT_LE(0, zombie_pid); if (!zombie_pid) { sleep(1); exit(0); } sleep(period.count()); exit(42); } waitForPid(child_pid); } TEST(llkd, driver) { if (checkKill("kernel_panic,sysrq,livelock,driver")) { return; } const auto period = llkdSleepPeriod('D'); /* Create a Persistent Device Process */ auto child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { // vfork() parent is uninterruptable D state waiting for child to exec() auto driver_pid = vfork(); ASSERT_LE(0, driver_pid); sleep(period.count()); exit(driver_pid ? 42 : 0); } waitForPid(child_pid); } TEST(llkd, sleep) { if (checkKill("kernel_panic,sysrq,livelock,sleeping")) { return; } if (!android::base::GetBoolProperty("ro.debuggable", false)) { GTEST_LOG_WARNING << "Features not available on user builds\n"; } const auto period = llkdSleepPeriod('S'); /* Create a Persistent SyS_openat for single-ended pipe */ static constexpr char stack_pipe_file[] = "/dev/stack_pipe_file"; unlink(stack_pipe_file); auto pipe_ret = mknod(stack_pipe_file, S_IFIFO | 0666, 0); ASSERT_LE(0, pipe_ret); auto child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { sleep(period.count()); auto fd = open(stack_pipe_file, O_RDONLY | O_CLOEXEC); close(fd); exit(0); } else { auto fd = open(stack_pipe_file, O_WRONLY | O_CLOEXEC); close(fd); exit(42); } } waitForPid(child_pid); unlink(stack_pipe_file); } // b/120983740 TEST(llkd, adbd_and_setsid) { if (checkKill("kernel_panic,sysrq,livelock,zombie")) { return; } const auto period = llkdSleepPeriod('S'); // expect llkd.zombie to trigger, but not for adbd&[setsid] // Create a Persistent Zombie setsid Process pid_t child_pid = fork(); ASSERT_LE(0, child_pid); if (!child_pid) { prctl(PR_SET_NAME, "adbd"); auto zombie_pid = fork(); ASSERT_LE(0, zombie_pid); if (!zombie_pid) { prctl(PR_SET_NAME, "setsid"); sleep(1); exit(0); } sleep(period.count()); exit(42); } // Reverse of waitForPid, do _not_ expect kill int wstatus; ASSERT_LE(0, waitpid(child_pid, &wstatus, 0)); EXPECT_TRUE(WIFEXITED(wstatus)); if (WIFEXITED(wstatus)) { EXPECT_EQ(42, WEXITSTATUS(wstatus)); } ASSERT_FALSE(WIFSIGNALED(wstatus)) << "[ INFO ] signo=" << WTERMSIG(wstatus); }