diff --git a/llkd/README.md b/llkd/README.md index 7cc7e1f94..43bb94ae0 100644 --- a/llkd/README.md +++ b/llkd/README.md @@ -86,7 +86,13 @@ kernel instead of deal with more graceful kill operation. Android Properties ------------------ -Android Properties llkd respond to (*prop*_ms parms are in milliseconds): +The following are the Android Properties llkd respond to. +*prop*_ms named properties are in milliseconds. +Properties that use comma (*,*) separator for lists, use a leading separator to +preserve default and add or subtract entries with (*optional*) plus (*+*) and +minus (*-*) prefixes respectively. +For these lists, the string "*false*" is synonymous with an *empty* list, +and *blank* or *missing* resorts to the specified *default* value. #### ro.config.low_ram device is configured with limited memory. @@ -137,7 +143,6 @@ default 2 minutes samples of threads for D or Z. #### ro.llk.stack default cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable comma separated list of kernel symbols. -The string "*false*" is the equivalent to an *empty* list. Look for kernel stack symbols that if ever persistently present can indicate a subsystem is locked up. Beware, check does not on purpose do forward scheduling ABA except by polling @@ -154,7 +159,6 @@ concerns on user builds prevents this checking. default 0,1,2 (kernel, init and [kthreadd]) plus process names init,[kthreadd],[khungtaskd],lmkd,llkd,watchdogd, [watchdogd],[watchdogd/0],...,[watchdogd/***get_nprocs**-1*]. -The string "*false*" is the equivalent to an *empty* list. Do not watch these processes. A process can be comm, cmdline or pid reference. NB: automated default here can be larger than the current maximum property size of 92. @@ -162,18 +166,15 @@ NB: false is a very very very unlikely process to want to blacklist. #### ro.llk.blacklist.parent default 0,2,adbd (kernel, [kthreadd] and adbd). -The string "*false*" is the equivalent to an *empty* list. Do not watch processes that have this parent. A parent process can be comm, cmdline or pid reference. #### ro.llk.blacklist.uid default *empty* or false, comma separated list of uid numbers or names. -The string "*false*" is the equivalent to an *empty* list. Do not watch processes that match this uid. #### ro.llk.blacklist.process.stack default process names init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd. -The string "*false*" is the equivalent to an *empty* list. This subset of processes are not monitored for live lock stack signatures. Also prevents the sepolicy violation associated with processes that block ptrace, as these can not be checked anyways. diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp index 7f47fc964..3a593ecc1 100644 --- a/llkd/libllkd.cpp +++ b/llkd/libllkd.cpp @@ -24,6 +24,7 @@ #include // getpwuid() #include #include +#include #include // ___STRING, __predict_true() and _predict_false() #include // mlockall() #include @@ -617,17 +618,24 @@ std::string llkFormat(bool flag) { std::string llkFormat(const std::unordered_set& blacklist) { std::string ret; for (const auto& entry : blacklist) { - if (ret.size()) { - ret += ","; - } + if (!ret.empty()) ret += ","; ret += entry; } return ret; } +// This function parses the properties as a list, incorporating the supplied +// default. A leading comma separator means preserve the defaults and add +// entries (with an optional leading + sign), or removes entries with a leading +// - sign. +// // We only officially support comma separators, but wetware being what they // are will take some liberty and I do not believe they should be punished. -std::unordered_set llkSplit(const std::string& s) { +std::unordered_set llkSplit(const std::string& prop, const std::string& def) { + auto s = android::base::GetProperty(prop, def); + constexpr char separators[] = ", \t:;"; + if (!s.empty() && (s != def) && strchr(separators, s[0])) s = def + s; + std::unordered_set result; // Special case, allow boolean false to empty the list, otherwise expected @@ -637,9 +645,29 @@ std::unordered_set llkSplit(const std::string& s) { size_t base = 0; while (s.size() > base) { - auto found = s.find_first_of(", \t:", base); - // Only emplace content, empty entries are not an option - if (found != base) result.emplace(s.substr(base, found - base)); + auto found = s.find_first_of(separators, base); + // Only emplace unique content, empty entries are not an option + if (found != base) { + switch (s[base]) { + case '-': + ++base; + if (base >= s.size()) break; + if (base != found) { + auto have = result.find(s.substr(base, found - base)); + if (have != result.end()) result.erase(have); + } + break; + case '+': + ++base; + if (base >= s.size()) break; + if (base == found) break; + // FALLTHRU (for gcc, lint, pcc, etc; following for clang) + FALLTHROUGH_INTENDED; + default: + result.emplace(s.substr(base, found - base)); + break; + } + } if (found == s.npos) break; base = found + 1; } @@ -648,13 +676,21 @@ std::unordered_set llkSplit(const std::string& s) { bool llkSkipName(const std::string& name, const std::unordered_set& blacklist = llkBlacklistProcess) { - if ((name.size() == 0) || (blacklist.size() == 0)) { - return false; - } + if (name.empty() || blacklist.empty()) return false; return blacklist.find(name) != blacklist.end(); } +bool llkSkipProc(proc* procp, + const std::unordered_set& blacklist = llkBlacklistProcess) { + if (!procp) return false; + if (llkSkipName(std::to_string(procp->pid), blacklist)) return true; + if (llkSkipName(procp->getComm(), blacklist)) return true; + if (llkSkipName(procp->getCmdline(), blacklist)) return true; + if (llkSkipName(android::base::Basename(procp->getCmdline()), blacklist)) return true; + return false; +} + bool llkSkipPid(pid_t pid) { return llkSkipName(std::to_string(pid), llkBlacklistProcess); } @@ -730,11 +766,7 @@ bool llkCheckStack(proc* procp, const std::string& piddir) { } // Don't check process that are known to block ptrace, save sepolicy noise. - if (llkSkipName(std::to_string(procp->pid), llkBlacklistStack)) return false; - if (llkSkipName(procp->getComm(), llkBlacklistStack)) return false; - if (llkSkipName(procp->getCmdline(), llkBlacklistStack)) return false; - if (llkSkipName(android::base::Basename(procp->getCmdline()), llkBlacklistStack)) return false; - + if (llkSkipProc(procp, llkBlacklistStack)) return false; auto kernel_stack = ReadFile(piddir + "/stack"); if (kernel_stack.empty()) { LOG(VERBOSE) << piddir << "/stack empty comm=" << procp->getComm() @@ -780,12 +812,12 @@ void llkCheckSchedUpdate(proc* procp, const std::string& piddir) { // but if there are problems we assume at least a few // samples of reads occur before we take any real action. std::string schedString = ReadFile(piddir + "/sched"); - if (schedString.size() == 0) { + if (schedString.empty()) { // /schedstat is not as standardized, but in 3.1+ // Android devices, the third field is nr_switches // from /sched: schedString = ReadFile(piddir + "/schedstat"); - if (schedString.size() == 0) { + if (schedString.empty()) { return; } auto val = static_cast(-1); @@ -943,7 +975,7 @@ milliseconds llkCheck(bool checkRunning) { // Get the process stat std::string stat = ReadFile(piddir + "/stat"); - if (stat.size() == 0) { + if (stat.empty()) { continue; } unsigned tid = -1; @@ -1032,11 +1064,10 @@ milliseconds llkCheck(bool checkRunning) { if (pprocp == nullptr) { pprocp = llkTidAlloc(ppid, ppid, 0, "", 0, '?'); } - if ((pprocp != nullptr) && - (llkSkipName(pprocp->getComm(), llkBlacklistParent) || - llkSkipName(pprocp->getCmdline(), llkBlacklistParent) || - llkSkipName(android::base::Basename(pprocp->getCmdline()), llkBlacklistParent))) { - break; + if (pprocp) { + if (llkSkipProc(pprocp, llkBlacklistParent)) break; + } else { + if (llkSkipName(std::to_string(ppid), llkBlacklistParent)) break; } if ((llkBlacklistUid.size() != 0) && llkSkipUid(procp->getUid())) { @@ -1135,21 +1166,15 @@ milliseconds llkCheck(bool checkRunning) { if (!p->second.updated) { IF_ALOG(LOG_VERBOSE, LOG_TAG) { std::string ppidCmdline = llkProcGetName(p->second.ppid, nullptr, nullptr); - if (ppidCmdline.size()) { - ppidCmdline = "(" + ppidCmdline + ")"; - } + if (!ppidCmdline.empty()) ppidCmdline = "(" + ppidCmdline + ")"; std::string pidCmdline; if (p->second.pid != p->second.tid) { pidCmdline = llkProcGetName(p->second.pid, nullptr, p->second.getCmdline()); - if (pidCmdline.size()) { - pidCmdline = "(" + pidCmdline + ")"; - } + if (!pidCmdline.empty()) pidCmdline = "(" + pidCmdline + ")"; } std::string tidCmdline = llkProcGetName(p->second.tid, p->second.getComm(), p->second.getCmdline()); - if (tidCmdline.size()) { - tidCmdline = "(" + tidCmdline + ")"; - } + if (!tidCmdline.empty()) tidCmdline = "(" + tidCmdline + ")"; LOG(VERBOSE) << "thread " << p->second.ppid << ppidCmdline << "->" << p->second.pid << pidCmdline << "->" << p->second.tid << tidCmdline << " removed"; } @@ -1226,13 +1251,11 @@ bool llkInit(const char* threadname) { llkValidate(); // validate all (effectively minus llkTimeoutMs) #ifdef __PTRACE_ENABLED__ if (debuggable) { - llkCheckStackSymbols = llkSplit( - android::base::GetProperty(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT)); + llkCheckStackSymbols = llkSplit(LLK_CHECK_STACK_PROPERTY, LLK_CHECK_STACK_DEFAULT); } std::string defaultBlacklistStack(LLK_BLACKLIST_STACK_DEFAULT); if (!debuggable) defaultBlacklistStack += ",logd,/system/bin/logd"; - llkBlacklistStack = llkSplit( - android::base::GetProperty(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack)); + llkBlacklistStack = llkSplit(LLK_BLACKLIST_STACK_PROPERTY, defaultBlacklistStack); #endif std::string defaultBlacklistProcess( std::to_string(kernelPid) + "," + std::to_string(initPid) + "," + @@ -1244,17 +1267,14 @@ bool llkInit(const char* threadname) { for (int cpu = 1; cpu < get_nprocs_conf(); ++cpu) { defaultBlacklistProcess += ",[watchdog/" + std::to_string(cpu) + "]"; } - defaultBlacklistProcess = - android::base::GetProperty(LLK_BLACKLIST_PROCESS_PROPERTY, defaultBlacklistProcess); - llkBlacklistProcess = llkSplit(defaultBlacklistProcess); + llkBlacklistProcess = llkSplit(LLK_BLACKLIST_PROCESS_PROPERTY, defaultBlacklistProcess); if (!llkSkipName("[khungtaskd]")) { // ALWAYS ignore as special llkBlacklistProcess.emplace("[khungtaskd]"); } - llkBlacklistParent = llkSplit(android::base::GetProperty( - LLK_BLACKLIST_PARENT_PROPERTY, std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) + - "," LLK_BLACKLIST_PARENT_DEFAULT)); - llkBlacklistUid = - llkSplit(android::base::GetProperty(LLK_BLACKLIST_UID_PROPERTY, LLK_BLACKLIST_UID_DEFAULT)); + llkBlacklistParent = llkSplit(LLK_BLACKLIST_PARENT_PROPERTY, + std::to_string(kernelPid) + "," + std::to_string(kthreaddPid) + + "," LLK_BLACKLIST_PARENT_DEFAULT); + llkBlacklistUid = llkSplit(LLK_BLACKLIST_UID_PROPERTY, LLK_BLACKLIST_UID_DEFAULT); // internal watchdog ::signal(SIGALRM, llkAlarmHandler); diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp index f54932bab..d73893583 100644 --- a/llkd/tests/llkd_test.cpp +++ b/llkd/tests/llkd_test.cpp @@ -87,7 +87,8 @@ seconds llkdSleepPeriod(char state) { execute("stop llkd-1"); rest(); std::string setprop("setprop "); - execute((setprop + LLK_CHECK_STACK_PROPERTY + " SyS_openat").c_str()); + // 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();