Merge "libdebuggerd: fix out of bounds write."
diff --git a/adb/adb.cpp b/adb/adb.cpp
index 4fbfafb..38c11b9 100644
--- a/adb/adb.cpp
+++ b/adb/adb.cpp
@@ -1221,7 +1221,7 @@
std::string error;
atransport* t = acquire_one_transport(type, serial, transport_id, nullptr, &error);
if (!t) {
- return SendFail(reply_fd, error);
+ return -1;
}
int ret = handle_forward_request(service, t, reply_fd);
diff --git a/adb/test_adb.py b/adb/test_adb.py
index 3bb433d..363002f 100644
--- a/adb/test_adb.py
+++ b/adb/test_adb.py
@@ -21,9 +21,11 @@
"""
from __future__ import print_function
+import binascii
import contextlib
import os
import random
+import select
import socket
import struct
import subprocess
@@ -33,6 +35,52 @@
import adb
+@contextlib.contextmanager
+def fake_adb_server(protocol=socket.AF_INET, port=0):
+ """Creates a fake ADB server that just replies with a CNXN packet."""
+
+ serversock = socket.socket(protocol, socket.SOCK_STREAM)
+ if protocol == socket.AF_INET:
+ serversock.bind(('127.0.0.1', port))
+ else:
+ serversock.bind(('::1', port))
+ serversock.listen(1)
+
+ # A pipe that is used to signal the thread that it should terminate.
+ readpipe, writepipe = os.pipe()
+
+ def _handle():
+ rlist = [readpipe, serversock]
+ while True:
+ ready, _, _ = select.select(rlist, [], [])
+ for r in ready:
+ if r == readpipe:
+ # Closure pipe
+ os.close(r)
+ serversock.shutdown(socket.SHUT_RDWR)
+ serversock.close()
+ return
+ elif r == serversock:
+ # Server socket
+ conn, _ = r.accept()
+ rlist.append(conn)
+ else:
+ # Client socket
+ data = r.recv(1024)
+ if not data:
+ rlist.remove(r)
+
+ port = serversock.getsockname()[1]
+ server_thread = threading.Thread(target=_handle)
+ server_thread.start()
+
+ try:
+ yield port
+ finally:
+ os.close(writepipe)
+ server_thread.join()
+
+
class NonApiTest(unittest.TestCase):
"""Tests for ADB that aren't a part of the AndroidDevice API."""
@@ -211,45 +259,32 @@
Bug: http://b/30313466
"""
- ipv4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- ipv4.bind(('127.0.0.1', 0))
- ipv4.listen(1)
+ for protocol in (socket.AF_INET, socket.AF_INET6):
+ try:
+ with fake_adb_server(protocol=protocol) as port:
+ output = subprocess.check_output(
+ ['adb', 'connect', 'localhost:{}'.format(port)])
- ipv6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
- try:
- ipv6.bind(('::1', ipv4.getsockname()[1] + 1))
- ipv6.listen(1)
- except socket.error:
- print("IPv6 not available, skipping")
- return
+ self.assertEqual(
+ output.strip(), 'connected to localhost:{}'.format(port))
+ except socket.error:
+ print("IPv6 not available, skipping")
+ continue
- for s in (ipv4, ipv6):
- port = s.getsockname()[1]
+ def test_already_connected(self):
+ with fake_adb_server() as port:
output = subprocess.check_output(
['adb', 'connect', 'localhost:{}'.format(port)])
self.assertEqual(
output.strip(), 'connected to localhost:{}'.format(port))
- s.close()
- def test_already_connected(self):
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(('127.0.0.1', 0))
- s.listen(2)
+ # b/31250450: this always returns 0 but probably shouldn't.
+ output = subprocess.check_output(
+ ['adb', 'connect', 'localhost:{}'.format(port)])
- port = s.getsockname()[1]
- output = subprocess.check_output(
- ['adb', 'connect', 'localhost:{}'.format(port)])
-
- self.assertEqual(
- output.strip(), 'connected to localhost:{}'.format(port))
-
- # b/31250450: this always returns 0 but probably shouldn't.
- output = subprocess.check_output(
- ['adb', 'connect', 'localhost:{}'.format(port)])
-
- self.assertEqual(
- output.strip(), 'already connected to localhost:{}'.format(port))
+ self.assertEqual(
+ output.strip(), 'already connected to localhost:{}'.format(port))
def main():
random.seed(0)
diff --git a/adb/test_device.py b/adb/test_device.py
index b1ad043..f995be2 100644
--- a/adb/test_device.py
+++ b/adb/test_device.py
@@ -188,8 +188,6 @@
finally:
self.device.reverse_remove_all()
- # Note: If you run this test when adb connect'd to a physical device over
- # TCP, it will fail in adb reverse due to https://code.google.com/p/android/issues/detail?id=189821
def test_forward_reverse_echo(self):
"""Send data through adb forward and read it back via adb reverse"""
forward_port = 12345
diff --git a/adb/transport.cpp b/adb/transport.cpp
index 92c52e2..f5f6d26 100644
--- a/adb/transport.cpp
+++ b/adb/transport.cpp
@@ -974,7 +974,7 @@
VLOG(TRANSPORT) << "socket transport " << transport->serial
<< " is already in pending_list and fails to register";
delete t;
- return -1;
+ return -EALREADY;
}
}
@@ -983,7 +983,7 @@
VLOG(TRANSPORT) << "socket transport " << transport->serial
<< " is already in transport_list and fails to register";
delete t;
- return -1;
+ return -EALREADY;
}
}
diff --git a/adb/transport_local.cpp b/adb/transport_local.cpp
index c09fcb7..8032421 100644
--- a/adb/transport_local.cpp
+++ b/adb/transport_local.cpp
@@ -101,7 +101,11 @@
int ret = register_socket_transport(fd, serial.c_str(), port, 0);
if (ret < 0) {
adb_close(fd);
- *response = android::base::StringPrintf("already connected to %s", serial.c_str());
+ if (ret == -EALREADY) {
+ *response = android::base::StringPrintf("already connected to %s", serial.c_str());
+ } else {
+ *response = android::base::StringPrintf("failed to connect to %s", serial.c_str());
+ }
} else {
*response = android::base::StringPrintf("connected to %s", serial.c_str());
}
diff --git a/base/include/android-base/parseint.h b/base/include/android-base/parseint.h
index 2c8570e..1b7cc5f 100644
--- a/base/include/android-base/parseint.h
+++ b/base/include/android-base/parseint.h
@@ -19,6 +19,7 @@
#include <errno.h>
#include <stdlib.h>
+#include <string.h>
#include <limits>
#include <string>
@@ -31,14 +32,20 @@
// otherwise valid values will be rejected. Returns boolean success; 'out'
// is untouched if parsing fails.
template <typename T>
-bool ParseUint(const char* s, T* out,
- T max = std::numeric_limits<T>::max()) {
+bool ParseUint(const char* s, T* out, T max = std::numeric_limits<T>::max(),
+ bool allow_suffixes = false) {
int base = (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) ? 16 : 10;
errno = 0;
char* end;
unsigned long long int result = strtoull(s, &end, base);
- if (errno != 0 || s == end || *end != '\0') {
- return false;
+ if (errno != 0 || end == s) return false;
+ if (*end != '\0') {
+ const char* suffixes = "bkmgtpe";
+ const char* suffix;
+ if (!allow_suffixes || (suffix = strchr(suffixes, tolower(*end))) == nullptr) return false;
+#if __clang__ // TODO: win32 still builds with GCC :-(
+ if (__builtin_mul_overflow(result, 1ULL << (10 * (suffix - suffixes)), &result)) return false;
+#endif
}
if (max < result) {
return false;
@@ -49,9 +56,20 @@
// TODO: string_view
template <typename T>
-bool ParseUint(const std::string& s, T* out,
- T max = std::numeric_limits<T>::max()) {
- return ParseUint(s.c_str(), out, max);
+bool ParseUint(const std::string& s, T* out, T max = std::numeric_limits<T>::max(),
+ bool allow_suffixes = false) {
+ return ParseUint(s.c_str(), out, max, allow_suffixes);
+}
+
+template <typename T>
+bool ParseByteCount(const char* s, T* out, T max = std::numeric_limits<T>::max()) {
+ return ParseUint(s, out, max, true);
+}
+
+// TODO: string_view
+template <typename T>
+bool ParseByteCount(const std::string& s, T* out, T max = std::numeric_limits<T>::max()) {
+ return ParseByteCount(s.c_str(), out, max);
}
// Parses the signed decimal integer in the string 's' and sets 'out' to
diff --git a/base/parseint_test.cpp b/base/parseint_test.cpp
index 483b1d3..fb1c339 100644
--- a/base/parseint_test.cpp
+++ b/base/parseint_test.cpp
@@ -96,3 +96,44 @@
ASSERT_FALSE(android::base::ParseInt("456x", &u));
ASSERT_EQ(123u, u);
}
+
+TEST(parseint, ParseByteCount) {
+ uint64_t i = 0;
+ ASSERT_TRUE(android::base::ParseByteCount("123b", &i));
+ ASSERT_EQ(123ULL, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("8k", &i));
+ ASSERT_EQ(8ULL * 1024, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("8M", &i));
+ ASSERT_EQ(8ULL * 1024 * 1024, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("6g", &i));
+ ASSERT_EQ(6ULL * 1024 * 1024 * 1024, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("1T", &i));
+ ASSERT_EQ(1ULL * 1024 * 1024 * 1024 * 1024, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("2p", &i));
+ ASSERT_EQ(2ULL * 1024 * 1024 * 1024 * 1024 * 1024, i);
+
+ ASSERT_TRUE(android::base::ParseByteCount("4e", &i));
+ ASSERT_EQ(4ULL * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, i);
+}
+
+TEST(parseint, ParseByteCount_invalid_suffix) {
+ unsigned u;
+ ASSERT_FALSE(android::base::ParseByteCount("1x", &u));
+}
+
+TEST(parseint, ParseByteCount_overflow) {
+ uint64_t u64;
+ ASSERT_FALSE(android::base::ParseByteCount("4294967295E", &u64));
+
+ uint16_t u16;
+ ASSERT_TRUE(android::base::ParseByteCount("63k", &u16));
+ ASSERT_EQ(63U * 1024, u16);
+ ASSERT_TRUE(android::base::ParseByteCount("65535b", &u16));
+ ASSERT_EQ(65535U, u16);
+ ASSERT_FALSE(android::base::ParseByteCount("65k", &u16));
+}
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index e60e6be..8ce9dfc 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -38,6 +38,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/log.h>
#include <cutils/android_reboot.h>
@@ -302,6 +303,9 @@
{"kernel_panic,init", 158},
{"kernel_panic,oom", 159},
{"kernel_panic,stack", 160},
+ {"kernel_panic,sysrq,livelock,alarm", 161}, // llkd
+ {"kernel_panic,sysrq,livelock,driver", 162}, // llkd
+ {"kernel_panic,sysrq,livelock,zombie", 163}, // llkd
};
// Converts a string value representing the reason the system booted to an
@@ -1096,14 +1100,28 @@
boot_event_store->AddBootEventWithValue("absolute_boot_time", absolute_total.count());
}
+// Gets the boot time offset. This is useful when Android is running in a
+// container, because the boot_clock is not reset when Android reboots.
+std::chrono::nanoseconds GetBootTimeOffset() {
+ static const int64_t boottime_offset =
+ android::base::GetIntProperty<int64_t>("ro.boot.boottime_offset", 0);
+ return std::chrono::nanoseconds(boottime_offset);
+}
+
+// Returns the current uptime, accounting for any offset in the CLOCK_BOOTTIME
+// clock.
+android::base::boot_clock::duration GetUptime() {
+ return android::base::boot_clock::now().time_since_epoch() - GetBootTimeOffset();
+}
+
// Records several metrics related to the time it takes to boot the device,
// including disambiguating boot time on encrypted or non-encrypted devices.
void RecordBootComplete() {
BootEventRecordStore boot_event_store;
BootEventRecordStore::BootEventRecord record;
- auto time_since_epoch = android::base::boot_clock::now().time_since_epoch();
- auto uptime = std::chrono::duration_cast<std::chrono::seconds>(time_since_epoch);
+ auto uptime_ns = GetUptime();
+ auto uptime_s = std::chrono::duration_cast<std::chrono::seconds>(uptime_ns);
time_t current_time_utc = time(nullptr);
if (boot_event_store.GetBootEvent("last_boot_time_utc", &record)) {
@@ -1128,19 +1146,20 @@
// Log the amount of time elapsed until the device is decrypted, which
// includes the variable amount of time the user takes to enter the
// decryption password.
- boot_event_store.AddBootEventWithValue("boot_decryption_complete", uptime.count());
+ boot_event_store.AddBootEventWithValue("boot_decryption_complete", uptime_s.count());
// Subtract the decryption time to normalize the boot cycle timing.
- std::chrono::seconds boot_complete = std::chrono::seconds(uptime.count() - record.second);
+ std::chrono::seconds boot_complete = std::chrono::seconds(uptime_s.count() - record.second);
boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_post_decrypt",
boot_complete.count());
} else {
- boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_no_encryption", uptime.count());
+ boot_event_store.AddBootEventWithValue(boot_complete_prefix + "_no_encryption",
+ uptime_s.count());
}
// Record the total time from device startup to boot complete, regardless of
// encryption state.
- boot_event_store.AddBootEventWithValue(boot_complete_prefix, uptime.count());
+ boot_event_store.AddBootEventWithValue(boot_complete_prefix, uptime_s.count());
RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init");
RecordInitBootTimeProp(&boot_event_store, "ro.boottime.init.selinux");
@@ -1149,7 +1168,7 @@
const BootloaderTimingMap bootloader_timings = GetBootLoaderTimings();
RecordBootloaderTimings(&boot_event_store, bootloader_timings);
- auto uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(time_since_epoch);
+ auto uptime_ms = std::chrono::duration_cast<std::chrono::milliseconds>(uptime_ns);
RecordAbsoluteBootTime(&boot_event_store, bootloader_timings, uptime_ms);
}
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 3a3503e..780ff50 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -82,7 +82,7 @@
// libsparse will support INT_MAX, but this results in large allocations, so
// let's keep it at 1GB to avoid memory pressure on the host.
static constexpr int64_t RESPARSE_LIMIT = 1 * 1024 * 1024 * 1024;
-static int64_t sparse_limit = -1;
+static uint64_t sparse_limit = 0;
static int64_t target_sparse_limit = -1;
static unsigned g_base_addr = 0x10000000;
@@ -375,7 +375,7 @@
" -w Wipe userdata.\n"
" -s SERIAL Specify a USB device.\n"
" -s tcp|udp:HOST[:PORT] Specify a network device.\n"
- " -S SIZE[K|M|G] Use sparse files above this limit (0 to disable).\n"
+ " -S SIZE[K|M|G] Break into sparse files no larger than SIZE.\n"
" --slot SLOT Use SLOT; 'all' for both slots, 'other' for\n"
" non-current slot (default: current active slot).\n"
" --set-active[=SLOT] Sets the active slot before rebooting.\n"
@@ -410,12 +410,6 @@
if (!g_cmdline.empty()) {
bootimg_set_cmdline(reinterpret_cast<boot_img_hdr_v1*>(kdata), g_cmdline);
}
- uint32_t header_version_existing =
- reinterpret_cast<boot_img_hdr_v1*>(kdata)->header_version;
- if (g_boot_img_hdr.header_version != header_version_existing) {
- die("header version mismatch, expected: %" PRIu32 " found %" PRIu32 "",
- g_boot_img_hdr.header_version, header_version_existing);
- }
if (!ramdisk.empty()) die("cannot boot a boot.img *and* ramdisk");
@@ -736,13 +730,10 @@
}
static int64_t get_sparse_limit(Transport* transport, int64_t size) {
- int64_t limit;
-
- if (sparse_limit == 0) {
- return 0;
- } else if (sparse_limit > 0) {
- limit = sparse_limit;
- } else {
+ int64_t limit = sparse_limit;
+ if (limit == 0) {
+ // Unlimited, so see what the target device's limit is.
+ // TODO: shouldn't we apply this limit even if you've used -S?
if (target_sparse_limit == -1) {
target_sparse_limit = get_target_sparse_limit(transport);
}
@@ -1203,47 +1194,6 @@
fb_queue_command(command, "");
}
-static int64_t parse_num(const char *arg)
-{
- char *endptr;
- unsigned long long num;
-
- num = strtoull(arg, &endptr, 0);
- if (endptr == arg) {
- return -1;
- }
-
- if (*endptr == 'k' || *endptr == 'K') {
- if (num >= (-1ULL) / 1024) {
- return -1;
- }
- num *= 1024LL;
- endptr++;
- } else if (*endptr == 'm' || *endptr == 'M') {
- if (num >= (-1ULL) / (1024 * 1024)) {
- return -1;
- }
- num *= 1024LL * 1024LL;
- endptr++;
- } else if (*endptr == 'g' || *endptr == 'G') {
- if (num >= (-1ULL) / (1024 * 1024 * 1024)) {
- return -1;
- }
- num *= 1024LL * 1024LL * 1024LL;
- endptr++;
- }
-
- if (*endptr != '\0') {
- return -1;
- }
-
- if (num > INT64_MAX) {
- return -1;
- }
-
- return num;
-}
-
static std::string fb_fix_numeric_var(std::string var) {
// Some bootloaders (angler, for example), send spurious leading whitespace.
var = android::base::Trim(var);
@@ -1477,8 +1427,9 @@
serial = optarg;
break;
case 'S':
- sparse_limit = parse_num(optarg);
- if (sparse_limit < 0) die("invalid sparse limit");
+ if (!android::base::ParseByteCount(optarg, &sparse_limit)) {
+ die("invalid sparse limit %s", optarg);
+ }
break;
case 'v':
set_verbose();
diff --git a/init/README.md b/init/README.md
index 59ddd77..b14521c 100644
--- a/init/README.md
+++ b/init/README.md
@@ -282,6 +282,10 @@
"shutdown critical" will be killed. When the service tagged with "shutdown critical"
is not running when shut down starts, it will be started.
+`sigstop`
+> Send SIGSTOP to the service immediately before exec is called. This is intended for debugging.
+ See the below section on debugging for how this can be used.
+
`socket <name> <type> <perm> [ <user> [ <group> [ <seclabel> ] ] ]`
> Create a unix domain socket named /dev/socket/_name_ and pass its fd to the
launched process. _type_ must be "dgram", "stream" or "seqpacket". User and
@@ -708,23 +712,39 @@
Debugging init
--------------
-By default, programs executed by init will drop stdout and stderr into
-/dev/null. To help with debugging, you can execute your program via the
-Android program logwrapper. This will redirect stdout/stderr into the
-Android logging system (accessed via logcat).
+Launching init services without init is not recommended as init sets up a significant amount of
+environment (user, groups, security label, capabilities, etc) that is hard to replicate manually.
-For example
-service akmd /system/bin/logwrapper /sbin/akmd
+If it is required to debug a service from its very start, the `sigstop` service option is added.
+This option will send SIGSTOP to a service immediately before calling exec. This gives a window
+where developers can attach a debugger, strace, etc before continuing the service with SIGCONT.
-For quicker turnaround when working on init itself, use:
+This flag can also be dynamically controled via the ctl.sigstop_on and ctl.sigstop_off properties.
- mm -j &&
- m ramdisk-nodeps &&
- m bootimage-nodeps &&
- adb reboot bootloader &&
- fastboot boot $ANDROID_PRODUCT_OUT/boot.img
+Below is an example of dynamically debugging logd via the above:
-Alternatively, use the emulator:
+ stop logd
+ setprop ctl.sigstop_on logd
+ start logd
+ ps -e | grep logd
+ > logd 4343 1 18156 1684 do_signal_stop 538280 T init
+ gdbclient.py -p 4343
+ b main
+ c
+ c
+ c
+ > Breakpoint 1, main (argc=1, argv=0x7ff8c9a488) at system/core/logd/main.cpp:427
- emulator -partition-size 1024 \
- -verbose -show-kernel -no-window
+Below is an example of doing the same but with strace
+
+ stop logd
+ setprop ctl.sigstop_on logd
+ start logd
+ ps -e | grep logd
+ > logd 4343 1 18156 1684 do_signal_stop 538280 T init
+ strace -p 4343
+
+ (From a different shell)
+ kill -SIGCONT 4343
+
+ > strace runs
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index 379b4fa..c2cf573 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -32,12 +32,14 @@
#include <mutex>
#include <thread>
+#include <android-base/chrono_utils.h>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
using android::base::StringPrintf;
+using android::base::boot_clock;
using namespace std::chrono_literals;
namespace android {
@@ -50,9 +52,9 @@
static bool g_bootcharting_finished;
static long long get_uptime_jiffies() {
- std::string uptime;
- if (!android::base::ReadFileToString("/proc/uptime", &uptime)) return 0;
- return 100LL * strtod(uptime.c_str(), NULL);
+ constexpr int64_t kNanosecondsPerJiffy = 10000000;
+ boot_clock::time_point uptime = boot_clock::now();
+ return uptime.time_since_epoch().count() / kNanosecondsPerJiffy;
}
static std::unique_ptr<FILE, decltype(&fclose)> fopen_unique(const char* filename,
diff --git a/init/builtins.cpp b/init/builtins.cpp
index fc74dda..8bd92cc 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -82,6 +82,7 @@
static constexpr std::chrono::nanoseconds kCommandRetryTimeout = 5s;
static Result<Success> reboot_into_recovery(const std::vector<std::string>& options) {
+ LOG(ERROR) << "Rebooting into recovery";
std::string err;
if (!write_bootloader_message(options, &err)) {
return Error() << "Failed to set bootloader message: " << err;
@@ -285,11 +286,8 @@
if (e4crypt_is_native()) {
if (e4crypt_set_directory_policy(args[1].c_str())) {
- const std::vector<std::string> options = {
- "--prompt_and_wipe_data",
- "--reason=set_policy_failed:"s + args[1]};
- reboot_into_recovery(options);
- return Success();
+ return reboot_into_recovery(
+ {"--prompt_and_wipe_data", "--reason=set_policy_failed:"s + args[1]});
}
}
return Success();
@@ -493,8 +491,7 @@
/* Setup a wipe via recovery, and reboot into recovery */
PLOG(ERROR) << "fs_mgr_mount_all suggested recovery, so wiping data via recovery.";
const std::vector<std::string> options = {"--wipe_data", "--reason=fs_mgr_mount_all" };
- reboot_into_recovery(options);
- return Success();
+ return reboot_into_recovery(options);
/* If reboot worked, there is no return. */
} else if (code == FS_MGR_MNTALL_DEV_FILE_ENCRYPTED) {
if (e4crypt_install_keyring()) {
@@ -522,6 +519,7 @@
if (e4crypt_install_keyring()) {
return Error() << "e4crypt_install_keyring() failed";
}
+ property_set("ro.crypto.state", "encrypted");
property_set("ro.crypto.type", "file");
// Although encrypted, vold has already set the device up, so we do not need to
@@ -987,6 +985,29 @@
return android::base::GetProperty("ro.crypto.type", "") == "file";
}
+static Result<Success> ExecWithRebootOnFailure(const std::string& reboot_reason,
+ const BuiltinArguments& args) {
+ auto service = Service::MakeTemporaryOneshotService(args.args);
+ if (!service) {
+ return Error() << "Could not create exec service";
+ }
+ service->AddReapCallback([reboot_reason](const siginfo_t& siginfo) {
+ if (siginfo.si_code != CLD_EXITED || siginfo.si_status != 0) {
+ if (e4crypt_is_native()) {
+ LOG(ERROR) << "Rebooting into recovery, reason: " << reboot_reason;
+ reboot_into_recovery({"--prompt_and_wipe_data", "--reason="s + reboot_reason});
+ } else {
+ LOG(ERROR) << "Failure (reboot suppressed): " << reboot_reason;
+ }
+ }
+ });
+ if (auto result = service->ExecStart(); !result) {
+ return Error() << "Could not start exec service: " << result.error();
+ }
+ ServiceList::GetInstance().AddService(std::move(service));
+ return Success();
+}
+
static Result<Success> do_installkey(const BuiltinArguments& args) {
if (!is_file_crypto()) return Success();
@@ -994,15 +1015,15 @@
if (!make_dir(unencrypted_dir, 0700) && errno != EEXIST) {
return ErrnoError() << "Failed to create " << unencrypted_dir;
}
- std::vector<std::string> exec_args = {"exec", "/system/bin/vdc", "--wait", "cryptfs",
- "enablefilecrypto"};
- return do_exec({std::move(exec_args), args.context});
+ return ExecWithRebootOnFailure(
+ "enablefilecrypto_failed",
+ {{"exec", "/system/bin/vdc", "--wait", "cryptfs", "enablefilecrypto"}, args.context});
}
static Result<Success> do_init_user0(const BuiltinArguments& args) {
- std::vector<std::string> exec_args = {"exec", "/system/bin/vdc", "--wait", "cryptfs",
- "init_user0"};
- return do_exec({std::move(exec_args), args.context});
+ return ExecWithRebootOnFailure(
+ "init_user0_failed",
+ {{"exec", "/system/bin/vdc", "--wait", "cryptfs", "init_user0"}, args.context});
}
// Builtin-function-map start
diff --git a/init/init.cpp b/init/init.cpp
index 2f3b28a..0d5690b 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -238,6 +238,10 @@
static const std::map<std::string, ControlMessageFunction>& get_control_message_map() {
// clang-format off
static const std::map<std::string, ControlMessageFunction> control_message_functions = {
+ {"sigstop_on", {ControlTarget::SERVICE,
+ [](auto* service) { service->set_sigstop(true); return Success(); }}},
+ {"sigstop_off", {ControlTarget::SERVICE,
+ [](auto* service) { service->set_sigstop(false); return Success(); }}},
{"start", {ControlTarget::SERVICE, DoControlStart}},
{"stop", {ControlTarget::SERVICE, DoControlStop}},
{"restart", {ControlTarget::SERVICE, DoControlRestart}},
@@ -624,6 +628,14 @@
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
+ // Mount staging areas for devices managed by vold
+ // See storage config details at http://source.android.com/devices/storage/
+ mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
+ "mode=0755,uid=0,gid=1000");
+ // /mnt/vendor is used to mount vendor-specific partitions that can not be
+ // part of the vendor partition, e.g. because they are mounted read-write.
+ mkdir("/mnt/vendor", 0755);
+
// Now that tmpfs is mounted on /dev and we have /dev/kmsg, we can actually
// talk to the outside world...
InitKernelLogging(argv);
diff --git a/init/service.cpp b/init/service.cpp
index 694e5e7..03c2cee 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -155,7 +155,7 @@
}
}
-static bool ExpandArgsAndExecv(const std::vector<std::string>& args) {
+static bool ExpandArgsAndExecv(const std::vector<std::string>& args, bool sigstop) {
std::vector<std::string> expanded_args;
std::vector<char*> c_strings;
@@ -169,6 +169,10 @@
}
c_strings.push_back(nullptr);
+ if (sigstop) {
+ kill(getpid(), SIGSTOP);
+ }
+
return execv(c_strings[0], c_strings.data()) == 0;
}
@@ -303,7 +307,7 @@
}
}
-void Service::Reap() {
+void Service::Reap(const siginfo_t& siginfo) {
if (!(flags_ & SVC_ONESHOT) || (flags_ & SVC_RESTART)) {
KillProcessGroup(SIGKILL);
}
@@ -312,6 +316,10 @@
std::for_each(descriptors_.begin(), descriptors_.end(),
std::bind(&DescriptorInfo::Clean, std::placeholders::_1));
+ for (const auto& f : reap_callbacks_) {
+ f(siginfo);
+ }
+
if (flags_ & SVC_EXEC) UnSetExec();
if (flags_ & SVC_TEMPORARY) return;
@@ -578,6 +586,11 @@
return Success();
}
+Result<Success> Service::ParseSigstop(const std::vector<std::string>& args) {
+ sigstop_ = true;
+ return Success();
+}
+
Result<Success> Service::ParseSetenv(const std::vector<std::string>& args) {
environment_vars_.emplace_back(args[1], args[2]);
return Success();
@@ -700,6 +713,7 @@
{"seclabel", {1, 1, &Service::ParseSeclabel}},
{"setenv", {2, 2, &Service::ParseSetenv}},
{"shutdown", {1, 1, &Service::ParseShutdown}},
+ {"sigstop", {0, 0, &Service::ParseSigstop}},
{"socket", {3, 6, &Service::ParseSocket}},
{"user", {1, 1, &Service::ParseUser}},
{"writepid", {1, kMax, &Service::ParseWritepid}},
@@ -858,7 +872,7 @@
// priority. Aborts on failure.
SetProcessAttributes();
- if (!ExpandArgsAndExecv(args_)) {
+ if (!ExpandArgsAndExecv(args_, sigstop_)) {
PLOG(ERROR) << "cannot execve('" << args_[0] << "')";
}
diff --git a/init/service.h b/init/service.h
index d46a413..cf38f69 100644
--- a/init/service.h
+++ b/init/service.h
@@ -17,6 +17,7 @@
#ifndef _INIT_SERVICE_H
#define _INIT_SERVICE_H
+#include <signal.h>
#include <sys/resource.h>
#include <sys/types.h>
@@ -81,7 +82,7 @@
void Stop();
void Terminate();
void Restart();
- void Reap();
+ void Reap(const siginfo_t& siginfo);
void DumpState() const;
void SetShutdownCritical() { flags_ |= SVC_SHUTDOWN_CRITICAL; }
bool IsShutdownCritical() const { return (flags_ & SVC_SHUTDOWN_CRITICAL) != 0; }
@@ -89,6 +90,9 @@
is_exec_service_running_ = false;
flags_ &= ~SVC_EXEC;
}
+ void AddReapCallback(std::function<void(const siginfo_t& siginfo)> callback) {
+ reap_callbacks_.emplace_back(std::move(callback));
+ }
static bool is_exec_service_running() { return is_exec_service_running_; }
@@ -114,6 +118,7 @@
bool is_override() const { return override_; }
bool process_cgroup_empty() const { return process_cgroup_empty_; }
unsigned long start_order() const { return start_order_; }
+ void set_sigstop(bool value) { sigstop_ = value; }
const std::vector<std::string>& args() const { return args_; }
private:
@@ -149,6 +154,7 @@
Result<Success> ParseSeclabel(const std::vector<std::string>& args);
Result<Success> ParseSetenv(const std::vector<std::string>& args);
Result<Success> ParseShutdown(const std::vector<std::string>& args);
+ Result<Success> ParseSigstop(const std::vector<std::string>& args);
Result<Success> ParseSocket(const std::vector<std::string>& args);
Result<Success> ParseFile(const std::vector<std::string>& args);
Result<Success> ParseUser(const std::vector<std::string>& args);
@@ -209,7 +215,11 @@
std::vector<std::pair<int, rlimit>> rlimits_;
+ bool sigstop_ = false;
+
std::vector<std::string> args_;
+
+ std::vector<std::function<void(const siginfo_t& siginfo)>> reap_callbacks_;
};
class ServiceList {
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index 3ec76df..0b03324 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -81,16 +81,15 @@
}
}
- auto status = siginfo.si_status;
- if (WIFEXITED(status)) {
- LOG(INFO) << name << " exited with status " << WEXITSTATUS(status) << wait_string;
- } else if (WIFSIGNALED(status)) {
- LOG(INFO) << name << " killed by signal " << WTERMSIG(status) << wait_string;
+ if (siginfo.si_code == CLD_EXITED) {
+ LOG(INFO) << name << " exited with status " << siginfo.si_status << wait_string;
+ } else {
+ LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
}
if (!service) return true;
- service->Reap();
+ service->Reap(siginfo);
if (service->flags() & SVC_TEMPORARY) {
ServiceList::GetInstance().RemoveService(*service);
diff --git a/libcutils/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index 5d17698..3be8ad0 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -129,6 +129,8 @@
#define AID_STATSD 1066 /* statsd daemon */
#define AID_INCIDENTD 1067 /* incidentd daemon */
#define AID_SECURE_ELEMENT 1068 /* secure element subsystem */
+#define AID_LMKD 1069 /* low memory killer daemon */
+#define AID_LLKD 1070 /* live lock daemon */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libmemunreachable/Android.bp b/libmemunreachable/Android.bp
index f872d0f..619ee34 100644
--- a/libmemunreachable/Android.bp
+++ b/libmemunreachable/Android.bp
@@ -46,6 +46,12 @@
static_libs: ["libunwind_llvm"],
},
},
+
+ // TODO(b/78118944), clang lld link flags do not work with special link
+ // rules for libunwind_llvm yet. Linked aosp_arm-eng image failed to
+ // boot up in the emulator.
+ use_clang_lld: false,
+
export_include_dirs: ["include"],
local_include_dirs: ["include"],
}
diff --git a/libziparchive/Android.bp b/libziparchive/Android.bp
index 075fb86..ed1b9bc 100644
--- a/libziparchive/Android.bp
+++ b/libziparchive/Android.bp
@@ -61,6 +61,7 @@
vndk: {
enabled: true,
},
+ double_loadable: true,
defaults: [
"libziparchive_defaults",
diff --git a/llkd/Android.bp b/llkd/Android.bp
new file mode 100644
index 0000000..a6edd26
--- /dev/null
+++ b/llkd/Android.bp
@@ -0,0 +1,42 @@
+cc_library_headers {
+ name: "llkd_headers",
+
+ export_include_dirs: ["include"],
+}
+
+cc_library_static {
+ name: "libllkd",
+
+ srcs: [
+ "libllkd.cpp",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
+
+ export_include_dirs: ["include"],
+
+ cflags: ["-Werror"],
+}
+
+cc_binary {
+ name: "llkd",
+
+ srcs: [
+ "llkd.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ ],
+ static_libs: [
+ "libllkd",
+ ],
+ cflags: ["-Werror"],
+
+ init_rc: ["llkd.rc"],
+}
diff --git a/llkd/OWNERS b/llkd/OWNERS
new file mode 100644
index 0000000..b6af537
--- /dev/null
+++ b/llkd/OWNERS
@@ -0,0 +1,2 @@
+salyzyn@google.com
+surenb@google.com
diff --git a/llkd/README.md b/llkd/README.md
new file mode 100644
index 0000000..b2ba2a2
--- /dev/null
+++ b/llkd/README.md
@@ -0,0 +1,117 @@
+Android Live-LocK Daemon
+========================
+
+Introduction
+------------
+
+Android Live-LocK Daemon (llkd) is used to catch kernel deadlocks and mitigate.
+
+Code is structured to allow integration into another service as either as part
+of the main loop, or spun off as a thread should that be necessary. A default
+standalone implementation is provided by llkd component.
+
+The 'C' interface from libllkd component is thus:
+
+ #include "llkd.h"
+ bool llkInit(const char* threadname) /* return true if enabled */
+ unsigned llkCheckMillseconds(void) /* ms to sleep for next check */
+
+If a threadname is provided, a thread will be automatically spawned, otherwise
+caller must call llkCheckMilliseconds in its main loop. Function will return
+the period of time before the next expected call to this handler.
+
+Operations
+----------
+
+If a thread is in D or Z state with no forward progress for longer than
+ro.llk.timeout_ms, or ro.llk.[D|Z].timeout_ms, kill the process or parent
+process respectively. If another scan shows the same process continues to
+exist, then have a confirmed live-lock condition and need to panic. Panic
+the kernel in a manner to provide the greatest bugreporting details as to the
+condition. Add a alarm self watchdog should llkd ever get locked up that is
+double the expected time to flow through the mainloop. Sampling is every
+ro.llk_sample_ms.
+
+Default will not monitor init, or [kthreadd] and all that [kthreadd] spawns.
+This reduces the effectiveness of llkd by limiting its coverage. If there is
+value in covering [kthreadd] spawned threads, the requirement will be that
+the drivers not remain in a persistent 'D' state, or that they have mechanisms
+to recover the thread should it be killed externally (this is good driver
+coding hygiene, a common request to add such to publicly reviewed kernel.org
+maintained drivers). For instance use wait_event_interruptible() instead of
+wait_event(). The blacklists can be adjusted accordingly if these
+conditions are met to cover kernel components.
+
+An accompanying gTest set have been added, and will setup a persistent D or Z
+process, with and without forward progress, but not in a live-lock state
+because that would require a buggy kernel, or a module or kernel modification
+to stimulate. The test will check that llkd will mitigate first by killing
+the appropriate process. D state is setup by vfork() waiting for exec() in
+child process. Z state is setup by fork() and an un-waited for child process.
+Should be noted that both of these conditions should never happen on Android
+on purpose, and llkd effectively sweeps up processes that create these
+conditions. If the test can, it will reconfigure llkd to expedite the test
+duration by adjusting the ro.llk.* Android properties. Tests run the D state
+with some scheduling progress to ensure that ABA checking prevents false
+triggers. If 100% reliable ABA on platform, then ro.llk.killtest can be
+set to false; however this will result in some of the unit tests to panic
+kernel instead of deal with more graceful kill operation.
+
+Android Properties
+------------------
+
+Android Properties llkd respond to (<prop>_ms parms are in milliseconds):
+
+#### ro.config.low_ram
+default false, if true do not sysrq t (dump all threads).
+
+#### ro.llk.enable
+default false, allow live-lock daemon to be enabled.
+
+#### llk.enable
+default ro.llk.enable, and evaluated for eng.
+
+#### ro.khungtask.enable
+default false, allow [khungtask] daemon to be enabled.
+
+#### khungtask.enable
+default ro.khungtask.enable and evaluated for eng.
+
+#### ro.llk.mlockall
+default false, enable call to mlockall().
+
+#### ro.khungtask.timeout
+default value 12 minutes, [khungtask] maximum timelimit.
+
+#### ro.llk.timeout_ms
+default 10 minutes, D or Z maximum timelimit, double this value and it sets
+the alarm watchdog for llkd.
+
+#### ro.llk.D.timeout_ms
+default ro.llk.timeout_ms, D maximum timelimit.
+
+#### ro.llk.Z.timeout_ms
+default ro.llk.timeout_ms, Z maximum timelimit.
+
+#### ro.llk.check_ms
+default 2 minutes samples of threads for D or Z.
+
+#### ro.llk.blacklist.process
+default 0,1,2 (kernel, init and [kthreadd]) plus process names
+init,[kthreadd],[khungtaskd],lmkd,lmkd.llkd,llkd,watchdogd,
+[watchdogd],[watchdogd/0],...,[watchdogd/<get_nprocs-1>].
+
+#### ro.llk.blacklist.parent
+default 0,2 (kernel and [kthreadd]).
+
+#### ro.llk.blacklist.uid
+default <empty>, comma separated list of uid numbers or names.
+
+Architectural Concerns
+----------------------
+
+- Create kernel module and associated gTest to actually test panic.
+- Create gTest to test out blacklist (ro.llk.blacklist.<properties> generally
+ not be inputs). Could require more test-only interfaces to libllkd.
+- Speed up gTest using something else than ro.llk.<properties>, which should
+ not be inputs.
diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h
new file mode 100644
index 0000000..e3ae4bb
--- /dev/null
+++ b/llkd/include/llkd.h
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+#ifndef _LLKD_H_
+#define _LLKD_H_
+
+#ifndef LOG_TAG
+#define LOG_TAG "livelock"
+#endif
+
+#include <stdbool.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+bool llkInit(const char* threadname); /* threadname NULL, not spawned */
+unsigned llkCheckMilliseconds(void);
+
+/* clang-format off */
+#define LLK_ENABLE_WRITEABLE_PROPERTY "llk.enable"
+#define LLK_ENABLE_PROPERTY "ro." LLK_ENABLE_WRITEABLE_PROPERTY
+#define LLK_ENABLE_DEFAULT false /* "eng" and userdebug true */
+#define KHT_ENABLE_WRITEABLE_PROPERTY "khungtask.enable"
+#define KHT_ENABLE_PROPERTY "ro." KHT_ENABLE_WRITEABLE_PROPERTY
+#define LLK_MLOCKALL_PROPERTY "ro.llk.mlockall"
+#define LLK_MLOCKALL_DEFAULT true
+#define LLK_KILLTEST_PROPERTY "ro.llk.killtest"
+#define LLK_KILLTEST_DEFAULT true
+#define LLK_TIMEOUT_MS_PROPERTY "ro.llk.timeout_ms"
+#define KHT_TIMEOUT_PROPERTY "ro.khungtask.timeout"
+#define LLK_D_TIMEOUT_MS_PROPERTY "ro.llk.D.timeout_ms"
+#define LLK_Z_TIMEOUT_MS_PROPERTY "ro.llk.Z.timeout_ms"
+#define LLK_CHECK_MS_PROPERTY "ro.llk.check_ms"
+/* LLK_CHECK_MS_DEFAULT = actual timeout_ms / LLK_CHECKS_PER_TIMEOUT_DEFAULT */
+#define LLK_CHECKS_PER_TIMEOUT_DEFAULT 5
+#define LLK_BLACKLIST_PROCESS_PROPERTY "ro.llk.blacklist.process"
+#define LLK_BLACKLIST_PROCESS_DEFAULT \
+ "0,1,2,init,[kthreadd],[khungtaskd],lmkd,lmkd.llkd,llkd,watchdogd,[watchdogd],[watchdogd/0]"
+#define LLK_BLACKLIST_PARENT_PROPERTY "ro.llk.blacklist.parent"
+#define LLK_BLACKLIST_PARENT_DEFAULT "0,2,[kthreadd]"
+#define LLK_BLACKLIST_UID_PROPERTY "ro.llk.blacklist.uid"
+#define LLK_BLACKLIST_UID_DEFAULT ""
+/* clang-format on */
+
+__END_DECLS
+
+#ifdef __cplusplus
+extern "C++" { /* In case this included wrapped with __BEGIN_DECLS */
+
+#include <chrono>
+
+__BEGIN_DECLS
+/* C++ code allowed to not specify threadname argument for this C linkage */
+bool llkInit(const char* threadname = nullptr);
+__END_DECLS
+std::chrono::milliseconds llkCheck(bool checkRunning = false);
+
+/* clang-format off */
+#define LLK_TIMEOUT_MS_DEFAULT std::chrono::duration_cast<milliseconds>(std::chrono::minutes(10))
+#define LLK_TIMEOUT_MS_MINIMUM std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::seconds(10))
+#define LLK_CHECK_MS_MINIMUM std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::seconds(1))
+/* clang-format on */
+
+} /* extern "C++" */
+#endif /* __cplusplus */
+
+#endif /* _LLKD_H_ */
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
new file mode 100644
index 0000000..f357cc2
--- /dev/null
+++ b/llkd/libllkd.cpp
@@ -0,0 +1,1168 @@
+/*
+ * 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 "llkd.h"
+
+#include <ctype.h>
+#include <dirent.h> // opendir() and readdir()
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <pwd.h> // getpwuid()
+#include <signal.h>
+#include <stdint.h>
+#include <sys/cdefs.h> // ___STRING, __predict_true() and _predict_false()
+#include <sys/mman.h> // mlockall()
+#include <sys/prctl.h>
+#include <sys/stat.h> // lstat()
+#include <sys/syscall.h> // __NR_getdents64
+#include <sys/sysinfo.h> // get_nprocs_conf()
+#include <sys/types.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <ios>
+#include <sstream>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/strings.h>
+#include <cutils/android_get_control_file.h>
+#include <log/log_main.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x)))
+
+#define TASK_COMM_LEN 16 // internal kernel, not uapi, from .../linux/include/linux/sched.h
+
+using namespace std::chrono_literals;
+using namespace std::chrono;
+
+namespace {
+
+constexpr pid_t kernelPid = 0;
+constexpr pid_t initPid = 1;
+constexpr pid_t kthreaddPid = 2;
+
+constexpr char procdir[] = "/proc/";
+
+// Configuration
+milliseconds llkUpdate; // last check ms signature
+milliseconds llkCycle; // ms to next thread check
+bool llkEnable = LLK_ENABLE_DEFAULT; // llk daemon enabled
+bool llkRunning = false; // thread is running
+bool llkMlockall = LLK_MLOCKALL_DEFAULT; // run mlocked
+bool llkTestWithKill = LLK_KILLTEST_DEFAULT; // issue test kills
+milliseconds llkTimeoutMs = LLK_TIMEOUT_MS_DEFAULT; // default timeout
+enum { llkStateD, llkStateZ, llkNumStates }; // state indexes
+milliseconds llkStateTimeoutMs[llkNumStates]; // timeout override for each detection state
+milliseconds llkCheckMs; // checking interval to inspect any
+ // persistent live-locked states
+bool llkLowRam; // ro.config.low_ram
+bool khtEnable = LLK_ENABLE_DEFAULT; // [khungtaskd] panic
+// [khungtaskd] should have a timeout beyond the granularity of llkTimeoutMs.
+// Provides a wide angle of margin b/c khtTimeout is also its granularity.
+seconds khtTimeout = duration_cast<seconds>(llkTimeoutMs * (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT) /
+ LLK_CHECKS_PER_TIMEOUT_DEFAULT);
+
+// Blacklist variables, initialized with comma separated lists of high false
+// positive and/or dangerous references, e.g. without self restart, for pid,
+// ppid, name and uid:
+
+// list of pids, or tids or names to skip. kernel pid (0), init pid (1),
+// [kthreadd] pid (2), ourselves, "init", "[kthreadd]", "lmkd", "llkd" or
+// combinations of watchdogd in kernel and user space.
+std::unordered_set<std::string> llkBlacklistProcess;
+// list of parent pids, comm or cmdline names to skip. default:
+// kernel pid (0), [kthreadd] (2), or ourselves, enforced and implied
+std::unordered_set<std::string> llkBlacklistParent;
+// list of uids, and uid names, to skip, default nothing
+std::unordered_set<std::string> llkBlacklistUid;
+
+class dir {
+ public:
+ enum level { proc, task, numLevels };
+
+ private:
+ int fd;
+ size_t available_bytes;
+ dirent* next;
+ // each directory level picked to be just north of 4K in size
+ static constexpr size_t buffEntries = 15;
+ static dirent buff[numLevels][buffEntries];
+
+ bool fill(enum level index) {
+ if (index >= numLevels) return false;
+ if (available_bytes != 0) return true;
+ if (__predict_false(fd < 0)) return false;
+ // getdents64 has no libc wrapper
+ auto rc = TEMP_FAILURE_RETRY(syscall(__NR_getdents64, fd, buff[index], sizeof(buff[0]), 0));
+ if (rc <= 0) return false;
+ available_bytes = rc;
+ next = buff[index];
+ return true;
+ }
+
+ public:
+ dir() : fd(-1), available_bytes(0), next(nullptr) {}
+
+ explicit dir(const char* directory)
+ : fd(__predict_true(directory != nullptr)
+ ? ::open(directory, O_CLOEXEC | O_DIRECTORY | O_RDONLY)
+ : -1),
+ available_bytes(0),
+ next(nullptr) {}
+
+ explicit dir(const std::string&& directory)
+ : fd(::open(directory.c_str(), O_CLOEXEC | O_DIRECTORY | O_RDONLY)),
+ available_bytes(0),
+ next(nullptr) {}
+
+ explicit dir(const std::string& directory)
+ : fd(::open(directory.c_str(), O_CLOEXEC | O_DIRECTORY | O_RDONLY)),
+ available_bytes(0),
+ next(nullptr) {}
+
+ // Don't need any copy or move constructors.
+ explicit dir(const dir& c) = delete;
+ explicit dir(dir& c) = delete;
+ explicit dir(dir&& c) = delete;
+
+ ~dir() {
+ if (fd >= 0) {
+ ::close(fd);
+ }
+ }
+
+ operator bool() const { return fd >= 0; }
+
+ void reset(void) {
+ if (fd >= 0) {
+ ::close(fd);
+ fd = -1;
+ available_bytes = 0;
+ next = nullptr;
+ }
+ }
+
+ dir& reset(const char* directory) {
+ reset();
+ // available_bytes will _always_ be zero here as its value is
+ // intimately tied to fd < 0 or not.
+ fd = ::open(directory, O_CLOEXEC | O_DIRECTORY | O_RDONLY);
+ return *this;
+ }
+
+ void rewind(void) {
+ if (fd >= 0) {
+ ::lseek(fd, off_t(0), SEEK_SET);
+ available_bytes = 0;
+ next = nullptr;
+ }
+ }
+
+ dirent* read(enum level index = proc, dirent* def = nullptr) {
+ if (!fill(index)) return def;
+ auto ret = next;
+ available_bytes -= next->d_reclen;
+ next = reinterpret_cast<dirent*>(reinterpret_cast<char*>(next) + next->d_reclen);
+ return ret;
+ }
+} llkTopDirectory;
+
+dirent dir::buff[dir::numLevels][dir::buffEntries];
+
+// helper functions
+
+bool llkIsMissingExeLink(pid_t tid) {
+ char c;
+ // CAP_SYS_PTRACE is required to prevent ret == -1, but ENOENT is signal
+ auto ret = ::readlink((procdir + std::to_string(tid) + "/exe").c_str(), &c, sizeof(c));
+ return (ret == -1) && (errno == ENOENT);
+}
+
+// Common routine where caller accepts empty content as error/passthrough.
+// Reduces the churn of reporting read errors in the callers.
+std::string ReadFile(std::string&& path) {
+ std::string content;
+ if (!android::base::ReadFileToString(path, &content)) {
+ PLOG(DEBUG) << "Read " << path << " failed";
+ content = "";
+ }
+ return content;
+}
+
+std::string llkProcGetName(pid_t tid, const char* node = "/cmdline") {
+ std::string content = ReadFile(procdir + std::to_string(tid) + node);
+ static constexpr char needles[] = " \t\r\n"; // including trailing nul
+ auto pos = content.find_first_of(needles, 0, sizeof(needles));
+ if (pos != std::string::npos) {
+ content.erase(pos);
+ }
+ return content;
+}
+
+uid_t llkProcGetUid(pid_t tid) {
+ // Get the process' uid. The following read from /status is admittedly
+ // racy, prone to corruption due to shape-changes. The consequences are
+ // not catastrophic as we sample a few times before taking action.
+ //
+ // If /loginuid worked on reliably, or on Android (all tasks report -1)...
+ // Android lmkd causes /cgroup to contain memory:/<dom>/uid_<uid>/pid_<pid>
+ // which is tighter, but also not reliable.
+ std::string content = ReadFile(procdir + std::to_string(tid) + "/status");
+ static constexpr char Uid[] = "\nUid:";
+ auto pos = content.find(Uid);
+ if (pos == std::string::npos) {
+ return -1;
+ }
+ pos += ::strlen(Uid);
+ while ((pos < content.size()) && ::isblank(content[pos])) {
+ ++pos;
+ }
+ content.erase(0, pos);
+ for (pos = 0; (pos < content.size()) && ::isdigit(content[pos]); ++pos) {
+ ;
+ }
+ // Content of form 'Uid: 0 0 0 0', newline is error
+ if ((pos >= content.size()) || !::isblank(content[pos])) {
+ return -1;
+ }
+ content.erase(pos);
+ uid_t ret;
+ if (!android::base::ParseInt(content, &ret, uid_t(0))) {
+ return -1;
+ }
+ return ret;
+}
+
+struct proc {
+ pid_t tid; // monitored thread id (in Z or D state).
+ nanoseconds schedUpdate; // /proc/<tid>/sched "se.avg.lastUpdateTime",
+ uint64_t nrSwitches; // /proc/<tid>/sched "nr_switches" for
+ // refined ABA problem detection, determine
+ // forward scheduling progress.
+ milliseconds update; // llkUpdate millisecond signature of last.
+ milliseconds count; // duration in state.
+ pid_t pid; // /proc/<pid> before iterating through
+ // /proc/<pid>/task/<tid> for threads.
+ pid_t ppid; // /proc/<tid>/stat field 4 parent pid.
+ uid_t uid; // /proc/<tid>/status Uid: field.
+ unsigned time; // sum of /proc/<tid>/stat field 14 utime &
+ // 15 stime for coarse ABA problem detection.
+ std::string cmdline; // cached /cmdline content
+ char state; // /proc/<tid>/stat field 3: Z or D
+ // (others we do not monitor: S, R, T or ?)
+ char comm[TASK_COMM_LEN + 3]; // space for adding '[' and ']'
+ bool exeMissingValid; // exeMissing has been cached
+ bool cmdlineValid; // cmdline has been cached
+ bool updated; // cleared before monitoring pass.
+ bool killed; // sent a kill to this thread, next panic...
+
+ void setComm(const char* _comm) { strncpy(comm + 1, _comm, sizeof(comm) - 2); }
+
+ proc(pid_t tid, pid_t pid, pid_t ppid, const char* _comm, int time, char state)
+ : tid(tid),
+ schedUpdate(0),
+ nrSwitches(0),
+ update(llkUpdate),
+ count(0),
+ pid(pid),
+ ppid(ppid),
+ uid(-1),
+ time(time),
+ state(state),
+ exeMissingValid(false),
+ cmdlineValid(false),
+ updated(true),
+ killed(!llkTestWithKill) {
+ memset(comm, '\0', sizeof(comm));
+ setComm(_comm);
+ }
+
+ const char* getComm(void) {
+ if (comm[1] == '\0') { // comm Valid?
+ strncpy(comm + 1, llkProcGetName(tid, "/comm").c_str(), sizeof(comm) - 2);
+ }
+ if (!exeMissingValid) {
+ if (llkIsMissingExeLink(tid)) {
+ comm[0] = '[';
+ }
+ exeMissingValid = true;
+ }
+ size_t len = strlen(comm + 1);
+ if (__predict_true(len < (sizeof(comm) - 1))) {
+ if (comm[0] == '[') {
+ if ((comm[len] != ']') && __predict_true(len < (sizeof(comm) - 2))) {
+ comm[++len] = ']';
+ comm[++len] = '\0';
+ }
+ } else {
+ if (comm[len] == ']') {
+ comm[len] = '\0';
+ }
+ }
+ }
+ return &comm[comm[0] != '['];
+ }
+
+ const char* getCmdline(void) {
+ if (!cmdlineValid) {
+ cmdline = llkProcGetName(tid);
+ cmdlineValid = true;
+ }
+ return cmdline.c_str();
+ }
+
+ uid_t getUid(void) {
+ if (uid <= 0) { // Churn on root user, because most likely to setuid()
+ uid = llkProcGetUid(tid);
+ }
+ return uid;
+ }
+
+ void reset(void) { // reset cache, if we detected pid rollover
+ uid = -1;
+ state = '?';
+ cmdline = "";
+ comm[0] = '\0';
+ exeMissingValid = false;
+ cmdlineValid = false;
+ }
+};
+
+std::unordered_map<pid_t, proc> tids;
+
+// Check range and setup defaults, in order of propagation:
+// llkTimeoutMs
+// llkCheckMs
+// ...
+// KISS to keep it all self-contained, and called multiple times as parameters
+// are interpreted so that defaults, llkCheckMs and llkCycle make sense.
+void llkValidate() {
+ if (llkTimeoutMs == 0ms) {
+ llkTimeoutMs = LLK_TIMEOUT_MS_DEFAULT;
+ }
+ llkTimeoutMs = std::max(llkTimeoutMs, LLK_TIMEOUT_MS_MINIMUM);
+ if (llkCheckMs == 0ms) {
+ llkCheckMs = llkTimeoutMs / LLK_CHECKS_PER_TIMEOUT_DEFAULT;
+ }
+ llkCheckMs = std::min(llkCheckMs, llkTimeoutMs);
+
+ for (size_t state = 0; state < ARRAY_SIZE(llkStateTimeoutMs); ++state) {
+ if (llkStateTimeoutMs[state] == 0ms) {
+ llkStateTimeoutMs[state] = llkTimeoutMs;
+ }
+ llkStateTimeoutMs[state] =
+ std::min(std::max(llkStateTimeoutMs[state], LLK_TIMEOUT_MS_MINIMUM), llkTimeoutMs);
+ llkCheckMs = std::min(llkCheckMs, llkStateTimeoutMs[state]);
+ }
+
+ llkCheckMs = std::max(llkCheckMs, LLK_CHECK_MS_MINIMUM);
+ if (llkCycle == 0ms) {
+ llkCycle = llkCheckMs;
+ }
+ llkCycle = std::min(llkCycle, llkCheckMs);
+}
+
+milliseconds llkGetTimespecDiffMs(timespec* from, timespec* to) {
+ return duration_cast<milliseconds>(seconds(to->tv_sec - from->tv_sec)) +
+ duration_cast<milliseconds>(nanoseconds(to->tv_nsec - from->tv_nsec));
+}
+
+std::string llkProcGetName(pid_t tid, const char* comm, const char* cmdline) {
+ if ((cmdline != nullptr) && (*cmdline != '\0')) {
+ return cmdline;
+ }
+ if ((comm != nullptr) && (*comm != '\0')) {
+ return comm;
+ }
+
+ // UNLIKELY! Here because killed before we kill it?
+ // Assume change is afoot, do not call llkTidAlloc
+
+ // cmdline ?
+ std::string content = llkProcGetName(tid);
+ if (content.size() != 0) {
+ return content;
+ }
+ // Comm instead?
+ content = llkProcGetName(tid, "/comm");
+ if (llkIsMissingExeLink(tid) && (content.size() != 0)) {
+ return '[' + content + ']';
+ }
+ return content;
+}
+
+int llkKillOneProcess(pid_t pid, char state, pid_t tid, const char* tcomm = nullptr,
+ const char* tcmdline = nullptr, const char* pcomm = nullptr,
+ const char* pcmdline = nullptr) {
+ std::string forTid;
+ if (tid != pid) {
+ forTid = " for '" + llkProcGetName(tid, tcomm, tcmdline) + "' (" + std::to_string(tid) + ")";
+ }
+ LOG(INFO) << "Killing '" << llkProcGetName(pid, pcomm, pcmdline) << "' (" << pid
+ << ") to check forward scheduling progress in " << state << " state" << forTid;
+ // CAP_KILL required
+ errno = 0;
+ auto r = ::kill(pid, SIGKILL);
+ if (r) {
+ PLOG(ERROR) << "kill(" << pid << ")=" << r << ' ';
+ }
+
+ return r;
+}
+
+// Kill one process
+int llkKillOneProcess(pid_t pid, proc* tprocp) {
+ return llkKillOneProcess(pid, tprocp->state, tprocp->tid, tprocp->getComm(),
+ tprocp->getCmdline());
+}
+
+// Kill one process specified by kprocp
+int llkKillOneProcess(proc* kprocp, proc* tprocp) {
+ if (kprocp == nullptr) {
+ return -2;
+ }
+
+ return llkKillOneProcess(kprocp->tid, tprocp->state, tprocp->tid, tprocp->getComm(),
+ tprocp->getCmdline(), kprocp->getComm(), kprocp->getCmdline());
+}
+
+// Acquire file descriptor from environment, or open and cache it.
+// NB: cache is unnecessary in our current context, pedantically
+// required to prevent leakage of file descriptors in the future.
+int llkFileToWriteFd(const std::string& file) {
+ static std::unordered_map<std::string, int> cache;
+ auto search = cache.find(file);
+ if (search != cache.end()) return search->second;
+ auto fd = android_get_control_file(file.c_str());
+ if (fd >= 0) return fd;
+ fd = TEMP_FAILURE_RETRY(::open(file.c_str(), O_WRONLY | O_CLOEXEC));
+ if (fd >= 0) cache.emplace(std::make_pair(file, fd));
+ return fd;
+}
+
+// Wrap android::base::WriteStringToFile to use android_get_control_file.
+bool llkWriteStringToFile(const std::string& string, const std::string& file) {
+ auto fd = llkFileToWriteFd(file);
+ if (fd < 0) return false;
+ return android::base::WriteStringToFd(string, fd);
+}
+
+bool llkWriteStringToFileConfirm(const std::string& string, const std::string& file) {
+ auto fd = llkFileToWriteFd(file);
+ auto ret = (fd < 0) ? false : android::base::WriteStringToFd(string, fd);
+ std::string content;
+ if (!android::base::ReadFileToString(file, &content)) return ret;
+ return android::base::Trim(content) == string;
+}
+
+void llkPanicKernel(bool dump, pid_t tid, const char* state) __noreturn;
+void llkPanicKernel(bool dump, pid_t tid, const char* state) {
+ auto sysrqTriggerFd = llkFileToWriteFd("/proc/sysrq-trigger");
+ if (sysrqTriggerFd < 0) {
+ // DYB
+ llkKillOneProcess(initPid, 'R', tid);
+ // The answer to life, the universe and everything
+ ::exit(42);
+ // NOTREACHED
+ }
+ ::sync();
+ if (dump) {
+ // Show all locks that are held
+ android::base::WriteStringToFd("d", sysrqTriggerFd);
+ // This can trigger hardware watchdog, that is somewhat _ok_.
+ // But useless if pstore configured for <256KB, low ram devices ...
+ if (!llkLowRam) {
+ android::base::WriteStringToFd("t", sysrqTriggerFd);
+ }
+ ::usleep(200000); // let everything settle
+ }
+ llkWriteStringToFile(std::string("SysRq : Trigger a crash : 'livelock,") + state + "'\n",
+ "/dev/kmsg");
+ android::base::WriteStringToFd("c", sysrqTriggerFd);
+ // NOTREACHED
+ // DYB
+ llkKillOneProcess(initPid, 'R', tid);
+ // I sat at my desk, stared into the garden and thought '42 will do'.
+ // I typed it out. End of story
+ ::exit(42);
+ // NOTREACHED
+}
+
+void llkAlarmHandler(int) {
+ llkPanicKernel(false, ::getpid(), "alarm");
+}
+
+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())));
+}
+
+proc* llkTidLookup(pid_t tid) {
+ auto search = tids.find(tid);
+ if (search == tids.end()) {
+ return nullptr;
+ }
+ return &search->second;
+}
+
+void llkTidRemove(pid_t tid) {
+ tids.erase(tid);
+}
+
+proc* llkTidAlloc(pid_t tid, pid_t pid, pid_t ppid, const char* comm, int time, char state) {
+ auto it = tids.emplace(std::make_pair(tid, proc(tid, pid, ppid, comm, time, state)));
+ return &it.first->second;
+}
+
+std::string llkFormat(milliseconds ms) {
+ auto sec = duration_cast<seconds>(ms);
+ std::ostringstream s;
+ s << sec.count() << '.';
+ auto f = s.fill('0');
+ auto w = s.width(3);
+ s << std::right << (ms - sec).count();
+ s.width(w);
+ s.fill(f);
+ s << 's';
+ return s.str();
+}
+
+std::string llkFormat(seconds s) {
+ return std::to_string(s.count()) + 's';
+}
+
+std::string llkFormat(bool flag) {
+ return flag ? "true" : "false";
+}
+
+std::string llkFormat(const std::unordered_set<std::string>& blacklist) {
+ std::string ret;
+ for (auto entry : blacklist) {
+ if (ret.size()) {
+ ret += ",";
+ }
+ ret += entry;
+ }
+ return ret;
+}
+
+// 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<std::string> llkSplit(const std::string& s,
+ const std::string& delimiters = ", \t:") {
+ std::unordered_set<std::string> result;
+
+ size_t base = 0;
+ size_t found;
+ while (true) {
+ found = s.find_first_of(delimiters, base);
+ result.emplace(s.substr(base, found - base));
+ if (found == s.npos) break;
+ base = found + 1;
+ }
+ return result;
+}
+
+bool llkSkipName(const std::string& name,
+ const std::unordered_set<std::string>& blacklist = llkBlacklistProcess) {
+ if ((name.size() == 0) || (blacklist.size() == 0)) {
+ return false;
+ }
+
+ return blacklist.find(name) != blacklist.end();
+}
+
+bool llkSkipPid(pid_t pid) {
+ return llkSkipName(std::to_string(pid), llkBlacklistProcess);
+}
+
+bool llkSkipPpid(pid_t ppid) {
+ return llkSkipName(std::to_string(ppid), llkBlacklistParent);
+}
+
+bool llkSkipUid(uid_t uid) {
+ // Match by number?
+ if (llkSkipName(std::to_string(uid), llkBlacklistUid)) {
+ return true;
+ }
+
+ // Match by name?
+ auto pwd = ::getpwuid(uid);
+ return (pwd != nullptr) && __predict_true(pwd->pw_name != nullptr) &&
+ __predict_true(pwd->pw_name[0] != '\0') && llkSkipName(pwd->pw_name, llkBlacklistUid);
+}
+
+bool getValidTidDir(dirent* dp, std::string* piddir) {
+ if (!::isdigit(dp->d_name[0])) {
+ return false;
+ }
+
+ // Corner case can not happen in reality b/c of above ::isdigit check
+ if (__predict_false(dp->d_type != DT_DIR)) {
+ if (__predict_false(dp->d_type == DT_UNKNOWN)) { // can't b/c procfs
+ struct stat st;
+ *piddir = procdir;
+ *piddir += dp->d_name;
+ return (lstat(piddir->c_str(), &st) == 0) && (st.st_mode & S_IFDIR);
+ }
+ return false;
+ }
+
+ *piddir = procdir;
+ *piddir += dp->d_name;
+ return true;
+}
+
+bool llkIsMonitorState(char state) {
+ return (state == 'Z') || (state == 'D');
+}
+
+// returns -1 if not found
+long long getSchedValue(const std::string& schedString, const char* key) {
+ auto pos = schedString.find(key);
+ if (pos == std::string::npos) {
+ return -1;
+ }
+ pos = schedString.find(':', pos);
+ if (__predict_false(pos == std::string::npos)) {
+ return -1;
+ }
+ while ((++pos < schedString.size()) && ::isblank(schedString[pos])) {
+ ;
+ }
+ long long ret;
+ if (!android::base::ParseInt(schedString.substr(pos), &ret, static_cast<long long>(0))) {
+ return -1;
+ }
+ return ret;
+}
+
+// Primary ABA mitigation watching last time schedule activity happened
+void llkCheckSchedUpdate(proc* procp, const std::string& piddir) {
+ // Audit finds /proc/<tid>/sched is just over 1K, and
+ // is rarely larger than 2K, even less on Android.
+ // For example, the "se.avg.lastUpdateTime" field we are
+ // interested in typically within the primary set in
+ // the first 1K.
+ //
+ // Proc entries can not be read >1K atomically via libbase,
+ // 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) {
+ // /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) {
+ return;
+ }
+ auto val = static_cast<unsigned long long>(-1);
+ if (((::sscanf(schedString.c_str(), "%*d %*d %llu", &val)) == 1) &&
+ (val != static_cast<unsigned long long>(-1)) && (val != 0) &&
+ (val != procp->nrSwitches)) {
+ procp->nrSwitches = val;
+ procp->count = 0ms;
+ procp->killed = !llkTestWithKill;
+ }
+ return;
+ }
+
+ auto val = getSchedValue(schedString, "\nse.avg.lastUpdateTime");
+ if (val == -1) {
+ val = getSchedValue(schedString, "\nse.svg.last_update_time");
+ }
+ if (val != -1) {
+ auto schedUpdate = nanoseconds(val);
+ if (schedUpdate != procp->schedUpdate) {
+ procp->schedUpdate = schedUpdate;
+ procp->count = 0ms;
+ procp->killed = !llkTestWithKill;
+ }
+ }
+
+ val = getSchedValue(schedString, "\nnr_switches");
+ if (val != -1) {
+ if (static_cast<uint64_t>(val) != procp->nrSwitches) {
+ procp->nrSwitches = val;
+ procp->count = 0ms;
+ procp->killed = !llkTestWithKill;
+ }
+ }
+}
+
+void llkLogConfig(void) {
+ LOG(INFO) << "ro.config.low_ram=" << llkFormat(llkLowRam) << "\n"
+ << LLK_ENABLE_PROPERTY "=" << llkFormat(llkEnable) << "\n"
+ << KHT_ENABLE_PROPERTY "=" << llkFormat(khtEnable) << "\n"
+ << LLK_MLOCKALL_PROPERTY "=" << llkFormat(llkMlockall) << "\n"
+ << LLK_KILLTEST_PROPERTY "=" << llkFormat(llkTestWithKill) << "\n"
+ << KHT_TIMEOUT_PROPERTY "=" << llkFormat(khtTimeout) << "\n"
+ << LLK_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkTimeoutMs) << "\n"
+ << LLK_D_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkStateTimeoutMs[llkStateD]) << "\n"
+ << LLK_Z_TIMEOUT_MS_PROPERTY "=" << llkFormat(llkStateTimeoutMs[llkStateZ]) << "\n"
+ << LLK_CHECK_MS_PROPERTY "=" << llkFormat(llkCheckMs) << "\n"
+ << LLK_BLACKLIST_PROCESS_PROPERTY "=" << llkFormat(llkBlacklistProcess) << "\n"
+ << LLK_BLACKLIST_PARENT_PROPERTY "=" << llkFormat(llkBlacklistParent) << "\n"
+ << LLK_BLACKLIST_UID_PROPERTY "=" << llkFormat(llkBlacklistUid);
+}
+
+void* llkThread(void* obj) {
+ LOG(INFO) << "started";
+
+ std::string name = std::to_string(::gettid());
+ if (!llkSkipName(name)) {
+ llkBlacklistProcess.emplace(name);
+ }
+ name = static_cast<const char*>(obj);
+ prctl(PR_SET_NAME, name.c_str());
+ if (__predict_false(!llkSkipName(name))) {
+ llkBlacklistProcess.insert(name);
+ }
+ // No longer modifying llkBlacklistProcess.
+ llkRunning = true;
+ llkLogConfig();
+ while (llkRunning) {
+ ::usleep(duration_cast<microseconds>(llkCheck(true)).count());
+ }
+ // NOTREACHED
+ LOG(INFO) << "exiting";
+ return nullptr;
+}
+
+} // namespace
+
+milliseconds llkCheck(bool checkRunning) {
+ if (!llkEnable || (checkRunning != llkRunning)) {
+ return milliseconds::max();
+ }
+
+ // Reset internal watchdog, which is a healthy engineering margin of
+ // double the maximum wait or cycle time for the mainloop that calls us.
+ //
+ // This alarm is effectively the live lock detection of llkd, as
+ // we understandably can not monitor ourselves otherwise.
+ ::alarm(duration_cast<seconds>(llkTimeoutMs * 2).count());
+
+ // kernel jiffy precision fastest acquisition
+ static timespec last;
+ timespec now;
+ ::clock_gettime(CLOCK_MONOTONIC_COARSE, &now);
+ auto ms = llkGetTimespecDiffMs(&last, &now);
+ if (ms < llkCycle) {
+ return llkCycle - ms;
+ }
+ last = now;
+
+ LOG(VERBOSE) << "opendir(\"" << procdir << "\")";
+ if (__predict_false(!llkTopDirectory)) {
+ // gid containing AID_READPROC required
+ llkTopDirectory.reset(procdir);
+ if (__predict_false(!llkTopDirectory)) {
+ // Most likely reason we could be here is a resource limit.
+ // Keep our processing down to a minimum, but not so low that
+ // we do not recover in a timely manner should the issue be
+ // transitory.
+ LOG(DEBUG) << "opendir(\"" << procdir << "\") failed";
+ return llkTimeoutMs;
+ }
+ }
+
+ for (auto& it : tids) {
+ it.second.updated = false;
+ }
+
+ auto prevUpdate = llkUpdate;
+ llkUpdate += ms;
+ ms -= llkCycle;
+ auto myPid = ::getpid();
+ auto myTid = ::gettid();
+ for (auto dp = llkTopDirectory.read(); dp != nullptr; dp = llkTopDirectory.read()) {
+ std::string piddir;
+
+ if (!getValidTidDir(dp, &piddir)) {
+ continue;
+ }
+
+ // Get the process tasks
+ std::string taskdir = piddir + "/task/";
+ int pid = -1;
+ LOG(VERBOSE) << "+opendir(\"" << taskdir << "\")";
+ dir taskDirectory(taskdir);
+ if (__predict_false(!taskDirectory)) {
+ LOG(DEBUG) << "+opendir(\"" << taskdir << "\") failed";
+ }
+ for (auto tp = taskDirectory.read(dir::task, dp); tp != nullptr;
+ tp = taskDirectory.read(dir::task)) {
+ if (!getValidTidDir(tp, &piddir)) {
+ continue;
+ }
+
+ // Get the process stat
+ std::string stat = ReadFile(piddir + "/stat");
+ if (stat.size() == 0) {
+ continue;
+ }
+ unsigned tid = -1;
+ char pdir[TASK_COMM_LEN + 1];
+ char state = '?';
+ unsigned ppid = -1;
+ unsigned utime = -1;
+ unsigned stime = -1;
+ int dummy;
+ pdir[0] = '\0';
+ // tid should not change value
+ auto match = ::sscanf(
+ stat.c_str(),
+ "%u (%" ___STRING(
+ TASK_COMM_LEN) "[^)]) %c %u %*d %*d %*d %*d %*d %*d %*d %*d %*d %u %u %d",
+ &tid, pdir, &state, &ppid, &utime, &stime, &dummy);
+ if (pid == -1) {
+ pid = tid;
+ }
+ LOG(VERBOSE) << "match " << match << ' ' << tid << " (" << pdir << ") " << state << ' '
+ << ppid << " ... " << utime << ' ' << stime << ' ' << dummy;
+ if (match != 7) {
+ continue;
+ }
+
+ auto procp = llkTidLookup(tid);
+ if (procp == nullptr) {
+ procp = llkTidAlloc(tid, pid, ppid, pdir, utime + stime, state);
+ } else {
+ // comm can change ...
+ procp->setComm(pdir);
+ procp->updated = true;
+ // pid/ppid/tid wrap?
+ if (((procp->update != prevUpdate) && (procp->update != llkUpdate)) ||
+ (procp->ppid != ppid) || (procp->pid != pid)) {
+ procp->reset();
+ } else if (procp->time != (utime + stime)) { // secondary ABA.
+ // watching utime+stime granularity jiffy
+ procp->state = '?';
+ }
+ procp->update = llkUpdate;
+ procp->pid = pid;
+ procp->ppid = ppid;
+ procp->time = utime + stime;
+ if (procp->state != state) {
+ procp->count = 0ms;
+ procp->killed = !llkTestWithKill;
+ procp->state = state;
+ } else {
+ procp->count += llkCycle;
+ }
+ }
+
+ // Filter checks in intuitive order of CPU cost to evaluate
+ // If tid unique continue, if ppid or pid unique break
+
+ if (pid == myPid) {
+ break;
+ }
+ if (!llkIsMonitorState(state)) {
+ continue;
+ }
+ if ((tid == myTid) || llkSkipPid(tid)) {
+ continue;
+ }
+ if (llkSkipPpid(ppid)) {
+ break;
+ }
+
+ if (llkSkipName(procp->getComm())) {
+ continue;
+ }
+ if (llkSkipName(procp->getCmdline())) {
+ break;
+ }
+
+ auto pprocp = llkTidLookup(ppid);
+ if (pprocp == nullptr) {
+ pprocp = llkTidAlloc(ppid, ppid, 0, "", 0, '?');
+ }
+ if ((pprocp != nullptr) && (llkSkipName(pprocp->getComm(), llkBlacklistParent) ||
+ llkSkipName(pprocp->getCmdline(), llkBlacklistParent))) {
+ break;
+ }
+
+ if ((llkBlacklistUid.size() != 0) && llkSkipUid(procp->getUid())) {
+ continue;
+ }
+
+ // ABA mitigation watching last time schedule activity happened
+ llkCheckSchedUpdate(procp, piddir);
+
+ // Can only fall through to here if registered D or Z state !!!
+ if (procp->count < llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {
+ LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"
+ << pid << "->" << tid << ' ' << procp->getComm();
+ continue;
+ }
+
+ // We have to kill it to determine difference between live lock
+ // and persistent state blocked on a resource. Is there something
+ // wrong with a process that has no forward scheduling progress in
+ // Z or D? Yes, generally means improper accounting in the
+ // process, but not always ...
+ //
+ // Whomever we hit with a test kill must accept the Android
+ // Aphorism that everything can be burned to the ground and
+ // must survive.
+ if (procp->killed == false) {
+ procp->killed = true;
+ // confirm: re-read uid before committing to a panic.
+ procp->uid = -1;
+ switch (state) {
+ case 'Z': // kill ppid to free up a Zombie
+ // Killing init will kernel panic without diagnostics
+ // so skip right to controlled kernel panic with
+ // diagnostics.
+ if (ppid == initPid) {
+ break;
+ }
+ LOG(WARNING) << "Z " << llkFormat(procp->count) << ' ' << ppid << "->"
+ << pid << "->" << tid << ' ' << procp->getComm() << " [kill]";
+ if ((llkKillOneProcess(pprocp, procp) >= 0) ||
+ (llkKillOneProcess(ppid, procp) >= 0)) {
+ continue;
+ }
+ break;
+
+ case 'D': // kill tid to free up an uninterruptible D
+ // If ABA is doing its job, we would not need or
+ // want the following. Test kill is a Hail Mary
+ // to make absolutely sure there is no forward
+ // scheduling progress. The cost when ABA is
+ // not working is we kill a process that likes to
+ // stay in 'D' state, instead of panicing the
+ // kernel (worse).
+ LOG(WARNING) << "D " << llkFormat(procp->count) << ' ' << pid << "->" << tid
+ << ' ' << procp->getComm() << " [kill]";
+ if ((llkKillOneProcess(llkTidLookup(pid), procp) >= 0) ||
+ (llkKillOneProcess(pid, 'D', tid) >= 0) ||
+ (llkKillOneProcess(procp, procp) >= 0) ||
+ (llkKillOneProcess(tid, 'D', tid) >= 0)) {
+ continue;
+ }
+ break;
+ }
+ }
+ // We are here because we have confirmed kernel live-lock
+ LOG(ERROR) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->" << pid
+ << "->" << tid << ' ' << procp->getComm() << " [panic]";
+ llkPanicKernel(true, tid, (state == 'Z') ? "zombie" : "driver");
+ }
+ LOG(VERBOSE) << "+closedir()";
+ }
+ llkTopDirectory.rewind();
+ LOG(VERBOSE) << "closedir()";
+
+ // garbage collection of old process references
+ for (auto p = tids.begin(); p != tids.end();) {
+ if (!p->second.updated) {
+ IF_ALOG(LOG_VERBOSE, LOG_TAG) {
+ std::string ppidCmdline = llkProcGetName(p->second.ppid, nullptr, nullptr);
+ if (ppidCmdline.size()) {
+ 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 + ")";
+ }
+ }
+ std::string tidCmdline =
+ llkProcGetName(p->second.tid, p->second.getComm(), p->second.getCmdline());
+ if (tidCmdline.size()) {
+ tidCmdline = "(" + tidCmdline + ")";
+ }
+ LOG(VERBOSE) << "thread " << p->second.ppid << ppidCmdline << "->" << p->second.pid
+ << pidCmdline << "->" << p->second.tid << tidCmdline << " removed";
+ }
+ p = tids.erase(p);
+ } else {
+ ++p;
+ }
+ }
+ if (__predict_false(tids.empty())) {
+ llkTopDirectory.reset();
+ }
+
+ llkCycle = llkCheckMs;
+
+ timespec end;
+ ::clock_gettime(CLOCK_MONOTONIC_COARSE, &end);
+ auto milli = llkGetTimespecDiffMs(&now, &end);
+ LOG((milli > 10s) ? ERROR : (milli > 1s) ? WARNING : VERBOSE) << "sample " << llkFormat(milli);
+
+ // cap to minimum sleep for 1 second since last cycle
+ if (llkCycle < (ms + 1s)) {
+ return 1s;
+ }
+ return llkCycle - ms;
+}
+
+unsigned llkCheckMilliseconds() {
+ return duration_cast<milliseconds>(llkCheck()).count();
+}
+
+bool llkInit(const char* threadname) {
+ llkLowRam = android::base::GetBoolProperty("ro.config.low_ram", false);
+ if (!LLK_ENABLE_DEFAULT && android::base::GetBoolProperty("ro.debuggable", false)) {
+ llkEnable = android::base::GetProperty(LLK_ENABLE_PROPERTY, "eng") == "eng";
+ khtEnable = android::base::GetProperty(KHT_ENABLE_PROPERTY, "eng") == "eng";
+ }
+ llkEnable = android::base::GetBoolProperty(LLK_ENABLE_PROPERTY, llkEnable);
+ if (llkEnable && !llkTopDirectory.reset(procdir)) {
+ // Most likely reason we could be here is llkd was started
+ // incorrectly without the readproc permissions. Keep our
+ // processing down to a minimum.
+ llkEnable = false;
+ }
+ khtEnable = android::base::GetBoolProperty(KHT_ENABLE_PROPERTY, khtEnable);
+ llkMlockall = android::base::GetBoolProperty(LLK_MLOCKALL_PROPERTY, llkMlockall);
+ llkTestWithKill = android::base::GetBoolProperty(LLK_KILLTEST_PROPERTY, llkTestWithKill);
+ // if LLK_TIMOUT_MS_PROPERTY was not set, we will use a set
+ // KHT_TIMEOUT_PROPERTY as co-operative guidance for the default value.
+ khtTimeout = GetUintProperty(KHT_TIMEOUT_PROPERTY, khtTimeout);
+ if (khtTimeout == 0s) {
+ khtTimeout = duration_cast<seconds>(llkTimeoutMs * (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT) /
+ LLK_CHECKS_PER_TIMEOUT_DEFAULT);
+ }
+ llkTimeoutMs =
+ khtTimeout * LLK_CHECKS_PER_TIMEOUT_DEFAULT / (1 + LLK_CHECKS_PER_TIMEOUT_DEFAULT);
+ llkTimeoutMs = GetUintProperty(LLK_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+ llkValidate(); // validate llkTimeoutMs, llkCheckMs and llkCycle
+ llkStateTimeoutMs[llkStateD] = GetUintProperty(LLK_D_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+ llkStateTimeoutMs[llkStateZ] = GetUintProperty(LLK_Z_TIMEOUT_MS_PROPERTY, llkTimeoutMs);
+ llkCheckMs = GetUintProperty(LLK_CHECK_MS_PROPERTY, llkCheckMs);
+ llkValidate(); // validate all (effectively minus llkTimeoutMs)
+ std::string defaultBlacklistProcess(
+ std::to_string(kernelPid) + "," + std::to_string(initPid) + "," +
+ std::to_string(kthreaddPid) + "," + std::to_string(::getpid()) + "," +
+ std::to_string(::gettid()) + "," LLK_BLACKLIST_PROCESS_DEFAULT);
+ if (threadname) {
+ defaultBlacklistProcess += std::string(",") + 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);
+ 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));
+
+ // internal watchdog
+ ::signal(SIGALRM, llkAlarmHandler);
+
+ // kernel hung task configuration? Otherwise leave it as-is
+ if (khtEnable) {
+ // EUID must be AID_ROOT to write to /proc/sys/kernel/ nodes, there
+ // are no capability overrides. For security reasons we do not want
+ // to run as AID_ROOT. We may not be able to write them successfully,
+ // we will try, but the least we can do is read the values back to
+ // confirm expectations and report whether configured or not.
+ auto configured = llkWriteStringToFileConfirm(std::to_string(khtTimeout.count()),
+ "/proc/sys/kernel/hung_task_timeout_secs");
+ if (configured) {
+ llkWriteStringToFile("65535", "/proc/sys/kernel/hung_task_warnings");
+ llkWriteStringToFile("65535", "/proc/sys/kernel/hung_task_check_count");
+ configured = llkWriteStringToFileConfirm("1", "/proc/sys/kernel/hung_task_panic");
+ }
+ if (configured) {
+ LOG(INFO) << "[khungtaskd] configured";
+ } else {
+ LOG(WARNING) << "[khungtaskd] not configurable";
+ }
+ }
+
+ bool logConfig = true;
+ if (llkEnable) {
+ if (llkMlockall &&
+ // MCL_ONFAULT pins pages as they fault instead of loading
+ // everything immediately all at once. (Which would be bad,
+ // because as of this writing, we have a lot of mapped pages we
+ // never use.) Old kernels will see MCL_ONFAULT and fail with
+ // EINVAL; we ignore this failure.
+ //
+ // N.B. read the man page for mlockall. MCL_CURRENT | MCL_ONFAULT
+ // pins ⊆ MCL_CURRENT, converging to just MCL_CURRENT as we fault
+ // in pages.
+
+ // CAP_IPC_LOCK required
+ mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
+ PLOG(WARNING) << "mlockall failed ";
+ }
+
+ if (threadname) {
+ pthread_attr_t attr;
+
+ if (!pthread_attr_init(&attr)) {
+ sched_param param;
+
+ memset(¶m, 0, sizeof(param));
+ pthread_attr_setschedparam(&attr, ¶m);
+ pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
+ if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
+ pthread_t thread;
+ if (!pthread_create(&thread, &attr, llkThread, const_cast<char*>(threadname))) {
+ // wait a second for thread to start
+ for (auto retry = 50; retry && !llkRunning; --retry) {
+ ::usleep(20000);
+ }
+ logConfig = !llkRunning; // printed in llkd context?
+ } else {
+ LOG(ERROR) << "failed to spawn llkd thread";
+ }
+ } else {
+ LOG(ERROR) << "failed to detach llkd thread";
+ }
+ pthread_attr_destroy(&attr);
+ } else {
+ LOG(ERROR) << "failed to allocate attibutes for llkd thread";
+ }
+ }
+ } else {
+ LOG(DEBUG) << "[khungtaskd] left unconfigured";
+ }
+ if (logConfig) {
+ llkLogConfig();
+ }
+
+ return llkEnable;
+}
diff --git a/llkd/llkd.cpp b/llkd/llkd.cpp
new file mode 100644
index 0000000..f10253d
--- /dev/null
+++ b/llkd/llkd.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 "llkd.h"
+
+#include <sched.h>
+#include <unistd.h>
+
+#include <chrono>
+
+#include <android-base/logging.h>
+
+using namespace std::chrono;
+
+int main(int, char**) {
+ LOG(INFO) << "started";
+
+ bool enabled = llkInit();
+
+ // Would like this policy to be automatic as part of libllkd,
+ // but that would be presumptuous and bad side-effect.
+ struct sched_param param;
+ memset(¶m, 0, sizeof(param));
+ sched_setscheduler(0, SCHED_BATCH, ¶m);
+
+ while (true) {
+ if (enabled) {
+ ::usleep(duration_cast<microseconds>(llkCheck()).count());
+ } else {
+ ::pause();
+ }
+ }
+ // NOTREACHED
+
+ LOG(INFO) << "exiting";
+ return 0;
+}
diff --git a/llkd/llkd.rc b/llkd/llkd.rc
new file mode 100644
index 0000000..e538cdb
--- /dev/null
+++ b/llkd/llkd.rc
@@ -0,0 +1,49 @@
+# eng default for ro.llk.enable and ro.khungtask.enable
+on property:ro.debuggable=*
+ setprop llk.enable ${ro.llk.enable:-0}
+ setprop khungtask.enable ${ro.khungtask.enable:-0}
+
+on property:ro.debuggable=1
+ setprop llk.enable ${ro.llk.enable:-1}
+ setprop khungtask.enable ${ro.khungtask.enable:-1}
+
+on property:ro.llk.enable=eng
+ setprop llk.enable ${ro.debuggable:-0}
+
+on property:ro.khungtask.enable=eng
+ setprop khungtask.enable ${ro.debuggable:-0}
+
+on property:llk.enable=1
+ setprop llk.enable true
+
+on property:llk.enable=0
+ setprop llk.enable false
+
+on property:khungtask.enable=1
+ setprop khungtask.enable true
+
+on property:khungtask.enable=0
+ setprop khungtask.enable false
+
+# Configure [khungtaskd]
+on property:khungtask.enable=true
+ write /proc/sys/kernel/hung_task_timeout_secs ${ro.khungtask.timeout:-720}
+ write /proc/sys/kernel/hung_task_warnings 65535
+ write /proc/sys/kernel/hung_task_check_count 65535
+ write /proc/sys/kernel/hung_task_panic 1
+
+on property:khungtask.enable=false
+ write /proc/sys/kernel/hung_task_panic 0
+
+on property:llk.enable=true
+ start llkd
+
+service llkd /system/bin/llkd
+ class late_start
+ disabled
+ user llkd
+ group llkd readproc
+ capabilities KILL IPC_LOCK
+ file /dev/kmsg w
+ file /proc/sysrq-trigger w
+ writepid /dev/cpuset/system-background/tasks
diff --git a/llkd/tests/Android.bp b/llkd/tests/Android.bp
new file mode 100644
index 0000000..6dd5938
--- /dev/null
+++ b/llkd/tests/Android.bp
@@ -0,0 +1,41 @@
+// 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.
+
+cc_test {
+ name: "llkd_unit_test",
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ header_libs: [
+ "llkd_headers",
+ ],
+
+ target: {
+ android: {
+ srcs: [
+ "llkd_test.cpp",
+ ],
+ },
+ },
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+
+ compile_multilib: "first",
+}
diff --git a/llkd/tests/llkd_test.cpp b/llkd/tests/llkd_test.cpp
new file mode 100644
index 0000000..3a15ff1
--- /dev/null
+++ b/llkd/tests/llkd_test.cpp
@@ -0,0 +1,287 @@
+/*
+ * 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 <signal.h>
+#include <stdint.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");
+ rest();
+ std::string setprop("setprop ");
+ 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();
+ 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");
+ 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 : 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);
+}
diff --git a/lmkd/lmkd.c b/lmkd/lmkd.c
index 151e1dc..80711bc 100644
--- a/lmkd/lmkd.c
+++ b/lmkd/lmkd.c
@@ -68,6 +68,7 @@
#define MEMINFO_PATH "/proc/meminfo"
#define LINE_MAX 128
+/* gid containing AID_SYSTEM required */
#define INKERNEL_MINFREE_PATH "/sys/module/lowmemorykiller/parameters/minfree"
#define INKERNEL_ADJ_PATH "/sys/module/lowmemorykiller/parameters/adj"
@@ -455,6 +456,9 @@
return;
}
+ /* gid containing AID_READPROC required */
+ /* CAP_SYS_RESOURCE required */
+ /* CAP_DAC_OVERRIDE required */
snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", params.pid);
snprintf(val, sizeof(val), "%d", params.oomadj);
if (!writefilestring(path, val, false)) {
@@ -496,8 +500,7 @@
soft_limit_mult = 64;
}
- snprintf(path, sizeof(path),
- "/dev/memcg/apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
+ snprintf(path, sizeof(path), MEMCG_SYSFS_PATH "apps/uid_%d/pid_%d/memory.soft_limit_in_bytes",
params.uid, params.pid);
snprintf(val, sizeof(val), "%d", soft_limit_mult * EIGHT_MEGA);
@@ -859,6 +862,7 @@
int total;
ssize_t ret;
+ /* gid containing AID_READPROC required */
snprintf(path, PATH_MAX, "/proc/%d/statm", pid);
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd == -1)
@@ -882,6 +886,7 @@
char *cp;
ssize_t ret;
+ /* gid containing AID_READPROC required */
snprintf(path, PATH_MAX, "/proc/%d/cmdline", pid);
fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd == -1)
@@ -949,6 +954,7 @@
TRACE_KILL_START(pid);
+ /* CAP_KILL required */
r = kill(pid, SIGKILL);
ALOGI(
"Killing '%s' (%d), uid %d, adj %d\n"
@@ -1267,6 +1273,7 @@
int level_idx = (int)level;
const char *levelstr = level_name[level_idx];
+ /* gid containing AID_SYSTEM required */
mpfd = open(MEMCG_SYSFS_PATH "memory.pressure_level", O_RDONLY | O_CLOEXEC);
if (mpfd < 0) {
ALOGI("No kernel memory.pressure_level support (errno=%d)", errno);
@@ -1478,11 +1485,15 @@
* pins ⊆ MCL_CURRENT, converging to just MCL_CURRENT as we fault
* in pages.
*/
+ /* CAP_IPC_LOCK required */
if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
ALOGW("mlockall failed %s", strerror(errno));
}
- sched_setscheduler(0, SCHED_FIFO, ¶m);
+ /* CAP_NICE required */
+ if (sched_setscheduler(0, SCHED_FIFO, ¶m)) {
+ ALOGW("set SCHED_FIFO failed %s", strerror(errno));
+ }
}
mainloop();
diff --git a/lmkd/lmkd.rc b/lmkd/lmkd.rc
index 3bb84ab..76b6055 100644
--- a/lmkd/lmkd.rc
+++ b/lmkd/lmkd.rc
@@ -1,6 +1,8 @@
service lmkd /system/bin/lmkd
class core
- group root readproc
+ user lmkd
+ group lmkd system readproc
+ capabilities DAC_OVERRIDE KILL IPC_LOCK SYS_NICE SYS_RESOURCE
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
diff --git a/logcat/event.logtags b/logcat/event.logtags
index 0983676..7c40a77 100644
--- a/logcat/event.logtags
+++ b/logcat/event.logtags
@@ -119,6 +119,9 @@
70200 aggregation (aggregation time|2|3)
70201 aggregation_test (field1|1|2),(field2|1|2),(field3|1|2),(field4|1|2),(field5|1|2)
+# gms refuses to register this log tag, b/30156345
+70220 gms_unknown
+
# libc failure logging
80100 bionic_event_memcpy_buffer_overflow (uid|1)
80105 bionic_event_strcat_buffer_overflow (uid|1)
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 1462570..d3504ad 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -21,9 +21,6 @@
# Set the security context of /adb_keys if present.
restorecon /adb_keys
- # Shouldn't be necessary, but sdcard won't start without it. http://b/22568628.
- mkdir /mnt 0775 root system
-
# Set the security context of /postinstall if present.
restorecon /postinstall
@@ -34,6 +31,9 @@
# root memory control cgroup, used by lmkd
mkdir /dev/memcg 0700 root system
mount cgroup none /dev/memcg nodev noexec nosuid memory
+ # memory.pressure_level used by lmkd
+ chown root system /dev/memcg/memory.pressure_level
+ chmod 0040 /dev/memcg/memory.pressure_level
# app mem cgroups, used by activity manager, lmkd and zygote
mkdir /dev/memcg/apps/ 0755 system system
# cgroup for system_server and surfaceflinger
@@ -80,9 +80,6 @@
chmod 0664 /dev/stune/top-app/tasks
chmod 0664 /dev/stune/rt/tasks
- # Mount staging areas for devices managed by vold
- # See storage config details at http://source.android.com/tech/storage/
- mount tmpfs tmpfs /mnt nodev noexec nosuid mode=0755,uid=0,gid=1000
restorecon_recursive /mnt
mount configfs none /config nodev noexec nosuid
@@ -509,6 +506,7 @@
mkdir /data/ss 0700 system system
mkdir /data/system 0775 system system
+ mkdir /data/system/dropbox 0700 system system
mkdir /data/system/heapdump 0700 system system
mkdir /data/system/users 0775 system system
diff --git a/toolbox/Android.bp b/toolbox/Android.bp
index ddd95b2..c6d4d9b 100644
--- a/toolbox/Android.bp
+++ b/toolbox/Android.bp
@@ -63,7 +63,6 @@
static_libs: ["libpropertyinfoparser"],
symlinks: [
- "dd",
"getevent",
"getprop",
"newfs_msdos",