diff options
114 files changed, 4554 insertions, 1242 deletions
diff --git a/NOTICE b/NOTICE deleted file mode 100644 index 8e8a91ca1..000000000 --- a/NOTICE +++ /dev/null @@ -1,16 +0,0 @@ -Copyright (C) 2017 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. - -------------------------------------------------------------------- - diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp index ee1ae3178..d6ebb0d54 100644 --- a/bootstat/bootstat.cpp +++ b/bootstat/bootstat.cpp @@ -438,6 +438,7 @@ const std::map<std::string, int32_t> kBootReasonMap = { {"reboot,mount_userdata_failed", 190}, {"reboot,forcedsilent", 191}, {"reboot,forcednonsilent", 192}, + {"reboot,thermal,tj", 193}, }; // Converts a string value representing the reason the system booted to an diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp index 49dd915f0..f1f9080c1 100644 --- a/debuggerd/Android.bp +++ b/debuggerd/Android.bp @@ -237,6 +237,7 @@ cc_library_static { "gwp_asan_crash_handler", "libscudo", "libtombstone_proto", + "libprocinfo", "libprotobuf-cpp-lite", ], diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp index 230002a8e..04e1e4ee3 100644 --- a/debuggerd/crash_dump.cpp +++ b/debuggerd/crash_dump.cpp @@ -303,6 +303,7 @@ static void ReadCrashInfo(unique_fd& fd, siginfo_t* siginfo, process_info->gwp_asan_metadata = crash_info->data.d.gwp_asan_metadata; process_info->scudo_stack_depot = crash_info->data.d.scudo_stack_depot; process_info->scudo_region_info = crash_info->data.d.scudo_region_info; + process_info->scudo_ring_buffer = crash_info->data.d.scudo_ring_buffer; FALLTHROUGH_INTENDED; case 1: case 2: @@ -390,7 +391,7 @@ int main(int argc, char** argv) { // There appears to be a bug in the kernel where our death causes SIGHUP to // be sent to our process group if we exit while it has stopped jobs (e.g. - // because of wait_for_gdb). Use setsid to create a new process group to + // because of wait_for_debugger). Use setsid to create a new process group to // avoid hitting this. setsid(); @@ -547,15 +548,17 @@ int main(int argc, char** argv) { fork_exit_write.reset(); // Defer the message until later, for readability. - bool wait_for_gdb = android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false); + bool wait_for_debugger = android::base::GetBoolProperty( + "debug.debuggerd.wait_for_debugger", + android::base::GetBoolProperty("debug.debuggerd.wait_for_gdb", false)); if (siginfo.si_signo == BIONIC_SIGNAL_DEBUGGER) { - wait_for_gdb = false; + wait_for_debugger = false; } // Detach from all of our attached threads before resuming. for (const auto& [tid, thread] : thread_info) { int resume_signal = thread.signo == BIONIC_SIGNAL_DEBUGGER ? 0 : thread.signo; - if (wait_for_gdb) { + if (wait_for_debugger) { resume_signal = 0; if (tgkill(target_process, tid, SIGSTOP) != 0) { PLOG(WARNING) << "failed to send SIGSTOP to " << tid; @@ -640,12 +643,12 @@ int main(int argc, char** argv) { } } - if (wait_for_gdb) { + if (wait_for_debugger) { // Use ALOGI to line up with output from engrave_tombstone. ALOGI( "***********************************************************\n" "* Process %d has been suspended while crashing.\n" - "* To attach gdbserver and start gdb, run this on the host:\n" + "* To attach the debugger, run this on the host:\n" "*\n" "* gdbclient.py -p %d\n" "*\n" diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp index 7975a3a32..23b106ea4 100644 --- a/debuggerd/crasher/Android.bp +++ b/debuggerd/crasher/Android.bp @@ -13,7 +13,6 @@ cc_defaults { "-Werror", "-O0", "-fstack-protector-all", - "-Wno-free-nonheap-object", "-Wno-date-time", ], srcs: ["crasher.cpp"], diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp index a2b13a36c..db30b8f07 100644 --- a/debuggerd/crasher/crasher.cpp +++ b/debuggerd/crasher/crasher.cpp @@ -134,10 +134,14 @@ noinline int crash(int a) { return a*2; } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfree-nonheap-object" + noinline void abuse_heap() { char buf[16]; free(buf); // GCC is smart enough to warn about this, but we're doing it deliberately. } +#pragma clang diagnostic pop noinline void leak() { while (true) { diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp index 9e9557f18..144faeec0 100644 --- a/debuggerd/debuggerd_test.cpp +++ b/debuggerd/debuggerd_test.cpp @@ -34,6 +34,7 @@ #include <android/fdsan.h> #include <android/set_abort_message.h> +#include <bionic/malloc.h> #include <bionic/mte.h> #include <bionic/reserved_signals.h> @@ -69,7 +70,7 @@ using android::base::unique_fd; #define ARCH_SUFFIX "" #endif -constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb"; +constexpr char kWaitForDebuggerKey[] = "debug.debuggerd.wait_for_debugger"; #define TIMEOUT(seconds, expr) \ [&]() { \ @@ -98,6 +99,13 @@ constexpr char kWaitForGdbKey[] = "debug.debuggerd.wait_for_gdb"; ASSERT_MATCH(result, \ R"(#\d\d pc [0-9a-f]+\s+ \S+ (\(offset 0x[0-9a-f]+\) )?\()" frame_name R"(\+)"); +// Enable GWP-ASan at the start of this process. GWP-ASan is enabled using +// process sampling, so we need to ensure we force GWP-ASan on. +__attribute__((constructor)) static void enable_gwp_asan() { + bool force = true; + android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force)); +} + static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd, InterceptStatus* status, DebuggerdDumpType intercept_type) { intercept_fd->reset(socket_local_client(kTombstonedInterceptSocketName, @@ -157,7 +165,7 @@ static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, uniq class CrasherTest : public ::testing::Test { public: pid_t crasher_pid = -1; - bool previous_wait_for_gdb; + bool previous_wait_for_debugger; unique_fd crasher_pipe; unique_fd intercept_fd; @@ -178,8 +186,13 @@ class CrasherTest : public ::testing::Test { }; CrasherTest::CrasherTest() { - previous_wait_for_gdb = android::base::GetBoolProperty(kWaitForGdbKey, false); - android::base::SetProperty(kWaitForGdbKey, "0"); + previous_wait_for_debugger = android::base::GetBoolProperty(kWaitForDebuggerKey, false); + android::base::SetProperty(kWaitForDebuggerKey, "0"); + + // Clear the old property too, just in case someone's been using it + // on this device. (We only document the new name, but we still support + // the old name so we don't break anyone's existing setups.) + android::base::SetProperty("debug.debuggerd.wait_for_gdb", "0"); } CrasherTest::~CrasherTest() { @@ -189,7 +202,7 @@ CrasherTest::~CrasherTest() { TEMP_FAILURE_RETRY(waitpid(crasher_pid, &status, WUNTRACED)); } - android::base::SetProperty(kWaitForGdbKey, previous_wait_for_gdb ? "1" : "0"); + android::base::SetProperty(kWaitForDebuggerKey, previous_wait_for_debugger ? "1" : "0"); } void CrasherTest::StartIntercept(unique_fd* output_fd, DebuggerdDumpType intercept_type) { @@ -392,7 +405,77 @@ static void SetTagCheckingLevelSync() { } #endif -TEST_F(CrasherTest, mte_uaf) { +// Number of iterations required to reliably guarantee a GWP-ASan crash. +// GWP-ASan's sample rate is not truly nondeterministic, it initialises a +// thread-local counter at 2*SampleRate, and decrements on each malloc(). Once +// the counter reaches zero, we provide a sampled allocation. Then, double that +// figure to allow for left/right allocation alignment, as this is done randomly +// without bias. +#define GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH (0x20000) + +struct GwpAsanTestParameters { + size_t alloc_size; + bool free_before_access; + int access_offset; + std::string cause_needle; // Needle to be found in the "Cause: [GWP-ASan]" line. +}; + +struct GwpAsanCrasherTest : CrasherTest, testing::WithParamInterface<GwpAsanTestParameters> {}; + +GwpAsanTestParameters gwp_asan_tests[] = { + {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 0, "Use After Free, 0 bytes into a 7-byte allocation"}, + {/* alloc_size */ 7, /* free_before_access */ true, /* access_offset */ 1, "Use After Free, 1 byte into a 7-byte allocation"}, + {/* alloc_size */ 7, /* free_before_access */ false, /* access_offset */ 16, "Buffer Overflow, 9 bytes right of a 7-byte allocation"}, + {/* alloc_size */ 16, /* free_before_access */ false, /* access_offset */ -1, "Buffer Underflow, 1 byte left of a 16-byte allocation"}, +}; + +INSTANTIATE_TEST_SUITE_P(GwpAsanTests, GwpAsanCrasherTest, testing::ValuesIn(gwp_asan_tests)); + +TEST_P(GwpAsanCrasherTest, gwp_asan_uaf) { + if (mte_supported()) { + // Skip this test on MTE hardware, as MTE will reliably catch these errors + // instead of GWP-ASan. + GTEST_SKIP() << "Skipped on MTE."; + } + + GwpAsanTestParameters params = GetParam(); + + int intercept_result; + unique_fd output_fd; + StartProcess([¶ms]() { + for (unsigned i = 0; i < GWP_ASAN_ITERATIONS_TO_ENSURE_CRASH; ++i) { + volatile char* p = reinterpret_cast<volatile char*>(malloc(params.alloc_size)); + if (params.free_before_access) free(static_cast<void*>(const_cast<char*>(p))); + p[params.access_offset] = 42; + if (!params.free_before_access) free(static_cast<void*>(const_cast<char*>(p))); + } + }); + + StartIntercept(&output_fd); + FinishCrasher(); + AssertDeath(SIGSEGV); + FinishIntercept(&intercept_result); + + ASSERT_EQ(1, intercept_result) << "tombstoned reported failure"; + + std::string result; + ConsumeFd(std::move(output_fd), &result); + + ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 2 \(SEGV_ACCERR\))"); + ASSERT_MATCH(result, R"(Cause: \[GWP-ASan\]: )" + params.cause_needle); + if (params.free_before_access) { + ASSERT_MATCH(result, R"(deallocated by thread .* + #00 pc)"); + } + ASSERT_MATCH(result, R"(allocated by thread .* + #00 pc)"); +} + +struct SizeParamCrasherTest : CrasherTest, testing::WithParamInterface<size_t> {}; + +INSTANTIATE_TEST_SUITE_P(Sizes, SizeParamCrasherTest, testing::Values(16, 131072)); + +TEST_P(SizeParamCrasherTest, mte_uaf) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; @@ -400,9 +483,9 @@ TEST_F(CrasherTest, mte_uaf) { int intercept_result; unique_fd output_fd; - StartProcess([]() { + StartProcess([&]() { SetTagCheckingLevelSync(); - volatile int* p = (volatile int*)malloc(16); + volatile int* p = (volatile int*)malloc(GetParam()); free((void *)p); p[0] = 42; }); @@ -417,19 +500,19 @@ TEST_F(CrasherTest, mte_uaf) { std::string result; ConsumeFd(std::move(output_fd), &result); - ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); - ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a 16-byte allocation.* - -allocated by thread .* - #00 pc)"); + ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); + ASSERT_MATCH(result, R"(Cause: \[MTE\]: Use After Free, 0 bytes into a )" + + std::to_string(GetParam()) + R"(-byte allocation)"); ASSERT_MATCH(result, R"(deallocated by thread .* #00 pc)"); + ASSERT_MATCH(result, R"(allocated by thread .* + #00 pc)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } -TEST_F(CrasherTest, mte_overflow) { +TEST_P(SizeParamCrasherTest, mte_overflow) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; @@ -437,10 +520,10 @@ TEST_F(CrasherTest, mte_overflow) { int intercept_result; unique_fd output_fd; - StartProcess([]() { + StartProcess([&]() { SetTagCheckingLevelSync(); - volatile int* p = (volatile int*)malloc(16); - p[4] = 42; + volatile char* p = (volatile char*)malloc(GetParam()); + p[GetParam()] = 42; }); StartIntercept(&output_fd); @@ -454,16 +537,16 @@ TEST_F(CrasherTest, mte_overflow) { ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))"); - ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a 16-byte allocation.* - -allocated by thread .* + ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" + + std::to_string(GetParam()) + R"(-byte allocation)"); + ASSERT_MATCH(result, R"(allocated by thread .* #00 pc)"); #else GTEST_SKIP() << "Requires aarch64"; #endif } -TEST_F(CrasherTest, mte_underflow) { +TEST_P(SizeParamCrasherTest, mte_underflow) { #if defined(__aarch64__) if (!mte_supported()) { GTEST_SKIP() << "Requires MTE"; @@ -471,9 +554,9 @@ TEST_F(CrasherTest, mte_underflow) { int intercept_result; unique_fd output_fd; - StartProcess([]() { + StartProcess([&]() { SetTagCheckingLevelSync(); - volatile int* p = (volatile int*)malloc(16); + volatile int* p = (volatile int*)malloc(GetParam()); p[-1] = 42; }); @@ -488,9 +571,9 @@ TEST_F(CrasherTest, mte_underflow) { ConsumeFd(std::move(output_fd), &result); ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))"); - ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a 16-byte allocation.* - -allocated by thread .* + ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" + + std::to_string(GetParam()) + R"(-byte allocation)"); + ASSERT_MATCH(result, R"(allocated by thread .* #00 pc)"); #else GTEST_SKIP() << "Requires aarch64"; @@ -727,9 +810,9 @@ TEST_F(CrasherTest, intercept_timeout) { AssertDeath(SIGABRT); } -TEST_F(CrasherTest, wait_for_gdb) { - if (!android::base::SetProperty(kWaitForGdbKey, "1")) { - FAIL() << "failed to enable wait_for_gdb"; +TEST_F(CrasherTest, wait_for_debugger) { + if (!android::base::SetProperty(kWaitForDebuggerKey, "1")) { + FAIL() << "failed to enable wait_for_debugger"; } sleep(1); diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp index 9e7b6ba9b..74b5c719e 100644 --- a/debuggerd/handler/debuggerd_handler.cpp +++ b/debuggerd/handler/debuggerd_handler.cpp @@ -274,7 +274,7 @@ static void create_vm_process() { // There appears to be a bug in the kernel where our death causes SIGHUP to // be sent to our process group if we exit while it has stopped jobs (e.g. - // because of wait_for_gdb). Use setsid to create a new process group to + // because of wait_for_debugger). Use setsid to create a new process group to // avoid hitting this. setsid(); @@ -604,7 +604,7 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c // starting to dump right before our death. pthread_mutex_unlock(&crash_mutex); } else { - // Resend the signal, so that either gdb or the parent's waitpid sees it. + // Resend the signal, so that either the debugger or the parent's waitpid sees it. resend_signal(info); } } diff --git a/debuggerd/include/debuggerd/handler.h b/debuggerd/include/debuggerd/handler.h index 254ed4f7b..bc08327a1 100644 --- a/debuggerd/include/debuggerd/handler.h +++ b/debuggerd/include/debuggerd/handler.h @@ -42,6 +42,7 @@ struct debugger_process_info { const gwp_asan::AllocationMetadata* gwp_asan_metadata; const char* scudo_stack_depot; const char* scudo_region_info; + const char* scudo_ring_buffer; }; // These callbacks are called in a signal handler, and thus must be async signal safe. diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp index 9750fc4b0..3ee309fc5 100644 --- a/debuggerd/libdebuggerd/gwp_asan.cpp +++ b/debuggerd/libdebuggerd/gwp_asan.cpp @@ -15,6 +15,7 @@ */ #include "libdebuggerd/gwp_asan.h" +#include "libdebuggerd/tombstone.h" #include "libdebuggerd/utility.h" #include "gwp_asan/common.h" @@ -25,6 +26,8 @@ #include <unwindstack/Regs.h> #include <unwindstack/Unwinder.h> +#include "tombstone.pb.h" + // Retrieve GWP-ASan state from `state_addr` inside the process at // `process_memory`. Place the state into `*state`. static bool retrieve_gwp_asan_state(unwindstack::Memory* process_memory, uintptr_t state_addr, @@ -98,6 +101,67 @@ bool GwpAsanCrashData::CrashIsMine() const { return is_gwp_asan_responsible_; } +constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect; + +void GwpAsanCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const { + if (!CrashIsMine()) { + ALOGE("Internal Error: AddCauseProtos() on a non-GWP-ASan crash."); + return; + } + + Cause* cause = tombstone->add_causes(); + MemoryError* memory_error = cause->mutable_memory_error(); + HeapObject* heap_object = memory_error->mutable_heap(); + + memory_error->set_tool(MemoryError_Tool_GWP_ASAN); + switch (error_) { + case gwp_asan::Error::USE_AFTER_FREE: + memory_error->set_type(MemoryError_Type_USE_AFTER_FREE); + break; + case gwp_asan::Error::DOUBLE_FREE: + memory_error->set_type(MemoryError_Type_DOUBLE_FREE); + break; + case gwp_asan::Error::INVALID_FREE: + memory_error->set_type(MemoryError_Type_INVALID_FREE); + break; + case gwp_asan::Error::BUFFER_OVERFLOW: + memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW); + break; + case gwp_asan::Error::BUFFER_UNDERFLOW: + memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW); + break; + default: + memory_error->set_type(MemoryError_Type_UNKNOWN); + break; + } + + heap_object->set_address(__gwp_asan_get_allocation_address(responsible_allocation_)); + heap_object->set_size(__gwp_asan_get_allocation_size(responsible_allocation_)); + unwinder->SetDisplayBuildID(true); + + std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]); + + heap_object->set_allocation_tid(__gwp_asan_get_allocation_thread_id(responsible_allocation_)); + size_t num_frames = + __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); + for (size_t i = 0; i != num_frames; ++i) { + unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); + BacktraceFrame* f = heap_object->add_allocation_backtrace(); + fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps()); + } + + heap_object->set_deallocation_tid(__gwp_asan_get_deallocation_thread_id(responsible_allocation_)); + num_frames = + __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); + for (size_t i = 0; i != num_frames; ++i) { + unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); + BacktraceFrame* f = heap_object->add_deallocation_backtrace(); + fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps()); + } + + set_human_readable_cause(cause, crash_address_); +} + void GwpAsanCrashData::DumpCause(log_t* log) const { if (!CrashIsMine()) { ALOGE("Internal Error: DumpCause() on a non-GWP-ASan crash."); @@ -119,13 +183,6 @@ void GwpAsanCrashData::DumpCause(log_t* log) const { uintptr_t alloc_address = __gwp_asan_get_allocation_address(responsible_allocation_); size_t alloc_size = __gwp_asan_get_allocation_size(responsible_allocation_); - if (crash_address_ == alloc_address) { - // Use After Free on a 41-byte allocation at 0xdeadbeef. - _LOG(log, logtype::HEADER, "Cause: [GWP-ASan]: %s on a %zu-byte allocation at 0x%" PRIxPTR "\n", - error_string_, alloc_size, alloc_address); - return; - } - uintptr_t diff; const char* location_str; @@ -157,8 +214,6 @@ void GwpAsanCrashData::DumpCause(log_t* log) const { error_string_, diff, byte_suffix, location_str, alloc_size, alloc_address); } -constexpr size_t kMaxTraceLength = gwp_asan::AllocationMetadata::kMaxTraceLengthToCollect; - bool GwpAsanCrashData::HasDeallocationTrace() const { assert(CrashIsMine() && "HasDeallocationTrace(): Crash is not mine!"); if (!responsible_allocation_ || !__gwp_asan_is_deallocated(responsible_allocation_)) { @@ -171,7 +226,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* assert(HasDeallocationTrace() && "DumpDeallocationTrace(): No dealloc trace!"); uint64_t thread_id = __gwp_asan_get_deallocation_thread_id(responsible_allocation_); - std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]); + std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]); size_t num_frames = __gwp_asan_get_deallocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); @@ -183,7 +238,7 @@ void GwpAsanCrashData::DumpDeallocationTrace(log_t* log, unwindstack::Unwinder* unwinder->SetDisplayBuildID(true); for (size_t i = 0; i < num_frames; ++i) { - unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]); + unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); frame_data.num = i; _LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str()); } @@ -198,7 +253,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un assert(HasAllocationTrace() && "DumpAllocationTrace(): No dealloc trace!"); uint64_t thread_id = __gwp_asan_get_allocation_thread_id(responsible_allocation_); - std::unique_ptr<uintptr_t> frames(new uintptr_t[kMaxTraceLength]); + std::unique_ptr<uintptr_t[]> frames(new uintptr_t[kMaxTraceLength]); size_t num_frames = __gwp_asan_get_allocation_trace(responsible_allocation_, frames.get(), kMaxTraceLength); @@ -210,7 +265,7 @@ void GwpAsanCrashData::DumpAllocationTrace(log_t* log, unwindstack::Unwinder* un unwinder->SetDisplayBuildID(true); for (size_t i = 0; i < num_frames; ++i) { - unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames.get()[i]); + unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(frames[i]); frame_data.num = i; _LOG(log, logtype::BACKTRACE, " %s\n", unwinder->FormatFrame(frame_data).c_str()); } diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h index 6c8873341..f9c2481a9 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/gwp_asan.h @@ -26,6 +26,9 @@ #include "types.h" #include "utility.h" +class Cause; +class Tombstone; + class GwpAsanCrashData { public: GwpAsanCrashData() = delete; @@ -69,6 +72,8 @@ class GwpAsanCrashData { // HasAllocationTrace() returns true. void DumpAllocationTrace(log_t* log, unwindstack::Unwinder* unwinder) const; + void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const; + protected: // Is GWP-ASan responsible for this crash. bool is_gwp_asan_responsible_ = false; diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h index 4d00ecede..c3b95d608 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/scudo.h @@ -23,6 +23,9 @@ #include "scudo/interface.h" +class Cause; +class Tombstone; + class ScudoCrashData { public: ScudoCrashData() = delete; @@ -32,6 +35,7 @@ class ScudoCrashData { bool CrashIsMine() const; void DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const; + void AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const; private: scudo_error_info error_info_ = {}; @@ -39,4 +43,7 @@ class ScudoCrashData { void DumpReport(const scudo_error_report* report, log_t* log, unwindstack::Unwinder* unwinder) const; + + void FillInCause(Cause* cause, const scudo_error_report* report, + unwindstack::Unwinder* unwinder) const; }; diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h index bf2cbb3fb..2331f1e6e 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/tombstone.h @@ -31,9 +31,13 @@ #include "types.h" // Forward declarations +class BacktraceFrame; +class Cause; class Tombstone; namespace unwindstack { +struct FrameData; +class Maps; class Unwinder; } @@ -64,4 +68,8 @@ bool tombstone_proto_to_text( const Tombstone& tombstone, std::function<void(const std::string& line, bool should_log)> callback); +void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame, + unwindstack::Maps* maps); +void set_human_readable_cause(Cause* cause, uint64_t fault_addr); + #endif // _DEBUGGERD_TOMBSTONE_H diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/types.h b/debuggerd/libdebuggerd/include/libdebuggerd/types.h index d5b07355f..dcb52f92a 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h @@ -46,6 +46,7 @@ struct ProcessInfo { uintptr_t gwp_asan_metadata = 0; uintptr_t scudo_stack_depot = 0; uintptr_t scudo_region_info = 0; + uintptr_t scudo_ring_buffer = 0; bool has_fault_address = false; uintptr_t untagged_fault_address = 0; diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h index d71b76f7c..c490fb1b9 100644 --- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h +++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h @@ -81,7 +81,7 @@ class Memory; void log_backtrace(log_t* log, unwindstack::Unwinder* unwinder, const char* prefix); -ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr, +ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr, unwindstack::Memory* memory); void dump_memory(log_t* log, unwindstack::Memory* backtrace, uint64_t addr, const std::string&); @@ -93,4 +93,7 @@ void get_signal_sender(char* buf, size_t n, const siginfo_t*); const char* get_signame(const siginfo_t*); const char* get_sigcode(const siginfo_t*); +// Number of bytes per MTE granule. +constexpr size_t kTagGranuleSize = 16; + #endif // _DEBUGGERD_UTILITY_H diff --git a/debuggerd/libdebuggerd/scudo.cpp b/debuggerd/libdebuggerd/scudo.cpp index 141c3bd18..f4690bac3 100644 --- a/debuggerd/libdebuggerd/scudo.cpp +++ b/debuggerd/libdebuggerd/scudo.cpp @@ -15,13 +15,16 @@ */ #include "libdebuggerd/scudo.h" -#include "libdebuggerd/gwp_asan.h" +#include "libdebuggerd/tombstone.h" #include "unwindstack/Memory.h" #include "unwindstack/Unwinder.h" +#include <android-base/macros.h> #include <bionic/macros.h> +#include "tombstone.pb.h" + std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, uint64_t addr, size_t size) { auto buf = std::make_unique<char[]>(size); @@ -31,8 +34,6 @@ std::unique_ptr<char[]> AllocAndReadFully(unwindstack::Memory* process_memory, u return buf; } -static const uintptr_t kTagGranuleSize = 16; - ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory, const ProcessInfo& process_info) { if (!process_info.has_fault_address) { @@ -43,6 +44,8 @@ ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory, __scudo_get_stack_depot_size()); auto region_info = AllocAndReadFully(process_memory, process_info.scudo_region_info, __scudo_get_region_info_size()); + auto ring_buffer = AllocAndReadFully(process_memory, process_info.scudo_ring_buffer, + __scudo_get_ring_buffer_size()); untagged_fault_addr_ = process_info.untagged_fault_address; uintptr_t fault_page = untagged_fault_addr_ & ~(PAGE_SIZE - 1); @@ -68,14 +71,66 @@ ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory, } __scudo_get_error_info(&error_info_, process_info.maybe_tagged_fault_address, stack_depot.get(), - region_info.get(), memory.get(), memory_tags.get(), memory_begin, - memory_end - memory_begin); + region_info.get(), ring_buffer.get(), memory.get(), memory_tags.get(), + memory_begin, memory_end - memory_begin); } bool ScudoCrashData::CrashIsMine() const { return error_info_.reports[0].error_type != UNKNOWN; } +void ScudoCrashData::FillInCause(Cause* cause, const scudo_error_report* report, + unwindstack::Unwinder* unwinder) const { + MemoryError* memory_error = cause->mutable_memory_error(); + HeapObject* heap_object = memory_error->mutable_heap(); + + memory_error->set_tool(MemoryError_Tool_SCUDO); + switch (report->error_type) { + case USE_AFTER_FREE: + memory_error->set_type(MemoryError_Type_USE_AFTER_FREE); + break; + case BUFFER_OVERFLOW: + memory_error->set_type(MemoryError_Type_BUFFER_OVERFLOW); + break; + case BUFFER_UNDERFLOW: + memory_error->set_type(MemoryError_Type_BUFFER_UNDERFLOW); + break; + default: + memory_error->set_type(MemoryError_Type_UNKNOWN); + break; + } + + heap_object->set_address(report->allocation_address); + heap_object->set_size(report->allocation_size); + unwinder->SetDisplayBuildID(true); + + heap_object->set_allocation_tid(report->allocation_tid); + for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; ++i) { + unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]); + BacktraceFrame* f = heap_object->add_allocation_backtrace(); + fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps()); + } + + heap_object->set_deallocation_tid(report->deallocation_tid); + for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i]; + ++i) { + unwindstack::FrameData frame_data = + unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]); + BacktraceFrame* f = heap_object->add_deallocation_backtrace(); + fill_in_backtrace_frame(f, frame_data, unwinder->GetMaps()); + } + + set_human_readable_cause(cause, untagged_fault_addr_); +} + +void ScudoCrashData::AddCauseProtos(Tombstone* tombstone, unwindstack::Unwinder* unwinder) const { + size_t report_num = 0; + while (report_num < sizeof(error_info_.reports) / sizeof(error_info_.reports[0]) && + error_info_.reports[report_num].error_type != UNKNOWN) { + FillInCause(tombstone->add_causes(), &error_info_.reports[report_num++], unwinder); + } +} + void ScudoCrashData::DumpCause(log_t* log, unwindstack::Unwinder* unwinder) const { if (error_info_.reports[1].error_type != UNKNOWN) { _LOG(log, logtype::HEADER, @@ -138,7 +193,8 @@ void ScudoCrashData::DumpReport(const scudo_error_report* report, log_t* log, if (report->allocation_trace[0]) { _LOG(log, logtype::BACKTRACE, "\nallocated by thread %u:\n", report->allocation_tid); unwinder->SetDisplayBuildID(true); - for (size_t i = 0; i < 64 && report->allocation_trace[i]; ++i) { + for (size_t i = 0; i < arraysize(report->allocation_trace) && report->allocation_trace[i]; + ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->allocation_trace[i]); frame_data.num = i; @@ -149,7 +205,8 @@ void ScudoCrashData::DumpReport(const scudo_error_report* report, log_t* log, if (report->deallocation_trace[0]) { _LOG(log, logtype::BACKTRACE, "\ndeallocated by thread %u:\n", report->deallocation_tid); unwinder->SetDisplayBuildID(true); - for (size_t i = 0; i < 64 && report->deallocation_trace[i]; ++i) { + for (size_t i = 0; i < arraysize(report->deallocation_trace) && report->deallocation_trace[i]; + ++i) { unwindstack::FrameData frame_data = unwinder->BuildFrameFromPcOnly(report->deallocation_trace[i]); frame_data.num = i; diff --git a/debuggerd/libdebuggerd/test/UnwinderMock.h b/debuggerd/libdebuggerd/test/UnwinderMock.h index 44a9214e5..8f84346af 100644 --- a/debuggerd/libdebuggerd/test/UnwinderMock.h +++ b/debuggerd/libdebuggerd/test/UnwinderMock.h @@ -33,8 +33,7 @@ class UnwinderMock : public unwindstack::Unwinder { void MockSetBuildID(uint64_t offset, const std::string& build_id) { unwindstack::MapInfo* map_info = GetMaps()->Find(offset); if (map_info != nullptr) { - std::string* new_build_id = new std::string(build_id); - map_info->build_id = new_build_id; + map_info->SetBuildID(std::string(build_id)); } } }; diff --git a/debuggerd/libdebuggerd/test/dump_memory_test.cpp b/debuggerd/libdebuggerd/test/dump_memory_test.cpp index f16f578a4..5be145aad 100644 --- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp +++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp @@ -73,34 +73,14 @@ const char g_expected_partial_dump[] = \ " 0000000012345600 2726252423222120 2f2e2d2c2b2a2928 !\"#$%&'()*+,-./\n" " 0000000012345610 3736353433323130 3f3e3d3c3b3a3938 0123456789:;<=>?\n" " 0000000012345620 4746454443424140 4f4e4d4c4b4a4948 @ABCDEFGHIJKLMNO\n" -" 0000000012345630 5756555453525150 5f5e5d5c5b5a5958 PQRSTUVWXYZ[\\]^_\n" -" 0000000012345640 6766656463626160 ---------------- `abcdefg........\n" -" 0000000012345650 ---------------- ---------------- ................\n" -" 0000000012345660 ---------------- ---------------- ................\n" -" 0000000012345670 ---------------- ---------------- ................\n" -" 0000000012345680 ---------------- ---------------- ................\n" -" 0000000012345690 ---------------- ---------------- ................\n" -" 00000000123456a0 ---------------- ---------------- ................\n" -" 00000000123456b0 ---------------- ---------------- ................\n" -" 00000000123456c0 ---------------- ---------------- ................\n" -" 00000000123456d0 ---------------- ---------------- ................\n"; +" 0000000012345630 5756555453525150 5f5e5d5c5b5a5958 PQRSTUVWXYZ[\\]^_\n"; #else " 123455e0 03020100 07060504 0b0a0908 0f0e0d0c ................\n" " 123455f0 13121110 17161514 1b1a1918 1f1e1d1c ................\n" " 12345600 23222120 27262524 2b2a2928 2f2e2d2c !\"#$%&'()*+,-./\n" " 12345610 33323130 37363534 3b3a3938 3f3e3d3c 0123456789:;<=>?\n" " 12345620 43424140 47464544 4b4a4948 4f4e4d4c @ABCDEFGHIJKLMNO\n" -" 12345630 53525150 57565554 5b5a5958 5f5e5d5c PQRSTUVWXYZ[\\]^_\n" -" 12345640 63626160 67666564 -------- -------- `abcdefg........\n" -" 12345650 -------- -------- -------- -------- ................\n" -" 12345660 -------- -------- -------- -------- ................\n" -" 12345670 -------- -------- -------- -------- ................\n" -" 12345680 -------- -------- -------- -------- ................\n" -" 12345690 -------- -------- -------- -------- ................\n" -" 123456a0 -------- -------- -------- -------- ................\n" -" 123456b0 -------- -------- -------- -------- ................\n" -" 123456c0 -------- -------- -------- -------- ................\n" -" 123456d0 -------- -------- -------- -------- ................\n"; +" 12345630 53525150 57565554 5b5a5958 5f5e5d5c PQRSTUVWXYZ[\\]^_\n"; #endif class MemoryMock : public unwindstack::Memory { @@ -513,15 +493,7 @@ TEST_F(DumpMemoryTest, first_read_empty) { const char* expected_dump = \ "\nmemory near r4:\n" #if defined(__LP64__) -R"( 0000000010000f80 ---------------- ---------------- ................ - 0000000010000f90 ---------------- ---------------- ................ - 0000000010000fa0 ---------------- ---------------- ................ - 0000000010000fb0 ---------------- ---------------- ................ - 0000000010000fc0 ---------------- ---------------- ................ - 0000000010000fd0 ---------------- ---------------- ................ - 0000000010000fe0 ---------------- ---------------- ................ - 0000000010000ff0 ---------------- ---------------- ................ - 0000000010001000 8786858483828180 8f8e8d8c8b8a8988 ................ +R"( 0000000010001000 8786858483828180 8f8e8d8c8b8a8988 ................ 0000000010001010 9796959493929190 9f9e9d9c9b9a9998 ................ 0000000010001020 a7a6a5a4a3a2a1a0 afaeadacabaaa9a8 ................ 0000000010001030 b7b6b5b4b3b2b1b0 bfbebdbcbbbab9b8 ................ @@ -531,15 +503,7 @@ R"( 0000000010000f80 ---------------- ---------------- ................ 0000000010001070 f7f6f5f4f3f2f1f0 fffefdfcfbfaf9f8 ................ )"; #else -R"( 10000f80 -------- -------- -------- -------- ................ - 10000f90 -------- -------- -------- -------- ................ - 10000fa0 -------- -------- -------- -------- ................ - 10000fb0 -------- -------- -------- -------- ................ - 10000fc0 -------- -------- -------- -------- ................ - 10000fd0 -------- -------- -------- -------- ................ - 10000fe0 -------- -------- -------- -------- ................ - 10000ff0 -------- -------- -------- -------- ................ - 10001000 83828180 87868584 8b8a8988 8f8e8d8c ................ +R"( 10001000 83828180 87868584 8b8a8988 8f8e8d8c ................ 10001010 93929190 97969594 9b9a9998 9f9e9d9c ................ 10001020 a3a2a1a0 a7a6a5a4 abaaa9a8 afaeadac ................ 10001030 b3b2b1b0 b7b6b5b4 bbbab9b8 bfbebdbc ................ @@ -574,39 +538,11 @@ TEST_F(DumpMemoryTest, first_read_empty_second_read_stops) { const char* expected_dump = \ "\nmemory near r4:\n" #if defined(__LP64__) -" 0000000010000f40 ---------------- ---------------- ................\n" -" 0000000010000f50 ---------------- ---------------- ................\n" -" 0000000010000f60 ---------------- ---------------- ................\n" -" 0000000010000f70 ---------------- ---------------- ................\n" -" 0000000010000f80 ---------------- ---------------- ................\n" -" 0000000010000f90 ---------------- ---------------- ................\n" -" 0000000010000fa0 ---------------- ---------------- ................\n" -" 0000000010000fb0 ---------------- ---------------- ................\n" -" 0000000010000fc0 ---------------- ---------------- ................\n" -" 0000000010000fd0 ---------------- ---------------- ................\n" -" 0000000010000fe0 ---------------- ---------------- ................\n" -" 0000000010000ff0 ---------------- ---------------- ................\n" " 0000000010001000 c7c6c5c4c3c2c1c0 cfcecdcccbcac9c8 ................\n" -" 0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n" -" 0000000010001020 ---------------- ---------------- ................\n" -" 0000000010001030 ---------------- ---------------- ................\n"; +" 0000000010001010 d7d6d5d4d3d2d1d0 dfdedddcdbdad9d8 ................\n"; #else -" 10000f40 -------- -------- -------- -------- ................\n" -" 10000f50 -------- -------- -------- -------- ................\n" -" 10000f60 -------- -------- -------- -------- ................\n" -" 10000f70 -------- -------- -------- -------- ................\n" -" 10000f80 -------- -------- -------- -------- ................\n" -" 10000f90 -------- -------- -------- -------- ................\n" -" 10000fa0 -------- -------- -------- -------- ................\n" -" 10000fb0 -------- -------- -------- -------- ................\n" -" 10000fc0 -------- -------- -------- -------- ................\n" -" 10000fd0 -------- -------- -------- -------- ................\n" -" 10000fe0 -------- -------- -------- -------- ................\n" -" 10000ff0 -------- -------- -------- -------- ................\n" " 10001000 c3c2c1c0 c7c6c5c4 cbcac9c8 cfcecdcc ................\n" -" 10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n" -" 10001020 -------- -------- -------- -------- ................\n" -" 10001030 -------- -------- -------- -------- ................\n"; +" 10001010 d3d2d1d0 d7d6d5d4 dbdad9d8 dfdedddc ................\n"; #endif ASSERT_STREQ(expected_dump, tombstone_contents.c_str()); diff --git a/debuggerd/libdebuggerd/test/sys/system_properties.h b/debuggerd/libdebuggerd/test/sys/system_properties.h deleted file mode 100644 index 1f4f58afc..000000000 --- a/debuggerd/libdebuggerd/test/sys/system_properties.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#ifndef _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H -#define _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H - -// This is just enough to get the property code to compile on -// the host. - -#define PROP_VALUE_MAX 92 - -#endif // _DEBUGGERD_TEST_SYS_SYSTEM_PROPERTIES_H diff --git a/debuggerd/libdebuggerd/test/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp index 7fe8f8297..79ac122a7 100644 --- a/debuggerd/libdebuggerd/test/tombstone_test.cpp +++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp @@ -388,9 +388,8 @@ TEST_F(TombstoneTest, gwp_asan_cause_uaf_exact) { ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); std::string tombstone_contents; ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - ASSERT_THAT(tombstone_contents, - MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free on a 32-byte " - "allocation at 0x[a-fA-F0-9]+\n")); + ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Use After Free, 0 bytes " + "into a 32-byte allocation at 0x[a-fA-F0-9]+\n")); } TEST_F(TombstoneTest, gwp_asan_cause_double_free) { @@ -405,9 +404,8 @@ TEST_F(TombstoneTest, gwp_asan_cause_double_free) { ASSERT_TRUE(lseek(log_.tfd, 0, SEEK_SET) == 0); std::string tombstone_contents; ASSERT_TRUE(android::base::ReadFdToString(log_.tfd, &tombstone_contents)); - ASSERT_THAT(tombstone_contents, - MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free on a 32-byte " - "allocation at 0x[a-fA-F0-9]+\n")); + ASSERT_THAT(tombstone_contents, MatchesRegex("Cause: \\[GWP-ASan\\]: Double Free, 0 bytes into a " + "32-byte allocation at 0x[a-fA-F0-9]+\n")); } TEST_F(TombstoneTest, gwp_asan_cause_overflow) { diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp index 23ca070e5..3444e29e1 100644 --- a/debuggerd/libdebuggerd/tombstone_proto.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto.cpp @@ -17,6 +17,8 @@ #define LOG_TAG "DEBUG" #include "libdebuggerd/tombstone.h" +#include "libdebuggerd/gwp_asan.h" +#include "libdebuggerd/scudo.h" #include <errno.h> #include <fcntl.h> @@ -29,10 +31,12 @@ #include <time.h> #include <memory> +#include <optional> #include <string> #include <async_safe/log.h> +#include <android-base/file.h> #include <android-base/properties.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> @@ -45,6 +49,7 @@ #include <log/logprint.h> #include <private/android_filesystem_config.h> +#include <procinfo/process.h> #include <unwindstack/Maps.h> #include <unwindstack/Memory.h> #include <unwindstack/Regs.h> @@ -106,32 +111,120 @@ static std::optional<std::string> get_stack_overflow_cause(uint64_t fault_addr, return {}; } -static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwindstack::Maps* maps, - unwindstack::Regs* regs) { +void set_human_readable_cause(Cause* cause, uint64_t fault_addr) { + if (!cause->has_memory_error() || !cause->memory_error().has_heap()) { + return; + } + + const MemoryError& memory_error = cause->memory_error(); + const HeapObject& heap_object = memory_error.heap(); + + const char *tool_str; + switch (memory_error.tool()) { + case MemoryError_Tool_GWP_ASAN: + tool_str = "GWP-ASan"; + break; + case MemoryError_Tool_SCUDO: + tool_str = "MTE"; + break; + default: + tool_str = "Unknown"; + break; + } + + const char *error_type_str; + switch (memory_error.type()) { + case MemoryError_Type_USE_AFTER_FREE: + error_type_str = "Use After Free"; + break; + case MemoryError_Type_DOUBLE_FREE: + error_type_str = "Double Free"; + break; + case MemoryError_Type_INVALID_FREE: + error_type_str = "Invalid (Wild) Free"; + break; + case MemoryError_Type_BUFFER_OVERFLOW: + error_type_str = "Buffer Overflow"; + break; + case MemoryError_Type_BUFFER_UNDERFLOW: + error_type_str = "Buffer Underflow"; + break; + default: + cause->set_human_readable( + StringPrintf("[%s]: Unknown error occurred at 0x%" PRIx64 ".", tool_str, fault_addr)); + return; + } + + uint64_t diff; + const char* location_str; + + if (fault_addr < heap_object.address()) { + // Buffer Underflow, 6 bytes left of a 41-byte allocation at 0xdeadbeef. + location_str = "left of"; + diff = heap_object.address() - fault_addr; + } else if (fault_addr - heap_object.address() < heap_object.size()) { + // Use After Free, 40 bytes into a 41-byte allocation at 0xdeadbeef. + location_str = "into"; + diff = fault_addr - heap_object.address(); + } else { + // Buffer Overflow, 6 bytes right of a 41-byte allocation at 0xdeadbeef. + location_str = "right of"; + diff = fault_addr - heap_object.address() - heap_object.size(); + } + + // Suffix of 'bytes', i.e. 4 bytes' vs. '1 byte'. + const char* byte_suffix = "s"; + if (diff == 1) { + byte_suffix = ""; + } + + cause->set_human_readable(StringPrintf( + "[%s]: %s, %" PRIu64 " byte%s %s a %" PRIu64 "-byte allocation at 0x%" PRIx64, tool_str, + error_type_str, diff, byte_suffix, location_str, heap_object.size(), heap_object.address())); +} + +static void dump_probable_cause(Tombstone* tombstone, unwindstack::Unwinder* unwinder, + const ProcessInfo& process_info, const ThreadInfo& main_thread) { + ScudoCrashData scudo_crash_data(unwinder->GetProcessMemory().get(), process_info); + if (scudo_crash_data.CrashIsMine()) { + scudo_crash_data.AddCauseProtos(tombstone, unwinder); + return; + } + + GwpAsanCrashData gwp_asan_crash_data(unwinder->GetProcessMemory().get(), process_info, + main_thread); + if (gwp_asan_crash_data.CrashIsMine()) { + gwp_asan_crash_data.AddCauseProtos(tombstone, unwinder); + return; + } + + const siginfo *si = main_thread.siginfo; + auto fault_addr = reinterpret_cast<uint64_t>(si->si_addr); + unwindstack::Maps* maps = unwinder->GetMaps(); + std::optional<std::string> cause; if (si->si_signo == SIGSEGV && si->si_code == SEGV_MAPERR) { - if (si->si_addr < reinterpret_cast<void*>(4096)) { + if (fault_addr < 4096) { cause = "null pointer dereference"; - } else if (si->si_addr == reinterpret_cast<void*>(0xffff0ffc)) { + } else if (fault_addr == 0xffff0ffc) { cause = "call to kuser_helper_version"; - } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fe0)) { + } else if (fault_addr == 0xffff0fe0) { cause = "call to kuser_get_tls"; - } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fc0)) { + } else if (fault_addr == 0xffff0fc0) { cause = "call to kuser_cmpxchg"; - } else if (si->si_addr == reinterpret_cast<void*>(0xffff0fa0)) { + } else if (fault_addr == 0xffff0fa0) { cause = "call to kuser_memory_barrier"; - } else if (si->si_addr == reinterpret_cast<void*>(0xffff0f60)) { + } else if (fault_addr == 0xffff0f60) { cause = "call to kuser_cmpxchg64"; } else { - cause = get_stack_overflow_cause(reinterpret_cast<uint64_t>(si->si_addr), regs->sp(), maps); + cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps); } } else if (si->si_signo == SIGSEGV && si->si_code == SEGV_ACCERR) { - uint64_t fault_addr = reinterpret_cast<uint64_t>(si->si_addr); unwindstack::MapInfo* map_info = maps->Find(fault_addr); if (map_info != nullptr && map_info->flags == PROT_EXEC) { cause = "execute-only (no-read) memory access error; likely due to data in .text."; } else { - cause = get_stack_overflow_cause(fault_addr, regs->sp(), maps); + cause = get_stack_overflow_cause(fault_addr, main_thread.registers->sp(), maps); } } else if (si->si_signo == SIGSYS && si->si_code == SYS_SECCOMP) { cause = StringPrintf("seccomp prevented call to disallowed %s system call %d", ABI_STRING, @@ -139,7 +232,8 @@ static void dump_probable_cause(Tombstone* tombstone, const siginfo_t* si, unwin } if (cause) { - tombstone->mutable_cause()->set_human_readable(*cause); + Cause *cause_proto = tombstone->add_causes(); + cause_proto->set_human_readable(*cause); } } @@ -205,12 +299,49 @@ static void dump_open_fds(Tombstone* tombstone, const OpenFilesList* open_files) } } +void fill_in_backtrace_frame(BacktraceFrame* f, const unwindstack::FrameData& frame, + unwindstack::Maps* maps) { + f->set_rel_pc(frame.rel_pc); + f->set_pc(frame.pc); + f->set_sp(frame.sp); + + if (!frame.function_name.empty()) { + // TODO: Should this happen here, or on the display side? + char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr); + if (demangled_name) { + f->set_function_name(demangled_name); + free(demangled_name); + } else { + f->set_function_name(frame.function_name); + } + } + + f->set_function_offset(frame.function_offset); + + if (frame.map_start == frame.map_end) { + // No valid map associated with this frame. + f->set_file_name("<unknown>"); + } else if (!frame.map_name.empty()) { + f->set_file_name(frame.map_name); + } else { + f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start)); + } + + f->set_file_map_offset(frame.map_elf_start_offset); + + unwindstack::MapInfo* map_info = maps->Find(frame.map_start); + if (map_info) { + f->set_build_id(map_info->GetPrintableBuildID()); + } +} + static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, const ThreadInfo& thread_info, bool memory_dump = false) { Thread thread; thread.set_id(thread_info.tid); thread.set_name(thread_info.thread_name); + thread.set_tagged_addr_ctrl(thread_info.tagged_addr_ctrl); unwindstack::Maps* maps = unwinder->GetMaps(); unwindstack::Memory* memory = unwinder->GetProcessMemory().get(); @@ -225,20 +356,19 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, if (memory_dump) { MemoryDump dump; - char buf[256]; - size_t start_offset = 0; - ssize_t bytes = dump_memory(buf, sizeof(buf), &start_offset, &value, memory); - if (bytes == -1) { - return; - } - dump.set_register_name(name); - unwindstack::MapInfo* map_info = maps->Find(untag_address(value)); if (map_info) { dump.set_mapping_name(map_info->name); } + char buf[256]; + uint8_t tags[256 / kTagGranuleSize]; + size_t start_offset = 0; + ssize_t bytes = dump_memory(buf, sizeof(buf), tags, sizeof(tags), &value, memory); + if (bytes == -1) { + return; + } dump.set_begin_address(value); if (start_offset + bytes > sizeof(buf)) { @@ -246,7 +376,8 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, start_offset, bytes); } - dump.set_memory(buf, start_offset + bytes); + dump.set_memory(buf, bytes); + dump.set_tags(tags, bytes / kTagGranuleSize); *thread.add_memory_dump() = std::move(dump); } @@ -267,39 +398,7 @@ static void dump_thread(Tombstone* tombstone, unwindstack::Unwinder* unwinder, unwinder->SetDisplayBuildID(true); for (const auto& frame : unwinder->frames()) { BacktraceFrame* f = thread.add_current_backtrace(); - f->set_rel_pc(frame.rel_pc); - f->set_pc(frame.pc); - f->set_sp(frame.sp); - - if (!frame.function_name.empty()) { - // TODO: Should this happen here, or on the display side? - char* demangled_name = - __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr); - if (demangled_name) { - f->set_function_name(demangled_name); - free(demangled_name); - } else { - f->set_function_name(frame.function_name); - } - } - - f->set_function_offset(frame.function_offset); - - if (frame.map_start == frame.map_end) { - // No valid map associated with this frame. - f->set_file_name("<unknown>"); - } else if (!frame.map_name.empty()) { - f->set_file_name(frame.map_name); - } else { - f->set_file_name(StringPrintf("<anonymous:%" PRIx64 ">", frame.map_start)); - } - - f->set_file_map_offset(frame.map_elf_start_offset); - - unwindstack::MapInfo* map_info = maps->Find(frame.map_start); - if (map_info) { - f->set_build_id(map_info->GetPrintableBuildID()); - } + fill_in_backtrace_frame(f, frame, maps); } } @@ -423,6 +522,14 @@ static void dump_logcat(Tombstone* tombstone, pid_t pid) { dump_log_file(tombstone, "main", pid); } +static std::optional<uint64_t> read_uptime_secs() { + std::string uptime; + if (!android::base::ReadFileToString("/proc/uptime", &uptime)) { + return {}; + } + return strtoll(uptime.c_str(), nullptr, 10); +} + void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwinder, const std::map<pid_t, ThreadInfo>& threads, pid_t target_thread, const ProcessInfo& process_info, const OpenFilesList* open_files) { @@ -433,6 +540,22 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind result.set_revision(android::base::GetProperty("ro.revision", "unknown")); result.set_timestamp(get_timestamp()); + std::optional<uint64_t> system_uptime = read_uptime_secs(); + if (system_uptime) { + android::procinfo::ProcessInfo proc_info; + std::string error; + if (android::procinfo::GetProcessInfo(target_thread, &proc_info, &error)) { + uint64_t starttime = proc_info.starttime / sysconf(_SC_CLK_TCK); + result.set_process_uptime(*system_uptime - starttime); + } else { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read process info: %s", + error.c_str()); + } + } else { + async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read /proc/uptime: %s", + strerror(errno)); + } + const ThreadInfo& main_thread = threads.at(target_thread); result.set_pid(main_thread.pid); result.set_tid(main_thread.tid); @@ -458,7 +581,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind if (process_info.has_fault_address) { sig.set_has_fault_address(true); - sig.set_fault_address(process_info.untagged_fault_address); + sig.set_fault_address(process_info.maybe_tagged_fault_address); } *result.mutable_signal_info() = sig; @@ -473,8 +596,7 @@ void engrave_tombstone_proto(Tombstone* tombstone, unwindstack::Unwinder* unwind } } - dump_probable_cause(&result, main_thread.siginfo, unwinder->GetMaps(), - main_thread.registers.get()); + dump_probable_cause(&result, unwinder, process_info, main_thread); dump_mappings(&result, unwinder); diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp index 187379daf..00ca7c18f 100644 --- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp +++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp @@ -74,6 +74,9 @@ static void print_thread_header(CallbackType callback, const Tombstone& tombston CB(should_log, "pid: %d, tid: %d, name: %s >>> %s <<<", tombstone.pid(), thread.id(), thread.name().c_str(), tombstone.process_name().c_str()); CB(should_log, "uid: %d", tombstone.uid()); + if (thread.tagged_addr_ctrl() != -1) { + CB(should_log, "tagged_addr_ctrl: %016" PRIx64, thread.tagged_addr_ctrl()); + } } static void print_register_row(CallbackType callback, int word_size, @@ -136,12 +139,11 @@ static void print_thread_registers(CallbackType callback, const Tombstone& tombs print_register_row(callback, word_size, special_row, should_log); } -static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone, - const Thread& thread, bool should_log) { - CBS(""); - CB(should_log, "backtrace:"); +static void print_backtrace(CallbackType callback, const Tombstone& tombstone, + const google::protobuf::RepeatedPtrField<BacktraceFrame>& backtrace, + bool should_log) { int index = 0; - for (const auto& frame : thread.current_backtrace()) { + for (const auto& frame : backtrace) { std::string function; if (!frame.function_name().empty()) { @@ -159,16 +161,32 @@ static void print_thread_backtrace(CallbackType callback, const Tombstone& tombs } } +static void print_thread_backtrace(CallbackType callback, const Tombstone& tombstone, + const Thread& thread, bool should_log) { + CBS(""); + CB(should_log, "backtrace:"); + print_backtrace(callback, tombstone, thread.current_backtrace(), should_log); +} + static void print_thread_memory_dump(CallbackType callback, const Tombstone& tombstone, const Thread& thread) { static constexpr size_t bytes_per_line = 16; + static_assert(bytes_per_line == kTagGranuleSize); int word_size = pointer_width(tombstone); for (const auto& mem : thread.memory_dump()) { CBS(""); - CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str()); + if (mem.mapping_name().empty()) { + CBS("memory near %s:", mem.register_name().c_str()); + } else { + CBS("memory near %s (%s):", mem.register_name().c_str(), mem.mapping_name().c_str()); + } uint64_t addr = mem.begin_address(); for (size_t offset = 0; offset < mem.memory().size(); offset += bytes_per_line) { - std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, addr + offset); + uint64_t tagged_addr = addr; + if (mem.tags().size() > offset / kTagGranuleSize) { + tagged_addr |= static_cast<uint64_t>(mem.tags()[offset / kTagGranuleSize]) << 56; + } + std::string line = StringPrintf(" %0*" PRIx64, word_size * 2, tagged_addr + offset); size_t bytes = std::min(bytes_per_line, mem.memory().size() - offset); for (size_t i = 0; i < bytes; i += word_size) { @@ -231,9 +249,8 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, sender_desc.c_str(), fault_addr_desc.c_str()); } - if (tombstone.has_cause()) { - const Cause& cause = tombstone.cause(); - CBL("Cause: %s", cause.human_readable().c_str()); + if (tombstone.causes_size() == 1) { + CBL("Cause: %s", tombstone.causes(0).human_readable().c_str()); } if (!tombstone.abort_message().empty()) { @@ -242,6 +259,36 @@ static void print_main_thread(CallbackType callback, const Tombstone& tombstone, print_thread_registers(callback, tombstone, thread, true); print_thread_backtrace(callback, tombstone, thread, true); + + if (tombstone.causes_size() > 1) { + CBS(""); + CBS("Note: multiple potential causes for this crash were detected, listing them in decreasing " + "order of probability."); + } + + for (const Cause& cause : tombstone.causes()) { + if (tombstone.causes_size() > 1) { + CBS(""); + CBS("Cause: %s", cause.human_readable().c_str()); + } + + if (cause.has_memory_error() && cause.memory_error().has_heap()) { + const HeapObject& heap_object = cause.memory_error().heap(); + + if (heap_object.deallocation_backtrace_size() != 0) { + CBS(""); + CBS("deallocated by thread %" PRIu64 ":", heap_object.deallocation_tid()); + print_backtrace(callback, tombstone, heap_object.deallocation_backtrace(), false); + } + + if (heap_object.allocation_backtrace_size() != 0) { + CBS(""); + CBS("allocated by thread %" PRIu64 ":", heap_object.allocation_tid()); + print_backtrace(callback, tombstone, heap_object.allocation_backtrace(), false); + } + } + } + print_thread_memory_dump(callback, tombstone, thread); CBS(""); @@ -313,6 +360,7 @@ bool tombstone_proto_to_text(const Tombstone& tombstone, CallbackType callback) CBL("Revision: '%s'", tombstone.revision().c_str()); CBL("ABI: '%s'", abi_string(tombstone)); CBL("Timestamp: %s", tombstone.timestamp().c_str()); + CBL("Process uptime: %ds", tombstone.process_uptime()); // Process header const auto& threads = tombstone.threads(); diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp index 6f13ed4c2..2c645b542 100644 --- a/debuggerd/libdebuggerd/utility.cpp +++ b/debuggerd/libdebuggerd/utility.cpp @@ -125,8 +125,9 @@ void _VLOG(log_t* log, enum logtype ltype, const char* fmt, va_list ap) { #define MEMORY_BYTES_TO_DUMP 256 #define MEMORY_BYTES_PER_LINE 16 +static_assert(MEMORY_BYTES_PER_LINE == kTagGranuleSize); -ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr, +ssize_t dump_memory(void* out, size_t len, uint8_t* tags, size_t tags_len, uint64_t* addr, unwindstack::Memory* memory) { // Align the address to the number of bytes per line to avoid confusing memory tag output if // memory is tagged and we start from a misaligned address. Start 32 bytes before the address. @@ -154,17 +155,17 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr, bytes &= ~(sizeof(uintptr_t) - 1); } - *start_offset = 0; bool skip_2nd_read = false; if (bytes == 0) { // In this case, we might want to try another read at the beginning of // the next page only if it's within the amount of memory we would have // read. size_t page_size = sysconf(_SC_PAGE_SIZE); - *start_offset = ((*addr + (page_size - 1)) & ~(page_size - 1)) - *addr; - if (*start_offset == 0 || *start_offset >= len) { + uint64_t next_page = (*addr + (page_size - 1)) & ~(page_size - 1); + if (next_page == *addr || next_page >= *addr + len) { skip_2nd_read = true; } + *addr = next_page; } if (bytes < len && !skip_2nd_read) { @@ -174,8 +175,7 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr, // into a readable map. Only requires one extra read because a map has // to contain at least one page, and the total number of bytes to dump // is smaller than a page. - size_t bytes2 = memory->Read(*addr + *start_offset + bytes, static_cast<uint8_t*>(out) + bytes, - len - bytes - *start_offset); + size_t bytes2 = memory->Read(*addr + bytes, static_cast<uint8_t*>(out) + bytes, len - bytes); bytes += bytes2; if (bytes2 > 0 && bytes % sizeof(uintptr_t) != 0) { // This should never happen, but we'll try and continue any way. @@ -190,15 +190,24 @@ ssize_t dump_memory(void* out, size_t len, size_t* start_offset, uint64_t* addr, return -1; } + for (uint64_t tag_granule = 0; tag_granule < bytes / kTagGranuleSize; ++tag_granule) { + long tag = memory->ReadTag(*addr + kTagGranuleSize * tag_granule); + if (tag_granule < tags_len) { + tags[tag_granule] = tag >= 0 ? tag : 0; + } else { + ALOGE("Insufficient space for tags"); + } + } + return bytes; } void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const std::string& label) { // Dump 256 bytes uintptr_t data[MEMORY_BYTES_TO_DUMP / sizeof(uintptr_t)]; - size_t start_offset = 0; + uint8_t tags[MEMORY_BYTES_TO_DUMP / kTagGranuleSize]; - ssize_t bytes = dump_memory(data, sizeof(data), &start_offset, &addr, memory); + ssize_t bytes = dump_memory(data, sizeof(data), tags, sizeof(tags), &addr, memory); if (bytes == -1) { return; } @@ -212,38 +221,27 @@ void dump_memory(log_t* log, unwindstack::Memory* memory, uint64_t addr, const s // On 32-bit machines, there are still 16 bytes per line but addresses and // words are of course presented differently. uintptr_t* data_ptr = data; - size_t current = 0; - size_t total_bytes = start_offset + bytes; - for (size_t line = 0; line < MEMORY_BYTES_TO_DUMP / MEMORY_BYTES_PER_LINE; line++) { - uint64_t tagged_addr = addr; - long tag = memory->ReadTag(addr); - if (tag >= 0) { - tagged_addr |= static_cast<uint64_t>(tag) << 56; - } + uint8_t* tags_ptr = tags; + for (size_t line = 0; line < static_cast<size_t>(bytes) / MEMORY_BYTES_PER_LINE; line++) { + uint64_t tagged_addr = addr | static_cast<uint64_t>(*tags_ptr++) << 56; std::string logline; android::base::StringAppendF(&logline, " %" PRIPTR, tagged_addr); addr += MEMORY_BYTES_PER_LINE; std::string ascii; for (size_t i = 0; i < MEMORY_BYTES_PER_LINE / sizeof(uintptr_t); i++) { - if (current >= start_offset && current + sizeof(uintptr_t) <= total_bytes) { - android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr)); - - // Fill out the ascii string from the data. - uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr); - for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) { - if (*ptr >= 0x20 && *ptr < 0x7f) { - ascii += *ptr; - } else { - ascii += '.'; - } + android::base::StringAppendF(&logline, " %" PRIPTR, static_cast<uint64_t>(*data_ptr)); + + // Fill out the ascii string from the data. + uint8_t* ptr = reinterpret_cast<uint8_t*>(data_ptr); + for (size_t val = 0; val < sizeof(uintptr_t); val++, ptr++) { + if (*ptr >= 0x20 && *ptr < 0x7f) { + ascii += *ptr; + } else { + ascii += '.'; } - data_ptr++; - } else { - logline += ' ' + std::string(sizeof(uintptr_t) * 2, '-'); - ascii += std::string(sizeof(uintptr_t), '.'); } - current += sizeof(uintptr_t); + data_ptr++; } _LOG(log, logtype::MEMORY, "%s %s\n", logline.c_str(), ascii.c_str()); } diff --git a/debuggerd/proto/tombstone.proto b/debuggerd/proto/tombstone.proto index 2c7156bcb..dd15ff637 100644 --- a/debuggerd/proto/tombstone.proto +++ b/debuggerd/proto/tombstone.proto @@ -19,16 +19,19 @@ message Tombstone { string process_name = 9; + // Process uptime in seconds. + uint32 process_uptime = 20; + Signal signal_info = 10; string abort_message = 14; - Cause cause = 15; + repeated Cause causes = 15; map<uint32, Thread> threads = 16; repeated MemoryMapping memory_mappings = 17; repeated LogBuffer log_buffers = 18; repeated FD open_fds = 19; - reserved 20 to 999; + reserved 21 to 999; } enum Architecture { @@ -57,10 +60,52 @@ message Signal { reserved 10 to 999; } +message HeapObject { + uint64 address = 1; + uint64 size = 2; + + uint64 allocation_tid = 3; + repeated BacktraceFrame allocation_backtrace = 4; + + uint64 deallocation_tid = 5; + repeated BacktraceFrame deallocation_backtrace = 6; +} + +message MemoryError { + enum Tool { + GWP_ASAN = 0; + SCUDO = 1; + + reserved 2 to 999; + } + Tool tool = 1; + + enum Type { + UNKNOWN = 0; + USE_AFTER_FREE = 1; + DOUBLE_FREE = 2; + INVALID_FREE = 3; + BUFFER_OVERFLOW = 4; + BUFFER_UNDERFLOW = 5; + + reserved 6 to 999; + } + Type type = 2; + + oneof location { + HeapObject heap = 3; + } + + reserved 4 to 999; +} + message Cause { string human_readable = 1; + oneof details { + MemoryError memory_error = 2; + } - reserved 2 to 999; + reserved 3 to 999; } message Register { @@ -76,8 +121,9 @@ message Thread { repeated Register registers = 3; repeated BacktraceFrame current_backtrace = 4; repeated MemoryDump memory_dump = 5; + int64 tagged_addr_ctrl = 6; - reserved 6 to 999; + reserved 7 to 999; } message BacktraceFrame { @@ -100,8 +146,9 @@ message MemoryDump { string mapping_name = 2; uint64 begin_address = 3; bytes memory = 4; + bytes tags = 5; - reserved 5 to 999; + reserved 6 to 999; } message MemoryMapping { diff --git a/debuggerd/protocol.h b/debuggerd/protocol.h index 53a76ea71..f33b2f0c9 100644 --- a/debuggerd/protocol.h +++ b/debuggerd/protocol.h @@ -97,6 +97,7 @@ struct __attribute__((__packed__)) CrashInfoDataDynamic : public CrashInfoDataSt uintptr_t gwp_asan_metadata; uintptr_t scudo_stack_depot; uintptr_t scudo_region_info; + uintptr_t scudo_ring_buffer; }; struct __attribute__((__packed__)) CrashInfo { diff --git a/fastboot/Android.bp b/fastboot/Android.bp index a1f1c17cb..43b2ddd67 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -55,6 +55,7 @@ cc_library_host_static { "tcp.cpp", "udp.cpp", "util.cpp", + "vendor_boot_img_utils.cpp", "fastboot_driver.cpp", ], @@ -75,7 +76,9 @@ cc_library_host_static { ], header_libs: [ + "avb_headers", "bootimg_headers", + "libstorage_literals_headers", ], export_header_lib_headers: [ @@ -124,6 +127,7 @@ cc_defaults { "-Wextra", "-Werror", "-Wvla", + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", ], rtti: true, @@ -138,6 +142,12 @@ cc_binary { recovery: true, + product_variables: { + debuggable: { + cppflags: ["-DFB_ENABLE_FETCH"], + }, + }, + srcs: [ "device/commands.cpp", "device/fastboot_device.cpp", @@ -183,7 +193,8 @@ cc_binary { header_libs: [ "avb_headers", "libsnapshot_headers", - ] + "libstorage_literals_headers", + ], } cc_defaults { @@ -196,6 +207,8 @@ cc_defaults { "-Wextra", "-Werror", "-Wunreachable-code", + "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", + "-D_FILE_OFFSET_BITS=64" ], target: { @@ -262,12 +275,17 @@ cc_library_host_static { "tcp.cpp", "udp.cpp", "util.cpp", + "vendor_boot_img_utils.cpp", "fastboot_driver.cpp", ], // Only version the final binaries use_version_lib: false, static_libs: ["libbuildversion"], + header_libs: [ + "avb_headers", + "libstorage_literals_headers", + ], generated_headers: ["platform_tools_version"], @@ -359,3 +377,33 @@ cc_test_host { }, }, } + +cc_test_host { + name: "fastboot_vendor_boot_img_utils_test", + srcs: ["vendor_boot_img_utils_test.cpp"], + static_libs: [ + "libbase", + "libc++fs", + "libfastboot", + "libgmock", + "liblog", + ], + header_libs: [ + "avb_headers", + "bootimg_headers", + ], + cflags: [ + "-Wall", + "-Werror", + ], + data: [ + ":fastboot_test_dtb", + ":fastboot_test_bootconfig", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_vendor_ramdisk_platform", + ":fastboot_test_vendor_ramdisk_replace", + ":fastboot_test_vendor_boot_v3", + ":fastboot_test_vendor_boot_v4_without_frag", + ":fastboot_test_vendor_boot_v4_with_frag" + ], +} diff --git a/fastboot/bootimg_utils.cpp b/fastboot/bootimg_utils.cpp index 2c0989ed1..d2056aa41 100644 --- a/fastboot/bootimg_utils.cpp +++ b/fastboot/bootimg_utils.cpp @@ -34,22 +34,22 @@ #include <stdlib.h> #include <string.h> -static void bootimg_set_cmdline_v3(boot_img_hdr_v3* h, const std::string& cmdline) { +static void bootimg_set_cmdline_v3_and_above(boot_img_hdr_v3* h, const std::string& cmdline) { if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size()); strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str()); } void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline) { - if (h->header_version == 3) { - return bootimg_set_cmdline_v3(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline); + if (h->header_version >= 3) { + return bootimg_set_cmdline_v3_and_above(reinterpret_cast<boot_img_hdr_v3*>(h), cmdline); } if (cmdline.size() >= sizeof(h->cmdline)) die("command line too large: %zu", cmdline.size()); strcpy(reinterpret_cast<char*>(h->cmdline), cmdline.c_str()); } -static boot_img_hdr_v3* mkbootimg_v3(const std::vector<char>& kernel, - const std::vector<char>& ramdisk, const boot_img_hdr_v2& src, - std::vector<char>* out) { +static void mkbootimg_v3_and_above(const std::vector<char>& kernel, + const std::vector<char>& ramdisk, const boot_img_hdr_v2& src, + std::vector<char>* out) { #define V3_PAGE_SIZE 4096 const size_t page_mask = V3_PAGE_SIZE - 1; int64_t kernel_actual = (kernel.size() + page_mask) & (~page_mask); @@ -65,22 +65,27 @@ static boot_img_hdr_v3* mkbootimg_v3(const std::vector<char>& kernel, hdr->ramdisk_size = ramdisk.size(); hdr->os_version = src.os_version; hdr->header_size = sizeof(boot_img_hdr_v3); - hdr->header_version = 3; + hdr->header_version = src.header_version; + + if (src.header_version >= 4) { + auto hdr_v4 = reinterpret_cast<boot_img_hdr_v4*>(hdr); + hdr_v4->signature_size = 0; + } memcpy(hdr->magic + V3_PAGE_SIZE, kernel.data(), kernel.size()); memcpy(hdr->magic + V3_PAGE_SIZE + kernel_actual, ramdisk.data(), ramdisk.size()); - - return hdr; } -boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk, - const std::vector<char>& second, const std::vector<char>& dtb, - size_t base, const boot_img_hdr_v2& src, std::vector<char>* out) { - if (src.header_version == 3) { +void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk, + const std::vector<char>& second, const std::vector<char>& dtb, size_t base, + const boot_img_hdr_v2& src, std::vector<char>* out) { + if (src.header_version >= 3) { if (!second.empty() || !dtb.empty()) { - die("Second stage bootloader and dtb not supported in v3 boot image\n"); + die("Second stage bootloader and dtb not supported in v%d boot image\n", + src.header_version); } - return reinterpret_cast<boot_img_hdr_v2*>(mkbootimg_v3(kernel, ramdisk, src, out)); + mkbootimg_v3_and_above(kernel, ramdisk, src, out); + return; } const size_t page_mask = src.page_size - 1; @@ -122,5 +127,4 @@ boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<ch second.size()); memcpy(hdr->magic + hdr->page_size + kernel_actual + ramdisk_actual + second_actual, dtb.data(), dtb.size()); - return hdr; } diff --git a/fastboot/bootimg_utils.h b/fastboot/bootimg_utils.h index b7cf9bd90..0eb003d2f 100644 --- a/fastboot/bootimg_utils.h +++ b/fastboot/bootimg_utils.h @@ -35,7 +35,8 @@ #include <string> #include <vector> -boot_img_hdr_v2* mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk, - const std::vector<char>& second, const std::vector<char>& dtb, - size_t base, const boot_img_hdr_v2& src, std::vector<char>* out); +void mkbootimg(const std::vector<char>& kernel, const std::vector<char>& ramdisk, + const std::vector<char>& second, const std::vector<char>& dtb, size_t base, + const boot_img_hdr_v2& src, std::vector<char>* out); + void bootimg_set_cmdline(boot_img_hdr_v2* h, const std::string& cmdline); diff --git a/fastboot/constants.h b/fastboot/constants.h index ba43ca5bd..4ea68daff 100644 --- a/fastboot/constants.h +++ b/fastboot/constants.h @@ -35,6 +35,7 @@ #define FB_CMD_OEM "oem" #define FB_CMD_GSI "gsi" #define FB_CMD_SNAPSHOT_UPDATE "snapshot-update" +#define FB_CMD_FETCH "fetch" #define RESPONSE_OKAY "OKAY" #define RESPONSE_FAIL "FAIL" @@ -77,3 +78,4 @@ #define FB_VAR_FIRST_API_LEVEL "first-api-level" #define FB_VAR_SECURITY_PATCH_LEVEL "security-patch-level" #define FB_VAR_TREBLE_ENABLED "treble-enabled" +#define FB_VAR_MAX_FETCH_SIZE "max-fetch-size" diff --git a/fastboot/device/commands.cpp b/fastboot/device/commands.cpp index b2b6a9e5c..0a72812c2 100644 --- a/fastboot/device/commands.cpp +++ b/fastboot/device/commands.cpp @@ -16,6 +16,7 @@ #include "commands.h" +#include <inttypes.h> #include <sys/socket.h> #include <sys/un.h> @@ -36,6 +37,7 @@ #include <liblp/builder.h> #include <liblp/liblp.h> #include <libsnapshot/snapshot.h> +#include <storage_literals/storage_literals.h> #include <uuid/uuid.h> #include "constants.h" @@ -43,6 +45,12 @@ #include "flashing.h" #include "utility.h" +#ifdef FB_ENABLE_FETCH +static constexpr bool kEnableFetch = true; +#else +static constexpr bool kEnableFetch = false; +#endif + using android::fs_mgr::MetadataBuilder; using ::android::hardware::hidl_string; using ::android::hardware::boot::V1_0::BoolResult; @@ -54,6 +62,8 @@ using ::android::hardware::fastboot::V1_0::Status; using android::snapshot::SnapshotManager; using IBootControl1_1 = ::android::hardware::boot::V1_1::IBootControl; +using namespace android::storage_literals; + struct VariableHandlers { // Callback to retrieve the value of a single variable. std::function<bool(FastbootDevice*, const std::vector<std::string>&, std::string*)> get; @@ -136,7 +146,9 @@ bool GetVarHandler(FastbootDevice* device, const std::vector<std::string>& args) {FB_VAR_DYNAMIC_PARTITION, {GetDynamicPartition, nullptr}}, {FB_VAR_FIRST_API_LEVEL, {GetFirstApiLevel, nullptr}}, {FB_VAR_SECURITY_PATCH_LEVEL, {GetSecurityPatchLevel, nullptr}}, - {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}}; + {FB_VAR_TREBLE_ENABLED, {GetTrebleEnabled, nullptr}}, + {FB_VAR_MAX_FETCH_SIZE, {GetMaxFetchSize, nullptr}}, + }; if (args.size() < 2) { return device->WriteFail("Missing argument"); @@ -380,13 +392,13 @@ static bool EnterRecovery() { struct sockaddr_un addr = {.sun_family = AF_UNIX}; strncpy(addr.sun_path, "/dev/socket/recovery", sizeof(addr.sun_path) - 1); - if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + if (connect(sock.get(), (struct sockaddr*)&addr, sizeof(addr)) < 0) { PLOG(ERROR) << "Couldn't connect to recovery"; return false; } // Switch to recovery will not update the boot reason since it does not // require a reboot. - auto ret = write(sock, &msg_switch_to_recovery, sizeof(msg_switch_to_recovery)); + auto ret = write(sock.get(), &msg_switch_to_recovery, sizeof(msg_switch_to_recovery)); if (ret != sizeof(msg_switch_to_recovery)) { PLOG(ERROR) << "Couldn't write message to switch to recovery"; return false; @@ -671,3 +683,175 @@ bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string } return device->WriteStatus(FastbootResult::OKAY, "Success"); } + +namespace { +// Helper of FetchHandler. +class PartitionFetcher { + public: + static bool Fetch(FastbootDevice* device, const std::vector<std::string>& args) { + if constexpr (!kEnableFetch) { + return device->WriteFail("Fetch is not allowed on user build"); + } + + if (GetDeviceLockStatus()) { + return device->WriteFail("Fetch is not allowed on locked devices"); + } + + PartitionFetcher fetcher(device, args); + if (fetcher.Open()) { + fetcher.Fetch(); + } + CHECK(fetcher.ret_.has_value()); + return *fetcher.ret_; + } + + private: + PartitionFetcher(FastbootDevice* device, const std::vector<std::string>& args) + : device_(device), args_(&args) {} + // Return whether the partition is successfully opened. + // If successfully opened, ret_ is left untouched. Otherwise, ret_ is set to the value + // that FetchHandler should return. + bool Open() { + if (args_->size() < 2) { + ret_ = device_->WriteFail("Missing partition arg"); + return false; + } + + partition_name_ = args_->at(1); + if (std::find(kAllowedPartitions.begin(), kAllowedPartitions.end(), partition_name_) == + kAllowedPartitions.end()) { + ret_ = device_->WriteFail("Fetch is only allowed on [" + + android::base::Join(kAllowedPartitions, ", ") + "]"); + return false; + } + + if (!OpenPartition(device_, partition_name_, &handle_, true /* read */)) { + ret_ = device_->WriteFail( + android::base::StringPrintf("Cannot open %s", partition_name_.c_str())); + return false; + } + + partition_size_ = get_block_device_size(handle_.fd()); + if (partition_size_ == 0) { + ret_ = device_->WriteOkay(android::base::StringPrintf("Partition %s has size 0", + partition_name_.c_str())); + return false; + } + + start_offset_ = 0; + if (args_->size() >= 3) { + if (!android::base::ParseUint(args_->at(2), &start_offset_)) { + ret_ = device_->WriteFail("Invalid offset, must be integer"); + return false; + } + if (start_offset_ > std::numeric_limits<off64_t>::max()) { + ret_ = device_->WriteFail( + android::base::StringPrintf("Offset overflows: %" PRIx64, start_offset_)); + return false; + } + } + if (start_offset_ > partition_size_) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Invalid offset 0x%" PRIx64 ", partition %s has size 0x%" PRIx64, start_offset_, + partition_name_.c_str(), partition_size_)); + return false; + } + uint64_t maximum_total_size_to_read = partition_size_ - start_offset_; + total_size_to_read_ = maximum_total_size_to_read; + if (args_->size() >= 4) { + if (!android::base::ParseUint(args_->at(3), &total_size_to_read_)) { + ret_ = device_->WriteStatus(FastbootResult::FAIL, "Invalid size, must be integer"); + return false; + } + } + if (total_size_to_read_ == 0) { + ret_ = device_->WriteOkay("Read 0 bytes"); + return false; + } + if (total_size_to_read_ > maximum_total_size_to_read) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Invalid size to read 0x%" PRIx64 ", partition %s has size 0x%" PRIx64 + " and fetching from offset 0x%" PRIx64, + total_size_to_read_, partition_name_.c_str(), partition_size_, start_offset_)); + return false; + } + + if (total_size_to_read_ > kMaxFetchSizeDefault) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "Cannot fetch 0x%" PRIx64 + " bytes because it exceeds maximum transport size 0x%x", + partition_size_, kMaxDownloadSizeDefault)); + return false; + } + + return true; + } + + // Assume Open() returns true. + // After execution, ret_ is set to the value that FetchHandler should return. + void Fetch() { + CHECK(start_offset_ <= std::numeric_limits<off64_t>::max()); + if (lseek64(handle_.fd(), start_offset_, SEEK_SET) != static_cast<off64_t>(start_offset_)) { + ret_ = device_->WriteFail(android::base::StringPrintf( + "On partition %s, unable to lseek(0x%" PRIx64 ": %s", partition_name_.c_str(), + start_offset_, strerror(errno))); + return; + } + + if (!device_->WriteStatus(FastbootResult::DATA, + android::base::StringPrintf( + "%08x", static_cast<uint32_t>(total_size_to_read_)))) { + ret_ = false; + return; + } + uint64_t end_offset = start_offset_ + total_size_to_read_; + std::vector<char> buf(1_MiB); + uint64_t current_offset = start_offset_; + while (current_offset < end_offset) { + // On any error, exit. We can't return a status message to the driver because + // we are in the middle of writing data, so just let the driver guess what's wrong + // by ending the data stream prematurely. + uint64_t remaining = end_offset - current_offset; + uint64_t chunk_size = std::min<uint64_t>(buf.size(), remaining); + if (!android::base::ReadFully(handle_.fd(), buf.data(), chunk_size)) { + PLOG(ERROR) << std::hex << "Unable to read 0x" << chunk_size << " bytes from " + << partition_name_ << " @ offset 0x" << current_offset; + ret_ = false; + return; + } + if (!device_->HandleData(false /* is read */, buf.data(), chunk_size)) { + PLOG(ERROR) << std::hex << "Unable to send 0x" << chunk_size << " bytes of " + << partition_name_ << " @ offset 0x" << current_offset; + ret_ = false; + return; + } + current_offset += chunk_size; + } + + ret_ = device_->WriteOkay(android::base::StringPrintf( + "Fetched %s (offset=0x%" PRIx64 ", size=0x%" PRIx64, partition_name_.c_str(), + start_offset_, total_size_to_read_)); + } + + static constexpr std::array<const char*, 3> kAllowedPartitions{ + "vendor_boot", + "vendor_boot_a", + "vendor_boot_b", + }; + + FastbootDevice* device_; + const std::vector<std::string>* args_ = nullptr; + std::string partition_name_; + PartitionHandle handle_; + uint64_t partition_size_ = 0; + uint64_t start_offset_ = 0; + uint64_t total_size_to_read_ = 0; + + // What FetchHandler should return. + std::optional<bool> ret_ = std::nullopt; +}; +} // namespace + +bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args) { + return PartitionFetcher::Fetch(device, args); +} diff --git a/fastboot/device/commands.h b/fastboot/device/commands.h index c1324bc40..345ae1afe 100644 --- a/fastboot/device/commands.h +++ b/fastboot/device/commands.h @@ -20,6 +20,7 @@ #include <vector> constexpr unsigned int kMaxDownloadSizeDefault = 0x10000000; +constexpr unsigned int kMaxFetchSizeDefault = 0x10000000; class FastbootDevice; @@ -50,3 +51,4 @@ bool UpdateSuperHandler(FastbootDevice* device, const std::vector<std::string>& bool OemCmdHandler(FastbootDevice* device, const std::vector<std::string>& args); bool GsiHandler(FastbootDevice* device, const std::vector<std::string>& args); bool SnapshotUpdateHandler(FastbootDevice* device, const std::vector<std::string>& args); +bool FetchHandler(FastbootDevice* device, const std::vector<std::string>& args); diff --git a/fastboot/device/fastboot_device.cpp b/fastboot/device/fastboot_device.cpp index 35f3de020..64a934ddb 100644 --- a/fastboot/device/fastboot_device.cpp +++ b/fastboot/device/fastboot_device.cpp @@ -61,6 +61,7 @@ FastbootDevice::FastbootDevice() {FB_CMD_OEM, OemCmdHandler}, {FB_CMD_GSI, GsiHandler}, {FB_CMD_SNAPSHOT_UPDATE, SnapshotUpdateHandler}, + {FB_CMD_FETCH, FetchHandler}, }), boot_control_hal_(IBootControl::getService()), health_hal_(get_health_service()), @@ -137,14 +138,18 @@ bool FastbootDevice::WriteStatus(FastbootResult result, const std::string& messa } bool FastbootDevice::HandleData(bool read, std::vector<char>* data) { - auto read_write_data_size = read ? this->get_transport()->Read(data->data(), data->size()) - : this->get_transport()->Write(data->data(), data->size()); + return HandleData(read, data->data(), data->size()); +} + +bool FastbootDevice::HandleData(bool read, char* data, uint64_t size) { + auto read_write_data_size = read ? this->get_transport()->Read(data, size) + : this->get_transport()->Write(data, size); if (read_write_data_size == -1) { LOG(ERROR) << (read ? "read from" : "write to") << " transport failed"; return false; } - if (static_cast<size_t>(read_write_data_size) != data->size()) { - LOG(ERROR) << (read ? "read" : "write") << " expected " << data->size() << " bytes, got " + if (static_cast<size_t>(read_write_data_size) != size) { + LOG(ERROR) << (read ? "read" : "write") << " expected " << size << " bytes, got " << read_write_data_size; return false; } diff --git a/fastboot/device/fastboot_device.h b/fastboot/device/fastboot_device.h index 23be72143..35361365c 100644 --- a/fastboot/device/fastboot_device.h +++ b/fastboot/device/fastboot_device.h @@ -40,6 +40,7 @@ class FastbootDevice { void ExecuteCommands(); bool WriteStatus(FastbootResult result, const std::string& message); bool HandleData(bool read, std::vector<char>* data); + bool HandleData(bool read, char* data, uint64_t size); std::string GetCurrentSlot(); // Shortcuts for writing status results. diff --git a/fastboot/device/usb.cpp b/fastboot/device/usb.cpp index 4bee7b20c..4115a6d28 100644 --- a/fastboot/device/usb.cpp +++ b/fastboot/device/usb.cpp @@ -82,7 +82,7 @@ static int usb_ffs_write(usb_handle* h, const void* data, int len) { int orig_len = len; while (len > 0) { int write_len = std::min(USB_FFS_BULK_SIZE, len); - int n = write(h->bulk_in, buf, write_len); + int n = write(h->bulk_in.get(), buf, write_len); if (n < 0) { D("ERROR: fd = %d, n = %d: %s", h->bulk_in.get(), n, strerror(errno)); return -1; @@ -103,7 +103,7 @@ static int usb_ffs_read(usb_handle* h, void* data, int len, bool allow_partial) unsigned count = 0; while (len > 0) { int read_len = std::min(USB_FFS_BULK_SIZE, len); - int n = read(h->bulk_out, buf, read_len); + int n = read(h->bulk_out.get(), buf, read_len); if (n < 0) { D("ERROR: fd = %d, n = %d: %s", h->bulk_out.get(), n, strerror(errno)); return -1; diff --git a/fastboot/device/utility.cpp b/fastboot/device/utility.cpp index 7c6ac8993..07ad9028c 100644 --- a/fastboot/device/utility.cpp +++ b/fastboot/device/utility.cpp @@ -77,7 +77,8 @@ bool OpenLogicalPartition(FastbootDevice* device, const std::string& partition_n } // namespace -bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle) { +bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, + bool read) { // We prioritize logical partitions over physical ones, and do this // consistently for other partition operations (like getvar:partition-size). if (LogicalPartitionExists(device, name)) { @@ -89,7 +90,9 @@ bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHan return false; } - unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), O_WRONLY | O_EXCL))); + int flags = (read ? O_RDONLY : O_WRONLY); + flags |= (O_EXCL | O_CLOEXEC | O_BINARY); + unique_fd fd(TEMP_FAILURE_RETRY(open(handle->path().c_str(), flags))); if (fd < 0) { PLOG(ERROR) << "Failed to open block device: " << handle->path(); return false; @@ -201,12 +204,7 @@ std::vector<std::string> ListPartitions(FastbootDevice* device) { } bool GetDeviceLockStatus() { - std::string cmdline; - // Return lock status true if unable to read kernel command line. - if (!android::base::ReadFileToString("/proc/cmdline", &cmdline)) { - return true; - } - return cmdline.find("androidboot.verifiedbootstate=orange") == std::string::npos; + return android::base::GetProperty("ro.boot.verifiedbootstate", "") != "orange"; } bool UpdateAllPartitionMetadata(FastbootDevice* device, const std::string& super_name, diff --git a/fastboot/device/utility.h b/fastboot/device/utility.h index 3b71ef006..c2646d718 100644 --- a/fastboot/device/utility.h +++ b/fastboot/device/utility.h @@ -75,7 +75,11 @@ std::string GetSuperSlotSuffix(FastbootDevice* device, const std::string& partit std::optional<std::string> FindPhysicalPartition(const std::string& name); bool LogicalPartitionExists(FastbootDevice* device, const std::string& name, bool* is_zero_length = nullptr); -bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle); + +// If read, partition is readonly. Else it is write only. +bool OpenPartition(FastbootDevice* device, const std::string& name, PartitionHandle* handle, + bool read = false); + bool GetSlotNumber(const std::string& slot, android::hardware::boot::V1_0::Slot* number); std::vector<std::string> ListPartitions(FastbootDevice* device); bool GetDeviceLockStatus(); diff --git a/fastboot/device/variables.cpp b/fastboot/device/variables.cpp index e7d8bc366..ee1eed876 100644 --- a/fastboot/device/variables.cpp +++ b/fastboot/device/variables.cpp @@ -33,6 +33,12 @@ #include "flashing.h" #include "utility.h" +#ifdef FB_ENABLE_FETCH +static constexpr bool kEnableFetch = true; +#else +static constexpr bool kEnableFetch = false; +#endif + using ::android::hardware::boot::V1_0::BoolResult; using ::android::hardware::boot::V1_0::Slot; using ::android::hardware::boot::V1_1::MergeStatus; @@ -509,3 +515,13 @@ bool GetTrebleEnabled(FastbootDevice* /* device */, const std::vector<std::strin *message = android::base::GetProperty("ro.treble.enabled", ""); return true; } + +bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */, + std::string* message) { + if (!kEnableFetch) { + *message = "fetch not supported on user builds"; + return false; + } + *message = android::base::StringPrintf("0x%X", kMaxFetchSizeDefault); + return true; +} diff --git a/fastboot/device/variables.h b/fastboot/device/variables.h index c11e472b7..f40a0257e 100644 --- a/fastboot/device/variables.h +++ b/fastboot/device/variables.h @@ -80,6 +80,8 @@ bool GetSecurityPatchLevel(FastbootDevice* device, const std::vector<std::string std::string* message); bool GetTrebleEnabled(FastbootDevice* device, const std::vector<std::string>& args, std::string* message); +bool GetMaxFetchSize(FastbootDevice* /* device */, const std::vector<std::string>& /* args */, + std::string* message); // Helpers for getvar all. std::vector<std::vector<std::string>> GetAllPartitionArgsWithSlot(FastbootDevice* device); diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp index 38be934b9..c946ba95e 100644 --- a/fastboot/fastboot.cpp +++ b/fastboot/fastboot.cpp @@ -76,12 +76,15 @@ #include "udp.h" #include "usb.h" #include "util.h" +#include "vendor_boot_img_utils.h" +using android::base::borrowed_fd; using android::base::ReadFully; using android::base::Split; using android::base::Trim; using android::base::unique_fd; using namespace std::string_literals; +using namespace std::placeholders; static const char* serial = nullptr; @@ -114,7 +117,7 @@ struct fastboot_buffer { enum fb_buffer_type type; void* data; int64_t sz; - int fd; + unique_fd fd; int64_t image_size; }; @@ -227,9 +230,9 @@ static void InfoMessage(const std::string& info) { fprintf(stderr, "(bootloader) %s\n", info.c_str()); } -static int64_t get_file_size(int fd) { +static int64_t get_file_size(borrowed_fd fd) { struct stat sb; - if (fstat(fd, &sb) == -1) { + if (fstat(fd.get(), &sb) == -1) { die("could not get file size"); } return sb.st_size; @@ -414,6 +417,7 @@ static int show_help() { " snapshot-update merge On devices that support snapshot-based updates, finish\n" " an in-progress update if it is in the \"merging\"\n" " phase.\n" + " fetch PARTITION Fetch a partition image from the device." "\n" "boot image:\n" " boot KERNEL [RAMDISK [SECOND]]\n" @@ -466,7 +470,7 @@ static int show_help() { " --version Display version.\n" " --help, -h Show this message.\n" ); - // clang-format off + // clang-format on return 0; } @@ -519,10 +523,12 @@ static std::vector<char> LoadBootableImage(const std::string& kernel, const std: fprintf(stderr,"creating boot image...\n"); std::vector<char> out; - boot_img_hdr_v2* boot_image_data = mkbootimg(kernel_data, ramdisk_data, second_stage_data, - dtb_data, g_base_addr, g_boot_img_hdr, &out); + mkbootimg(kernel_data, ramdisk_data, second_stage_data, dtb_data, g_base_addr, g_boot_img_hdr, + &out); - if (!g_cmdline.empty()) bootimg_set_cmdline(boot_image_data, g_cmdline); + if (!g_cmdline.empty()) { + bootimg_set_cmdline(reinterpret_cast<boot_img_hdr_v2*>(out.data()), g_cmdline); + } fprintf(stderr, "creating boot image - %zu bytes\n", out.size()); return out; } @@ -640,31 +646,31 @@ static void delete_fbemarker_tmpdir(const std::string& dir) { } } -static int unzip_to_file(ZipArchiveHandle zip, const char* entry_name) { +static unique_fd unzip_to_file(ZipArchiveHandle zip, const char* entry_name) { unique_fd fd(make_temporary_fd(entry_name)); ZipEntry64 zip_entry; if (FindEntry(zip, entry_name, &zip_entry) != 0) { fprintf(stderr, "archive does not contain '%s'\n", entry_name); errno = ENOENT; - return -1; + return unique_fd(); } fprintf(stderr, "extracting %s (%" PRIu64 " MB) to disk...", entry_name, zip_entry.uncompressed_length / 1024 / 1024); double start = now(); - int error = ExtractEntryToFile(zip, &zip_entry, fd); + int error = ExtractEntryToFile(zip, &zip_entry, fd.get()); if (error != 0) { die("\nfailed to extract '%s': %s", entry_name, ErrorCodeString(error)); } - if (lseek(fd, 0, SEEK_SET) != 0) { + if (lseek(fd.get(), 0, SEEK_SET) != 0) { die("\nlseek on extracted file '%s' failed: %s", entry_name, strerror(errno)); } fprintf(stderr, " took %.3fs\n", now() - start); - return fd.release(); + return fd; } static void CheckRequirement(const std::string& cur_product, const std::string& var, @@ -851,24 +857,23 @@ static struct sparse_file** load_sparse_files(int fd, int64_t max_size) { return out_s; } -static int64_t get_target_sparse_limit() { - std::string max_download_size; - if (fb->GetVar("max-download-size", &max_download_size) != fastboot::SUCCESS || - max_download_size.empty()) { - verbose("target didn't report max-download-size"); +static uint64_t get_uint_var(const char* var_name) { + std::string value_str; + if (fb->GetVar(var_name, &value_str) != fastboot::SUCCESS || value_str.empty()) { + verbose("target didn't report %s", var_name); return 0; } // Some bootloaders (angler, for example) send spurious whitespace too. - max_download_size = android::base::Trim(max_download_size); + value_str = android::base::Trim(value_str); - uint64_t limit; - if (!android::base::ParseUint(max_download_size, &limit)) { - fprintf(stderr, "couldn't parse max-download-size '%s'\n", max_download_size.c_str()); + uint64_t value; + if (!android::base::ParseUint(value_str, &value)) { + fprintf(stderr, "couldn't parse %s '%s'\n", var_name, value_str.c_str()); return 0; } - if (limit > 0) verbose("target reported max download size of %" PRId64 " bytes", limit); - return limit; + if (value > 0) verbose("target reported %s of %" PRId64 " bytes", var_name, value); + return value; } static int64_t get_sparse_limit(int64_t size) { @@ -877,7 +882,7 @@ static int64_t get_sparse_limit(int64_t size) { // 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(); + target_sparse_limit = static_cast<int64_t>(get_uint_var("max-download-size")); } if (target_sparse_limit > 0) { limit = target_sparse_limit; @@ -893,23 +898,24 @@ static int64_t get_sparse_limit(int64_t size) { return 0; } -static bool load_buf_fd(int fd, struct fastboot_buffer* buf) { +static bool load_buf_fd(unique_fd fd, struct fastboot_buffer* buf) { int64_t sz = get_file_size(fd); if (sz == -1) { return false; } - if (sparse_file* s = sparse_file_import(fd, false, false)) { + if (sparse_file* s = sparse_file_import(fd.get(), false, false)) { buf->image_size = sparse_file_len(s, false, false); sparse_file_destroy(s); } else { buf->image_size = sz; } - lseek(fd, 0, SEEK_SET); + lseek(fd.get(), 0, SEEK_SET); int64_t limit = get_sparse_limit(sz); + buf->fd = std::move(fd); if (limit) { - sparse_file** s = load_sparse_files(fd, limit); + sparse_file** s = load_sparse_files(buf->fd.get(), limit); if (s == nullptr) { return false; } @@ -918,7 +924,6 @@ static bool load_buf_fd(int fd, struct fastboot_buffer* buf) { } else { buf->type = FB_BUFFER_FD; buf->data = nullptr; - buf->fd = fd; buf->sz = sz; } @@ -933,7 +938,7 @@ static bool load_buf(const char* fname, struct fastboot_buffer* buf) { } struct stat s; - if (fstat(fd, &s)) { + if (fstat(fd.get(), &s)) { return false; } if (!S_ISREG(s.st_mode)) { @@ -941,7 +946,7 @@ static bool load_buf(const char* fname, struct fastboot_buffer* buf) { return false; } - return load_buf_fd(fd.release(), buf); + return load_buf_fd(std::move(fd), buf); } static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_boot) { @@ -987,13 +992,12 @@ static void rewrite_vbmeta_buffer(struct fastboot_buffer* buf, bool vbmeta_in_bo data[flags_offset] |= 0x02; } - int fd = make_temporary_fd("vbmeta rewriting"); + unique_fd fd(make_temporary_fd("vbmeta rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified vbmeta"); } - close(buf->fd); - buf->fd = fd; - lseek(fd, 0, SEEK_SET); + buf->fd = std::move(fd); + lseek(buf->fd.get(), 0, SEEK_SET); } static bool has_vbmeta_partition() { @@ -1012,21 +1016,28 @@ static std::string fb_fix_numeric_var(std::string var) { return var; } -static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) { - if (buf->sz < AVB_FOOTER_SIZE) { - return; - } - +static uint64_t get_partition_size(const std::string& partition) { std::string partition_size_str; if (fb->GetVar("partition-size:" + partition, &partition_size_str) != fastboot::SUCCESS) { - die("cannot get boot partition size"); + die("cannot get partition size for %s", partition.c_str()); } partition_size_str = fb_fix_numeric_var(partition_size_str); - int64_t partition_size; - if (!android::base::ParseInt(partition_size_str, &partition_size)) { + uint64_t partition_size; + if (!android::base::ParseUint(partition_size_str, &partition_size)) { die("Couldn't parse partition size '%s'.", partition_size_str.c_str()); } + return partition_size; +} + +static void copy_boot_avb_footer(const std::string& partition, struct fastboot_buffer* buf) { + if (buf->sz < AVB_FOOTER_SIZE) { + return; + } + + // If overflows and negative, it should be < buf->sz. + int64_t partition_size = static_cast<int64_t>(get_partition_size(partition)); + if (partition_size == buf->sz) { return; } @@ -1044,18 +1055,17 @@ static void copy_boot_avb_footer(const std::string& partition, struct fastboot_b return; } - int fd = make_temporary_fd("boot rewriting"); + unique_fd fd(make_temporary_fd("boot rewriting")); if (!android::base::WriteStringToFd(data, fd)) { die("Failed writing to modified boot"); } - lseek(fd, partition_size - AVB_FOOTER_SIZE, SEEK_SET); + lseek(fd.get(), partition_size - AVB_FOOTER_SIZE, SEEK_SET); if (!android::base::WriteStringToFd(data.substr(footer_offset), fd)) { die("Failed copying AVB footer in boot"); } - close(buf->fd); - buf->fd = fd; + buf->fd = std::move(fd); buf->sz = partition_size; - lseek(fd, 0, SEEK_SET); + lseek(buf->fd.get(), 0, SEEK_SET); } static void flash_buf(const std::string& partition, struct fastboot_buffer *buf) @@ -1185,8 +1195,10 @@ static void do_for_partition(const std::string& part, const std::string& slot, const std::function<void(const std::string&)>& func, bool force_slot) { std::string has_slot; std::string current_slot; + // |part| can be vendor_boot:default. Append slot to the first token. + auto part_tokens = android::base::Split(part, ":"); - if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) { + if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { /* If has-slot is not supported, the answer is no. */ has_slot = "no"; } @@ -1196,14 +1208,15 @@ static void do_for_partition(const std::string& part, const std::string& slot, if (current_slot == "") { die("Failed to identify current slot"); } - func(part + "_" + current_slot); + part_tokens[0] += "_" + current_slot; } else { - func(part + '_' + slot); + part_tokens[0] += "_" + slot; } + func(android::base::Join(part_tokens, ":")); } else { if (force_slot && slot != "") { - fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n", - part.c_str(), slot.c_str()); + fprintf(stderr, "Warning: %s does not support slots, and slot %s was requested.\n", + part_tokens[0].c_str(), slot.c_str()); } func(part); } @@ -1217,10 +1230,13 @@ static void do_for_partition(const std::string& part, const std::string& slot, static void do_for_partitions(const std::string& part, const std::string& slot, const std::function<void(const std::string&)>& func, bool force_slot) { std::string has_slot; + // |part| can be vendor_boot:default. Query has-slot on the first token only. + auto part_tokens = android::base::Split(part, ":"); if (slot == "all") { - if (fb->GetVar("has-slot:" + part, &has_slot) != fastboot::SUCCESS) { - die("Could not check if partition %s has slot %s", part.c_str(), slot.c_str()); + if (fb->GetVar("has-slot:" + part_tokens[0], &has_slot) != fastboot::SUCCESS) { + die("Could not check if partition %s has slot %s", part_tokens[0].c_str(), + slot.c_str()); } if (has_slot == "yes") { for (int i=0; i < get_slot_count(); i++) { @@ -1247,7 +1263,74 @@ static bool is_retrofit_device() { return android::base::StartsWith(value, "system_"); } +// Fetch a partition from the device to a given fd. This is a wrapper over FetchToFd to fetch +// the full image. +static uint64_t fetch_partition(const std::string& partition, borrowed_fd fd) { + uint64_t fetch_size = get_uint_var(FB_VAR_MAX_FETCH_SIZE); + if (fetch_size == 0) { + die("Unable to get %s. Device does not support fetch command.", FB_VAR_MAX_FETCH_SIZE); + } + uint64_t partition_size = get_partition_size(partition); + if (partition_size <= 0) { + die("Invalid partition size for partition %s: %" PRId64, partition.c_str(), partition_size); + } + + uint64_t offset = 0; + while (offset < partition_size) { + uint64_t chunk_size = std::min(fetch_size, partition_size - offset); + if (fb->FetchToFd(partition, fd, offset, chunk_size) != fastboot::RetCode::SUCCESS) { + die("Unable to fetch %s (offset=%" PRIx64 ", size=%" PRIx64 ")", partition.c_str(), + offset, chunk_size); + } + offset += chunk_size; + } + return partition_size; +} + +static void do_fetch(const std::string& partition, const std::string& slot_override, + const std::string& outfile) { + unique_fd fd(TEMP_FAILURE_RETRY( + open(outfile.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_BINARY, 0644))); + auto fetch = std::bind(fetch_partition, _1, borrowed_fd(fd)); + do_for_partitions(partition, slot_override, fetch, false /* force slot */); +} + +// Return immediately if not flashing a vendor boot image. If flashing a vendor boot image, +// repack vendor_boot image with an updated ramdisk. After execution, buf is set +// to the new image to flash, and return value is the real partition name to flash. +static std::string repack_ramdisk(const char* pname, struct fastboot_buffer* buf) { + std::string_view pname_sv{pname}; + + if (!android::base::StartsWith(pname_sv, "vendor_boot:") && + !android::base::StartsWith(pname_sv, "vendor_boot_a:") && + !android::base::StartsWith(pname_sv, "vendor_boot_b:")) { + return std::string(pname_sv); + } + if (buf->type != FB_BUFFER_FD) { + die("Flashing sparse vendor ramdisk image is not supported."); + } + if (buf->sz <= 0) { + die("repack_ramdisk() sees negative size: %" PRId64, buf->sz); + } + std::string partition(pname_sv.substr(0, pname_sv.find(':'))); + std::string ramdisk(pname_sv.substr(pname_sv.find(':') + 1)); + + unique_fd vendor_boot(make_temporary_fd("vendor boot repack")); + uint64_t vendor_boot_size = fetch_partition(partition, vendor_boot); + auto repack_res = replace_vendor_ramdisk(vendor_boot, vendor_boot_size, ramdisk, buf->fd, + static_cast<uint64_t>(buf->sz)); + if (!repack_res.ok()) { + die("%s", repack_res.error().message().c_str()); + } + + buf->fd = std::move(vendor_boot); + buf->sz = vendor_boot_size; + buf->image_size = vendor_boot_size; + return partition; +} + static void do_flash(const char* pname, const char* fname) { + verbose("Do flash %s %s", pname, fname); struct fastboot_buffer buf; if (!load_buf(fname, &buf)) { @@ -1256,7 +1339,8 @@ static void do_flash(const char* pname, const char* fname) { if (is_logical(pname)) { fb->ResizePartition(pname, std::to_string(buf.image_size)); } - flash_buf(pname, &buf); + std::string flash_pname = repack_ramdisk(pname, &buf); + flash_buf(flash_pname, &buf); } // Sets slot_override as the active slot. If slot_override is blank, @@ -1309,8 +1393,9 @@ static void CancelSnapshotIfNeeded() { class ImageSource { public: + virtual ~ImageSource() {}; virtual bool ReadFile(const std::string& name, std::vector<char>* out) const = 0; - virtual int OpenFile(const std::string& name) const = 0; + virtual unique_fd OpenFile(const std::string& name) const = 0; }; class FlashAllTool { @@ -1428,8 +1513,8 @@ void FlashAllTool::CollectImages() { void FlashAllTool::FlashImages(const std::vector<std::pair<const Image*, std::string>>& images) { for (const auto& [image, slot] : images) { fastboot_buffer buf; - int fd = source_.OpenFile(image->img_name); - if (fd < 0 || !load_buf_fd(fd, &buf)) { + unique_fd fd = source_.OpenFile(image->img_name); + if (fd < 0 || !load_buf_fd(std::move(fd), &buf)) { if (image->optional_if_no_image) { continue; } @@ -1456,7 +1541,7 @@ void FlashAllTool::FlashImage(const Image& image, const std::string& slot, fastb } void FlashAllTool::UpdateSuperPartition() { - int fd = source_.OpenFile("super_empty.img"); + unique_fd fd = source_.OpenFile("super_empty.img"); if (fd < 0) { return; } @@ -1494,7 +1579,7 @@ class ZipImageSource final : public ImageSource { public: explicit ZipImageSource(ZipArchiveHandle zip) : zip_(zip) {} bool ReadFile(const std::string& name, std::vector<char>* out) const override; - int OpenFile(const std::string& name) const override; + unique_fd OpenFile(const std::string& name) const override; private: ZipArchiveHandle zip_; @@ -1504,7 +1589,7 @@ bool ZipImageSource::ReadFile(const std::string& name, std::vector<char>* out) c return UnzipToMemory(zip_, name, out); } -int ZipImageSource::OpenFile(const std::string& name) const { +unique_fd ZipImageSource::OpenFile(const std::string& name) const { return unzip_to_file(zip_, name.c_str()); } @@ -1524,7 +1609,7 @@ static void do_update(const char* filename, const std::string& slot_override, bo class LocalImageSource final : public ImageSource { public: bool ReadFile(const std::string& name, std::vector<char>* out) const override; - int OpenFile(const std::string& name) const override; + unique_fd OpenFile(const std::string& name) const override; }; bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) const { @@ -1535,9 +1620,9 @@ bool LocalImageSource::ReadFile(const std::string& name, std::vector<char>* out) return ReadFileToVector(path, out); } -int LocalImageSource::OpenFile(const std::string& name) const { +unique_fd LocalImageSource::OpenFile(const std::string& name) const { auto path = find_item_given_name(name); - return open(path.c_str(), O_RDONLY | O_BINARY); + return unique_fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_BINARY))); } static void do_flashall(const std::string& slot_override, bool skip_secondary, bool wipe) { @@ -1656,7 +1741,7 @@ static void fb_perform_format( if (fd == -1) { die("Cannot open generated image: %s", strerror(errno)); } - if (!load_buf_fd(fd.release(), &buf)) { + if (!load_buf_fd(std::move(fd), &buf)) { die("Cannot read image: %s", strerror(errno)); } flash_buf(partition, &buf); @@ -2115,7 +2200,7 @@ int FastBootTool::Main(int argc, char* argv[]) { if (!load_buf(filename.c_str(), &buf) || buf.type != FB_BUFFER_FD) { die("cannot load '%s'", filename.c_str()); } - fb->Download(filename, buf.fd, buf.sz); + fb->Download(filename, buf.fd.get(), buf.sz); } else if (command == "get_staged") { std::string filename = next_arg(&args); fb->Upload(filename); @@ -2169,6 +2254,10 @@ int FastBootTool::Main(int argc, char* argv[]) { syntax_error("expected: snapshot-update [cancel|merge]"); } fb->SnapshotUpdateCommand(arg); + } else if (command == FB_CMD_FETCH) { + std::string partition = next_arg(&args); + std::string outfile = next_arg(&args); + do_fetch(partition, slot_override, outfile); } else { syntax_error("unknown command %s", command.c_str()); } diff --git a/fastboot/fastboot_driver.cpp b/fastboot/fastboot_driver.cpp index 8d534ea32..99a48734c 100644 --- a/fastboot/fastboot_driver.cpp +++ b/fastboot/fastboot_driver.cpp @@ -30,6 +30,7 @@ #include <errno.h> #include <fcntl.h> +#include <inttypes.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -42,14 +43,17 @@ #include <android-base/file.h> #include <android-base/mapped_file.h> +#include <android-base/parseint.h> #include <android-base/stringprintf.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> +#include <storage_literals/storage_literals.h> #include "constants.h" #include "transport.h" using android::base::StringPrintf; +using namespace android::storage_literals; namespace fastboot { @@ -140,7 +144,8 @@ RetCode FastBootDriver::FlashPartition(const std::string& partition, return Flash(partition); } -RetCode FastBootDriver::FlashPartition(const std::string& partition, int fd, uint32_t size) { +RetCode FastBootDriver::FlashPartition(const std::string& partition, android::base::borrowed_fd fd, + uint32_t size) { RetCode ret; if ((ret = Download(partition, fd, size))) { return ret; @@ -178,15 +183,16 @@ RetCode FastBootDriver::Partitions(std::vector<std::tuple<std::string, uint64_t> return SUCCESS; } -RetCode FastBootDriver::Download(const std::string& name, int fd, size_t size, - std::string* response, std::vector<std::string>* info) { +RetCode FastBootDriver::Download(const std::string& name, android::base::borrowed_fd fd, + size_t size, std::string* response, + std::vector<std::string>* info) { prolog_(StringPrintf("Sending '%s' (%zu KB)", name.c_str(), size / 1024)); auto result = Download(fd, size, response, info); epilog_(result); return result; } -RetCode FastBootDriver::Download(int fd, size_t size, std::string* response, +RetCode FastBootDriver::Download(android::base::borrowed_fd fd, size_t size, std::string* response, std::vector<std::string>* info) { RetCode ret; @@ -297,41 +303,85 @@ RetCode FastBootDriver::Upload(const std::string& outfile, std::string* response return result; } -RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response, - std::vector<std::string>* info) { +// This function executes cmd, then expect a "DATA" response with a number N, followed +// by N bytes, and another response. +// This is the common way for the device to send data to the driver used by upload and fetch. +RetCode FastBootDriver::RunAndReadBuffer( + const std::string& cmd, std::string* response, std::vector<std::string>* info, + const std::function<RetCode(const char* data, uint64_t size)>& write_fn) { RetCode ret; int dsize = 0; - if ((ret = RawCommand(FB_CMD_UPLOAD, response, info, &dsize))) { - error_ = "Upload request failed: " + error_; + if ((ret = RawCommand(cmd, response, info, &dsize))) { + error_ = android::base::StringPrintf("%s request failed: %s", cmd.c_str(), error_.c_str()); return ret; } - if (!dsize) { - error_ = "Upload request failed, device reports 0 bytes available"; + if (dsize <= 0) { + error_ = android::base::StringPrintf("%s request failed, device reports %d bytes available", + cmd.c_str(), dsize); return BAD_DEV_RESP; } - std::vector<char> data; - data.resize(dsize); - - if ((ret = ReadBuffer(data))) { - return ret; + const uint64_t total_size = dsize; + const uint64_t buf_size = std::min<uint64_t>(total_size, 1_MiB); + std::vector<char> data(buf_size); + uint64_t current_offset = 0; + while (current_offset < total_size) { + uint64_t remaining = total_size - current_offset; + uint64_t chunk_size = std::min(buf_size, remaining); + if ((ret = ReadBuffer(data.data(), chunk_size)) != SUCCESS) { + return ret; + } + if ((ret = write_fn(data.data(), chunk_size)) != SUCCESS) { + return ret; + } + current_offset += chunk_size; } + return HandleResponse(response, info); +} +RetCode FastBootDriver::UploadInner(const std::string& outfile, std::string* response, + std::vector<std::string>* info) { std::ofstream ofs; ofs.open(outfile, std::ofstream::out | std::ofstream::binary); if (ofs.fail()) { error_ = android::base::StringPrintf("Failed to open '%s'", outfile.c_str()); return IO_ERROR; } - ofs.write(data.data(), data.size()); - if (ofs.fail() || ofs.bad()) { - error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str()); - return IO_ERROR; - } + auto write_fn = [&](const char* data, uint64_t size) { + ofs.write(data, size); + if (ofs.fail() || ofs.bad()) { + error_ = android::base::StringPrintf("Writing to '%s' failed", outfile.c_str()); + return IO_ERROR; + } + return SUCCESS; + }; + RetCode ret = RunAndReadBuffer(FB_CMD_UPLOAD, response, info, write_fn); ofs.close(); + return ret; +} - return HandleResponse(response, info); +RetCode FastBootDriver::FetchToFd(const std::string& partition, android::base::borrowed_fd fd, + int64_t offset, int64_t size, std::string* response, + std::vector<std::string>* info) { + prolog_(android::base::StringPrintf("Fetching %s (offset=%" PRIx64 ", size=%" PRIx64 ")", + partition.c_str(), offset, size)); + std::string cmd = FB_CMD_FETCH ":" + partition; + if (offset >= 0) { + cmd += android::base::StringPrintf(":0x%08" PRIx64, offset); + if (size >= 0) { + cmd += android::base::StringPrintf(":0x%08" PRIx64, size); + } + } + RetCode ret = RunAndReadBuffer(cmd, response, info, [&](const char* data, uint64_t size) { + if (!android::base::WriteFully(fd, data, size)) { + error_ = android::base::StringPrintf("Cannot write: %s", strerror(errno)); + return IO_ERROR; + } + return SUCCESS; + }); + epilog_(ret); + return ret; } // Helpers @@ -473,7 +523,7 @@ std::string FastBootDriver::ErrnoStr(const std::string& msg) { } /******************************* PRIVATE **************************************/ -RetCode FastBootDriver::SendBuffer(int fd, size_t size) { +RetCode FastBootDriver::SendBuffer(android::base::borrowed_fd fd, size_t size) { static constexpr uint32_t MAX_MAP_SIZE = 512 * 1024 * 1024; off64_t offset = 0; uint32_t remaining = size; @@ -524,11 +574,6 @@ RetCode FastBootDriver::SendBuffer(const void* buf, size_t size) { return SUCCESS; } -RetCode FastBootDriver::ReadBuffer(std::vector<char>& buf) { - // Read the buffer - return ReadBuffer(buf.data(), buf.size()); -} - RetCode FastBootDriver::ReadBuffer(void* buf, size_t size) { // Read the buffer ssize_t tmp = transport_->Read(buf, size); diff --git a/fastboot/fastboot_driver.h b/fastboot/fastboot_driver.h index 72656322e..bccd6685f 100644 --- a/fastboot/fastboot_driver.h +++ b/fastboot/fastboot_driver.h @@ -34,6 +34,7 @@ #include <android-base/logging.h> #include <android-base/stringprintf.h> +#include <android-base/unique_fd.h> #include <bootimg.h> #include <inttypes.h> #include <sparse/sparse.h> @@ -76,9 +77,9 @@ class FastBootDriver { RetCode Continue(std::string* response = nullptr, std::vector<std::string>* info = nullptr); RetCode CreatePartition(const std::string& partition, const std::string& size); RetCode DeletePartition(const std::string& partition); - RetCode Download(const std::string& name, int fd, size_t size, std::string* response = nullptr, - std::vector<std::string>* info = nullptr); - RetCode Download(int fd, size_t size, std::string* response = nullptr, + RetCode Download(const std::string& name, android::base::borrowed_fd fd, size_t size, + std::string* response = nullptr, std::vector<std::string>* info = nullptr); + RetCode Download(android::base::borrowed_fd fd, size_t size, std::string* response = nullptr, std::vector<std::string>* info = nullptr); RetCode Download(const std::string& name, const std::vector<char>& buf, std::string* response = nullptr, std::vector<std::string>* info = nullptr); @@ -106,10 +107,14 @@ class FastBootDriver { std::vector<std::string>* info = nullptr); RetCode SnapshotUpdateCommand(const std::string& command, std::string* response = nullptr, std::vector<std::string>* info = nullptr); + RetCode FetchToFd(const std::string& partition, android::base::borrowed_fd fd, + int64_t offset = -1, int64_t size = -1, std::string* response = nullptr, + std::vector<std::string>* info = nullptr); /* HIGHER LEVEL COMMANDS -- Composed of the commands above */ RetCode FlashPartition(const std::string& partition, const std::vector<char>& data); - RetCode FlashPartition(const std::string& partition, int fd, uint32_t sz); + RetCode FlashPartition(const std::string& partition, android::base::borrowed_fd fd, + uint32_t sz); RetCode FlashPartition(const std::string& partition, sparse_file* s, uint32_t sz, size_t current, size_t total); @@ -145,15 +150,17 @@ class FastBootDriver { Transport* transport_; private: - RetCode SendBuffer(int fd, size_t size); + RetCode SendBuffer(android::base::borrowed_fd fd, size_t size); RetCode SendBuffer(const std::vector<char>& buf); RetCode SendBuffer(const void* buf, size_t size); - RetCode ReadBuffer(std::vector<char>& buf); RetCode ReadBuffer(void* buf, size_t size); RetCode UploadInner(const std::string& outfile, std::string* response = nullptr, std::vector<std::string>* info = nullptr); + RetCode RunAndReadBuffer(const std::string& cmd, std::string* response, + std::vector<std::string>* info, + const std::function<RetCode(const char*, uint64_t)>& write_fn); int SparseWriteCallback(std::vector<char>& tpbuf, const char* data, size_t len); diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp index 34ab32c12..b6beaf93a 100644 --- a/fastboot/fuzzy_fastboot/main.cpp +++ b/fastboot/fuzzy_fastboot/main.cpp @@ -43,8 +43,10 @@ #include <thread> #include <vector> +#include <android-base/file.h> #include <android-base/parseint.h> #include <android-base/stringprintf.h> +#include <android-base/strings.h> #include <gtest/gtest.h> #include <sparse/sparse.h> @@ -349,22 +351,35 @@ TEST_F(Conformance, GetVarBattVoltageOk) { EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'"; } -TEST_F(Conformance, GetVarDownloadSize) { - std::string var; - EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; - EXPECT_NE(var, "") << "getvar:max-download-size responded with empty string"; +void AssertHexUint32(const std::string& name, const std::string& var) { + ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string"; // This must start with 0x - EXPECT_FALSE(isspace(var.front())) - << "getvar:max-download-size responded with a string with leading whitespace"; - EXPECT_FALSE(var.compare(0, 2, "0x")) - << "getvar:max-download-size responded with a string that does not start with 0x..."; + ASSERT_FALSE(isspace(var.front())) + << "getvar:" << name << " responded with a string with leading whitespace"; + ASSERT_FALSE(var.compare(0, 2, "0x")) + << "getvar:" << name << " responded with a string that does not start with 0x..."; int64_t size = strtoll(var.c_str(), nullptr, 16); - EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size"; + ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name; // At most 32-bits - EXPECT_LE(size, std::numeric_limits<uint32_t>::max()) - << "getvar:max-download-size must fit in a uint32_t"; - EXPECT_LE(var.size(), FB_RESPONSE_SZ - 4) - << "getvar:max-download-size responded with too large of string: " + var; + ASSERT_LE(size, std::numeric_limits<uint32_t>::max()) + << "getvar:" << name << " must fit in a uint32_t"; + ASSERT_LE(var.size(), FB_RESPONSE_SZ - 4) + << "getvar:" << name << " responded with too large of string: " + var; +} + +TEST_F(Conformance, GetVarDownloadSize) { + std::string var; + EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; + AssertHexUint32("max-download-size", var); +} + +// If fetch is supported, getvar:max-fetch-size must return a hex string. +TEST_F(Conformance, GetVarFetchSize) { + std::string var; + if (SUCCESS != fb->GetVar("max-fetch-size", &var)) { + GTEST_SKIP() << "getvar:max-fetch-size failed"; + } + AssertHexUint32("max-fetch-size", var); } TEST_F(Conformance, GetVarAll) { @@ -656,6 +671,33 @@ TEST_F(UnlockPermissions, DownloadFlash) { EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed in unlocked mode"; } +// If the implementation supports getvar:max-fetch-size, it must also support fetch:vendor_boot*. +TEST_F(UnlockPermissions, FetchVendorBoot) { + std::string var; + uint64_t fetch_size; + if (fb->GetVar("max-fetch-size", &var) != SUCCESS) { + GTEST_SKIP() << "This test is skipped because fetch is not supported."; + } + ASSERT_FALSE(var.empty()); + ASSERT_TRUE(android::base::ParseUint(var, &fetch_size)) << var << " is not an integer"; + std::vector<std::tuple<std::string, uint64_t>> parts; + EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; + for (const auto& [partition, partition_size] : parts) { + if (!android::base::StartsWith(partition, "vendor_boot")) continue; + TemporaryFile fetched; + + uint64_t offset = 0; + while (offset < partition_size) { + uint64_t chunk_size = std::min(fetch_size, partition_size - offset); + auto ret = fb->FetchToFd(partition, fetched.fd, offset, chunk_size); + ASSERT_EQ(fastboot::RetCode::SUCCESS, ret) + << "Unable to fetch " << partition << " (offset=" << offset + << ", size=" << chunk_size << ")"; + offset += chunk_size; + } + } +} + TEST_F(LockPermissions, DownloadFlash) { std::vector<char> buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download failed in locked mode"; @@ -717,6 +759,16 @@ TEST_F(LockPermissions, Boot) { EXPECT_GT(resp.size(), 0) << "No error message was returned by device after FAIL"; } +TEST_F(LockPermissions, FetchVendorBoot) { + std::vector<std::tuple<std::string, uint64_t>> parts; + EXPECT_EQ(fb->Partitions(&parts), SUCCESS) << "getvar:all failed"; + for (const auto& [partition, _] : parts) { + TemporaryFile fetched; + ASSERT_EQ(fb->FetchToFd(partition, fetched.fd, 0, 0), DEVICE_FAIL) + << "fetch:" << partition << ":0:0 did not fail in locked mode"; + } +} + TEST_F(Fuzz, DownloadSize) { std::string var; EXPECT_EQ(fb->GetVar("max-download-size", &var), SUCCESS) << "getvar:max-download-size failed"; diff --git a/fastboot/testdata/Android.bp b/fastboot/testdata/Android.bp new file mode 100644 index 000000000..a490fe270 --- /dev/null +++ b/fastboot/testdata/Android.bp @@ -0,0 +1,140 @@ +// Copyright (C) 2021 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +python_binary_host { + name: "fastboot_gen_rand", + visibility: [":__subpackages__"], + srcs: ["fastboot_gen_rand.py"], +} + +genrule_defaults { + name: "fastboot_test_data_gen_defaults", + visibility: ["//system/core/fastboot"], + tools: [ + "fastboot_gen_rand", + ], +} + +// Genrules for components of test vendor boot image. + +// Fake dtb image. +genrule { + name: "fastboot_test_dtb", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_dtb.img"], + cmd: "$(location fastboot_gen_rand) --seed dtb --length 1024 > $(out)", +} + +// Fake bootconfig image. +genrule { + name: "fastboot_test_bootconfig", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_bootconfig.img"], + cmd: "$(location fastboot_gen_rand) --seed bootconfig --length 1024 > $(out)", +} + +// Fake vendor ramdisk with type "none". +genrule { + name: "fastboot_test_vendor_ramdisk_none", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_none.img"], + cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_none --length 1024 > $(out)", +} + +// Fake vendor ramdisk with type "platform". +genrule { + name: "fastboot_test_vendor_ramdisk_platform", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_platform.img"], + cmd: "$(location fastboot_gen_rand) --seed vendor_ramdisk_platform --length 1024 > $(out)", +} + +// Fake replacement ramdisk. +genrule { + name: "fastboot_test_vendor_ramdisk_replace", + defaults: ["fastboot_test_data_gen_defaults"], + out: ["test_vendor_ramdisk_replace.img"], + cmd: "$(location fastboot_gen_rand) --seed replace --length 3072 > $(out)", +} + +// Genrules for test vendor boot images. + +fastboot_sign_test_image = "$(location avbtool) add_hash_footer --salt 00 --image $(out) " + + "--partition_name vendor_boot --partition_size $$(( 1 * 1024 * 1024 ))" + +genrule_defaults { + name: "fastboot_test_vendor_boot_gen_defaults", + defaults: ["fastboot_test_data_gen_defaults"], + tools: [ + "avbtool", + "mkbootimg", + ], +} + +genrule { + name: "fastboot_test_vendor_boot_v3", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v3.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ], + cmd: "$(location mkbootimg) --header_version 3 " + + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} + +genrule { + name: "fastboot_test_vendor_boot_v4_without_frag", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v4_without_frag.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_bootconfig", + ], + cmd: "$(location mkbootimg) --header_version 4 " + + "--vendor_ramdisk $(location :fastboot_test_vendor_ramdisk_none) " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} + +genrule { + name: "fastboot_test_vendor_boot_v4_with_frag", + defaults: ["fastboot_test_vendor_boot_gen_defaults"], + out: ["vendor_boot_v4_with_frag.img"], + srcs: [ + ":fastboot_test_dtb", + ":fastboot_test_vendor_ramdisk_none", + ":fastboot_test_vendor_ramdisk_platform", + ":fastboot_test_bootconfig", + ], + cmd: "$(location mkbootimg) --header_version 4 " + + "--dtb $(location :fastboot_test_dtb) " + + "--vendor_bootconfig $(location :fastboot_test_bootconfig) " + + "--ramdisk_type none --ramdisk_name none_ramdisk " + + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_none) " + + "--ramdisk_type platform --ramdisk_name platform_ramdisk " + + "--vendor_ramdisk_fragment $(location :fastboot_test_vendor_ramdisk_platform) " + + "--vendor_boot $(out) && " + + fastboot_sign_test_image, +} diff --git a/fastboot/testdata/fastboot_gen_rand.py b/fastboot/testdata/fastboot_gen_rand.py new file mode 100644 index 000000000..a87467b88 --- /dev/null +++ b/fastboot/testdata/fastboot_gen_rand.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2021 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. + +""" +Write given number of random bytes, generated with optional seed. +""" + +import random, argparse + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--seed', help='Seed to random generator') + parser.add_argument('--length', type=int, required=True, help='Length of output') + args = parser.parse_args() + + if args.seed: + random.seed(args.seed) + + print(''.join(chr(random.randrange(0,0xff)) for _ in range(args.length))) diff --git a/fastboot/vendor_boot_img_utils.cpp b/fastboot/vendor_boot_img_utils.cpp new file mode 100644 index 000000000..9e09abbd1 --- /dev/null +++ b/fastboot/vendor_boot_img_utils.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2021 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 "vendor_boot_img_utils.h" + +#include <string.h> + +#include <android-base/file.h> +#include <android-base/result.h> +#include <bootimg.h> +#include <libavb/libavb.h> + +namespace { + +using android::base::Result; + +// Updates a given buffer by creating a new one. +class DataUpdater { + public: + DataUpdater(const std::string& old_data) : old_data_(&old_data) { + old_data_ptr_ = old_data_->data(); + new_data_.resize(old_data_->size(), '\0'); + new_data_ptr_ = new_data_.data(); + } + // Copy |num_bytes| from src to dst. + [[nodiscard]] Result<void> Copy(uint32_t num_bytes) { + if (num_bytes == 0) return {}; + if (auto res = CheckAdvance(old_data_ptr_, old_end(), num_bytes, __FUNCTION__); !res.ok()) + return res; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), num_bytes, __FUNCTION__); !res.ok()) + return res; + memcpy(new_data_ptr_, old_data_ptr_, num_bytes); + old_data_ptr_ += num_bytes; + new_data_ptr_ += num_bytes; + return {}; + } + // Replace |old_num_bytes| from src with new data. + [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const std::string& new_data) { + return Replace(old_num_bytes, new_data.data(), new_data.size()); + } + [[nodiscard]] Result<void> Replace(uint32_t old_num_bytes, const void* new_data, + uint32_t new_data_size) { + if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_num_bytes, __FUNCTION__); + !res.ok()) + return res; + old_data_ptr_ += old_num_bytes; + + if (new_data_size == 0) return {}; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_data_size, __FUNCTION__); + !res.ok()) + return res; + memcpy(new_data_ptr_, new_data, new_data_size); + new_data_ptr_ += new_data_size; + return {}; + } + // Skip |old_skip| from src and |new_skip| from dst, respectively. + [[nodiscard]] Result<void> Skip(uint32_t old_skip, uint32_t new_skip) { + if (auto res = CheckAdvance(old_data_ptr_, old_end(), old_skip, __FUNCTION__); !res.ok()) + return res; + old_data_ptr_ += old_skip; + if (auto res = CheckAdvance(new_data_ptr_, new_end(), new_skip, __FUNCTION__); !res.ok()) + return res; + new_data_ptr_ += new_skip; + return {}; + } + + [[nodiscard]] Result<void> Seek(uint32_t offset) { + if (offset > size()) return Errorf("Cannot seek 0x{:x}, size is 0x{:x}", offset, size()); + old_data_ptr_ = old_begin() + offset; + new_data_ptr_ = new_begin() + offset; + return {}; + } + + std::string Finish() { + new_data_ptr_ = nullptr; + return std::move(new_data_); + } + + [[nodiscard]] Result<void> CheckOffset(uint32_t old_offset, uint32_t new_offset) { + if (old_begin() + old_offset != old_cur()) + return Errorf("Old offset mismatch: expected: 0x{:x}, actual: 0x{:x}", old_offset, + old_cur() - old_begin()); + if (new_begin() + new_offset != new_cur()) + return Errorf("New offset mismatch: expected: 0x{:x}, actual: 0x{:x}", new_offset, + new_cur() - new_begin()); + return {}; + } + + uint64_t size() const { return old_data_->size(); } + const char* old_begin() const { return old_data_->data(); } + const char* old_cur() { return old_data_ptr_; } + const char* old_end() const { return old_data_->data() + old_data_->size(); } + char* new_begin() { return new_data_.data(); } + char* new_cur() { return new_data_ptr_; } + char* new_end() { return new_data_.data() + new_data_.size(); } + + private: + // Check if it is okay to advance |num_bytes| from |current|. + [[nodiscard]] Result<void> CheckAdvance(const char* current, const char* end, + uint32_t num_bytes, const char* op) { + auto new_end = current + num_bytes; + if (new_end < current /* add overflow */) + return Errorf("{}: Addition overflow: 0x{} + 0x{:x} < 0x{}", op, fmt::ptr(current), + num_bytes, fmt::ptr(current)); + if (new_end > end) + return Errorf("{}: Boundary overflow: 0x{} + 0x{:x} > 0x{}", op, fmt::ptr(current), + num_bytes, fmt::ptr(end)); + return {}; + } + const std::string* old_data_; + std::string new_data_; + const char* old_data_ptr_; + char* new_data_ptr_; +}; + +// Get the size of vendor boot header. +[[nodiscard]] Result<uint32_t> get_vendor_boot_header_size(const vendor_boot_img_hdr_v3* hdr) { + if (hdr->header_version == 3) return sizeof(vendor_boot_img_hdr_v3); + if (hdr->header_version == 4) return sizeof(vendor_boot_img_hdr_v4); + return Errorf("Unrecognized vendor boot header version {}", hdr->header_version); +} + +// Check that content contains a valid vendor boot image header with a version at least |version|. +[[nodiscard]] Result<void> check_vendor_boot_hdr(const std::string& content, uint32_t version) { + // get_vendor_boot_header_size reads header_version, so make sure reading it does not + // go out of bounds by ensuring that the content has at least the size of V3 header. + if (content.size() < sizeof(vendor_boot_img_hdr_v3)) { + return Errorf("Size of vendor boot is 0x{:x}, less than size of V3 header: 0x{:x}", + content.size(), sizeof(vendor_boot_img_hdr_v3)); + } + // Now read hdr->header_version and assert the size. + auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(content.data()); + auto expect_header_size = get_vendor_boot_header_size(hdr); + if (!expect_header_size.ok()) return expect_header_size.error(); + if (content.size() < *expect_header_size) { + return Errorf("Size of vendor boot is 0x{:x}, less than size of V{} header: 0x{:x}", + content.size(), version, *expect_header_size); + } + if (memcmp(hdr->magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE) != 0) { + return Errorf("Vendor boot image magic mismatch"); + } + if (hdr->header_version < version) { + return Errorf("Require vendor boot header V{} but is V{}", version, hdr->header_version); + } + return {}; +} + +// Wrapper of ReadFdToString. Seek to the beginning and read the whole file to string. +[[nodiscard]] Result<std::string> load_file(android::base::borrowed_fd fd, uint64_t expected_size, + const char* what) { + if (lseek(fd.get(), 0, SEEK_SET) != 0) { + return ErrnoErrorf("Can't seek to the beginning of {} image", what); + } + std::string content; + if (!android::base::ReadFdToString(fd, &content)) { + return ErrnoErrorf("Cannot read {} to string", what); + } + if (content.size() != expected_size) { + return Errorf("Size of {} does not match, expected 0x{:x}, read 0x{:x}", what, + expected_size, content.size()); + } + return content; +} + +// Wrapper of WriteStringToFd. Seek to the beginning and write the whole file to string. +[[nodiscard]] Result<void> store_file(android::base::borrowed_fd fd, const std::string& data, + const char* what) { + if (lseek(fd.get(), 0, SEEK_SET) != 0) { + return ErrnoErrorf("Cannot seek to beginning of {} before writing", what); + } + if (!android::base::WriteStringToFd(data, fd)) { + return ErrnoErrorf("Cannot write new content to {}", what); + } + if (TEMP_FAILURE_RETRY(ftruncate(fd.get(), data.size())) == -1) { + return ErrnoErrorf("Truncating new vendor boot image to 0x{:x} fails", data.size()); + } + return {}; +} + +// Copy AVB footer if it exists in the old buffer. +[[nodiscard]] Result<void> copy_avb_footer(DataUpdater* updater) { + if (updater->size() < AVB_FOOTER_SIZE) return {}; + if (auto res = updater->Seek(updater->size() - AVB_FOOTER_SIZE); !res.ok()) return res; + if (memcmp(updater->old_cur(), AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) return {}; + return updater->Copy(AVB_FOOTER_SIZE); +} + +// round |value| up to a multiple of |page_size|. +inline uint32_t round_up(uint32_t value, uint32_t page_size) { + return (value + page_size - 1) / page_size * page_size; +} + +// Replace the vendor ramdisk as a whole. +[[nodiscard]] Result<std::string> replace_default_vendor_ramdisk(const std::string& vendor_boot, + const std::string& new_ramdisk) { + if (auto res = check_vendor_boot_hdr(vendor_boot, 3); !res.ok()) return res.error(); + auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(vendor_boot.data()); + auto hdr_size = get_vendor_boot_header_size(hdr); + if (!hdr_size.ok()) return hdr_size.error(); + // Refer to bootimg.h for details. Numbers are in bytes. + const uint32_t o = round_up(*hdr_size, hdr->page_size); + const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); + + DataUpdater updater(vendor_boot); + + // Copy header (O bytes), then update fields in header. + if (auto res = updater.Copy(o); !res.ok()) return res.error(); + auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v3*>(updater.new_begin()); + new_hdr->vendor_ramdisk_size = new_ramdisk.size(); + // Because it is unknown how the new ramdisk is fragmented, the whole table is replaced + // with a single entry representing the full ramdisk. + if (new_hdr->header_version >= 4) { + auto new_hdr_v4 = static_cast<vendor_boot_img_hdr_v4*>(new_hdr); + new_hdr_v4->vendor_ramdisk_table_entry_size = sizeof(vendor_ramdisk_table_entry_v4); + new_hdr_v4->vendor_ramdisk_table_entry_num = 1; + new_hdr_v4->vendor_ramdisk_table_size = new_hdr_v4->vendor_ramdisk_table_entry_num * + new_hdr_v4->vendor_ramdisk_table_entry_size; + } + + // Copy the new ramdisk. + if (auto res = updater.Replace(hdr->vendor_ramdisk_size, new_ramdisk); !res.ok()) + return res.error(); + const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); + if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); + + // Copy DTB (Q bytes). + if (auto res = updater.Copy(q); !res.ok()) return res.error(); + + if (new_hdr->header_version >= 4) { + auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr); + const uint32_t r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); + const uint32_t s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); + + auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur()); + auto new_hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(new_hdr); + auto new_r = round_up(new_hdr_v4->vendor_ramdisk_table_size, new_hdr->page_size); + if (auto res = updater.Skip(r, new_r); !res.ok()) return res.error(); + if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + new_r); !res.ok()) + return res.error(); + + // Replace table with single entry representing the full ramdisk. + new_entry->ramdisk_size = new_hdr->vendor_ramdisk_size; + new_entry->ramdisk_offset = 0; + new_entry->ramdisk_type = VENDOR_RAMDISK_TYPE_NONE; + memset(new_entry->ramdisk_name, '\0', VENDOR_RAMDISK_NAME_SIZE); + memset(new_entry->board_id, '\0', VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE); + + // Copy bootconfig (S bytes). + if (auto res = updater.Copy(s); !res.ok()) return res.error(); + } + + if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); + return updater.Finish(); +} + +// Find a ramdisk fragment with a unique name. Abort if none or multiple fragments are found. +[[nodiscard]] Result<const vendor_ramdisk_table_entry_v4*> find_unique_ramdisk( + const std::string& ramdisk_name, const vendor_ramdisk_table_entry_v4* table, + uint32_t size) { + const vendor_ramdisk_table_entry_v4* ret = nullptr; + uint32_t idx = 0; + const vendor_ramdisk_table_entry_v4* entry = table; + for (; idx < size; idx++, entry++) { + auto entry_name_c_str = reinterpret_cast<const char*>(entry->ramdisk_name); + auto entry_name_len = strnlen(entry_name_c_str, VENDOR_RAMDISK_NAME_SIZE); + std::string_view entry_name(entry_name_c_str, entry_name_len); + if (entry_name == ramdisk_name) { + if (ret != nullptr) { + return Errorf("Multiple vendor ramdisk '{}' found, name should be unique", + ramdisk_name.c_str()); + } + ret = entry; + } + } + if (ret == nullptr) { + return Errorf("Vendor ramdisk '{}' not found", ramdisk_name.c_str()); + } + return ret; +} + +// Find the vendor ramdisk fragment with |ramdisk_name| within the content of |vendor_boot|, and +// replace it with the content of |new_ramdisk|. +[[nodiscard]] Result<std::string> replace_vendor_ramdisk_fragment(const std::string& ramdisk_name, + const std::string& vendor_boot, + const std::string& new_ramdisk) { + if (auto res = check_vendor_boot_hdr(vendor_boot, 4); !res.ok()) return res.error(); + auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(vendor_boot.data()); + auto hdr_size = get_vendor_boot_header_size(hdr); + if (!hdr_size.ok()) return hdr_size.error(); + // Refer to bootimg.h for details. Numbers are in bytes. + const uint32_t o = round_up(*hdr_size, hdr->page_size); + const uint32_t p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + const uint32_t q = round_up(hdr->dtb_size, hdr->page_size); + const uint32_t r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); + const uint32_t s = round_up(hdr->bootconfig_size, hdr->page_size); + + if (hdr->vendor_ramdisk_table_entry_num == std::numeric_limits<uint32_t>::max()) { + return Errorf("Too many vendor ramdisk entries in table, overflow"); + } + + // Find entry with name |ramdisk_name|. + auto old_table_start = + reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(vendor_boot.data() + o + p + q); + auto find_res = + find_unique_ramdisk(ramdisk_name, old_table_start, hdr->vendor_ramdisk_table_entry_num); + if (!find_res.ok()) return find_res.error(); + const vendor_ramdisk_table_entry_v4* replace_entry = *find_res; + uint32_t replace_idx = replace_entry - old_table_start; + + // Now reconstruct. + DataUpdater updater(vendor_boot); + + // Copy header (O bytes), then update fields in header. + if (auto res = updater.Copy(o); !res.ok()) return res.error(); + auto new_hdr = reinterpret_cast<vendor_boot_img_hdr_v4*>(updater.new_begin()); + + // Copy ramdisk fragments, replace for the matching index. + { + auto old_ramdisk_entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>( + vendor_boot.data() + o + p + q); + uint32_t new_total_ramdisk_size = 0; + for (uint32_t new_ramdisk_idx = 0; new_ramdisk_idx < hdr->vendor_ramdisk_table_entry_num; + new_ramdisk_idx++, old_ramdisk_entry++) { + if (new_ramdisk_idx == replace_idx) { + if (auto res = updater.Replace(replace_entry->ramdisk_size, new_ramdisk); !res.ok()) + return res.error(); + new_total_ramdisk_size += new_ramdisk.size(); + } else { + if (auto res = updater.Copy(old_ramdisk_entry->ramdisk_size); !res.ok()) + return res.error(); + new_total_ramdisk_size += old_ramdisk_entry->ramdisk_size; + } + } + new_hdr->vendor_ramdisk_size = new_total_ramdisk_size; + } + + // Pad ramdisk to page boundary. + const uint32_t new_p = round_up(new_hdr->vendor_ramdisk_size, new_hdr->page_size); + if (auto res = updater.Skip(p - hdr->vendor_ramdisk_size, new_p - new_hdr->vendor_ramdisk_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p, o + new_p); !res.ok()) return res.error(); + + // Copy DTB (Q bytes). + if (auto res = updater.Copy(q); !res.ok()) return res.error(); + + // Copy table, but with corresponding entries modified, including: + // - ramdisk_size of the entry replaced + // - ramdisk_offset of subsequent entries. + for (uint32_t new_total_ramdisk_size = 0, new_entry_idx = 0; + new_entry_idx < hdr->vendor_ramdisk_table_entry_num; new_entry_idx++) { + auto new_entry = reinterpret_cast<vendor_ramdisk_table_entry_v4*>(updater.new_cur()); + if (auto res = updater.Copy(hdr->vendor_ramdisk_table_entry_size); !res.ok()) + return res.error(); + new_entry->ramdisk_offset = new_total_ramdisk_size; + + if (new_entry_idx == replace_idx) { + new_entry->ramdisk_size = new_ramdisk.size(); + } + new_total_ramdisk_size += new_entry->ramdisk_size; + } + + // Copy padding of R pages; this is okay because table size is not changed. + if (auto res = updater.Copy(r - hdr->vendor_ramdisk_table_entry_num * + hdr->vendor_ramdisk_table_entry_size); + !res.ok()) + return res.error(); + if (auto res = updater.CheckOffset(o + p + q + r, o + new_p + q + r); !res.ok()) + return res.error(); + + // Copy bootconfig (S bytes). + if (auto res = updater.Copy(s); !res.ok()) return res.error(); + + if (auto res = copy_avb_footer(&updater); !res.ok()) return res.error(); + return updater.Finish(); +} + +} // namespace + +[[nodiscard]] Result<void> replace_vendor_ramdisk(android::base::borrowed_fd vendor_boot_fd, + uint64_t vendor_boot_size, + const std::string& ramdisk_name, + android::base::borrowed_fd new_ramdisk_fd, + uint64_t new_ramdisk_size) { + if (new_ramdisk_size > std::numeric_limits<uint32_t>::max()) { + return Errorf("New vendor ramdisk is too big"); + } + + auto vendor_boot = load_file(vendor_boot_fd, vendor_boot_size, "vendor boot"); + if (!vendor_boot.ok()) return vendor_boot.error(); + auto new_ramdisk = load_file(new_ramdisk_fd, new_ramdisk_size, "new vendor ramdisk"); + if (!new_ramdisk.ok()) return new_ramdisk.error(); + + Result<std::string> new_vendor_boot; + if (ramdisk_name == "default") { + new_vendor_boot = replace_default_vendor_ramdisk(*vendor_boot, *new_ramdisk); + } else { + new_vendor_boot = replace_vendor_ramdisk_fragment(ramdisk_name, *vendor_boot, *new_ramdisk); + } + if (!new_vendor_boot.ok()) return new_vendor_boot.error(); + if (auto res = store_file(vendor_boot_fd, *new_vendor_boot, "new vendor boot image"); !res.ok()) + return res.error(); + + return {}; +} diff --git a/fastboot/vendor_boot_img_utils.h b/fastboot/vendor_boot_img_utils.h new file mode 100644 index 000000000..0b702bc4d --- /dev/null +++ b/fastboot/vendor_boot_img_utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +#include <inttypes.h> + +#include <string> + +#include <android-base/result.h> +#include <android-base/unique_fd.h> + +// Replace the vendor ramdisk named |ramdisk_name| within the vendor boot image, +// specified by |vendor_boot_fd|, with the ramdisk specified by |new_ramdisk_fd|. Checks +// that the size of the files are |vendor_boot_size| and |new_ramdisk_size|, respectively. +// If |ramdisk_name| is "default", replace the vendor ramdisk as a whole. Otherwise, replace +// a vendor ramdisk fragment with the given unique name. +[[nodiscard]] android::base::Result<void> replace_vendor_ramdisk( + android::base::borrowed_fd vendor_boot_fd, uint64_t vendor_boot_size, + const std::string& ramdisk_name, android::base::borrowed_fd new_ramdisk_fd, + uint64_t new_ramdisk_size); diff --git a/fastboot/vendor_boot_img_utils_test.cpp b/fastboot/vendor_boot_img_utils_test.cpp new file mode 100644 index 000000000..1563b89c7 --- /dev/null +++ b/fastboot/vendor_boot_img_utils_test.cpp @@ -0,0 +1,429 @@ +/* + * Copyright (C) 2021 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 <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <filesystem> +#include <optional> +#include <string_view> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/result.h> +#include <android-base/strings.h> +#include <bootimg.h> +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <libavb/libavb.h> + +#include "vendor_boot_img_utils.h" + +using android::base::borrowed_fd; +using android::base::ErrnoError; +using android::base::GetExecutableDirectory; +using android::base::ReadFdToString; +using android::base::Result; +using testing::AllOf; +using testing::Each; +using testing::Eq; +using testing::HasSubstr; +using testing::Not; +using testing::Property; +using std::string_literals::operator""s; + +// Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|. +#define EXPECT_RESULT(expr, result_matcher) \ + EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \ + Property(&decltype(expr)::value, result_matcher))) + +// Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|. +#define EXPECT_ERROR(expr, error_matcher) \ + do { \ + EXPECT_THAT( \ + expr, \ + AllOf(Property(&decltype(expr)::ok, Eq(false)), \ + Property(&decltype(expr)::error, \ + Property(&decltype(expr)::error_type::message, error_matcher)))); \ + } while (0) + +namespace { + +// Wrapper of fstat. +Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) { + struct stat sb; + if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")"; + return sb.st_size; +} + +// Seek to beginning then read the whole file. +Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) { + if (lseek64(fd.get(), 0, SEEK_SET) != 0) + return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)"; + std::string content; + if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")"; + return content; +} + +// Round |value| up to page boundary. +inline uint32_t round_up(uint32_t value, uint32_t page_size) { + return (value + page_size - 1) / page_size * page_size; +} + +// Match is successful if |arg| is a zero-padded version of |expected|. +MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) { + if (arg.size() < expected.size()) return false; + if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false; + auto remainder = std::string_view(arg).substr(expected.size()); + for (char e : remainder) + if (e != '\0') return false; + return true; +} + +// Same as Eq, but don't print the content to avoid spam. +MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) { + if (arg.size() != expected.size()) return false; + return 0 == memcmp(arg.data(), expected.data(), expected.size()); +} + +// Expect that |arg| and |expected| has the same AVB footer. +MATCHER_P(HasSameAvbFooter, expected, + (negation ? "has" : "does not have") + "expected AVB footer"s) { + if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false; + return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) == + std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE); +} + +// A lazy handle of a file. +struct TestFileHandle { + virtual ~TestFileHandle() = default; + // Lazily call OpenImpl(), cache result in open_result_. + android::base::Result<void> Open() { + if (!open_result_.has_value()) open_result_ = OpenImpl(); + return open_result_.value(); + } + // The original size at the time when the file is opened. If the file has been modified, + // this field is NOT updated. + uint64_t size() { + CHECK(open_result_.has_value()); + return size_; + } + // The current size of the file. If the file has been modified since opened, + // this is updated. + Result<uint64_t> fsize() { + CHECK(open_result_.has_value()); + return FileSize(fd_, abs_path_); + } + borrowed_fd fd() { + CHECK(open_result_.has_value()); + return fd_; + } + Result<std::string> Read() { + CHECK(open_result_.has_value()); + return ReadStartOfFdToString(fd_, abs_path_); + } + + private: + std::filesystem::path abs_path_; + uint64_t size_; + std::optional<android::base::Result<void>> open_result_; + borrowed_fd fd_{-1}; + // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to + // |borrowed_fd_|. + android::base::Result<void> OpenImpl() { + android::base::unique_fd read_fd(TEMP_FAILURE_RETRY( + open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY))); + if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")"; + auto size = FileSize(read_fd, abs_path_); + if (!size.ok()) return size.error(); + size_ = *size; + + auto borrowed_fd = Transform(abs_path_, std::move(read_fd)); + if (!borrowed_fd.ok()) return borrowed_fd.error(); + fd_ = borrowed_fd.value(); + + return {}; + } + + protected: + // |rel_path| is the relative path under test data directory. + TestFileHandle(const std::filesystem::path& rel_path) + : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {} + // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client + // to use. Implementation is responsible for managing the lifetime of the returned fd. + virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path, + android::base::unique_fd read_fd) = 0; +}; + +// A TestFileHandle where the file is readonly. +struct ReadOnlyTestFileHandle : TestFileHandle { + ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} + + private: + android::base::unique_fd owned_fd_; + android::base::Result<borrowed_fd> Transform(const std::filesystem::path&, + android::base::unique_fd read_fd) override { + owned_fd_ = std::move(read_fd); + return owned_fd_; + } +}; + +// A TestFileHandle where the test file is copies, hence read-writable. +struct ReadWriteTestFileHandle : TestFileHandle { + ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {} + + private: + std::unique_ptr<TemporaryFile> temp_file_; + + android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path, + android::base::unique_fd read_fd) override { + // Make a copy to avoid writing to test data. Test files are small, so it is okay + // to read the whole file. + auto content = ReadStartOfFdToString(read_fd, abs_path); + if (!content.ok()) return content.error(); + temp_file_ = std::make_unique<TemporaryFile>(); + if (temp_file_->fd == -1) + return ErrnoError() << "copy " << abs_path << ": open temp file failed"; + if (!android::base::WriteStringToFd(*content, temp_file_->fd)) + return ErrnoError() << "copy " << abs_path << ": write temp file failed"; + + return temp_file_->fd; + } +}; + +class RepackVendorBootImgTestEnv : public ::testing::Environment { + public: + virtual void SetUp() { + OpenTestFile("test_dtb.img", &dtb, &dtb_content); + OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content); + OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content); + OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content); + OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content); + } + + std::unique_ptr<TestFileHandle> dtb; + std::string dtb_content; + std::unique_ptr<TestFileHandle> bootconfig; + std::string bootconfig_content; + std::unique_ptr<TestFileHandle> none; + std::string none_content; + std::unique_ptr<TestFileHandle> platform; + std::string platform_content; + std::unique_ptr<TestFileHandle> replace; + std::string replace_content; + + private: + void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle, + std::string* content) { + *handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path); + ASSERT_RESULT_OK((*handle)->Open()); + auto content_res = (*handle)->Read(); + ASSERT_RESULT_OK(content_res); + *content = *content_res; + } +}; +RepackVendorBootImgTestEnv* env = nullptr; + +struct RepackVendorBootImgTestParam { + std::string vendor_boot_file_name; + uint32_t expected_header_version; + friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) { + return os << param.vendor_boot_file_name; + } +}; + +class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> { + public: + virtual void SetUp() { + vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name); + ASSERT_RESULT_OK(vboot->Open()); + } + std::unique_ptr<TestFileHandle> vboot; +}; + +TEST_P(RepackVendorBootImgTest, InvalidSize) { + EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default", + env->replace->fd(), env->replace->size()), + HasSubstr("Size of vendor boot does not match")); + EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(), + env->replace->size() + 1), + HasSubstr("Size of new vendor ramdisk does not match")); +} + +TEST_P(RepackVendorBootImgTest, ReplaceUnknown) { + auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(), + env->replace->size()); + if (GetParam().expected_header_version == 3) { + EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3")); + } else if (GetParam().expected_header_version == 4) { + EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found")); + } +} + +TEST_P(RepackVendorBootImgTest, ReplaceDefault) { + auto old_content = vboot->Read(); + ASSERT_RESULT_OK(old_content); + + ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", + env->replace->fd(), env->replace->size())); + EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; + + auto new_content_res = vboot->Read(); + ASSERT_RESULT_OK(new_content_res); + std::string_view new_content(*new_content_res); + + auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data()); + ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); + ASSERT_EQ(GetParam().expected_header_version, hdr->header_version); + EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size()); + EXPECT_EQ(hdr->dtb_size, env->dtb->size()); + + auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); + auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + auto q = round_up(hdr->dtb_size, hdr->page_size); + + EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content)); + EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); + + if (hdr->header_version < 4) return; + + auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr); + EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1); + EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size); + EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); + auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]); + EXPECT_EQ(entry->ramdisk_offset, 0); + EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size); + EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); + + EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size()); + auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size); + auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size); + EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); + + EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); +} + +INSTANTIATE_TEST_SUITE_P( + RepackVendorBootImgTest, RepackVendorBootImgTest, + ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3}, + RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4}, + RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}), + [](const auto& info) { + return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false); + }); + +std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) { + auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name); + return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE)); +} + +class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> { + public: + virtual void SetUp() { + vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img"); + ASSERT_RESULT_OK(vboot->Open()); + } + std::unique_ptr<TestFileHandle> vboot; +}; + +TEST_P(RepackVendorBootImgTestV4, Replace) { + uint32_t replace_ramdisk_type = GetParam(); + std::string replace_ramdisk_name; + std::string expect_new_ramdisk_content; + uint32_t expect_none_size = env->none->size(); + uint32_t expect_platform_size = env->platform->size(); + switch (replace_ramdisk_type) { + case VENDOR_RAMDISK_TYPE_NONE: + replace_ramdisk_name = "none_ramdisk"; + expect_new_ramdisk_content = env->replace_content + env->platform_content; + expect_none_size = env->replace->size(); + break; + case VENDOR_RAMDISK_TYPE_PLATFORM: + replace_ramdisk_name = "platform_ramdisk"; + expect_new_ramdisk_content = env->none_content + env->replace_content; + expect_platform_size = env->replace->size(); + break; + default: + LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type + << " is not supported by this test."; + } + + auto old_content = vboot->Read(); + ASSERT_RESULT_OK(old_content); + + ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name, + env->replace->fd(), env->replace->size())); + EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack"; + + auto new_content_res = vboot->Read(); + ASSERT_RESULT_OK(new_content_res); + std::string_view new_content(*new_content_res); + + auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data()); + ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE)); + ASSERT_EQ(4, hdr->header_version); + EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size); + EXPECT_EQ(hdr->dtb_size, env->dtb->size()); + EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size()); + + auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size); + auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size); + auto q = round_up(hdr->dtb_size, hdr->page_size); + auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size); + auto s = round_up(hdr->bootconfig_size, hdr->page_size); + + EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content)); + EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content)); + + // Check changes in table. + EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2); + EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size); + EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4)); + auto entry_none = + reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]); + EXPECT_EQ(entry_none->ramdisk_offset, 0); + EXPECT_EQ(entry_none->ramdisk_size, expect_none_size); + EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE); + EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk"); + + auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>( + &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]); + EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size); + EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size); + EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM); + EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk"); + + EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content)); + + EXPECT_THAT(new_content, HasSameAvbFooter(*old_content)); +} +INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4, + ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM), + [](const auto& info) { + return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform"; + }); + +} // namespace + +int main(int argc, char* argv[]) { + ::testing::InitGoogleTest(&argc, argv); + env = static_cast<RepackVendorBootImgTestEnv*>( + testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv)); + return RUN_ALL_TESTS(); +} diff --git a/fs_mgr/fs_mgr_boot_config.cpp b/fs_mgr/fs_mgr_boot_config.cpp index 75d1e0db6..e3ef2321a 100644 --- a/fs_mgr/fs_mgr_boot_config.cpp +++ b/fs_mgr/fs_mgr_boot_config.cpp @@ -91,6 +91,12 @@ bool fs_mgr_get_boot_config_from_bootconfig(const std::string& bootconfig, if (key == bootconfig_key) { *out_val = value; return true; + } else if (android_key == "hardware" && android_key == key) { + // bootconfig doesn't allow subkeys and values to coexist, so + // "androidboot.hardware" cannot be used. It is replaced in + // bootconfig with "hardware" + *out_val = value; + return true; } } diff --git a/fs_mgr/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h index 50f4f33c2..7e305099b 100644 --- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h +++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h @@ -198,7 +198,7 @@ class MappedDevice final { ~MappedDevice(); - int fd() const { return fd_; } + int fd() const { return fd_.get(); } const std::string& path() const { return path_; } protected: diff --git a/fs_mgr/libfs_avb/TEST_MAPPING b/fs_mgr/libfs_avb/TEST_MAPPING deleted file mode 100644 index b0f36d40e..000000000 --- a/fs_mgr/libfs_avb/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "postsubmit": [ - { - "name": "libfs_avb_test", - "host": true - }, - { - "name": "libfs_avb_internal_test", - "host": true - } - ] -} diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index b80860998..ea92d25e0 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -416,6 +416,7 @@ cc_defaults { "snapuserd_server.cpp", "snapuserd.cpp", "snapuserd_daemon.cpp", + "snapuserd_worker.cpp", ], cflags: [ @@ -554,6 +555,7 @@ cc_test { srcs: [ "cow_snapuserd_test.cpp", "snapuserd.cpp", + "snapuserd_worker.cpp", ], cflags: [ "-Wall", diff --git a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto index e902fa4dd..1ebc29f95 100644 --- a/fs_mgr/libsnapshot/android/snapshot/snapshot.proto +++ b/fs_mgr/libsnapshot/android/snapshot/snapshot.proto @@ -162,7 +162,7 @@ message SnapshotUpdateStatus { MergePhase merge_phase = 6; } -// Next: 7 +// Next: 9 message SnapshotMergeReport { // Status of the update after the merge attempts. UpdateState state = 1; @@ -182,4 +182,10 @@ message SnapshotMergeReport { // Sum of the estimated COW fields in the OTA manifest. uint64 estimated_cow_size_bytes = 6; + + // Time from boot to sys.boot_completed, in milliseconds. + uint32 boot_complete_time_ms = 7; + + // Time from sys.boot_completed to merge start, in milliseconds. + uint32 boot_complete_to_merge_start_time_ms = 8; } diff --git a/fs_mgr/libsnapshot/cow_api_test.cpp b/fs_mgr/libsnapshot/cow_api_test.cpp index a96352a2d..5d632206d 100644 --- a/fs_mgr/libsnapshot/cow_api_test.cpp +++ b/fs_mgr/libsnapshot/cow_api_test.cpp @@ -757,6 +757,30 @@ TEST_F(CowTest, ClusterAppendTest) { ASSERT_TRUE(iter->Done()); } +TEST_F(CowTest, AppendAfterFinalize) { + CowOptions options; + options.cluster_ops = 0; + auto writer = std::make_unique<CowWriter>(options); + ASSERT_TRUE(writer->Initialize(cow_->fd)); + + std::string data = "This is some data, believe it"; + data.resize(options.block_size, '\0'); + ASSERT_TRUE(writer->AddRawBlocks(50, data.data(), data.size())); + ASSERT_TRUE(writer->AddLabel(3)); + ASSERT_TRUE(writer->Finalize()); + + std::string data2 = "More data!"; + data2.resize(options.block_size, '\0'); + ASSERT_TRUE(writer->AddRawBlocks(51, data2.data(), data2.size())); + ASSERT_TRUE(writer->Finalize()); + + ASSERT_EQ(lseek(cow_->fd, 0, SEEK_SET), 0); + + // COW should be valid. + CowReader reader; + ASSERT_TRUE(reader.Parse(cow_->fd)); +} + } // namespace snapshot } // namespace android diff --git a/fs_mgr/libsnapshot/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp index cf9f6eabf..7199b3829 100644 --- a/fs_mgr/libsnapshot/cow_reader.cpp +++ b/fs_mgr/libsnapshot/cow_reader.cpp @@ -42,6 +42,29 @@ static void SHA256(const void*, size_t, uint8_t[]) { #endif } +bool CowReader::InitForMerge(android::base::unique_fd&& fd) { + owned_fd_ = std::move(fd); + fd_ = owned_fd_.get(); + + auto pos = lseek(fd_.get(), 0, SEEK_END); + if (pos < 0) { + PLOG(ERROR) << "lseek end failed"; + return false; + } + fd_size_ = pos; + + if (lseek(fd_.get(), 0, SEEK_SET) < 0) { + PLOG(ERROR) << "lseek header failed"; + return false; + } + if (!android::base::ReadFully(fd_, &header_, sizeof(header_))) { + PLOG(ERROR) << "read header failed"; + return false; + } + + return true; +} + bool CowReader::Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label) { owned_fd_ = std::move(fd); return Parse(android::base::borrowed_fd{owned_fd_}, label); @@ -206,7 +229,8 @@ bool CowReader::ParseOps(std::optional<uint64_t> label) { if (footer_) { if (ops_buffer->size() != footer_->op.num_ops) { - LOG(ERROR) << "num ops does not match"; + LOG(ERROR) << "num ops does not match, expected " << footer_->op.num_ops << ", found " + << ops_buffer->size(); return false; } if (ops_buffer->size() * sizeof(CowOperation) != footer_->op.ops_size) { @@ -226,6 +250,8 @@ bool CowReader::ParseOps(std::optional<uint64_t> label) { } ops_ = ops_buffer; + ops_->shrink_to_fit(); + return true; } diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp index 81edc793b..59f6d6f92 100644 --- a/fs_mgr/libsnapshot/cow_writer.cpp +++ b/fs_mgr/libsnapshot/cow_writer.cpp @@ -376,6 +376,7 @@ bool CowWriter::Finalize() { auto continue_data_pos = next_data_pos_; auto continue_op_pos = next_op_pos_; auto continue_size = ops_.size(); + auto continue_num_ops = footer_.op.num_ops; bool extra_cluster = false; // Footer should be at the end of a file, so if there is data after the current block, end it @@ -408,9 +409,9 @@ bool CowWriter::Finalize() { current_data_size_ = continue_data_size; next_data_pos_ = continue_data_pos; next_op_pos_ = continue_op_pos; + footer_.op.num_ops = continue_num_ops; ops_.resize(continue_size); } - return Sync(); } diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h index 1de7473a9..552fd96d1 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_reader.h @@ -116,12 +116,15 @@ class ICowOpReverseIter { class CowReader : public ICowReader { public: CowReader(); + ~CowReader() { owned_fd_ = {}; } // Parse the COW, optionally, up to the given label. If no label is // specified, the COW must have an intact footer. bool Parse(android::base::unique_fd&& fd, std::optional<uint64_t> label = {}); bool Parse(android::base::borrowed_fd fd, std::optional<uint64_t> label = {}); + bool InitForMerge(android::base::unique_fd&& fd); + bool GetHeader(CowHeader* header) override; bool GetFooter(CowFooter* footer) override; @@ -146,6 +149,8 @@ class CowReader : public ICowReader { uint64_t total_data_ops() { return total_data_ops_; } + void CloseCowFd() { owned_fd_ = {}; } + private: bool ParseOps(std::optional<uint64_t> label); diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h index 3eeae64fe..e617d7a18 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stats.h @@ -32,9 +32,13 @@ class ISnapshotMergeStats { virtual void set_cow_file_size(uint64_t cow_file_size) = 0; virtual void set_total_cow_size_bytes(uint64_t bytes) = 0; virtual void set_estimated_cow_size_bytes(uint64_t bytes) = 0; + virtual void set_boot_complete_time_ms(uint32_t ms) = 0; + virtual void set_boot_complete_to_merge_start_time_ms(uint32_t ms) = 0; virtual uint64_t cow_file_size() = 0; virtual uint64_t total_cow_size_bytes() = 0; virtual uint64_t estimated_cow_size_bytes() = 0; + virtual uint32_t boot_complete_time_ms() = 0; + virtual uint32_t boot_complete_to_merge_start_time_ms() = 0; // Called when merge ends. Properly clean up permanent storage. class Result { @@ -62,6 +66,10 @@ class SnapshotMergeStats : public ISnapshotMergeStats { void set_estimated_cow_size_bytes(uint64_t bytes) override; uint64_t total_cow_size_bytes() override; uint64_t estimated_cow_size_bytes() override; + void set_boot_complete_time_ms(uint32_t ms) override; + uint32_t boot_complete_time_ms() override; + void set_boot_complete_to_merge_start_time_ms(uint32_t ms) override; + uint32_t boot_complete_to_merge_start_time_ms() override; std::unique_ptr<Result> Finish() override; private: diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 60cef5e15..bd1e284da 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -1265,7 +1265,7 @@ static bool DeleteDmDevice(const std::string& name, const std::chrono::milliseco LOG(ERROR) << "DeleteDevice timeout: " << name; return false; } - std::this_thread::sleep_for(250ms); + std::this_thread::sleep_for(400ms); } return true; diff --git a/fs_mgr/libsnapshot/snapshot_stats.cpp b/fs_mgr/libsnapshot/snapshot_stats.cpp index 35e2d92de..7fcfcea02 100644 --- a/fs_mgr/libsnapshot/snapshot_stats.cpp +++ b/fs_mgr/libsnapshot/snapshot_stats.cpp @@ -114,6 +114,22 @@ uint64_t SnapshotMergeStats::estimated_cow_size_bytes() { return report_.estimated_cow_size_bytes(); } +void SnapshotMergeStats::set_boot_complete_time_ms(uint32_t ms) { + report_.set_boot_complete_time_ms(ms); +} + +uint32_t SnapshotMergeStats::boot_complete_time_ms() { + return report_.boot_complete_time_ms(); +} + +void SnapshotMergeStats::set_boot_complete_to_merge_start_time_ms(uint32_t ms) { + report_.set_boot_complete_to_merge_start_time_ms(ms); +} + +uint32_t SnapshotMergeStats::boot_complete_to_merge_start_time_ms() { + return report_.boot_complete_to_merge_start_time_ms(); +} + class SnapshotMergeStatsResultImpl : public SnapshotMergeStats::Result { public: SnapshotMergeStatsResultImpl(const SnapshotMergeReport& report, diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp index 079e606c4..43825ccc9 100644 --- a/fs_mgr/libsnapshot/snapshot_stub.cpp +++ b/fs_mgr/libsnapshot/snapshot_stub.cpp @@ -131,6 +131,10 @@ class SnapshotMergeStatsStub : public ISnapshotMergeStats { void set_estimated_cow_size_bytes(uint64_t) override {} uint64_t total_cow_size_bytes() override { return 0; } uint64_t estimated_cow_size_bytes() override { return 0; } + void set_boot_complete_time_ms(uint32_t) override {} + uint32_t boot_complete_time_ms() override { return 0; } + void set_boot_complete_to_merge_start_time_ms(uint32_t) override {} + uint32_t boot_complete_to_merge_start_time_ms() override { return 0; } }; ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() { diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp index a44de8419..5eb200378 100644 --- a/fs_mgr/libsnapshot/snapshotctl.cpp +++ b/fs_mgr/libsnapshot/snapshotctl.cpp @@ -48,6 +48,17 @@ bool DumpCmdHandler(int /*argc*/, char** argv) { return SnapshotManager::New()->Dump(std::cout); } +bool MapCmdHandler(int, char** argv) { + android::base::InitLogging(argv, &android::base::StderrLogger); + using namespace std::chrono_literals; + return SnapshotManager::New()->MapAllSnapshots(5000ms); +} + +bool UnmapCmdHandler(int, char** argv) { + android::base::InitLogging(argv, &android::base::StderrLogger); + return SnapshotManager::New()->UnmapAllSnapshots(); +} + bool MergeCmdHandler(int /*argc*/, char** argv) { android::base::InitLogging(argv, &android::base::StderrLogger); LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead."; @@ -58,6 +69,8 @@ static std::map<std::string, std::function<bool(int, char**)>> kCmdMap = { // clang-format off {"dump", DumpCmdHandler}, {"merge", MergeCmdHandler}, + {"map", MapCmdHandler}, + {"unmap", UnmapCmdHandler}, // clang-format on }; diff --git a/fs_mgr/libsnapshot/snapuserd.cpp b/fs_mgr/libsnapshot/snapuserd.cpp index 4c4a34225..5ef9e29ed 100644 --- a/fs_mgr/libsnapshot/snapuserd.cpp +++ b/fs_mgr/libsnapshot/snapuserd.cpp @@ -32,41 +32,6 @@ using android::base::unique_fd; #define SNAP_LOG(level) LOG(level) << misc_name_ << ": " #define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " -static constexpr size_t PAYLOAD_SIZE = (1UL << 20); - -static_assert(PAYLOAD_SIZE >= BLOCK_SZ); - -void BufferSink::Initialize(size_t size) { - buffer_size_ = size; - buffer_offset_ = 0; - buffer_ = std::make_unique<uint8_t[]>(size); -} - -void* BufferSink::GetPayloadBuffer(size_t size) { - if ((buffer_size_ - buffer_offset_) < size) return nullptr; - - char* buffer = reinterpret_cast<char*>(GetBufPtr()); - struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); - return (char*)msg->payload.buf + buffer_offset_; -} - -void* BufferSink::GetBuffer(size_t requested, size_t* actual) { - void* buf = GetPayloadBuffer(requested); - if (!buf) { - *actual = 0; - return nullptr; - } - *actual = requested; - return buf; -} - -struct dm_user_header* BufferSink::GetHeaderPtr() { - CHECK(sizeof(struct dm_user_header) <= buffer_size_); - char* buf = reinterpret_cast<char*>(GetBufPtr()); - struct dm_user_header* header = (struct dm_user_header*)(&(buf[0])); - return header; -} - Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device, const std::string& backing_device) { misc_name_ = misc_name; @@ -75,356 +40,32 @@ Snapuserd::Snapuserd(const std::string& misc_name, const std::string& cow_device control_device_ = "/dev/dm-user/" + misc_name; } -// Construct kernel COW header in memory -// This header will be in sector 0. The IO -// request will always be 4k. After constructing -// the header, zero out the remaining block. -void Snapuserd::ConstructKernelCowHeader() { - void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); - CHECK(buffer != nullptr); - - memset(buffer, 0, BLOCK_SZ); - - struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer); - - dh->magic = SNAP_MAGIC; - dh->valid = SNAPSHOT_VALID; - dh->version = SNAPSHOT_DISK_VERSION; - dh->chunk_size = CHUNK_SIZE; -} - -// Start the replace operation. This will read the -// internal COW format and if the block is compressed, -// it will be de-compressed. -bool Snapuserd::ProcessReplaceOp(const CowOperation* cow_op) { - if (!reader_->ReadData(*cow_op, &bufsink_)) { - SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block; - return false; - } - - return true; -} - -// Start the copy operation. This will read the backing -// block device which is represented by cow_op->source. -bool Snapuserd::ProcessCopyOp(const CowOperation* cow_op) { - void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); - CHECK(buffer != nullptr); - - // Issue a single 4K IO. However, this can be optimized - // if the successive blocks are contiguous. - if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, - cow_op->source * BLOCK_SZ)) { - SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_ - << "at block :" << cow_op->source; - return false; - } - - return true; -} - -bool Snapuserd::ProcessZeroOp() { - // Zero out the entire block - void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); - CHECK(buffer != nullptr); - - memset(buffer, 0, BLOCK_SZ); - return true; -} - -bool Snapuserd::ProcessCowOp(const CowOperation* cow_op) { - CHECK(cow_op != nullptr); - - switch (cow_op->type) { - case kCowReplaceOp: { - return ProcessReplaceOp(cow_op); - } - - case kCowZeroOp: { - return ProcessZeroOp(); - } - - case kCowCopyOp: { - return ProcessCopyOp(cow_op); - } - - default: { - SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type; - } - } - return false; -} - -int Snapuserd::ReadUnalignedSector(sector_t sector, size_t size, - std::map<sector_t, const CowOperation*>::iterator& it) { - size_t skip_sector_size = 0; - - SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size - << " Aligned sector: " << it->second; - - if (!ProcessCowOp(it->second)) { - SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size; - return -1; - } - - int num_sectors_skip = sector - it->first; - - if (num_sectors_skip > 0) { - skip_sector_size = num_sectors_skip << SECTOR_SHIFT; - char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr()); - struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); - - memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size, - (BLOCK_SZ - skip_sector_size)); - } - - bufsink_.ResetBufferOffset(); - return std::min(size, (BLOCK_SZ - skip_sector_size)); -} - -/* - * Read the data for a given COW Operation. - * - * Kernel can issue IO at a sector granularity. - * Hence, an IO may end up with reading partial - * data from a COW operation or we may also - * end up with interspersed request between - * two COW operations. - * - */ -int Snapuserd::ReadData(sector_t sector, size_t size) { - /* - * chunk_map stores COW operation at 4k granularity. - * If the requested IO with the sector falls on the 4k - * boundary, then we can read the COW op directly without - * any issue. - * - * However, if the requested sector is not 4K aligned, - * then we will have the find the nearest COW operation - * and chop the 4K block to fetch the requested sector. - */ - std::map<sector_t, const CowOperation*>::iterator it = chunk_map_.find(sector); - if (it == chunk_map_.end()) { - it = chunk_map_.lower_bound(sector); - if (it != chunk_map_.begin()) { - --it; - } - - /* - * If the IO is spanned between two COW operations, - * split the IO into two parts: - * - * 1: Read the first part from the single COW op - * 2: Read the second part from the next COW op. - * - * Ex: Let's say we have a 1024 Bytes IO request. - * - * 0 COW OP-1 4096 COW OP-2 8192 - * |******************|*******************| - * |*****|*****| - * 3584 4608 - * <- 1024B - > - * - * We have two COW operations which are 4k blocks. - * The IO is requested for 1024 Bytes which are spanned - * between two COW operations. We will split this IO - * into two parts: - * - * 1: IO of size 512B from offset 3584 bytes (COW OP-1) - * 2: IO of size 512B from offset 4096 bytes (COW OP-2) - */ - return ReadUnalignedSector(sector, size, it); - } - - int num_ops = DIV_ROUND_UP(size, BLOCK_SZ); - while (num_ops) { - if (!ProcessCowOp(it->second)) { - return -1; - } - num_ops -= 1; - it++; - // Update the buffer offset - bufsink_.UpdateBufferOffset(BLOCK_SZ); - - SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size; - } - - // Reset the buffer offset - bufsink_.ResetBufferOffset(); - return size; -} - -/* - * dm-snap does prefetch reads while reading disk-exceptions. - * By default, prefetch value is set to 12; this means that - * dm-snap will issue 12 areas wherein each area is a 4k page - * of disk-exceptions. - * - * If during prefetch, if the chunk-id seen is beyond the - * actual number of metadata page, fill the buffer with zero. - * When dm-snap starts parsing the buffer, it will stop - * reading metadata page once the buffer content is zero. - */ -bool Snapuserd::ZerofillDiskExceptions(size_t read_size) { - size_t size = exceptions_per_area_ * sizeof(struct disk_exception); +bool Snapuserd::InitializeWorkers() { + for (int i = 0; i < NUM_THREADS_PER_PARTITION; i++) { + std::unique_ptr<WorkerThread> wt = std::make_unique<WorkerThread>( + cow_device_, backing_store_device_, control_device_, misc_name_, GetSharedPtr()); - if (read_size > size) { - return false; + worker_threads_.push_back(std::move(wt)); } - - void* buffer = bufsink_.GetPayloadBuffer(size); - CHECK(buffer != nullptr); - - memset(buffer, 0, size); return true; } -/* - * A disk exception is a simple mapping of old_chunk to new_chunk. - * When dm-snapshot device is created, kernel requests these mapping. - * - * Each disk exception is of size 16 bytes. Thus a single 4k page can - * have: - * - * exceptions_per_area_ = 4096/16 = 256. This entire 4k page - * is considered a metadata page and it is represented by chunk ID. - * - * Convert the chunk ID to index into the vector which gives us - * the metadata page. - */ -bool Snapuserd::ReadDiskExceptions(chunk_t chunk, size_t read_size) { - uint32_t stride = exceptions_per_area_ + 1; - size_t size; - - // ChunkID to vector index - lldiv_t divresult = lldiv(chunk, stride); - - if (divresult.quot < vec_.size()) { - size = exceptions_per_area_ * sizeof(struct disk_exception); - - CHECK(read_size == size); - - void* buffer = bufsink_.GetPayloadBuffer(size); - CHECK(buffer != nullptr); - - memcpy(buffer, vec_[divresult.quot].get(), size); - } else { - return ZerofillDiskExceptions(read_size); - } - - return true; -} - -loff_t Snapuserd::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer, - int* unmerged_exceptions) { - loff_t offset = 0; - *unmerged_exceptions = 0; - - while (*unmerged_exceptions <= exceptions_per_area_) { - struct disk_exception* merged_de = - reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset); - struct disk_exception* cow_de = - reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset); - - // Unmerged op by the kernel - if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) { - CHECK(merged_de->old_chunk == cow_de->old_chunk); - CHECK(merged_de->new_chunk == cow_de->new_chunk); - - offset += sizeof(struct disk_exception); - *unmerged_exceptions += 1; - continue; - } - - break; - } - - CHECK(!(*unmerged_exceptions == exceptions_per_area_)); - - SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset; - return offset; -} - -int Snapuserd::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset, - int unmerged_exceptions) { - int merged_ops_cur_iter = 0; - - // Find the operations which are merged in this cycle. - while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) { - struct disk_exception* merged_de = - reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset); - struct disk_exception* cow_de = - reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset); - - CHECK(merged_de->new_chunk == 0); - CHECK(merged_de->old_chunk == 0); - - if (cow_de->new_chunk != 0) { - merged_ops_cur_iter += 1; - offset += sizeof(struct disk_exception); - const CowOperation* cow_op = chunk_map_[ChunkToSector(cow_de->new_chunk)]; - CHECK(cow_op != nullptr); - - CHECK(cow_op->new_block == cow_de->old_chunk); - // zero out to indicate that operation is merged. - cow_de->old_chunk = 0; - cow_de->new_chunk = 0; - } else if (cow_de->old_chunk == 0) { - // Already merged op in previous iteration or - // This could also represent a partially filled area. - // - // If the op was merged in previous cycle, we don't have - // to count them. - CHECK(cow_de->new_chunk == 0); - break; - } else { - SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: " - << " merged_de-old-chunk: " << merged_de->old_chunk - << " merged_de-new-chunk: " << merged_de->new_chunk - << " cow_de-old-chunk: " << cow_de->old_chunk - << " cow_de-new-chunk: " << cow_de->new_chunk - << " unmerged_exceptions: " << unmerged_exceptions - << " merged_ops_cur_iter: " << merged_ops_cur_iter - << " offset: " << offset; - return -1; +bool Snapuserd::CommitMerge(int num_merge_ops) { + { + std::lock_guard<std::mutex> lock(lock_); + CowHeader header; + + reader_->GetHeader(&header); + header.num_merge_ops += num_merge_ops; + reader_->UpdateMergeProgress(num_merge_ops); + if (!writer_->CommitMerge(num_merge_ops)) { + SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << num_merge_ops + << " Total-merged-ops: " << header.num_merge_ops; + return false; } - } - return merged_ops_cur_iter; -} - -bool Snapuserd::ProcessMergeComplete(chunk_t chunk, void* buffer) { - uint32_t stride = exceptions_per_area_ + 1; - CowHeader header; - - if (!reader_->GetHeader(&header)) { - SNAP_LOG(ERROR) << "Failed to get header"; - return false; + merge_initiated_ = true; } - // ChunkID to vector index - lldiv_t divresult = lldiv(chunk, stride); - CHECK(divresult.quot < vec_.size()); - SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk - << " Metadata-Index: " << divresult.quot; - - int unmerged_exceptions = 0; - loff_t offset = GetMergeStartOffset(buffer, vec_[divresult.quot].get(), &unmerged_exceptions); - - int merged_ops_cur_iter = - GetNumberOfMergedOps(buffer, vec_[divresult.quot].get(), offset, unmerged_exceptions); - - // There should be at least one operation merged in this cycle - CHECK(merged_ops_cur_iter > 0); - - header.num_merge_ops += merged_ops_cur_iter; - reader_->UpdateMergeProgress(merged_ops_cur_iter); - if (!writer_->CommitMerge(merged_ops_cur_iter)) { - SNAP_LOG(ERROR) << "CommitMerge failed... merged_ops_cur_iter: " << merged_ops_cur_iter; - return false; - } - - SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk; - merge_initiated_ = true; return true; } @@ -589,7 +230,7 @@ bool Snapuserd::ReadMetadata() { // Store operation pointer. - chunk_map_[ChunkToSector(data_chunk_id)] = cow_op; + chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), cow_op)); num_ops += 1; offset += sizeof(struct disk_exception); cowop_riter_->Next(); @@ -781,7 +422,7 @@ bool Snapuserd::ReadMetadata() { de->new_chunk = data_chunk_id; // Store operation pointer. - chunk_map_[ChunkToSector(data_chunk_id)] = it->second; + chunk_vec_.push_back(std::make_pair(ChunkToSector(data_chunk_id), it->second)); offset += sizeof(struct disk_exception); num_ops += 1; copy_ops++; @@ -827,6 +468,12 @@ bool Snapuserd::ReadMetadata() { << "Areas : " << vec_.size(); } + chunk_vec_.shrink_to_fit(); + vec_.shrink_to_fit(); + + // Sort the vector based on sectors as we need this during un-aligned access + std::sort(chunk_vec_.begin(), chunk_vec_.end(), compare); + SNAP_LOG(INFO) << "ReadMetadata completed. Final-chunk-id: " << data_chunk_id << " Num Sector: " << ChunkToSector(data_chunk_id) << " Replace-ops: " << replace_ops << " Zero-ops: " << zero_ops @@ -836,7 +483,6 @@ bool Snapuserd::ReadMetadata() { // Total number of sectors required for creating dm-user device num_sectors_ = ChunkToSector(data_chunk_id); - metadata_read_done_ = true; merge_initiated_ = false; return true; } @@ -850,37 +496,6 @@ void MyLogger(android::base::LogId, android::base::LogSeverity severity, const c } } -// Read Header from dm-user misc device. This gives -// us the sector number for which IO is issued by dm-snapshot device -bool Snapuserd::ReadDmUserHeader() { - if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) { - SNAP_PLOG(ERROR) << "Control-read failed"; - return false; - } - - return true; -} - -// Send the payload/data back to dm-user misc device. -bool Snapuserd::WriteDmUserPayload(size_t size) { - if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(), - sizeof(struct dm_user_header) + size)) { - SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size; - return false; - } - - return true; -} - -bool Snapuserd::ReadDmUserPayload(void* buffer, size_t size) { - if (!android::base::ReadFully(ctrl_fd_, buffer, size)) { - SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size; - return false; - } - - return true; -} - bool Snapuserd::InitCowDevice() { cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); if (cow_fd_ < 0) { @@ -888,186 +503,26 @@ bool Snapuserd::InitCowDevice() { return false; } - // Allocate the buffer which is used to communicate between - // daemon and dm-user. The buffer comprises of header and a fixed payload. - // If the dm-user requests a big IO, the IO will be broken into chunks - // of PAYLOAD_SIZE. - size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE; - bufsink_.Initialize(buf_size); - return ReadMetadata(); } -bool Snapuserd::InitBackingAndControlDevice() { - backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY)); - if (backing_store_fd_ < 0) { - SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_; - return false; - } - - ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR)); - if (ctrl_fd_ < 0) { - SNAP_PLOG(ERROR) << "Unable to open " << control_device_; - return false; - } - - return true; -} - -bool Snapuserd::DmuserWriteRequest() { - struct dm_user_header* header = bufsink_.GetHeaderPtr(); - - // device mapper has the capability to allow - // targets to flush the cache when writes are completed. This - // is controlled by each target by a flag "flush_supported". - // This flag is set by dm-user. When flush is supported, - // a number of zero-length bio's will be submitted to - // the target for the purpose of flushing cache. It is the - // responsibility of the target driver - which is dm-user in this - // case, to remap these bio's to the underlying device. Since, - // there is no underlying device for dm-user, this zero length - // bio's gets routed to daemon. - // - // Flush operations are generated post merge by dm-snap by having - // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything - // to flush per se; hence, just respond back with a success message. - if (header->sector == 0) { - CHECK(header->len == 0); - header->type = DM_USER_RESP_SUCCESS; - if (!WriteDmUserPayload(0)) { - return false; - } - return true; - } - - size_t remaining_size = header->len; - size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); - CHECK(read_size == BLOCK_SZ); - - CHECK(header->sector > 0); - chunk_t chunk = SectorToChunk(header->sector); - CHECK(chunk_map_.find(header->sector) == chunk_map_.end()); - - void* buffer = bufsink_.GetPayloadBuffer(read_size); - CHECK(buffer != nullptr); - header->type = DM_USER_RESP_SUCCESS; - - if (!ReadDmUserPayload(buffer, read_size)) { - SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk - << "Sector: " << header->sector; - header->type = DM_USER_RESP_ERROR; - } - - if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) { - SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk - << "Sector: " << header->sector; - header->type = DM_USER_RESP_ERROR; - } else { - SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk - << "Sector: " << header->sector; - } - - if (!WriteDmUserPayload(0)) { - return false; - } - - return true; -} - -bool Snapuserd::DmuserReadRequest() { - struct dm_user_header* header = bufsink_.GetHeaderPtr(); - size_t remaining_size = header->len; - loff_t offset = 0; - sector_t sector = header->sector; - do { - size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); - - int ret = read_size; - header->type = DM_USER_RESP_SUCCESS; - chunk_t chunk = SectorToChunk(header->sector); - - // Request to sector 0 is always for kernel - // representation of COW header. This IO should be only - // once during dm-snapshot device creation. We should - // never see multiple IO requests. Additionally this IO - // will always be a single 4k. - if (header->sector == 0) { - CHECK(metadata_read_done_ == true); - CHECK(read_size == BLOCK_SZ); - ConstructKernelCowHeader(); - SNAP_LOG(DEBUG) << "Kernel header constructed"; - } else { - if (!offset && (read_size == BLOCK_SZ) && - chunk_map_.find(header->sector) == chunk_map_.end()) { - if (!ReadDiskExceptions(chunk, read_size)) { - SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk - << "Sector: " << header->sector; - header->type = DM_USER_RESP_ERROR; - } else { - SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk - << "Sector: " << header->sector; - } - } else { - chunk_t num_sectors_read = (offset >> SECTOR_SHIFT); - ret = ReadData(sector + num_sectors_read, read_size); - if (ret < 0) { - SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk - << " Sector: " << (sector + num_sectors_read) - << " size: " << read_size << " header-len: " << header->len; - header->type = DM_USER_RESP_ERROR; - } else { - SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk - << "Sector: " << header->sector; - } - } - } - - // Daemon will not be terminated if there is any error. We will - // just send the error back to dm-user. - if (!WriteDmUserPayload(ret)) { - return false; - } - - remaining_size -= ret; - offset += ret; - } while (remaining_size > 0); - - return true; -} - -bool Snapuserd::Run() { - struct dm_user_header* header = bufsink_.GetHeaderPtr(); - - bufsink_.Clear(); +/* + * Entry point to launch worker threads + */ +bool Snapuserd::Start() { + std::vector<std::future<bool>> threads; - if (!ReadDmUserHeader()) { - SNAP_LOG(ERROR) << "ReadDmUserHeader failed"; - return false; + for (int i = 0; i < worker_threads_.size(); i++) { + threads.emplace_back( + std::async(std::launch::async, &WorkerThread::RunThread, worker_threads_[i].get())); } - SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq; - SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type; - SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags; - SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector; - SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len; - - switch (header->type) { - case DM_USER_REQ_MAP_READ: { - if (!DmuserReadRequest()) { - return false; - } - break; - } - - case DM_USER_REQ_MAP_WRITE: { - if (!DmuserWriteRequest()) { - return false; - } - break; - } + bool ret = true; + for (auto& t : threads) { + ret = t.get() && ret; } - return true; + return ret; } } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h index 518d08b84..933536442 100644 --- a/fs_mgr/libsnapshot/snapuserd.h +++ b/fs_mgr/libsnapshot/snapuserd.h @@ -18,13 +18,17 @@ #include <stdint.h> #include <stdlib.h> +#include <bitset> #include <csignal> #include <cstring> +#include <future> #include <iostream> #include <limits> #include <map> +#include <mutex> #include <string> #include <thread> +#include <unordered_map> #include <vector> #include <android-base/file.h> @@ -40,6 +44,17 @@ namespace android { namespace snapshot { using android::base::unique_fd; +using namespace std::chrono_literals; + +static constexpr size_t PAYLOAD_SIZE = (1UL << 20); +static_assert(PAYLOAD_SIZE >= BLOCK_SZ); + +/* + * With 4 threads, we get optimal performance + * when update_verifier reads the partition during + * boot. + */ +static constexpr int NUM_THREADS_PER_PARTITION = 4; class BufferSink : public IByteSink { public: @@ -59,56 +74,60 @@ class BufferSink : public IByteSink { size_t buffer_size_; }; -class Snapuserd final { +class Snapuserd; + +class WorkerThread { public: - Snapuserd(const std::string& misc_name, const std::string& cow_device, - const std::string& backing_device); - bool InitBackingAndControlDevice(); - bool InitCowDevice(); - bool Run(); - const std::string& GetControlDevicePath() { return control_device_; } - const std::string& GetMiscName() { return misc_name_; } - uint64_t GetNumSectors() { return num_sectors_; } - bool IsAttached() const { return ctrl_fd_ >= 0; } - void CheckMergeCompletionStatus(); + WorkerThread(const std::string& cow_device, const std::string& backing_device, + const std::string& control_device, const std::string& misc_name, + std::shared_ptr<Snapuserd> snapuserd); + bool RunThread(); + + private: + // Initialization + void InitializeBufsink(); + bool InitializeFds(); + bool InitReader(); void CloseFds() { ctrl_fd_ = {}; - cow_fd_ = {}; backing_store_fd_ = {}; } - size_t GetMetadataAreaSize() { return vec_.size(); } - void* GetExceptionBuffer(size_t i) { return vec_[i].get(); } - private: + // Functions interacting with dm-user + bool ReadDmUserHeader(); bool DmuserReadRequest(); bool DmuserWriteRequest(); - - bool ReadDmUserHeader(); bool ReadDmUserPayload(void* buffer, size_t size); bool WriteDmUserPayload(size_t size); - void ConstructKernelCowHeader(); - bool ReadMetadata(); - bool ZerofillDiskExceptions(size_t read_size); + bool ReadDiskExceptions(chunk_t chunk, size_t size); - int ReadUnalignedSector(sector_t sector, size_t size, - std::map<sector_t, const CowOperation*>::iterator& it); + bool ZerofillDiskExceptions(size_t read_size); + void ConstructKernelCowHeader(); + + // IO Path + bool ProcessIORequest(); int ReadData(sector_t sector, size_t size); - bool IsChunkIdMetadata(chunk_t chunk); - chunk_t GetNextAllocatableChunkId(chunk_t chunk_id); + int ReadUnalignedSector(sector_t sector, size_t size, + std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it); + // Processing COW operations bool ProcessCowOp(const CowOperation* cow_op); bool ProcessReplaceOp(const CowOperation* cow_op); bool ProcessCopyOp(const CowOperation* cow_op); bool ProcessZeroOp(); + // Merge related functions + bool ProcessMergeComplete(chunk_t chunk, void* buffer); loff_t GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer, int* unmerged_exceptions); int GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset, int unmerged_exceptions); - bool ProcessMergeComplete(chunk_t chunk, void* buffer); + sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } - bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); } + + std::unique_ptr<CowReader> reader_; + BufferSink bufsink_; std::string cow_device_; std::string backing_store_device_; @@ -119,6 +138,58 @@ class Snapuserd final { unique_fd backing_store_fd_; unique_fd ctrl_fd_; + std::shared_ptr<Snapuserd> snapuserd_; + uint32_t exceptions_per_area_; +}; + +class Snapuserd : public std::enable_shared_from_this<Snapuserd> { + public: + Snapuserd(const std::string& misc_name, const std::string& cow_device, + const std::string& backing_device); + bool InitCowDevice(); + bool Start(); + const std::string& GetControlDevicePath() { return control_device_; } + const std::string& GetMiscName() { return misc_name_; } + uint64_t GetNumSectors() { return num_sectors_; } + bool IsAttached() const { return attached_; } + void AttachControlDevice() { attached_ = true; } + + void CheckMergeCompletionStatus(); + bool CommitMerge(int num_merge_ops); + + void CloseFds() { cow_fd_ = {}; } + size_t GetMetadataAreaSize() { return vec_.size(); } + void* GetExceptionBuffer(size_t i) { return vec_[i].get(); } + + bool InitializeWorkers(); + std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); } + + std::vector<std::pair<sector_t, const CowOperation*>>& GetChunkVec() { return chunk_vec_; } + const std::vector<std::unique_ptr<uint8_t[]>>& GetMetadataVec() const { return vec_; } + + static bool compare(std::pair<sector_t, const CowOperation*> p1, + std::pair<sector_t, const CowOperation*> p2) { + return p1.first < p2.first; + } + + private: + std::vector<std::unique_ptr<WorkerThread>> worker_threads_; + + bool IsChunkIdMetadata(chunk_t chunk); + chunk_t GetNextAllocatableChunkId(chunk_t chunk_id); + + bool ReadMetadata(); + sector_t ChunkToSector(chunk_t chunk) { return chunk << CHUNK_SHIFT; } + chunk_t SectorToChunk(sector_t sector) { return sector >> CHUNK_SHIFT; } + bool IsBlockAligned(int read_size) { return ((read_size & (BLOCK_SZ - 1)) == 0); } + + std::string cow_device_; + std::string backing_store_device_; + std::string control_device_; + std::string misc_name_; + + unique_fd cow_fd_; + uint32_t exceptions_per_area_; uint64_t num_sectors_; @@ -131,19 +202,14 @@ class Snapuserd final { // mapping of old-chunk to new-chunk std::vector<std::unique_ptr<uint8_t[]>> vec_; - // Key - Sector - // Value - cow operation - // - // chunk_map stores the pseudo mapping of sector - // to COW operations. Each COW op is 4k; however, - // we can get a read request which are as small - // as 512 bytes. Hence, we need to binary search - // in the chunk_map to find the nearest COW op. - std::map<sector_t, const CowOperation*> chunk_map_; - - bool metadata_read_done_ = false; + // chunk_vec stores the pseudo mapping of sector + // to COW operations. + std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_; + + std::mutex lock_; + bool merge_initiated_ = false; - BufferSink bufsink_; + bool attached_ = false; }; } // namespace snapshot diff --git a/fs_mgr/libsnapshot/snapuserd_server.cpp b/fs_mgr/libsnapshot/snapuserd_server.cpp index 017de3b1c..167895e8e 100644 --- a/fs_mgr/libsnapshot/snapuserd_server.cpp +++ b/fs_mgr/libsnapshot/snapuserd_server.cpp @@ -77,8 +77,8 @@ void SnapuserdServer::ShutdownThreads() { JoinAllThreads(); } -DmUserHandler::DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd) - : snapuserd_(std::move(snapuserd)), misc_name_(snapuserd_->GetMiscName()) {} +DmUserHandler::DmUserHandler(std::shared_ptr<Snapuserd> snapuserd) + : snapuserd_(snapuserd), misc_name_(snapuserd_->GetMiscName()) {} bool SnapuserdServer::Sendmsg(android::base::borrowed_fd fd, const std::string& msg) { ssize_t ret = TEMP_FAILURE_RETRY(send(fd.get(), msg.data(), msg.size(), 0)); @@ -204,10 +204,8 @@ bool SnapuserdServer::Receivemsg(android::base::borrowed_fd fd, const std::strin void SnapuserdServer::RunThread(std::shared_ptr<DmUserHandler> handler) { LOG(INFO) << "Entering thread for handler: " << handler->misc_name(); - while (!StopRequested()) { - if (!handler->snapuserd()->Run()) { - break; - } + if (!handler->snapuserd()->Start()) { + LOG(ERROR) << " Failed to launch all worker threads"; } handler->snapuserd()->CloseFds(); @@ -349,13 +347,18 @@ void SnapuserdServer::Interrupt() { std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& misc_name, const std::string& cow_device_path, const std::string& backing_device) { - auto snapuserd = std::make_unique<Snapuserd>(misc_name, cow_device_path, backing_device); + auto snapuserd = std::make_shared<Snapuserd>(misc_name, cow_device_path, backing_device); if (!snapuserd->InitCowDevice()) { LOG(ERROR) << "Failed to initialize Snapuserd"; return nullptr; } - auto handler = std::make_shared<DmUserHandler>(std::move(snapuserd)); + if (!snapuserd->InitializeWorkers()) { + LOG(ERROR) << "Failed to initialize workers"; + return nullptr; + } + + auto handler = std::make_shared<DmUserHandler>(snapuserd); { std::lock_guard<std::mutex> lock(lock_); if (FindHandler(&lock, misc_name) != dm_users_.end()) { @@ -370,10 +373,7 @@ std::shared_ptr<DmUserHandler> SnapuserdServer::AddHandler(const std::string& mi bool SnapuserdServer::StartHandler(const std::shared_ptr<DmUserHandler>& handler) { CHECK(!handler->snapuserd()->IsAttached()); - if (!handler->snapuserd()->InitBackingAndControlDevice()) { - LOG(ERROR) << "Failed to initialize control device: " << handler->misc_name(); - return false; - } + handler->snapuserd()->AttachControlDevice(); handler->thread() = std::thread(std::bind(&SnapuserdServer::RunThread, this, handler)); return true; diff --git a/fs_mgr/libsnapshot/snapuserd_server.h b/fs_mgr/libsnapshot/snapuserd_server.h index 7cbc2de61..e9d575d01 100644 --- a/fs_mgr/libsnapshot/snapuserd_server.h +++ b/fs_mgr/libsnapshot/snapuserd_server.h @@ -47,17 +47,17 @@ enum class DaemonOperations { class DmUserHandler { public: - explicit DmUserHandler(std::unique_ptr<Snapuserd>&& snapuserd); + explicit DmUserHandler(std::shared_ptr<Snapuserd> snapuserd); void FreeResources() { snapuserd_ = nullptr; } - const std::unique_ptr<Snapuserd>& snapuserd() const { return snapuserd_; } + const std::shared_ptr<Snapuserd>& snapuserd() const { return snapuserd_; } std::thread& thread() { return thread_; } const std::string& misc_name() const { return misc_name_; } private: std::thread thread_; - std::unique_ptr<Snapuserd> snapuserd_; + std::shared_ptr<Snapuserd> snapuserd_; std::string misc_name_; }; diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp new file mode 100644 index 000000000..1002569e0 --- /dev/null +++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp @@ -0,0 +1,695 @@ +/* + * Copyright (C) 2020 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 "snapuserd.h" + +#include <csignal> +#include <optional> +#include <set> + +#include <libsnapshot/snapuserd_client.h> + +namespace android { +namespace snapshot { + +using namespace android; +using namespace android::dm; +using android::base::unique_fd; + +#define SNAP_LOG(level) LOG(level) << misc_name_ << ": " +#define SNAP_PLOG(level) PLOG(level) << misc_name_ << ": " + +void BufferSink::Initialize(size_t size) { + buffer_size_ = size; + buffer_offset_ = 0; + buffer_ = std::make_unique<uint8_t[]>(size); +} + +void* BufferSink::GetPayloadBuffer(size_t size) { + if ((buffer_size_ - buffer_offset_) < size) return nullptr; + + char* buffer = reinterpret_cast<char*>(GetBufPtr()); + struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); + return (char*)msg->payload.buf + buffer_offset_; +} + +void* BufferSink::GetBuffer(size_t requested, size_t* actual) { + void* buf = GetPayloadBuffer(requested); + if (!buf) { + *actual = 0; + return nullptr; + } + *actual = requested; + return buf; +} + +struct dm_user_header* BufferSink::GetHeaderPtr() { + CHECK(sizeof(struct dm_user_header) <= buffer_size_); + char* buf = reinterpret_cast<char*>(GetBufPtr()); + struct dm_user_header* header = (struct dm_user_header*)(&(buf[0])); + return header; +} + +WorkerThread::WorkerThread(const std::string& cow_device, const std::string& backing_device, + const std::string& control_device, const std::string& misc_name, + std::shared_ptr<Snapuserd> snapuserd) { + cow_device_ = cow_device; + backing_store_device_ = backing_device; + control_device_ = control_device; + misc_name_ = misc_name; + snapuserd_ = snapuserd; + exceptions_per_area_ = (CHUNK_SIZE << SECTOR_SHIFT) / sizeof(struct disk_exception); +} + +bool WorkerThread::InitializeFds() { + backing_store_fd_.reset(open(backing_store_device_.c_str(), O_RDONLY)); + if (backing_store_fd_ < 0) { + SNAP_PLOG(ERROR) << "Open Failed: " << backing_store_device_; + return false; + } + + cow_fd_.reset(open(cow_device_.c_str(), O_RDWR)); + if (cow_fd_ < 0) { + SNAP_PLOG(ERROR) << "Open Failed: " << cow_device_; + return false; + } + + ctrl_fd_.reset(open(control_device_.c_str(), O_RDWR)); + if (ctrl_fd_ < 0) { + SNAP_PLOG(ERROR) << "Unable to open " << control_device_; + return false; + } + + return true; +} + +bool WorkerThread::InitReader() { + reader_ = std::make_unique<CowReader>(); + if (!reader_->InitForMerge(std::move(cow_fd_))) { + return false; + } + + return true; +} + +// Construct kernel COW header in memory +// This header will be in sector 0. The IO +// request will always be 4k. After constructing +// the header, zero out the remaining block. +void WorkerThread::ConstructKernelCowHeader() { + void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); + CHECK(buffer != nullptr); + + memset(buffer, 0, BLOCK_SZ); + + struct disk_header* dh = reinterpret_cast<struct disk_header*>(buffer); + + dh->magic = SNAP_MAGIC; + dh->valid = SNAPSHOT_VALID; + dh->version = SNAPSHOT_DISK_VERSION; + dh->chunk_size = CHUNK_SIZE; +} + +// Start the replace operation. This will read the +// internal COW format and if the block is compressed, +// it will be de-compressed. +bool WorkerThread::ProcessReplaceOp(const CowOperation* cow_op) { + if (!reader_->ReadData(*cow_op, &bufsink_)) { + SNAP_LOG(ERROR) << "ProcessReplaceOp failed for block " << cow_op->new_block; + return false; + } + + return true; +} + +// Start the copy operation. This will read the backing +// block device which is represented by cow_op->source. +bool WorkerThread::ProcessCopyOp(const CowOperation* cow_op) { + void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); + CHECK(buffer != nullptr); + + // Issue a single 4K IO. However, this can be optimized + // if the successive blocks are contiguous. + if (!android::base::ReadFullyAtOffset(backing_store_fd_, buffer, BLOCK_SZ, + cow_op->source * BLOCK_SZ)) { + SNAP_PLOG(ERROR) << "Copy-op failed. Read from backing store: " << backing_store_device_ + << "at block :" << cow_op->source; + return false; + } + + return true; +} + +bool WorkerThread::ProcessZeroOp() { + // Zero out the entire block + void* buffer = bufsink_.GetPayloadBuffer(BLOCK_SZ); + CHECK(buffer != nullptr); + + memset(buffer, 0, BLOCK_SZ); + return true; +} + +bool WorkerThread::ProcessCowOp(const CowOperation* cow_op) { + CHECK(cow_op != nullptr); + + switch (cow_op->type) { + case kCowReplaceOp: { + return ProcessReplaceOp(cow_op); + } + + case kCowZeroOp: { + return ProcessZeroOp(); + } + + case kCowCopyOp: { + return ProcessCopyOp(cow_op); + } + + default: { + SNAP_LOG(ERROR) << "Unknown operation-type found: " << cow_op->type; + } + } + return false; +} + +int WorkerThread::ReadUnalignedSector( + sector_t sector, size_t size, + std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it) { + size_t skip_sector_size = 0; + + SNAP_LOG(DEBUG) << "ReadUnalignedSector: sector " << sector << " size: " << size + << " Aligned sector: " << it->first; + + if (!ProcessCowOp(it->second)) { + SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size; + return -1; + } + + int num_sectors_skip = sector - it->first; + + if (num_sectors_skip > 0) { + skip_sector_size = num_sectors_skip << SECTOR_SHIFT; + char* buffer = reinterpret_cast<char*>(bufsink_.GetBufPtr()); + struct dm_user_message* msg = (struct dm_user_message*)(&(buffer[0])); + + memmove(msg->payload.buf, (char*)msg->payload.buf + skip_sector_size, + (BLOCK_SZ - skip_sector_size)); + } + + bufsink_.ResetBufferOffset(); + return std::min(size, (BLOCK_SZ - skip_sector_size)); +} + +/* + * Read the data for a given COW Operation. + * + * Kernel can issue IO at a sector granularity. + * Hence, an IO may end up with reading partial + * data from a COW operation or we may also + * end up with interspersed request between + * two COW operations. + * + */ +int WorkerThread::ReadData(sector_t sector, size_t size) { + std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec(); + std::vector<std::pair<sector_t, const CowOperation*>>::iterator it; + /* + * chunk_map stores COW operation at 4k granularity. + * If the requested IO with the sector falls on the 4k + * boundary, then we can read the COW op directly without + * any issue. + * + * However, if the requested sector is not 4K aligned, + * then we will have the find the nearest COW operation + * and chop the 4K block to fetch the requested sector. + */ + it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), std::make_pair(sector, nullptr), + Snapuserd::compare); + + CHECK(it != chunk_vec.end()); + + // We didn't find the required sector; hence find the previous sector + // as lower_bound will gives us the value greater than + // the requested sector + if (it->first != sector) { + if (it != chunk_vec.begin()) { + --it; + } + + /* + * If the IO is spanned between two COW operations, + * split the IO into two parts: + * + * 1: Read the first part from the single COW op + * 2: Read the second part from the next COW op. + * + * Ex: Let's say we have a 1024 Bytes IO request. + * + * 0 COW OP-1 4096 COW OP-2 8192 + * |******************|*******************| + * |*****|*****| + * 3584 4608 + * <- 1024B - > + * + * We have two COW operations which are 4k blocks. + * The IO is requested for 1024 Bytes which are spanned + * between two COW operations. We will split this IO + * into two parts: + * + * 1: IO of size 512B from offset 3584 bytes (COW OP-1) + * 2: IO of size 512B from offset 4096 bytes (COW OP-2) + */ + return ReadUnalignedSector(sector, size, it); + } + + int num_ops = DIV_ROUND_UP(size, BLOCK_SZ); + while (num_ops) { + if (!ProcessCowOp(it->second)) { + return -1; + } + num_ops -= 1; + it++; + // Update the buffer offset + bufsink_.UpdateBufferOffset(BLOCK_SZ); + + SNAP_LOG(DEBUG) << "ReadData at sector: " << sector << " size: " << size; + } + + // Reset the buffer offset + bufsink_.ResetBufferOffset(); + return size; +} + +/* + * dm-snap does prefetch reads while reading disk-exceptions. + * By default, prefetch value is set to 12; this means that + * dm-snap will issue 12 areas wherein each area is a 4k page + * of disk-exceptions. + * + * If during prefetch, if the chunk-id seen is beyond the + * actual number of metadata page, fill the buffer with zero. + * When dm-snap starts parsing the buffer, it will stop + * reading metadata page once the buffer content is zero. + */ +bool WorkerThread::ZerofillDiskExceptions(size_t read_size) { + size_t size = exceptions_per_area_ * sizeof(struct disk_exception); + + if (read_size > size) { + return false; + } + + void* buffer = bufsink_.GetPayloadBuffer(size); + CHECK(buffer != nullptr); + + memset(buffer, 0, size); + return true; +} + +/* + * A disk exception is a simple mapping of old_chunk to new_chunk. + * When dm-snapshot device is created, kernel requests these mapping. + * + * Each disk exception is of size 16 bytes. Thus a single 4k page can + * have: + * + * exceptions_per_area_ = 4096/16 = 256. This entire 4k page + * is considered a metadata page and it is represented by chunk ID. + * + * Convert the chunk ID to index into the vector which gives us + * the metadata page. + */ +bool WorkerThread::ReadDiskExceptions(chunk_t chunk, size_t read_size) { + uint32_t stride = exceptions_per_area_ + 1; + size_t size; + const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec(); + + // ChunkID to vector index + lldiv_t divresult = lldiv(chunk, stride); + + if (divresult.quot < vec.size()) { + size = exceptions_per_area_ * sizeof(struct disk_exception); + + CHECK(read_size == size); + + void* buffer = bufsink_.GetPayloadBuffer(size); + CHECK(buffer != nullptr); + + memcpy(buffer, vec[divresult.quot].get(), size); + } else { + return ZerofillDiskExceptions(read_size); + } + + return true; +} + +loff_t WorkerThread::GetMergeStartOffset(void* merged_buffer, void* unmerged_buffer, + int* unmerged_exceptions) { + loff_t offset = 0; + *unmerged_exceptions = 0; + + while (*unmerged_exceptions <= exceptions_per_area_) { + struct disk_exception* merged_de = + reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset); + struct disk_exception* cow_de = + reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset); + + // Unmerged op by the kernel + if (merged_de->old_chunk != 0 || merged_de->new_chunk != 0) { + CHECK(merged_de->old_chunk == cow_de->old_chunk); + CHECK(merged_de->new_chunk == cow_de->new_chunk); + + offset += sizeof(struct disk_exception); + *unmerged_exceptions += 1; + continue; + } + + break; + } + + CHECK(!(*unmerged_exceptions == exceptions_per_area_)); + + SNAP_LOG(DEBUG) << "Unmerged_Exceptions: " << *unmerged_exceptions << " Offset: " << offset; + return offset; +} + +int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset, + int unmerged_exceptions) { + int merged_ops_cur_iter = 0; + std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec(); + + // Find the operations which are merged in this cycle. + while ((unmerged_exceptions + merged_ops_cur_iter) < exceptions_per_area_) { + struct disk_exception* merged_de = + reinterpret_cast<struct disk_exception*>((char*)merged_buffer + offset); + struct disk_exception* cow_de = + reinterpret_cast<struct disk_exception*>((char*)unmerged_buffer + offset); + + CHECK(merged_de->new_chunk == 0); + CHECK(merged_de->old_chunk == 0); + + if (cow_de->new_chunk != 0) { + merged_ops_cur_iter += 1; + offset += sizeof(struct disk_exception); + auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), + std::make_pair(ChunkToSector(cow_de->new_chunk), nullptr), + Snapuserd::compare); + CHECK(it != chunk_vec.end()); + CHECK(it->first == ChunkToSector(cow_de->new_chunk)); + const CowOperation* cow_op = it->second; + + CHECK(cow_op != nullptr); + + CHECK(cow_op->new_block == cow_de->old_chunk); + // zero out to indicate that operation is merged. + cow_de->old_chunk = 0; + cow_de->new_chunk = 0; + } else if (cow_de->old_chunk == 0) { + // Already merged op in previous iteration or + // This could also represent a partially filled area. + // + // If the op was merged in previous cycle, we don't have + // to count them. + CHECK(cow_de->new_chunk == 0); + break; + } else { + SNAP_LOG(ERROR) << "Error in merge operation. Found invalid metadata: " + << " merged_de-old-chunk: " << merged_de->old_chunk + << " merged_de-new-chunk: " << merged_de->new_chunk + << " cow_de-old-chunk: " << cow_de->old_chunk + << " cow_de-new-chunk: " << cow_de->new_chunk + << " unmerged_exceptions: " << unmerged_exceptions + << " merged_ops_cur_iter: " << merged_ops_cur_iter + << " offset: " << offset; + return -1; + } + } + return merged_ops_cur_iter; +} + +bool WorkerThread::ProcessMergeComplete(chunk_t chunk, void* buffer) { + uint32_t stride = exceptions_per_area_ + 1; + const std::vector<std::unique_ptr<uint8_t[]>>& vec = snapuserd_->GetMetadataVec(); + + // ChunkID to vector index + lldiv_t divresult = lldiv(chunk, stride); + CHECK(divresult.quot < vec.size()); + SNAP_LOG(DEBUG) << "ProcessMergeComplete: chunk: " << chunk + << " Metadata-Index: " << divresult.quot; + + int unmerged_exceptions = 0; + loff_t offset = GetMergeStartOffset(buffer, vec[divresult.quot].get(), &unmerged_exceptions); + + int merged_ops_cur_iter = + GetNumberOfMergedOps(buffer, vec[divresult.quot].get(), offset, unmerged_exceptions); + + // There should be at least one operation merged in this cycle + CHECK(merged_ops_cur_iter > 0); + if (!snapuserd_->CommitMerge(merged_ops_cur_iter)) { + return false; + } + + SNAP_LOG(DEBUG) << "Merge success: " << merged_ops_cur_iter << "chunk: " << chunk; + return true; +} + +// Read Header from dm-user misc device. This gives +// us the sector number for which IO is issued by dm-snapshot device +bool WorkerThread::ReadDmUserHeader() { + if (!android::base::ReadFully(ctrl_fd_, bufsink_.GetBufPtr(), sizeof(struct dm_user_header))) { + if (errno != ENOTBLK) { + SNAP_PLOG(ERROR) << "Control-read failed"; + } + return false; + } + + return true; +} + +// Send the payload/data back to dm-user misc device. +bool WorkerThread::WriteDmUserPayload(size_t size) { + if (!android::base::WriteFully(ctrl_fd_, bufsink_.GetBufPtr(), + sizeof(struct dm_user_header) + size)) { + SNAP_PLOG(ERROR) << "Write to dm-user failed size: " << size; + return false; + } + + return true; +} + +bool WorkerThread::ReadDmUserPayload(void* buffer, size_t size) { + if (!android::base::ReadFully(ctrl_fd_, buffer, size)) { + SNAP_PLOG(ERROR) << "ReadDmUserPayload failed size: " << size; + return false; + } + + return true; +} + +bool WorkerThread::DmuserWriteRequest() { + struct dm_user_header* header = bufsink_.GetHeaderPtr(); + + // device mapper has the capability to allow + // targets to flush the cache when writes are completed. This + // is controlled by each target by a flag "flush_supported". + // This flag is set by dm-user. When flush is supported, + // a number of zero-length bio's will be submitted to + // the target for the purpose of flushing cache. It is the + // responsibility of the target driver - which is dm-user in this + // case, to remap these bio's to the underlying device. Since, + // there is no underlying device for dm-user, this zero length + // bio's gets routed to daemon. + // + // Flush operations are generated post merge by dm-snap by having + // REQ_PREFLUSH flag set. Snapuser daemon doesn't have anything + // to flush per se; hence, just respond back with a success message. + if (header->sector == 0) { + CHECK(header->len == 0); + header->type = DM_USER_RESP_SUCCESS; + if (!WriteDmUserPayload(0)) { + return false; + } + return true; + } + + std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec(); + size_t remaining_size = header->len; + size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); + CHECK(read_size == BLOCK_SZ) << "DmuserWriteRequest: read_size: " << read_size; + + CHECK(header->sector > 0); + chunk_t chunk = SectorToChunk(header->sector); + auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), + std::make_pair(header->sector, nullptr), Snapuserd::compare); + + bool not_found = (it == chunk_vec.end() || it->first != header->sector); + CHECK(not_found); + + void* buffer = bufsink_.GetPayloadBuffer(read_size); + CHECK(buffer != nullptr); + header->type = DM_USER_RESP_SUCCESS; + + if (!ReadDmUserPayload(buffer, read_size)) { + SNAP_LOG(ERROR) << "ReadDmUserPayload failed for chunk id: " << chunk + << "Sector: " << header->sector; + header->type = DM_USER_RESP_ERROR; + } + + if (header->type == DM_USER_RESP_SUCCESS && !ProcessMergeComplete(chunk, buffer)) { + SNAP_LOG(ERROR) << "ProcessMergeComplete failed for chunk id: " << chunk + << "Sector: " << header->sector; + header->type = DM_USER_RESP_ERROR; + } else { + SNAP_LOG(DEBUG) << "ProcessMergeComplete success for chunk id: " << chunk + << "Sector: " << header->sector; + } + + if (!WriteDmUserPayload(0)) { + return false; + } + + return true; +} + +bool WorkerThread::DmuserReadRequest() { + struct dm_user_header* header = bufsink_.GetHeaderPtr(); + size_t remaining_size = header->len; + loff_t offset = 0; + sector_t sector = header->sector; + std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec(); + do { + size_t read_size = std::min(PAYLOAD_SIZE, remaining_size); + + int ret = read_size; + header->type = DM_USER_RESP_SUCCESS; + chunk_t chunk = SectorToChunk(header->sector); + + // Request to sector 0 is always for kernel + // representation of COW header. This IO should be only + // once during dm-snapshot device creation. We should + // never see multiple IO requests. Additionally this IO + // will always be a single 4k. + if (header->sector == 0) { + CHECK(read_size == BLOCK_SZ) << " Sector 0 read request of size: " << read_size; + ConstructKernelCowHeader(); + SNAP_LOG(DEBUG) << "Kernel header constructed"; + } else { + auto it = std::lower_bound(chunk_vec.begin(), chunk_vec.end(), + std::make_pair(header->sector, nullptr), Snapuserd::compare); + bool not_found = (it == chunk_vec.end() || it->first != header->sector); + if (!offset && (read_size == BLOCK_SZ) && not_found) { + if (!ReadDiskExceptions(chunk, read_size)) { + SNAP_LOG(ERROR) << "ReadDiskExceptions failed for chunk id: " << chunk + << "Sector: " << header->sector; + header->type = DM_USER_RESP_ERROR; + } else { + SNAP_LOG(DEBUG) << "ReadDiskExceptions success for chunk id: " << chunk + << "Sector: " << header->sector; + } + } else { + chunk_t num_sectors_read = (offset >> SECTOR_SHIFT); + ret = ReadData(sector + num_sectors_read, read_size); + if (ret < 0) { + SNAP_LOG(ERROR) << "ReadData failed for chunk id: " << chunk + << " Sector: " << (sector + num_sectors_read) + << " size: " << read_size << " header-len: " << header->len; + header->type = DM_USER_RESP_ERROR; + } else { + SNAP_LOG(DEBUG) << "ReadData success for chunk id: " << chunk + << "Sector: " << header->sector; + } + } + } + + // Daemon will not be terminated if there is any error. We will + // just send the error back to dm-user. + if (!WriteDmUserPayload(ret)) { + return false; + } + + remaining_size -= ret; + offset += ret; + } while (remaining_size > 0); + + return true; +} + +void WorkerThread::InitializeBufsink() { + // Allocate the buffer which is used to communicate between + // daemon and dm-user. The buffer comprises of header and a fixed payload. + // If the dm-user requests a big IO, the IO will be broken into chunks + // of PAYLOAD_SIZE. + size_t buf_size = sizeof(struct dm_user_header) + PAYLOAD_SIZE; + bufsink_.Initialize(buf_size); +} + +bool WorkerThread::RunThread() { + InitializeBufsink(); + + if (!InitializeFds()) { + return false; + } + + if (!InitReader()) { + return false; + } + + // Start serving IO + while (true) { + if (!ProcessIORequest()) { + break; + } + } + + CloseFds(); + reader_->CloseCowFd(); + + return true; +} + +bool WorkerThread::ProcessIORequest() { + struct dm_user_header* header = bufsink_.GetHeaderPtr(); + + if (!ReadDmUserHeader()) { + return false; + } + + SNAP_LOG(DEBUG) << "msg->seq: " << std::hex << header->seq; + SNAP_LOG(DEBUG) << "msg->type: " << std::hex << header->type; + SNAP_LOG(DEBUG) << "msg->flags: " << std::hex << header->flags; + SNAP_LOG(DEBUG) << "msg->sector: " << std::hex << header->sector; + SNAP_LOG(DEBUG) << "msg->len: " << std::hex << header->len; + + switch (header->type) { + case DM_USER_REQ_MAP_READ: { + if (!DmuserReadRequest()) { + return false; + } + break; + } + + case DM_USER_REQ_MAP_WRITE: { + if (!DmuserWriteRequest()) { + return false; + } + break; + } + } + + return true; +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp index 635ca498c..5b0716851 100644 --- a/fs_mgr/libstorage_literals/Android.bp +++ b/fs_mgr/libstorage_literals/Android.bp @@ -8,4 +8,9 @@ cc_library_headers { host_supported: true, recovery_available: true, export_include_dirs: ["."], + target: { + windows: { + enabled: true, + }, + }, } diff --git a/fs_mgr/tests/fs_mgr_test.cpp b/fs_mgr/tests/fs_mgr_test.cpp index bcd518089..5887641e3 100644 --- a/fs_mgr/tests/fs_mgr_test.cpp +++ b/fs_mgr/tests/fs_mgr_test.cpp @@ -127,7 +127,7 @@ const std::string bootconfig = "androidboot.serialno = \"BLAHBLAHBLAH\"\n" "androidboot.slot_suffix = \"_a\"\n" "androidboot.hardware.platform = \"sdw813\"\n" - "androidboot.hardware = \"foo\"\n" + "hardware = \"foo\"\n" "androidboot.revision = \"EVT1.0\"\n" "androidboot.bootloader = \"burp-0.1-7521\"\n" "androidboot.hardware.sku = \"mary\"\n" @@ -159,7 +159,7 @@ const std::vector<std::pair<std::string, std::string>> bootconfig_result_space = {"androidboot.serialno", "BLAHBLAHBLAH"}, {"androidboot.slot_suffix", "_a"}, {"androidboot.hardware.platform", "sdw813"}, - {"androidboot.hardware", "foo"}, + {"hardware", "foo"}, {"androidboot.revision", "EVT1.0"}, {"androidboot.bootloader", "burp-0.1-7521"}, {"androidboot.hardware.sku", "mary"}, diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp index 49e8085ab..95e814b6a 100644 --- a/gatekeeperd/Android.bp +++ b/gatekeeperd/Android.bp @@ -40,8 +40,6 @@ cc_binary { "libbase", "libutils", "libcrypto", - "libkeystore_aidl", - "libkeystore_binder", "libhidlbase", "android.hardware.gatekeeper@1.0", "libgatekeeper_aidl", diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp index f9c0cddb4..8792c8352 100644 --- a/gatekeeperd/gatekeeperd.cpp +++ b/gatekeeperd/gatekeeperd.cpp @@ -29,13 +29,11 @@ #include <android-base/properties.h> #include <android/binder_ibinder.h> #include <android/binder_manager.h> -#include <android/security/keystore/IKeystoreService.h> #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/PermissionCache.h> #include <gatekeeper/password_handle.h> // for password_handle_t #include <hardware/hw_auth_token.h> -#include <keystore/keystore_return_types.h> #include <libgsi/libgsi.h> #include <log/log.h> #include <utils/String16.h> @@ -303,7 +301,7 @@ class GateKeeperProxy : public BnGateKeeperService { if (gkResponse->payload().size() != 0) { // try to connect to IKeystoreAuthorization AIDL service first. AIBinder* authzAIBinder = - AServiceManager_checkService("android.security.authorization"); + AServiceManager_getService("android.security.authorization"); ::ndk::SpAIBinder authzBinder(authzAIBinder); auto authzService = IKeystoreAuthorization::fromBinder(authzBinder); if (authzService) { @@ -328,21 +326,6 @@ class GateKeeperProxy : public BnGateKeeperService { LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService."; return GK_ERROR; } - } - sp<IServiceManager> sm = defaultServiceManager(); - - sp<IBinder> binder = sm->getService(String16("android.security.keystore")); - sp<security::keystore::IKeystoreService> service = - interface_cast<security::keystore::IKeystoreService>(binder); - - if (service) { - int result = 0; - auto binder_result = service->addAuthToken(gkResponse->payload(), &result); - if (!binder_result.isOk() || - !keystore::KeyStoreServiceReturnCode(result).isOk()) { - LOG(ERROR) << "Failure sending auth token to KeyStore: " << result; - return GK_ERROR; - } } else { LOG(ERROR) << "Cannot deliver auth token. Unable to communicate with " "Keystore."; diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp index 67bdb3df3..6dd2fae0f 100644 --- a/init/first_stage_init.cpp +++ b/init/first_stage_init.cpp @@ -337,12 +337,21 @@ int FirstStageMain(int argc, char** argv) { SwitchRoot("/first_stage_ramdisk"); } + std::string force_debuggable("/force_debuggable"); + std::string adb_debug_prop("/adb_debug.prop"); + std::string userdebug_sepolicy("/userdebug_plat_sepolicy.cil"); + if (IsRecoveryMode()) { + // Update these file paths since we didn't switch root + force_debuggable.insert(0, "/first_stage_ramdisk"); + adb_debug_prop.insert(0, "/first_stage_ramdisk"); + userdebug_sepolicy.insert(0, "/first_stage_ramdisk"); + } // If this file is present, the second-stage init will use a userdebug sepolicy // and load adb_debug.prop to allow adb root, if the device is unlocked. - if (access("/force_debuggable", F_OK) == 0) { + if (access(force_debuggable.c_str(), F_OK) == 0) { std::error_code ec; // to invoke the overloaded copy_file() that won't throw. - if (!fs::copy_file("/adb_debug.prop", kDebugRamdiskProp, ec) || - !fs::copy_file("/userdebug_plat_sepolicy.cil", kDebugRamdiskSEPolicy, ec)) { + if (!fs::copy_file(adb_debug_prop, kDebugRamdiskProp, ec) || + !fs::copy_file(userdebug_sepolicy, kDebugRamdiskSEPolicy, ec)) { LOG(ERROR) << "Failed to setup debug ramdisk"; } else { // setenv for second-stage init to read above kDebugRamdisk* files. diff --git a/init/init.cpp b/init/init.cpp index 4bd50e63a..07def4c49 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -533,11 +533,9 @@ static void export_oem_lock_status() { if (!android::base::GetBoolProperty("ro.oem_unlock_supported", false)) { return; } - ImportKernelCmdline([](const std::string& key, const std::string& value) { - if (key == "androidboot.verifiedbootstate") { - SetProperty("ro.boot.flash.locked", value == "orange" ? "0" : "1"); - } - }); + SetProperty( + "ro.boot.flash.locked", + android::base::GetProperty("ro.boot.verifiedbootstate", "") == "orange" ? "0" : "1"); } static Result<void> property_enable_triggers_action(const BuiltinArguments& args) { diff --git a/init/property_service.cpp b/init/property_service.cpp index 477e98b87..4c33d422d 100644 --- a/init/property_service.cpp +++ b/init/property_service.cpp @@ -44,6 +44,7 @@ #include <mutex> #include <optional> #include <queue> +#include <string_view> #include <thread> #include <vector> @@ -1265,28 +1266,55 @@ static void ProcessKernelDt() { } } +constexpr auto ANDROIDBOOT_PREFIX = "androidboot."sv; + +// emulator specific, should be removed once emulator is migrated to +// bootconfig, see b/182291166. +static std::string RemapEmulatorPropertyName(const std::string_view qemu_key) { + if (StartsWith(qemu_key, "dalvik."sv) || StartsWith(qemu_key, "opengles."sv) || + StartsWith(qemu_key, "config."sv)) { + return std::string(qemu_key); + } else if (qemu_key == "uirenderer"sv) { + return "debug.hwui.renderer"s; + } else if (qemu_key == "media.ccodec"sv) { + return "debug.stagefright.ccodec"s; + } else { + return ""s; // TBD + } +} + static void ProcessKernelCmdline() { - bool for_emulator = false; ImportKernelCmdline([&](const std::string& key, const std::string& value) { - if (key == "qemu") { - for_emulator = true; - } else if (StartsWith(key, "androidboot.")) { - InitPropertySet("ro.boot." + key.substr(12), value); + constexpr auto qemu_prefix = "qemu."sv; + + if (StartsWith(key, ANDROIDBOOT_PREFIX)) { + InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value); + } else if (StartsWith(key, qemu_prefix)) { + InitPropertySet("ro.kernel." + key, value); // emulator specific, deprecated + + // emulator specific, should be retired once emulator migrates to + // androidboot. + const auto new_name = + RemapEmulatorPropertyName(std::string_view(key).substr(qemu_prefix.size())); + if (!new_name.empty()) { + InitPropertySet("ro.boot." + new_name, value); + } + } else if (key == "qemu") { + // emulator specific, should be retired once emulator migrates to + // androidboot. + InitPropertySet("ro.boot." + key, value); } }); - - if (for_emulator) { - ImportKernelCmdline([&](const std::string& key, const std::string& value) { - // In the emulator, export any kernel option with the "ro.kernel." prefix. - InitPropertySet("ro.kernel." + key, value); - }); - } } static void ProcessBootconfig() { ImportBootconfig([&](const std::string& key, const std::string& value) { - if (StartsWith(key, "androidboot.")) { - InitPropertySet("ro.boot." + key.substr(12), value); + if (StartsWith(key, ANDROIDBOOT_PREFIX)) { + InitPropertySet("ro.boot." + key.substr(ANDROIDBOOT_PREFIX.size()), value); + } else if (key == "hardware") { + // "hardware" in bootconfig replaces "androidboot.hardware" kernel + // cmdline parameter + InitPropertySet("ro.boot." + key, value); } }); } diff --git a/init/selinux.cpp b/init/selinux.cpp index 033693655..62c458674 100644 --- a/init/selinux.cpp +++ b/init/selinux.cpp @@ -63,6 +63,7 @@ #include <android-base/file.h> #include <android-base/logging.h> #include <android-base/parseint.h> +#include <android-base/result.h> #include <android-base/strings.h> #include <android-base/unique_fd.h> #include <fs_avb/fs_avb.h> @@ -92,7 +93,7 @@ namespace { enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING }; -EnforcingStatus StatusFromCmdline() { +EnforcingStatus StatusFromProperty() { EnforcingStatus status = SELINUX_ENFORCING; ImportKernelCmdline([&](const std::string& key, const std::string& value) { @@ -101,12 +102,20 @@ EnforcingStatus StatusFromCmdline() { } }); + if (status == SELINUX_ENFORCING) { + ImportBootconfig([&](const std::string& key, const std::string& value) { + if (key == "androidboot.selinux" && value == "permissive") { + status = SELINUX_PERMISSIVE; + } + }); + } + return status; } bool IsEnforcing() { if (ALLOW_PERMISSIVE_SELINUX) { - return StatusFromCmdline() == SELINUX_ENFORCING; + return StatusFromProperty() == SELINUX_ENFORCING; } return true; } @@ -214,8 +223,8 @@ bool ReadFirstLine(const char* file, std::string* line) { return true; } -bool FindPrecompiledSplitPolicy(std::string* file) { - file->clear(); +Result<std::string> FindPrecompiledSplitPolicy() { + std::string precompiled_sepolicy; // If there is an odm partition, precompiled_sepolicy will be in // odm/etc/selinux. Otherwise it will be in vendor/etc/selinux. static constexpr const char vendor_precompiled_sepolicy[] = @@ -223,62 +232,49 @@ bool FindPrecompiledSplitPolicy(std::string* file) { static constexpr const char odm_precompiled_sepolicy[] = "/odm/etc/selinux/precompiled_sepolicy"; if (access(odm_precompiled_sepolicy, R_OK) == 0) { - *file = odm_precompiled_sepolicy; + precompiled_sepolicy = odm_precompiled_sepolicy; } else if (access(vendor_precompiled_sepolicy, R_OK) == 0) { - *file = vendor_precompiled_sepolicy; + precompiled_sepolicy = vendor_precompiled_sepolicy; } else { - PLOG(INFO) << "No precompiled sepolicy"; - return false; - } - std::string actual_plat_id; - if (!ReadFirstLine("/system/etc/selinux/plat_sepolicy_and_mapping.sha256", &actual_plat_id)) { - PLOG(INFO) << "Failed to read " - "/system/etc/selinux/plat_sepolicy_and_mapping.sha256"; - return false; - } - std::string actual_system_ext_id; - if (!ReadFirstLine("/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256", - &actual_system_ext_id)) { - PLOG(INFO) << "Failed to read " - "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256"; - return false; - } - std::string actual_product_id; - if (!ReadFirstLine("/product/etc/selinux/product_sepolicy_and_mapping.sha256", - &actual_product_id)) { - PLOG(INFO) << "Failed to read " - "/product/etc/selinux/product_sepolicy_and_mapping.sha256"; - return false; + return ErrnoError() << "No precompiled sepolicy at " << vendor_precompiled_sepolicy; } - std::string precompiled_plat_id; - std::string precompiled_plat_sha256 = *file + ".plat_sepolicy_and_mapping.sha256"; - if (!ReadFirstLine(precompiled_plat_sha256.c_str(), &precompiled_plat_id)) { - PLOG(INFO) << "Failed to read " << precompiled_plat_sha256; - file->clear(); - return false; - } - std::string precompiled_system_ext_id; - std::string precompiled_system_ext_sha256 = *file + ".system_ext_sepolicy_and_mapping.sha256"; - if (!ReadFirstLine(precompiled_system_ext_sha256.c_str(), &precompiled_system_ext_id)) { - PLOG(INFO) << "Failed to read " << precompiled_system_ext_sha256; - file->clear(); - return false; + // Use precompiled sepolicy only when all corresponding hashes are equal. + // plat_sepolicy is always checked, while system_ext and product are checked only when they + // exist. + std::vector<std::pair<std::string, std::string>> sepolicy_hashes{ + {"/system/etc/selinux/plat_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".plat_sepolicy_and_mapping.sha256"}, + }; + + if (access("/system_ext/etc/selinux/system_ext_sepolicy.cil", F_OK) == 0) { + sepolicy_hashes.emplace_back( + "/system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".system_ext_sepolicy_and_mapping.sha256"); } - std::string precompiled_product_id; - std::string precompiled_product_sha256 = *file + ".product_sepolicy_and_mapping.sha256"; - if (!ReadFirstLine(precompiled_product_sha256.c_str(), &precompiled_product_id)) { - PLOG(INFO) << "Failed to read " << precompiled_product_sha256; - file->clear(); - return false; + + if (access("/product/etc/selinux/product_sepolicy.cil", F_OK) == 0) { + sepolicy_hashes.emplace_back("/product/etc/selinux/product_sepolicy_and_mapping.sha256", + precompiled_sepolicy + ".product_sepolicy_and_mapping.sha256"); } - if (actual_plat_id.empty() || actual_plat_id != precompiled_plat_id || - actual_system_ext_id.empty() || actual_system_ext_id != precompiled_system_ext_id || - actual_product_id.empty() || actual_product_id != precompiled_product_id) { - file->clear(); - return false; + + for (const auto& [actual_id_path, precompiled_id_path] : sepolicy_hashes) { + std::string actual_id; + if (!ReadFirstLine(actual_id_path.c_str(), &actual_id)) { + return ErrnoError() << "Failed to read " << actual_id_path; + } + + std::string precompiled_id; + if (!ReadFirstLine(precompiled_id_path.c_str(), &precompiled_id)) { + return ErrnoError() << "Failed to read " << precompiled_id_path; + } + + if (actual_id.empty() || actual_id != precompiled_id) { + return Error() << actual_id_path << " and " << precompiled_id_path << " differ"; + } } - return true; + + return precompiled_sepolicy; } bool GetVendorMappingVersion(std::string* plat_vers) { @@ -325,15 +321,18 @@ bool OpenSplitPolicy(PolicyFile* policy_file) { // Load precompiled policy from vendor image, if a matching policy is found there. The policy // must match the platform policy on the system image. - std::string precompiled_sepolicy_file; // use_userdebug_policy requires compiling sepolicy with userdebug_plat_sepolicy.cil. // Thus it cannot use the precompiled policy from vendor image. - if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) { - unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); - if (fd != -1) { - policy_file->fd = std::move(fd); - policy_file->path = std::move(precompiled_sepolicy_file); - return true; + if (!use_userdebug_policy) { + if (auto res = FindPrecompiledSplitPolicy(); res.ok()) { + unique_fd fd(open(res->c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); + if (fd != -1) { + policy_file->fd = std::move(fd); + policy_file->path = std::move(*res); + return true; + } + } else { + LOG(INFO) << res.error(); } } // No suitable precompiled policy could be loaded diff --git a/init/util.cpp b/init/util.cpp index eab99d4e3..a40d10416 100644 --- a/init/util.cpp +++ b/init/util.cpp @@ -376,6 +376,15 @@ static std::string init_android_dt_dir() { android_dt_dir = value; } }); + // ..Or bootconfig + if (android_dt_dir == kDefaultAndroidDtDir) { + ImportBootconfig([&](const std::string& key, const std::string& value) { + if (key == "androidboot.android_dt_dir") { + android_dt_dir = value; + } + }); + } + LOG(INFO) << "Using Android DT directory " << android_dt_dir; return android_dt_dir; } diff --git a/libcutils/Android.bp b/libcutils/Android.bp index 0d9f2c79a..0f3763c50 100644 --- a/libcutils/Android.bp +++ b/libcutils/Android.bp @@ -354,3 +354,17 @@ cc_test { defaults: ["libcutils_test_static_defaults"], test_config: "KernelLibcutilsTest.xml", } + +rust_bindgen { + name: "libcutils_bindgen", + wrapper_src: "rust/cutils.h", + crate_name: "cutils_bindgen", + source_stem: "bindings", + local_include_dirs: ["include"], + bindgen_flags: [ + "--whitelist-function", "multiuser_get_app_id", + "--whitelist-function", "multiuser_get_user_id", + "--whitelist-function", "multiuser_get_uid", + "--whitelist-var", "AID_USER_OFFSET", + ], +} diff --git a/libcutils/fs_config.cpp b/libcutils/fs_config.cpp index d69c038bc..e9497a806 100644 --- a/libcutils/fs_config.cpp +++ b/libcutils/fs_config.cpp @@ -86,7 +86,7 @@ static const struct fs_path_config android_dirs[] = { { 00751, AID_ROOT, AID_SHELL, 0, "system/bin" }, { 00755, AID_ROOT, AID_ROOT, 0, "system/etc/ppp" }, { 00755, AID_ROOT, AID_SHELL, 0, "system/vendor" }, - { 00751, AID_ROOT, AID_SHELL, 0, "system/xbin" }, + { 00750, AID_ROOT, AID_SHELL, 0, "system/xbin" }, { 00751, AID_ROOT, AID_SHELL, 0, "system/apex/*/bin" }, { 00751, AID_ROOT, AID_SHELL, 0, "system_ext/bin" }, { 00751, AID_ROOT, AID_SHELL, 0, "system_ext/apex/*/bin" }, diff --git a/libcutils/rust/cutils.h b/libcutils/rust/cutils.h new file mode 100644 index 000000000..9b78af631 --- /dev/null +++ b/libcutils/rust/cutils.h @@ -0,0 +1,4 @@ +#pragma once + +#include <cutils/multiuser.h> +#include <private/android_filesystem_config.h> diff --git a/libkeyutils/Android.bp b/libkeyutils/Android.bp index 9848cd8a8..86f68fb57 100644 --- a/libkeyutils/Android.bp +++ b/libkeyutils/Android.bp @@ -2,25 +2,10 @@ package { default_applicable_licenses: ["system_core_libkeyutils_license"], } -// Added automatically by a large-scale-change that took the approach of -// 'apply every license found to every target'. While this makes sure we respect -// every license restriction, it may not be entirely correct. -// -// e.g. GPL in an MIT project might only apply to the contrib/ directory. -// -// Please consider splitting the single license below into multiple licenses, -// taking care not to lose any license_kind information, and overriding the -// default license using the 'licenses: [...]' property on targets as needed. -// -// For unused files, consider creating a 'fileGroup' with "//visibility:private" -// to attach the license to, and including a comment whether the files may be -// used in the current project. -// See: http://go/android-license-faq license { name: "system_core_libkeyutils_license", visibility: [":__subpackages__"], license_kinds: [ - "SPDX-license-identifier-Apache-2.0", "SPDX-license-identifier-BSD", ], // large-scale-change unable to identify any license_text files diff --git a/libmodprobe/OWNERS b/libmodprobe/OWNERS index e6b5bbad9..a6796cbba 100644 --- a/libmodprobe/OWNERS +++ b/libmodprobe/OWNERS @@ -1 +1,2 @@ -smuckle@google.com +dvander@google.com +willmcvicker@google.com diff --git a/libprocessgroup/cgrouprc/include/android/cgrouprc.h b/libprocessgroup/cgrouprc/include/android/cgrouprc.h index 9a799547d..100d60ea9 100644 --- a/libprocessgroup/cgrouprc/include/android/cgrouprc.h +++ b/libprocessgroup/cgrouprc/include/android/cgrouprc.h @@ -68,6 +68,7 @@ __attribute__((warn_unused_result)) uint32_t ACgroupController_getVersion(const */ #define CGROUPRC_CONTROLLER_FLAG_MOUNTED 0x1 #define CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION 0x2 +#define CGROUPRC_CONTROLLER_FLAG_OPTIONAL 0x4 /** * Returns the flags bitmask of the given controller. diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json index 9d623b768..23e4575cb 100644 --- a/libprocessgroup/profiles/cgroups.json +++ b/libprocessgroup/profiles/cgroups.json @@ -26,7 +26,8 @@ "Path": "/dev/memcg", "Mode": "0755", "UID": "system", - "GID": "system" + "GID": "system", + "Optional": true } ], "Cgroups2": { diff --git a/libprocessgroup/profiles/cgroups.proto b/libprocessgroup/profiles/cgroups.proto index 13adcae07..f2de3452a 100644 --- a/libprocessgroup/profiles/cgroups.proto +++ b/libprocessgroup/profiles/cgroups.proto @@ -24,7 +24,7 @@ message Cgroups { Cgroups2 cgroups2 = 2 [json_name = "Cgroups2"]; } -// Next: 7 +// Next: 8 message Cgroup { string controller = 1 [json_name = "Controller"]; string path = 2 [json_name = "Path"]; @@ -35,6 +35,7 @@ message Cgroup { // when a boolean is specified as false, so leave unspecified in that case // https://developers.google.com/protocol-buffers/docs/proto3#default bool needs_activation = 6 [json_name = "NeedsActivation"]; + bool is_optional = 7 [json_name = "Optional"]; } // Next: 6 diff --git a/libprocessgroup/profiles/cgroups_30.json b/libprocessgroup/profiles/cgroups_30.json index 17d492949..80a074bf1 100644 --- a/libprocessgroup/profiles/cgroups_30.json +++ b/libprocessgroup/profiles/cgroups_30.json @@ -5,7 +5,8 @@ "Path": "/dev/stune", "Mode": "0755", "UID": "system", - "GID": "system" + "GID": "system", + "Optional": true } ] } diff --git a/libprocessgroup/setup/cgroup_map_write.cpp b/libprocessgroup/setup/cgroup_map_write.cpp index aa41acbdc..3121d244b 100644 --- a/libprocessgroup/setup/cgroup_map_write.cpp +++ b/libprocessgroup/setup/cgroup_map_write.cpp @@ -161,6 +161,10 @@ static void MergeCgroupToDescriptors(std::map<std::string, CgroupDescriptor>* de controller_flags |= CGROUPRC_CONTROLLER_FLAG_NEEDS_ACTIVATION; } + if (cgroup["Optional"].isBool() && cgroup["Optional"].asBool()) { + controller_flags |= CGROUPRC_CONTROLLER_FLAG_OPTIONAL; + } + CgroupDescriptor descriptor( cgroups_version, name, path, std::strtoul(cgroup["Mode"].asString().c_str(), 0, 8), cgroup["UID"].asString(), cgroup["GID"].asString(), controller_flags); @@ -267,8 +271,6 @@ static bool SetupCgroup(const CgroupDescriptor& descriptor) { descriptor.gid())) { LOG(ERROR) << "Failed to create directory for " << controller->name() << " cgroup"; result = -1; - } else { - LOG(ERROR) << "restored ownership for " << controller->name() << " cgroup"; } } else { if (!Mkdir(controller->path(), descriptor.mode(), descriptor.uid(), descriptor.gid())) { @@ -310,8 +312,15 @@ static bool SetupCgroup(const CgroupDescriptor& descriptor) { } if (result < 0) { - PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup"; - return false; + bool optional = controller->flags() & CGROUPRC_CONTROLLER_FLAG_OPTIONAL; + + if (optional && errno == EINVAL) { + // Optional controllers are allowed to fail to mount if kernel does not support them + LOG(INFO) << "Optional " << controller->name() << " cgroup controller is not mounted"; + } else { + PLOG(ERROR) << "Failed to mount " << controller->name() << " cgroup"; + return false; + } } return true; diff --git a/libstats/OWNERS b/libstats/OWNERS index 7855774a7..d39167947 100644 --- a/libstats/OWNERS +++ b/libstats/OWNERS @@ -1,6 +1,7 @@ -joeo@google.com +jeffreyhuang@google.com +jtnguyen@google.com muhammadq@google.com -ruchirr@google.com +sharaienko@google.com singhtejinder@google.com tsaichristine@google.com yaochen@google.com diff --git a/libstats/pull_lazy/Android.bp b/libstats/pull_lazy/Android.bp new file mode 100644 index 000000000..65dce26a0 --- /dev/null +++ b/libstats/pull_lazy/Android.bp @@ -0,0 +1,48 @@ +// Lazy loading version of libstatspull that can be used by code +// that is running before the statsd APEX is mounted and +// libstatspull.so is available. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libstatspull_lazy", + header_libs: [ + "libstatspull_headers", + "libstatssocket_headers", + ], + export_header_lib_headers: [ + "libstatspull_headers", + ], + apex_available: ["//apex_available:platform"], + srcs: ["libstatspull_lazy.cpp"], +} + +cc_test { + name: "libstatspull_lazy_test", + srcs: [ + "tests/libstatspull_lazy_test.cpp", + ], + static_libs: [ + "libstatspull_lazy", + "libstatssocket_lazy", + ], + shared_libs: ["liblog"], + cflags: [ + "-Wall", + "-Werror", + ], + test_suites: ["device-tests", "mts-statsd"], + test_config: "libstatspull_lazy_test.xml", + // TODO(b/153588990): Remove when the build system properly separates. + // 32bit and 64bit architectures. + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, +} diff --git a/libstats/pull_lazy/TEST_MAPPING b/libstats/pull_lazy/TEST_MAPPING new file mode 100644 index 000000000..89b8c2a1f --- /dev/null +++ b/libstats/pull_lazy/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "libstatspull_lazy_test" + } + ] +}
\ No newline at end of file diff --git a/libstats/pull_lazy/libstatspull_lazy.cpp b/libstats/pull_lazy/libstatspull_lazy.cpp new file mode 100644 index 000000000..b11fceec1 --- /dev/null +++ b/libstats/pull_lazy/libstatspull_lazy.cpp @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2021 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 "libstatspull_lazy.h" + +#include <mutex> + +#include <dlfcn.h> +#include <stdatomic.h> + +#include "log/log.h" + +#include "stats_pull_atom_callback.h" + +// This file provides a lazy interface to libstatspull.so to address early boot dependencies. +// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and +// libstatspull.so is in the statsd APEX. + +// Method pointers to libstatspull methods are held in an array which simplifies checking +// all pointers are initialized. +enum MethodIndex { + // PullAtomMetadata APIs in stats_pull_atom_callback.h. + k_AStatsManager_PullAtomMetadata_obtain, + k_AStatsManager_PullAtomMetadata_release, + k_AStatsManager_PullAtomMetadata_setCoolDownMillis, + k_AStatsManager_PullAtomMetadata_getCoolDownMillis, + k_AStatsManager_PullAtomMetadata_setTimeoutMillis, + k_AStatsManager_PullAtomMetadata_getTimeoutMillis, + k_AStatsManager_PullAtomMetadata_setAdditiveFields, + k_AStatsManager_PullAtomMetadata_getNumAdditiveFields, + k_AStatsManager_PullAtomMetadata_getAdditiveFields, + + // AStatsEventList APIs in stats_pull_atom_callback.h + k_AStatsEventList_addStatsEvent, + + // PullAtomCallback APIs in stats_pull_atom_callback.h + k_AStatsManager_setPullAtomCallback, + k_AStatsManager_clearPullAtomCallback, + + // Marker for count of methods + k_MethodCount +}; + +// Table of methods pointers in libstatspull APIs. +static void* g_Methods[k_MethodCount]; + +// +// Libstatspull lazy loading. +// + +static atomic_bool gPreventLibstatspullLoading = false; // Allows tests to block loading. + +void PreventLibstatspullLazyLoadingForTests() { + gPreventLibstatspullLoading.store(true); +} + +static void* LoadLibstatspull(int dlopen_flags) { + if (gPreventLibstatspullLoading.load()) { + return nullptr; + } + return dlopen("libstatspull.so", dlopen_flags); +} + +// +// Initialization and symbol binding. + +static void BindSymbol(void* handle, const char* name, enum MethodIndex index) { + void* symbol = dlsym(handle, name); + LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatspull.so: %s", + name, dlerror()); + g_Methods[index] = symbol; +} + +static void InitializeOnce() { + void* handle = LoadLibstatspull(RTLD_NOW); + LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatspull.so: %s", dlerror()); + +#undef BIND_SYMBOL +#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name); + // PullAtomMetadata APIs in stats_pull_atom_callback.h. + BIND_SYMBOL(AStatsManager_PullAtomMetadata_obtain); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_release); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_setCoolDownMillis); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_getCoolDownMillis); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_setTimeoutMillis); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_getTimeoutMillis); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_setAdditiveFields); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_getNumAdditiveFields); + BIND_SYMBOL(AStatsManager_PullAtomMetadata_getAdditiveFields); + + // AStatsEventList APIs in stats_pull_atom_callback.h + BIND_SYMBOL(AStatsEventList_addStatsEvent); + + // PullAtomCallback APIs in stats_pull_atom_callback.h + BIND_SYMBOL(AStatsManager_setPullAtomCallback); + BIND_SYMBOL(AStatsManager_clearPullAtomCallback); + +#undef BIND_SYMBOL + + // Check every symbol is bound. + for (int i = 0; i < k_MethodCount; ++i) { + LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr, + "Uninitialized method in libstatspull_lazy at index: %d", i); + } +} + +static void EnsureInitialized() { + static std::once_flag initialize_flag; + std::call_once(initialize_flag, InitializeOnce); +} + +#define INVOKE_METHOD(name, args...) \ + do { \ + EnsureInitialized(); \ + void* method = g_Methods[k_##name]; \ + return reinterpret_cast<decltype(&name)>(method)(args); \ + } while (0) + +// +// Forwarding for methods in stats_pull_atom_callback.h. +// + +AStatsManager_PullAtomMetadata* AStatsManager_PullAtomMetadata_obtain() { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_obtain); +} + +void AStatsManager_PullAtomMetadata_release(AStatsManager_PullAtomMetadata* metadata) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_release, metadata); +} + +void AStatsManager_PullAtomMetadata_setCoolDownMillis(AStatsManager_PullAtomMetadata* metadata, + int64_t cool_down_millis) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_setCoolDownMillis, metadata, cool_down_millis); +} + +int64_t AStatsManager_PullAtomMetadata_getCoolDownMillis(AStatsManager_PullAtomMetadata* metadata) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_getCoolDownMillis, metadata); +} + +void AStatsManager_PullAtomMetadata_setTimeoutMillis(AStatsManager_PullAtomMetadata* metadata, + int64_t timeout_millis) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_setTimeoutMillis, metadata, timeout_millis); +} + +int64_t AStatsManager_PullAtomMetadata_getTimeoutMillis(AStatsManager_PullAtomMetadata* metadata) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_getTimeoutMillis, metadata); +} + +void AStatsManager_PullAtomMetadata_setAdditiveFields(AStatsManager_PullAtomMetadata* metadata, + int32_t* additive_fields, + int32_t num_fields) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_setAdditiveFields, metadata, additive_fields, + num_fields); +} + +int32_t AStatsManager_PullAtomMetadata_getNumAdditiveFields( + AStatsManager_PullAtomMetadata* metadata) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_getNumAdditiveFields, metadata); +} + +void AStatsManager_PullAtomMetadata_getAdditiveFields(AStatsManager_PullAtomMetadata* metadata, + int32_t* fields) { + INVOKE_METHOD(AStatsManager_PullAtomMetadata_getAdditiveFields, metadata, fields); +} + +AStatsEvent* AStatsEventList_addStatsEvent(AStatsEventList* pull_data) { + INVOKE_METHOD(AStatsEventList_addStatsEvent, pull_data); +} + +void AStatsManager_setPullAtomCallback(int32_t atom_tag, AStatsManager_PullAtomMetadata* metadata, + AStatsManager_PullAtomCallback callback, void* cookie) { + INVOKE_METHOD(AStatsManager_setPullAtomCallback, atom_tag, metadata, callback, cookie); +} + +void AStatsManager_clearPullAtomCallback(int32_t atom_tag) { + INVOKE_METHOD(AStatsManager_clearPullAtomCallback, atom_tag); +} diff --git a/libstats/pull_lazy/libstatspull_lazy.h b/libstats/pull_lazy/libstatspull_lazy.h new file mode 100644 index 000000000..2edddc770 --- /dev/null +++ b/libstats/pull_lazy/libstatspull_lazy.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +extern "C" void PreventLibstatspullLazyLoadingForTests();
\ No newline at end of file diff --git a/libstats/pull_lazy/libstatspull_lazy_test.xml b/libstats/pull_lazy/libstatspull_lazy_test.xml new file mode 100644 index 000000000..1b619af1e --- /dev/null +++ b/libstats/pull_lazy/libstatspull_lazy_test.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Runs libstatspull_lazy_test."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + <option name="test-suite-tag" value="mts" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="libstatspull_lazy_test->/data/local/tmp/libstatspull_lazy_test" /> + <option name="append-bitness" value="true" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="libstatspull_lazy_test" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> +</configuration>
\ No newline at end of file diff --git a/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp new file mode 100644 index 000000000..41f82d0b5 --- /dev/null +++ b/libstats/pull_lazy/tests/libstatspull_lazy_test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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 "../libstatspull_lazy.h" + +#include <gtest/gtest.h> + +#include "stats_pull_atom_callback.h" +//#include "stats_event.h" + +// The tests here are just for the case when libstatspull.so cannot be loaded by +// libstatspull_lazy. +class LibstatspullLazyTest : public ::testing::Test { + protected: + virtual void SetUp() { + ::testing::Test::SetUp(); + PreventLibstatspullLazyLoadingForTests(); + } +}; + +static const char* kLoadFailed = "Failed to load libstatspull.so"; + +TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomMetadata) { + AStatsManager_PullAtomMetadata* metadata = NULL; + EXPECT_DEATH(AStatsManager_PullAtomMetadata_obtain(), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_release(metadata), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_setCoolDownMillis(metadata, 0), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_getCoolDownMillis(metadata), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_setTimeoutMillis(metadata, 0), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_getTimeoutMillis(metadata), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_setAdditiveFields(metadata, NULL, 0), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_getNumAdditiveFields(metadata), kLoadFailed); + EXPECT_DEATH(AStatsManager_PullAtomMetadata_getAdditiveFields(metadata, NULL), kLoadFailed); +} + +TEST_F(LibstatspullLazyTest, NoLibstatspullForAStatsEventList) { + AStatsEventList* event_list = NULL; + EXPECT_DEATH(AStatsEventList_addStatsEvent(event_list), kLoadFailed); +} + +TEST_F(LibstatspullLazyTest, NoLibstatspullForPullAtomCallback) { + AStatsManager_PullAtomCallback callback = NULL; + EXPECT_DEATH(AStatsManager_setPullAtomCallback(0, NULL, callback, NULL), kLoadFailed); + EXPECT_DEATH(AStatsManager_clearPullAtomCallback(0), kLoadFailed); +}
\ No newline at end of file diff --git a/libstats/socket_lazy/Android.bp b/libstats/socket_lazy/Android.bp new file mode 100644 index 000000000..b2cd7b26b --- /dev/null +++ b/libstats/socket_lazy/Android.bp @@ -0,0 +1,44 @@ +// Lazy loading version of libstatssocket that can be used by code +// that is running before the statsd APEX is mounted and +// libstatssocket.so is available. +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libstatssocket_lazy", + header_libs: [ + "libstatssocket_headers", + ], + export_header_lib_headers: [ + "libstatssocket_headers", + ], + apex_available: ["//apex_available:platform"], + srcs: ["libstatssocket_lazy.cpp"], +} + +cc_test { + name: "libstatssocket_lazy_test", + srcs: [ + "tests/libstatssocket_lazy_test.cpp", + ], + static_libs: ["libstatssocket_lazy"], + shared_libs: ["liblog"], + cflags: [ + "-Wall", + "-Werror", + ], + test_suites: ["device-tests", "mts-statsd"], + test_config: "libstatssocket_lazy_test.xml", + // TODO(b/153588990): Remove when the build system properly separates. + // 32bit and 64bit architectures. + compile_multilib: "both", + multilib: { + lib64: { + suffix: "64", + }, + lib32: { + suffix: "32", + }, + }, +} diff --git a/libstats/socket_lazy/TEST_MAPPING b/libstats/socket_lazy/TEST_MAPPING new file mode 100644 index 000000000..13afc0087 --- /dev/null +++ b/libstats/socket_lazy/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit" : [ + { + "name" : "libstatssocket_lazy_test" + } + ] +}
\ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy.cpp b/libstats/socket_lazy/libstatssocket_lazy.cpp new file mode 100644 index 000000000..dd93eebf0 --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2021 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 "libstatssocket_lazy.h" + +#include <mutex> + +#include <dlfcn.h> +#include <stdatomic.h> + +#include "log/log.h" + +#include "stats_event.h" +#include "stats_socket.h" + +// This file provides a lazy interface to libstatssocket.so to address early boot dependencies. +// Specifically bootanimation, surfaceflinger, and lmkd run before the statsd APEX is loaded and +// libstatssocket.so is in the statsd APEX. + +// Method pointers to libstatssocket methods are held in an array which simplifies checking +// all pointers are initialized. +enum MethodIndex { + // Stats Event APIs in stats_event.h. + k_AStatsEvent_obtain, + k_AStatsEvent_build, + k_AStatsEvent_write, + k_AStatsEvent_release, + k_AStatsEvent_setAtomId, + k_AStatsEvent_writeInt32, + k_AStatsEvent_writeInt64, + k_AStatsEvent_writeFloat, + k_AStatsEvent_writeBool, + k_AStatsEvent_writeByteArray, + k_AStatsEvent_writeString, + k_AStatsEvent_writeAttributionChain, + k_AStatsEvent_addBoolAnnotation, + k_AStatsEvent_addInt32Annotation, + + // Stats Socket APIs in stats_socket.h. + k_AStatsSocket_close, + + // Marker for count of methods + k_MethodCount +}; + +// Table of methods pointers in libstatssocket APIs. +static void* g_Methods[k_MethodCount]; + +// +// Libstatssocket lazy loading. +// + +static atomic_bool gPreventLibstatssocketLoading = false; // Allows tests to block loading. + +void PreventLibstatssocketLazyLoadingForTests() { + gPreventLibstatssocketLoading.store(true); +} + +static void* LoadLibstatssocket(int dlopen_flags) { + if (gPreventLibstatssocketLoading.load()) { + return nullptr; + } + return dlopen("libstatssocket.so", dlopen_flags); +} + +// +// Initialization and symbol binding. + +static void BindSymbol(void* handle, const char* name, enum MethodIndex index) { + void* symbol = dlsym(handle, name); + LOG_ALWAYS_FATAL_IF(symbol == nullptr, "Failed to find symbol '%s' in libstatssocket.so: %s", + name, dlerror()); + g_Methods[index] = symbol; +} + +static void InitializeOnce() { + void* handle = LoadLibstatssocket(RTLD_NOW); + LOG_ALWAYS_FATAL_IF(handle == nullptr, "Failed to load libstatssocket.so: %s", dlerror()); + +#undef BIND_SYMBOL +#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_##name); + // Methods in stats_event.h. + BIND_SYMBOL(AStatsEvent_obtain); + BIND_SYMBOL(AStatsEvent_build); + BIND_SYMBOL(AStatsEvent_write); + BIND_SYMBOL(AStatsEvent_release); + BIND_SYMBOL(AStatsEvent_setAtomId); + BIND_SYMBOL(AStatsEvent_writeInt32); + BIND_SYMBOL(AStatsEvent_writeInt64); + BIND_SYMBOL(AStatsEvent_writeFloat); + BIND_SYMBOL(AStatsEvent_writeBool); + BIND_SYMBOL(AStatsEvent_writeByteArray); + BIND_SYMBOL(AStatsEvent_writeString); + BIND_SYMBOL(AStatsEvent_writeAttributionChain); + BIND_SYMBOL(AStatsEvent_addBoolAnnotation); + BIND_SYMBOL(AStatsEvent_addInt32Annotation); + + // Methods in stats_socket.h. + BIND_SYMBOL(AStatsSocket_close); +#undef BIND_SYMBOL + + // Check every symbol is bound. + for (int i = 0; i < k_MethodCount; ++i) { + LOG_ALWAYS_FATAL_IF(g_Methods[i] == nullptr, + "Uninitialized method in libstatssocket_lazy at index: %d", i); + } +} + +static void EnsureInitialized() { + static std::once_flag initialize_flag; + std::call_once(initialize_flag, InitializeOnce); +} + +#define INVOKE_METHOD(name, args...) \ + do { \ + EnsureInitialized(); \ + void* method = g_Methods[k_##name]; \ + return reinterpret_cast<decltype(&name)>(method)(args); \ + } while (0) + +// +// Forwarding for methods in stats_event.h. +// + +AStatsEvent* AStatsEvent_obtain() { + INVOKE_METHOD(AStatsEvent_obtain); +} + +void AStatsEvent_build(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_build, event); +} + +int AStatsEvent_write(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_write, event); +} + +void AStatsEvent_release(AStatsEvent* event) { + INVOKE_METHOD(AStatsEvent_release, event); +} + +void AStatsEvent_setAtomId(AStatsEvent* event, uint32_t atomId) { + INVOKE_METHOD(AStatsEvent_setAtomId, event, atomId); +} + +void AStatsEvent_writeInt32(AStatsEvent* event, int32_t value) { + INVOKE_METHOD(AStatsEvent_writeInt32, event, value); +} + +void AStatsEvent_writeInt64(AStatsEvent* event, int64_t value) { + INVOKE_METHOD(AStatsEvent_writeInt64, event, value); +} + +void AStatsEvent_writeFloat(AStatsEvent* event, float value) { + INVOKE_METHOD(AStatsEvent_writeFloat, event, value); +} + +void AStatsEvent_writeBool(AStatsEvent* event, bool value) { + INVOKE_METHOD(AStatsEvent_writeBool, event, value); +} + +void AStatsEvent_writeByteArray(AStatsEvent* event, const uint8_t* buf, size_t numBytes) { + INVOKE_METHOD(AStatsEvent_writeByteArray, event, buf, numBytes); +} + +void AStatsEvent_writeString(AStatsEvent* event, const char* value) { + INVOKE_METHOD(AStatsEvent_writeString, event, value); +} + +void AStatsEvent_writeAttributionChain(AStatsEvent* event, const uint32_t* uids, + const char* const* tags, uint8_t numNodes) { + INVOKE_METHOD(AStatsEvent_writeAttributionChain, event, uids, tags, numNodes); +} + +void AStatsEvent_addBoolAnnotation(AStatsEvent* event, uint8_t annotationId, bool value) { + INVOKE_METHOD(AStatsEvent_addBoolAnnotation, event, annotationId, value); +} + +void AStatsEvent_addInt32Annotation(AStatsEvent* event, uint8_t annotationId, int32_t value) { + INVOKE_METHOD(AStatsEvent_addInt32Annotation, event, annotationId, value); +} + +// +// Forwarding for methods in stats_socket.h. +// + +void AStatsSocket_close() { + INVOKE_METHOD(AStatsSocket_close); +}
\ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy.h b/libstats/socket_lazy/libstatssocket_lazy.h new file mode 100644 index 000000000..3ff87cbfa --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +extern "C" void PreventLibstatssocketLazyLoadingForTests();
\ No newline at end of file diff --git a/libstats/socket_lazy/libstatssocket_lazy_test.xml b/libstats/socket_lazy/libstatssocket_lazy_test.xml new file mode 100644 index 000000000..ca6339b13 --- /dev/null +++ b/libstats/socket_lazy/libstatssocket_lazy_test.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<configuration description="Runs libstatssocket_lazy_test."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + <option name="test-suite-tag" value="mts" /> + + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/> + + <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> + <option name="cleanup" value="true" /> + <option name="push" value="libstatssocket_lazy_test->/data/local/tmp/libstatssocket_lazy_test" /> + <option name="append-bitness" value="true" /> + </target_preparer> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="libstatssocket_lazy_test" /> + </test> + + <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController"> + <option name="mainline-module-package-name" value="com.google.android.os.statsd" /> + </object> +</configuration>
\ No newline at end of file diff --git a/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp new file mode 100644 index 000000000..fe1359860 --- /dev/null +++ b/libstats/socket_lazy/tests/libstatssocket_lazy_test.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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 "../libstatssocket_lazy.h" + +#include <gtest/gtest.h> + +#include "stats_event.h" +#include "stats_socket.h" + +// The tests here are just for the case when libstatssocket.so cannot be loaded by +// libstatssocket_lazy. +class LibstatssocketLazyTest : public ::testing::Test { + protected: + virtual void SetUp() { + ::testing::Test::SetUp(); + PreventLibstatssocketLazyLoadingForTests(); + } +}; + +static const char* kLoadFailed = "Failed to load libstatssocket.so"; + +TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsEvent) { + AStatsEvent* event = NULL; + EXPECT_DEATH(AStatsEvent_obtain(), kLoadFailed); + EXPECT_DEATH(AStatsEvent_build(event), kLoadFailed); + EXPECT_DEATH(AStatsEvent_write(event), kLoadFailed); + EXPECT_DEATH(AStatsEvent_release(event), kLoadFailed); + + EXPECT_DEATH(AStatsEvent_setAtomId(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeInt32(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeInt64(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeFloat(event, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeBool(event, false), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeByteArray(event, NULL, 0), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeString(event, NULL), kLoadFailed); + EXPECT_DEATH(AStatsEvent_writeAttributionChain(event, NULL, NULL, 0), kLoadFailed); + + EXPECT_DEATH(AStatsEvent_addBoolAnnotation(event, 0, false), kLoadFailed); + EXPECT_DEATH(AStatsEvent_addInt32Annotation(event, 0, 0), kLoadFailed); +} + +TEST_F(LibstatssocketLazyTest, NoLibstatssocketForStatsSocket) { + EXPECT_DEATH(AStatsSocket_close(), kLoadFailed); +}
\ No newline at end of file diff --git a/llkd/README.md b/llkd/README.md index 6f92f1474..9bcf806b5 100644 --- a/llkd/README.md +++ b/llkd/README.md @@ -207,7 +207,7 @@ Comma-separated list of uid numbers or names. Default is empty or false. The `llkd` does not monitor the specified subset of processes for live lock stack signatures. Default is process names -`init,lmkd.llkd,llkd,keystore,ueventd,apexd,logd`. Prevents the sepolicy +`init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd,logd`. Prevents the sepolicy violation associated with processes that block `ptrace` (as these can't be checked). **Active only on userdebug and eng builds**. For details on build types, refer to [Building Android](/setup/build/building#choose-a-target). diff --git a/llkd/include/llkd.h b/llkd/include/llkd.h index 4b20a56da..0822a3e2b 100644 --- a/llkd/include/llkd.h +++ b/llkd/include/llkd.h @@ -60,7 +60,7 @@ unsigned llkCheckMilliseconds(void); #define LLK_IGNORELIST_UID_PROPERTY "ro.llk.ignorelist.uid" #define LLK_IGNORELIST_UID_DEFAULT "" #define LLK_IGNORELIST_STACK_PROPERTY "ro.llk.ignorelist.process.stack" -#define LLK_IGNORELIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,ueventd,apexd" +#define LLK_IGNORELIST_STACK_DEFAULT "init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd" /* clang-format on */ __END_DECLS diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp index 27a8baf3b..c4c58eef3 100644 --- a/llkd/libllkd.cpp +++ b/llkd/libllkd.cpp @@ -115,8 +115,8 @@ std::unordered_map<std::string, std::unordered_set<std::string>> llkIgnorelistPa // list of uids, and uid names, to skip, default nothing std::unordered_set<std::string> llkIgnorelistUid; #ifdef __PTRACE_ENABLED__ -// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore" or -// "logd" (if not userdebug). +// list of names to skip stack checking. "init", "lmkd", "llkd", "keystore", +// "keystore2", or "logd" (if not userdebug). std::unordered_set<std::string> llkIgnorelistStack; #endif diff --git a/libkeyutils/mini_keyctl/Android.bp b/mini_keyctl/Android.bp index 417ddac18..417ddac18 100644 --- a/libkeyutils/mini_keyctl/Android.bp +++ b/mini_keyctl/Android.bp diff --git a/libkeyutils/mini_keyctl/mini_keyctl.cpp b/mini_keyctl/mini_keyctl.cpp index 8aace9adb..8aace9adb 100644 --- a/libkeyutils/mini_keyctl/mini_keyctl.cpp +++ b/mini_keyctl/mini_keyctl.cpp diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp b/mini_keyctl/mini_keyctl_utils.cpp index fb9503f14..fb9503f14 100644 --- a/libkeyutils/mini_keyctl/mini_keyctl_utils.cpp +++ b/mini_keyctl/mini_keyctl_utils.cpp diff --git a/libkeyutils/mini_keyctl/mini_keyctl_utils.h b/mini_keyctl/mini_keyctl_utils.h index cc31d29ce..cc31d29ce 100644 --- a/libkeyutils/mini_keyctl/mini_keyctl_utils.h +++ b/mini_keyctl/mini_keyctl_utils.h diff --git a/rootdir/etc/linker.config.json b/rootdir/etc/linker.config.json index 2faf60898..83cb6ff13 100644 --- a/rootdir/etc/linker.config.json +++ b/rootdir/etc/linker.config.json @@ -6,6 +6,7 @@ "libnativebridge.so", "libnativehelper.so", "libnativeloader.so", + "libsigchain.so", "libandroidicu.so", "libicu.so", // TODO(b/122876336): Remove libpac.so once it's migrated to Webview @@ -26,4 +27,4 @@ "libadb_pairing_connection.so", "libadb_pairing_server.so" ] -}
\ No newline at end of file +} diff --git a/rootdir/init.rc b/rootdir/init.rc index 29bdb1289..6f3807068 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -647,6 +647,9 @@ on late-fs write /sys/kernel/tracing/instances/bootreceiver/events/error_report/error_report_end/enable 1 on post-fs-data + # Boot level 30 - at this point daemons like apexd and odsign run + setprop keystore.boot_level 30 + mark_post_data # Start checkpoint before we touch data @@ -898,9 +901,12 @@ on post-fs-data wait_for_prop apexd.status activated perform_apex_config - # Export *CLASSPATH variables from /etc/classpath - # TODO(b/180105615): export from the generated file instead. - load_exports /etc/classpath + # Define and export *CLASSPATH variables + mkdir /data/system/environ 0700 system system + # Must start before 'odsign', as odsign depends on *CLASSPATH variables + exec_start derive_classpath + load_exports /data/system/environ/classpath + rm /data/system/environ/classpath # Special-case /data/media/obb per b/64566063 mkdir /data/media 0770 media_rw media_rw encryption=None @@ -916,7 +922,12 @@ on post-fs-data # Start the on-device signing daemon, and wait for it to finish, to ensure # ART artifacts are generated if needed. - exec_start odsign + # Must start after 'derive_classpath' to have *CLASSPATH variables set. + start odsign + + # Before we can lock keys and proceed to the next boot stage, wait for + # odsign to be done with the key + wait_for_prop odsign.key.done 1 # After apexes are mounted, tell keymaster early boot has ended, so it will # stop allowing use of early-boot keys @@ -925,6 +936,8 @@ on post-fs-data # Lock the fs-verity keyring, so no more keys can be added exec -- /system/bin/fsverity_init --lock + setprop keystore.boot_level 40 + # Allow apexd to snapshot and restore device encrypted apex data in the case # of a rollback. This should be done immediately after DE_user data keys # are loaded. APEXes should not access this data until this has been @@ -956,6 +969,7 @@ on post-fs-data # It is recommended to put unnecessary data/ initialization from post-fs-data # to start-zygote in device's init.rc to unblock zygote start. on zygote-start && property:ro.crypto.state=unencrypted + wait_for_prop odsign.verification.done 1 # A/B update verifier that marks a successful boot. exec_start update_verifier_nonencrypted start statsd @@ -964,6 +978,7 @@ on zygote-start && property:ro.crypto.state=unencrypted start zygote_secondary on zygote-start && property:ro.crypto.state=unsupported + wait_for_prop odsign.verification.done 1 # A/B update verifier that marks a successful boot. exec_start update_verifier_nonencrypted start statsd @@ -972,6 +987,7 @@ on zygote-start && property:ro.crypto.state=unsupported start zygote_secondary on zygote-start && property:ro.crypto.state=encrypted && property:ro.crypto.type=file + wait_for_prop odsign.verification.done 1 # A/B update verifier that marks a successful boot. exec_start update_verifier_nonencrypted start statsd @@ -1086,7 +1102,7 @@ on boot chown root radio /proc/cmdline # Define default initial receive window size in segments. - setprop net.tcp.default_init_rwnd 60 + setprop net.tcp_def_init_rwnd 60 # Start standard binderized HAL daemons class_start hal @@ -1151,6 +1167,11 @@ on property:sys.boot_completed=1 on property:sys.sysctl.extra_free_kbytes=* write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes} +# Allow users to drop caches +on property:perf.drop_caches=3 + write /proc/sys/vm/drop_caches 3 + setprop perf.drop_caches 0 + # "tcp_default_init_rwnd" Is too long! on property:net.tcp_def_init_rwnd=* write /proc/sys/net/ipv4/tcp_default_init_rwnd ${net.tcp_def_init_rwnd} diff --git a/trusty/fuzz/Android.bp b/trusty/fuzz/Android.bp index d1477673a..5d0ff7920 100644 --- a/trusty/fuzz/Android.bp +++ b/trusty/fuzz/Android.bp @@ -30,7 +30,6 @@ cc_defaults { "-Werror", ], fuzz_config: { - fuzz_on_haiku_device: false, fuzz_on_haiku_host: false, }, } diff --git a/trusty/fuzz/test/Android.bp b/trusty/fuzz/test/Android.bp index 7d7491392..e0bca55b1 100644 --- a/trusty/fuzz/test/Android.bp +++ b/trusty/fuzz/test/Android.bp @@ -24,5 +24,8 @@ cc_fuzz { "-DTRUSTY_APP_PORT=\"com.android.trusty.sancov.test.srv\"", "-DTRUSTY_APP_UUID=\"77f68803-c514-43ba-bdce-3254531c3d24\"", "-DTRUSTY_APP_FILENAME=\"srv.syms.elf\"", - ] + ], + fuzz_config: { + fuzz_on_haiku_device: false, + }, } diff --git a/trusty/fuzz/tipc_fuzzer.cpp b/trusty/fuzz/tipc_fuzzer.cpp index 24b0f98d2..325894470 100644 --- a/trusty/fuzz/tipc_fuzzer.cpp +++ b/trusty/fuzz/tipc_fuzzer.cpp @@ -51,13 +51,21 @@ extern "C" int LLVMFuzzerInitialize(int* /* argc */, char*** /* argv */) { exit(-1); } + /* Make sure lazy-loaded TAs have started and connected to coverage service. */ + TrustyApp ta(TIPC_DEV, TRUSTY_APP_PORT); + auto ret = ta.Connect(); + if (!ret.ok()) { + std::cerr << ret.error() << std::endl; + exit(-1); + } + record = std::make_unique<CoverageRecord>(TIPC_DEV, &module_uuid, TRUSTY_APP_FILENAME); if (!record) { std::cerr << "Failed to allocate coverage record" << std::endl; exit(-1); } - auto ret = record->Open(); + ret = record->Open(); if (!ret.ok()) { std::cerr << ret.error() << std::endl; exit(-1); diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c index 94aedd75b..29c6f939f 100644 --- a/trusty/libtrusty/tipc-test/tipc_test.c +++ b/trusty/libtrusty/tipc-test/tipc_test.c @@ -914,7 +914,7 @@ static int send_fd_test(void) { } size_t buf_size = PAGE_SIZE * num_pages; - dma_buf = DmabufHeapAlloc(allocator, "system", buf_size, 0); + dma_buf = DmabufHeapAlloc(allocator, "system", buf_size, 0, 0 /* legacy align */); if (dma_buf < 0) { ret = dma_buf; fprintf(stderr, "Failed to create dma-buf fd of size %zu err (%d)\n", buf_size, ret); diff --git a/trusty/utils/acvp/Android.bp b/trusty/utils/acvp/Android.bp new file mode 100644 index 000000000..b851e39c4 --- /dev/null +++ b/trusty/utils/acvp/Android.bp @@ -0,0 +1,40 @@ +// Copyright (C) 2021 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_binary { + name: "trusty_acvp_modulewrapper", + vendor: true, + + srcs: [ + "trusty_modulewrapper.cpp", + ], + static_libs: [ + "libacvp_modulewrapper", + ], + shared_libs: [ + "libbase", + "libc", + "libdmabufheap", + "liblog", + "libtrusty", + "libssl", + ], + cflags: [ + "-Wall", + "-Werror", + ], +} diff --git a/trusty/utils/acvp/acvp_ipc.h b/trusty/utils/acvp/acvp_ipc.h new file mode 100644 index 000000000..8b48ae3cd --- /dev/null +++ b/trusty/utils/acvp/acvp_ipc.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define ACVP_PORT "com.android.trusty.acvp" + +/* + * Maximum number of arguments + */ +#define ACVP_MAX_NUM_ARGUMENTS 8 + +/* + * Maximum length of an algorithm name + */ +#define ACVP_MAX_NAME_LENGTH 30 + +/* + * Maximum length of an ACVP request message + */ +#define ACVP_MAX_MESSAGE_LENGTH sizeof(struct acvp_req) + +/* + * Minimum length of the shared memory buffer + * + * This must be at least as long as the longest reply from the ACVP service + * (currently the reply from getConfig()). + */ +#define ACVP_MIN_SHARED_MEMORY 16384 + +/** + * acvp_req - Request for the Trusty ACVP app + * @num_args: Number of acvp_arg structures following this struct + * @buffer_size: Total size of shared memory buffer + * @lengths: Length of each argument in the shared memory buffer + * + * @num_args copies of the acvp_arg struct follow this structure. + */ +struct acvp_req { + uint32_t num_args; + uint32_t buffer_size; + uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS]; +}; + +/** + * acvp_resp - Response to a ACVP request + * + * @num_spans: Number of response sections + * @lengths: Length of each response section + */ +struct acvp_resp { + uint32_t num_spans; + uint32_t lengths[ACVP_MAX_NUM_ARGUMENTS]; +}; + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/trusty/utils/acvp/trusty_modulewrapper.cpp b/trusty/utils/acvp/trusty_modulewrapper.cpp new file mode 100644 index 000000000..70ffb52ec --- /dev/null +++ b/trusty/utils/acvp/trusty_modulewrapper.cpp @@ -0,0 +1,235 @@ +/* + * Copyright 2021, 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. + */ + +#define LOG_TAG "TrustyAcvpModulewrapper" + +#include <BufferAllocator/BufferAllocator.h> +#include <android-base/file.h> +#include <android-base/result.h> +#include <android-base/unique_fd.h> +#include <errno.h> +#include <log/log.h> +#include <modulewrapper.h> +#include <openssl/span.h> +#include <stdint.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <trusty/tipc.h> +#include <unistd.h> +#include <iostream> + +#include "acvp_ipc.h" + +constexpr const char kTrustyDeviceName[] = "/dev/trusty-ipc-dev0"; + +using android::base::ErrnoError; +using android::base::Error; +using android::base::Result; +using android::base::unique_fd; +using android::base::WriteFully; + +static inline size_t AlignUpToPage(size_t size) { + return (size + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1); +} + +namespace { + +class ModuleWrapper { + private: + static const char* kAcvpPort_; + static const char* kTrustyDeviceName_; + + public: + ModuleWrapper(); + ~ModuleWrapper(); + + Result<void> SendMessage(bssl::Span<const bssl::Span<const uint8_t>>); + + Result<void> ForwardResponse(); + + private: + // Connection to the Trusty ACVP service + int tipc_fd_ = -1; + + // Shared memory DMA buf + unique_fd dmabuf_fd_; + + // Size of shared memory mapping + size_t shm_size_ = 0; + + // Shared memory mapping + uint8_t* shm_buffer_ = nullptr; +}; + +} // namespace + +const char* ModuleWrapper::kAcvpPort_ = ACVP_PORT; +const char* ModuleWrapper::kTrustyDeviceName_ = kTrustyDeviceName; + +ModuleWrapper::ModuleWrapper() { + tipc_fd_ = tipc_connect(kTrustyDeviceName_, kAcvpPort_); + if (tipc_fd_ < 0) { + fprintf(stderr, "Failed to connect to Trusty ACVP test app: %s\n", strerror(-tipc_fd_)); + } +} + +ModuleWrapper::~ModuleWrapper() { + if (tipc_fd_ >= 0) { + tipc_close(tipc_fd_); + } + + if (shm_buffer_) { + munmap(shm_buffer_, shm_size_); + } +} + +Result<void> ModuleWrapper::SendMessage(bssl::Span<const bssl::Span<const uint8_t>> args) { + assert(args.size() < ACVP_MAX_NUM_ARGUMENTS); + assert(args[0].size() < ACVP_MAX_NAME_LENGTH); + + struct acvp_req request; + request.num_args = args.size(); + + size_t total_args_size = 0; + for (auto arg : args) { + total_args_size += arg.size(); + } + + shm_size_ = ACVP_MIN_SHARED_MEMORY; + if (total_args_size > shm_size_) { + shm_size_ = AlignUpToPage(total_args_size); + } + request.buffer_size = shm_size_; + + struct iovec iov = { + .iov_base = &request, + .iov_len = sizeof(struct acvp_req), + }; + + BufferAllocator alloc; + dmabuf_fd_.reset(alloc.Alloc(kDmabufSystemHeapName, shm_size_)); + if (!dmabuf_fd_.ok()) { + return ErrnoError() << "Error creating dmabuf"; + } + + shm_buffer_ = (uint8_t*)mmap(0, shm_size_, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd_, 0); + if (shm_buffer_ == MAP_FAILED) { + return ErrnoError() << "Failed to map shared memory dmabuf"; + } + + size_t cur_offset = 0; + for (int i = 0; i < args.size(); ++i) { + request.lengths[i] = args[i].size(); + memcpy(shm_buffer_ + cur_offset, args[i].data(), args[i].size()); + cur_offset += args[i].size(); + } + + struct trusty_shm shm = { + .fd = dmabuf_fd_.get(), + .transfer = TRUSTY_SHARE, + }; + + int rc = tipc_send(tipc_fd_, &iov, 1, &shm, 1); + if (rc != sizeof(struct acvp_req)) { + return ErrnoError() << "Failed to send request to Trusty ACVP service"; + } + + return {}; +} + +Result<void> ModuleWrapper::ForwardResponse() { + struct acvp_resp resp; + int bytes_read = read(tipc_fd_, &resp, sizeof(struct acvp_resp)); + if (bytes_read < 0) { + return ErrnoError() << "Failed to read response from Trusty ACVP service"; + } + + if (bytes_read != sizeof(struct acvp_resp)) { + return Error() << "Trusty ACVP response overflowed expected size"; + } + + size_t total_args_size = 0; + for (size_t i = 0; i < resp.num_spans; i++) { + total_args_size += resp.lengths[i]; + } + + iovec iovs[2]; + iovs[0].iov_base = &resp; + iovs[0].iov_len = sizeof(uint32_t) * (1 + resp.num_spans); + + iovs[1].iov_base = shm_buffer_; + iovs[1].iov_len = total_args_size; + + size_t iov_done = 0; + while (iov_done < 2) { + ssize_t r; + do { + r = writev(STDOUT_FILENO, &iovs[iov_done], 2 - iov_done); + } while (r == -1 && errno == EINTR); + + if (r <= 0) { + return Error() << "Failed to write ACVP response to standard out"; + } + + size_t written = r; + for (size_t i = iov_done; i < 2 && written > 0; i++) { + iovec& iov = iovs[i]; + + size_t done = written; + if (done > iov.iov_len) { + done = iov.iov_len; + } + + iov.iov_base = reinterpret_cast<uint8_t*>(iov.iov_base) + done; + iov.iov_len -= done; + written -= done; + + if (iov.iov_len == 0) { + iov_done++; + } + } + + assert(written == 0); + } + + return {}; +} + +int main() { + for (;;) { + auto buffer = bssl::acvp::RequestBuffer::New(); + auto args = bssl::acvp::ParseArgsFromFd(STDIN_FILENO, buffer.get()); + if (args.empty()) { + ALOGE("Could not parse arguments\n"); + return EXIT_FAILURE; + } + + ModuleWrapper wrapper; + auto res = wrapper.SendMessage(args); + if (!res.ok()) { + std::cerr << res.error() << std::endl; + return EXIT_FAILURE; + } + + res = wrapper.ForwardResponse(); + if (!res.ok()) { + std::cerr << res.error() << std::endl; + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +}; |