| /* | |
| * 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); | |
| } |