Merge "trusty: apploader: Log errors to logcat"
diff --git a/bootstat/bootstat.cpp b/bootstat/bootstat.cpp
index ee1ae31..d6ebb0d 100644
--- a/bootstat/bootstat.cpp
+++ b/bootstat/bootstat.cpp
@@ -438,6 +438,7 @@
{"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 fb274ec..7b6f6c0 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -238,6 +238,7 @@
"gwp_asan_crash_handler",
"libscudo",
"libtombstone_proto",
+ "libprocinfo",
"libprotobuf-cpp-lite",
],
diff --git a/debuggerd/client/debuggerd_client.cpp b/debuggerd/client/debuggerd_client.cpp
index 6bfb5f2..530e0e8 100644
--- a/debuggerd/client/debuggerd_client.cpp
+++ b/debuggerd/client/debuggerd_client.cpp
@@ -96,7 +96,7 @@
if (std::string str = data.str(); !str.empty()) {
buffer << "\n----- Waiting Channels: pid " << pid << " at " << get_timestamp() << " -----\n"
- << "Cmd line: " << get_process_name(pid) << "\n";
+ << "Cmd line: " << android::base::Join(get_command_line(pid), " ") << "\n";
buffer << "\n" << str << "\n";
buffer << "----- end " << std::to_string(pid) << " -----\n";
buffer << "\n";
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 04e1e4e..a152740 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -450,9 +450,6 @@
// unwind, do not make this too small. b/62828735
alarm(30 * android::base::HwTimeoutMultiplier());
- // Get the process name (aka cmdline).
- std::string process_name = get_process_name(g_target_thread);
-
// Collect the list of open files.
OpenFilesList open_files;
{
@@ -489,7 +486,6 @@
info.pid = target_process;
info.tid = thread;
info.uid = getuid();
- info.process_name = process_name;
info.thread_name = get_thread_name(thread);
unique_fd attr_fd(openat(target_proc_fd, "attr/current", O_RDONLY | O_CLOEXEC));
@@ -517,6 +513,8 @@
ReadCrashInfo(input_pipe, &siginfo, &info.registers, &process_info);
info.siginfo = &siginfo;
info.signo = info.siginfo->si_signo;
+
+ info.command_line = get_command_line(g_target_thread);
} else {
info.registers.reset(unwindstack::Regs::RemoteGet(thread));
if (!info.registers) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index de37a5b..144faee 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>
@@ -98,6 +99,13 @@
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,
@@ -397,6 +405,72 @@
}
#endif
+// 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));
@@ -428,12 +502,11 @@
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.*
-
-allocated by thread .*
- #00 pc)");
+ 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
@@ -465,9 +538,8 @@
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Overflow, 0 bytes right of a )" +
- std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
@@ -500,9 +572,8 @@
ASSERT_MATCH(result, R"(signal 11 \(SIGSEGV\), code 9 \(SEGV_MTESERR\))");
ASSERT_MATCH(result, R"(Cause: \[MTE\]: Buffer Underflow, 4 bytes left of a )" +
- std::to_string(GetParam()) + R"(-byte allocation.*
-
-allocated by thread .*
+ std::to_string(GetParam()) + R"(-byte allocation)");
+ ASSERT_MATCH(result, R"(allocated by thread .*
#00 pc)");
#else
GTEST_SKIP() << "Requires aarch64";
diff --git a/debuggerd/libdebuggerd/backtrace.cpp b/debuggerd/libdebuggerd/backtrace.cpp
index c543a83..fd91038 100644
--- a/debuggerd/libdebuggerd/backtrace.cpp
+++ b/debuggerd/libdebuggerd/backtrace.cpp
@@ -34,6 +34,7 @@
#include <memory>
#include <string>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <log/log.h>
#include <unwindstack/Unwinder.h>
@@ -42,11 +43,12 @@
#include "libdebuggerd/utility.h"
#include "util.h"
-static void dump_process_header(log_t* log, pid_t pid, const char* process_name) {
+static void dump_process_header(log_t* log, pid_t pid,
+ const std::vector<std::string>& command_line) {
_LOG(log, logtype::BACKTRACE, "\n\n----- pid %d at %s -----\n", pid, get_timestamp().c_str());
- if (process_name) {
- _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", process_name);
+ if (!command_line.empty()) {
+ _LOG(log, logtype::BACKTRACE, "Cmd line: %s\n", android::base::Join(command_line, " ").c_str());
}
_LOG(log, logtype::BACKTRACE, "ABI: '%s'\n", ABI_STRING);
}
@@ -89,7 +91,7 @@
return;
}
- dump_process_header(&log, target->second.pid, target->second.process_name.c_str());
+ dump_process_header(&log, target->second.pid, target->second.command_line);
dump_backtrace_thread(output_fd.get(), unwinder, target->second);
for (const auto& [tid, info] : thread_info) {
@@ -107,7 +109,7 @@
log.amfd_data = nullptr;
pid_t pid = getpid();
- dump_process_header(&log, pid, get_process_name(pid).c_str());
+ dump_process_header(&log, pid, get_command_line(pid));
}
void dump_backtrace_footer(int output_fd) {
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index 9750fc4..3ee309f 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 6c88733..f9c2481 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 @@
// 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 4d00ece..c3b95d6 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 @@
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 @@
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 bf2cbb3..2331f1e 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 @@
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 dcb52f9..086dc97 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/types.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/types.h
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
+#include <vector>
#include <unwindstack/Regs.h>
@@ -32,13 +33,14 @@
pid_t pid;
- std::string process_name;
+ std::vector<std::string> command_line;
std::string selinux_label;
int signo = 0;
siginfo_t* siginfo = nullptr;
};
+// This struct is written into a pipe from inside the crashing process.
struct ProcessInfo {
uintptr_t abort_msg_address = 0;
uintptr_t fdsan_table_address = 0;
diff --git a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
index d71b76f..c490fb1 100644
--- a/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
+++ b/debuggerd/libdebuggerd/include/libdebuggerd/utility.h
@@ -81,7 +81,7 @@
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 @@
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 1c3437f..f4690ba 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 @@
return buf;
}
-static const uintptr_t kTagGranuleSize = 16;
-
ScudoCrashData::ScudoCrashData(unwindstack::Memory* process_memory,
const ProcessInfo& process_info) {
if (!process_info.has_fault_address) {
@@ -78,6 +79,58 @@
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,
@@ -140,7 +193,8 @@
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;
@@ -151,7 +205,8 @@
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 44a9214..8f84346 100644
--- a/debuggerd/libdebuggerd/test/UnwinderMock.h
+++ b/debuggerd/libdebuggerd/test/UnwinderMock.h
@@ -33,8 +33,7 @@
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 f16f578..5be145a 100644
--- a/debuggerd/libdebuggerd/test/dump_memory_test.cpp
+++ b/debuggerd/libdebuggerd/test/dump_memory_test.cpp
@@ -73,34 +73,14 @@
" 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 @@
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 @@
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 @@
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/tombstone_test.cpp b/debuggerd/libdebuggerd/test/tombstone_test.cpp
index 7fe8f82..a14dcb0 100644
--- a/debuggerd/libdebuggerd/test/tombstone_test.cpp
+++ b/debuggerd/libdebuggerd/test/tombstone_test.cpp
@@ -350,11 +350,11 @@
}
TEST_F(TombstoneTest, dump_thread_info_uid) {
- dump_thread_info(&log_, ThreadInfo{.uid = 1,
- .tid = 3,
- .thread_name = "some_thread",
- .pid = 2,
- .process_name = "some_process"});
+ std::vector<std::string> cmdline = {"some_process"};
+ dump_thread_info(
+ &log_,
+ ThreadInfo{
+ .uid = 1, .tid = 3, .thread_name = "some_thread", .pid = 2, .command_line = cmdline});
std::string expected = "pid: 2, tid: 3, name: some_thread >>> some_process <<<\nuid: 1\n";
ASSERT_STREQ(expected.c_str(), amfd_data_.c_str());
}
@@ -388,9 +388,8 @@
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 @@
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.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 4f75ff1..e0cc662 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -182,8 +182,13 @@
// Don't try to collect logs from the threads that implement the logging system itself.
if (thread_info.uid == AID_LOGD) log->should_retrieve_logcat = false;
+ const char* process_name = "<unknown>";
+ if (!thread_info.command_line.empty()) {
+ process_name = thread_info.command_line[0].c_str();
+ }
+
_LOG(log, logtype::HEADER, "pid: %d, tid: %d, name: %s >>> %s <<<\n", thread_info.pid,
- thread_info.tid, thread_info.thread_name.c_str(), thread_info.process_name.c_str());
+ thread_info.tid, thread_info.thread_name.c_str(), process_name);
_LOG(log, logtype::HEADER, "uid: %d\n", thread_info.uid);
if (thread_info.tagged_addr_ctrl != -1) {
_LOG(log, logtype::HEADER, "tagged_addr_ctrl: %016lx\n", thread_info.tagged_addr_ctrl);
@@ -567,7 +572,7 @@
log.amfd_data = nullptr;
std::string thread_name = get_thread_name(tid);
- std::string process_name = get_process_name(pid);
+ std::vector<std::string> command_line = get_command_line(pid);
std::unique_ptr<unwindstack::Regs> regs(
unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
@@ -582,7 +587,7 @@
.tid = tid,
.thread_name = std::move(thread_name),
.pid = pid,
- .process_name = std::move(process_name),
+ .command_line = std::move(command_line),
.selinux_label = std::move(selinux_label),
.siginfo = siginfo,
};
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 23ca070..7657001 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 @@
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 @@
}
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 @@
}
}
+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 @@
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 @@
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 @@
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 @@
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,13 +540,33 @@
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);
result.set_uid(main_thread.uid);
result.set_selinux_label(main_thread.selinux_label);
- result.set_process_name(main_thread.process_name);
+ auto cmd_line = result.mutable_command_line();
+ for (const auto& arg : main_thread.command_line) {
+ *cmd_line->Add() = arg;
+ }
+
if (!main_thread.siginfo) {
async_safe_fatal("siginfo missing");
}
@@ -458,7 +585,7 @@
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 +600,7 @@
}
}
- 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 187379d..020b0a5 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -26,6 +26,7 @@
#include <vector>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
@@ -71,9 +72,17 @@
static void print_thread_header(CallbackType callback, const Tombstone& tombstone,
const Thread& thread, bool should_log) {
+ const char* process_name = "<unknown>";
+ if (!tombstone.command_line().empty()) {
+ process_name = tombstone.command_line()[0].c_str();
+ CB(should_log, "Cmdline: %s", android::base::Join(tombstone.command_line(), " ").c_str());
+ }
CB(should_log, "pid: %d, tid: %d, name: %s >>> %s <<<", tombstone.pid(), thread.id(),
- thread.name().c_str(), tombstone.process_name().c_str());
+ thread.name().c_str(), process_name);
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 +145,11 @@
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 +167,32 @@
}
}
+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 +255,8 @@
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 +265,36 @@
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 +366,7 @@
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 6f13ed4..2c645b5 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -125,8 +125,9 @@
#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 @@
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 @@
// 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 @@
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 @@
// 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));
+ 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 += '.';
- }
+ // 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 2c7156b..294e4e5 100644
--- a/debuggerd/proto/tombstone.proto
+++ b/debuggerd/proto/tombstone.proto
@@ -17,18 +17,21 @@
uint32 uid = 7;
string selinux_label = 8;
- string process_name = 9;
+ repeated string command_line = 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 @@
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 @@
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 @@
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/util.cpp b/debuggerd/util.cpp
index f3bff8c..ce0fd30 100644
--- a/debuggerd/util.cpp
+++ b/debuggerd/util.cpp
@@ -26,10 +26,31 @@
#include <android-base/strings.h>
#include "protocol.h"
+std::vector<std::string> get_command_line(pid_t pid) {
+ std::vector<std::string> result;
+
+ std::string cmdline;
+ android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &cmdline);
+
+ auto it = cmdline.cbegin();
+ while (it != cmdline.cend()) {
+ // string::iterator is a wrapped type, not a raw char*.
+ auto terminator = std::find(it, cmdline.cend(), '\0');
+ result.emplace_back(it, terminator);
+ it = std::find_if(terminator, cmdline.cend(), [](char c) { return c != '\0'; });
+ }
+ if (result.empty()) {
+ result.emplace_back("<unknown>");
+ }
+
+ return result;
+}
+
std::string get_process_name(pid_t pid) {
std::string result = "<unknown>";
android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/cmdline", pid), &result);
- return result;
+ // We only want the name, not the whole command line, so truncate at the first NUL.
+ return result.c_str();
}
std::string get_thread_name(pid_t tid) {
diff --git a/debuggerd/util.h b/debuggerd/util.h
index 07e7e99..ec2862a 100644
--- a/debuggerd/util.h
+++ b/debuggerd/util.h
@@ -17,10 +17,12 @@
#pragma once
#include <string>
+#include <vector>
#include <sys/cdefs.h>
#include <sys/types.h>
+std::vector<std::string> get_command_line(pid_t pid);
std::string get_process_name(pid_t pid);
std::string get_thread_name(pid_t tid);
diff --git a/fastboot/Android.bp b/fastboot/Android.bp
index a1f1c17..43b2ddd 100644
--- a/fastboot/Android.bp
+++ b/fastboot/Android.bp
@@ -55,6 +55,7 @@
"tcp.cpp",
"udp.cpp",
"util.cpp",
+ "vendor_boot_img_utils.cpp",
"fastboot_driver.cpp",
],
@@ -75,7 +76,9 @@
],
header_libs: [
+ "avb_headers",
"bootimg_headers",
+ "libstorage_literals_headers",
],
export_header_lib_headers: [
@@ -124,6 +127,7 @@
"-Wextra",
"-Werror",
"-Wvla",
+ "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
],
rtti: true,
@@ -138,6 +142,12 @@
recovery: true,
+ product_variables: {
+ debuggable: {
+ cppflags: ["-DFB_ENABLE_FETCH"],
+ },
+ },
+
srcs: [
"device/commands.cpp",
"device/fastboot_device.cpp",
@@ -183,7 +193,8 @@
header_libs: [
"avb_headers",
"libsnapshot_headers",
- ]
+ "libstorage_literals_headers",
+ ],
}
cc_defaults {
@@ -196,6 +207,8 @@
"-Wextra",
"-Werror",
"-Wunreachable-code",
+ "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+ "-D_FILE_OFFSET_BITS=64"
],
target: {
@@ -262,12 +275,17 @@
"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 {
+ 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 2c0989e..d2056aa 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 @@
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 @@
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 b7cf9bd..0eb003d 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 ba43ca5..4ea68da 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 b2b6a9e..0a72812 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::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 @@
{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 @@
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 @@
}
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 c1324bc..345ae1a 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 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 35f3de0..64a934d 100644
--- a/fastboot/device/fastboot_device.cpp
+++ b/fastboot/device/fastboot_device.cpp
@@ -61,6 +61,7 @@
{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::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 23be721..3536136 100644
--- a/fastboot/device/fastboot_device.h
+++ b/fastboot/device/fastboot_device.h
@@ -40,6 +40,7 @@
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 4bee7b2..4115a6d 100644
--- a/fastboot/device/usb.cpp
+++ b/fastboot/device/usb.cpp
@@ -82,7 +82,7 @@
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 @@
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 7c6ac89..07ad902 100644
--- a/fastboot/device/utility.cpp
+++ b/fastboot/device/utility.cpp
@@ -77,7 +77,8 @@
} // 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 @@
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 @@
}
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 3b71ef0..c2646d7 100644
--- a/fastboot/device/utility.h
+++ b/fastboot/device/utility.h
@@ -75,7 +75,11 @@
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 e7d8bc3..ee1eed8 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 @@
*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 c11e472..f40a025 100644
--- a/fastboot/device/variables.h
+++ b/fastboot/device/variables.h
@@ -80,6 +80,8 @@
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 38be934..700d4bd 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 @@
enum fb_buffer_type type;
void* data;
int64_t sz;
- int fd;
+ unique_fd fd;
int64_t image_size;
};
@@ -227,9 +230,9 @@
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 @@
" 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 @@
" --version Display version.\n"
" --help, -h Show this message.\n"
);
- // clang-format off
+ // clang-format on
return 0;
}
@@ -519,10 +523,12 @@
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 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 @@
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 @@
// 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 @@
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 @@
} else {
buf->type = FB_BUFFER_FD;
buf->data = nullptr;
- buf->fd = fd;
buf->sz = sz;
}
@@ -933,7 +938,7 @@
}
struct stat s;
- if (fstat(fd, &s)) {
+ if (fstat(fd.get(), &s)) {
return false;
}
if (!S_ISREG(s.st_mode)) {
@@ -941,7 +946,7 @@
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 @@
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() {
@@ -1003,6 +1007,11 @@
fb->GetVar("partition-type:vbmeta_b", &partition_type) == fastboot::SUCCESS;
}
+static bool is_logical(const std::string& partition) {
+ std::string value;
+ return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
+}
+
static std::string fb_fix_numeric_var(std::string var) {
// Some bootloaders (angler, for example), send spurious leading whitespace.
var = android::base::Trim(var);
@@ -1012,27 +1021,30 @@
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");
+ if (!is_logical(partition)) {
+ return 0;
+ }
+ 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)) {
+ if (!is_logical(partition)) {
+ return 0;
+ }
die("Couldn't parse partition size '%s'.", partition_size_str.c_str());
}
- if (partition_size == buf->sz) {
+ 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 (partition_size < buf->sz) {
- die("boot partition is smaller than boot image");
- }
std::string data;
if (!android::base::ReadFdToString(buf->fd, &data)) {
@@ -1043,19 +1055,27 @@
if (0 != data.compare(footer_offset, AVB_FOOTER_MAGIC_LEN, AVB_FOOTER_MAGIC)) {
return;
}
+ // If overflows and negative, it should be < buf->sz.
+ int64_t partition_size = static_cast<int64_t>(get_partition_size(partition));
- int fd = make_temporary_fd("boot rewriting");
+ if (partition_size == buf->sz) {
+ return;
+ }
+ if (partition_size < buf->sz) {
+ die("boot partition is smaller than boot image");
+ }
+
+ 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 +1205,10 @@
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 +1218,15 @@
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 +1240,13 @@
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++) {
@@ -1234,11 +1260,6 @@
}
}
-static bool is_logical(const std::string& partition) {
- std::string value;
- return fb->GetVar("is-logical:" + partition, &value) == fastboot::SUCCESS && value == "yes";
-}
-
static bool is_retrofit_device() {
std::string value;
if (fb->GetVar("super-partition-name", &value) != fastboot::SUCCESS) {
@@ -1247,7 +1268,74 @@
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 +1344,8 @@
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 +1398,9 @@
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 +1518,8 @@
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 +1546,7 @@
}
void FlashAllTool::UpdateSuperPartition() {
- int fd = source_.OpenFile("super_empty.img");
+ unique_fd fd = source_.OpenFile("super_empty.img");
if (fd < 0) {
return;
}
@@ -1494,7 +1584,7 @@
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 +1594,7 @@
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 +1614,7 @@
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 +1625,9 @@
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 +1746,7 @@
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 +2205,7 @@
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 +2259,10 @@
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 8d534ea..99a4873 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 @@
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 @@
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 @@
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 @@
}
/******************************* 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 @@
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 7265632..bccd668 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 @@
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 @@
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 @@
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 34ab32c..b6beaf9 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 @@
EXPECT_TRUE(var == "yes" || var == "no") << "getvar:battery-soc-ok must be 'yes' or 'no'";
}
+void AssertHexUint32(const std::string& name, const std::string& var) {
+ ASSERT_NE(var, "") << "getvar:" << name << " responded with empty string";
+ // This must 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);
+ ASSERT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:" << name;
+ // At most 32-bits
+ 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";
- EXPECT_NE(var, "") << "getvar:max-download-size 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...";
- int64_t size = strtoll(var.c_str(), nullptr, 16);
- EXPECT_GT(size, 0) << "'" + var + "' is not a valid response from getvar:max-download-size";
- // 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;
+ 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 @@
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 @@
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 0000000..a490fe2
--- /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 0000000..a87467b
--- /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 0000000..9e09abb
--- /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 0000000..0b702bc
--- /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 0000000..1563b89
--- /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/libfiemap/include/libfiemap/image_manager.h b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
index 50f4f33..7e30509 100644
--- a/fs_mgr/libfiemap/include/libfiemap/image_manager.h
+++ b/fs_mgr/libfiemap/include/libfiemap/image_manager.h
@@ -198,7 +198,7 @@
~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 b0f36d4..0000000
--- 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/cow_reader.cpp b/fs_mgr/libsnapshot/cow_reader.cpp
index 44a423c..7199b38 100644
--- a/fs_mgr/libsnapshot/cow_reader.cpp
+++ b/fs_mgr/libsnapshot/cow_reader.cpp
@@ -250,6 +250,8 @@
}
ops_ = ops_buffer;
+ ops_->shrink_to_fit();
+
return true;
}
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index bd1e284..2c5bf75 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -1619,6 +1619,18 @@
// as dm-snapshot (for example, after merge completes).
bool should_unmap = current_slot != Slot::Target;
bool should_delete = ShouldDeleteSnapshot(flashing_status, current_slot, name);
+ if (should_unmap && android::base::EndsWith(name, device_->GetSlotSuffix())) {
+ // Something very unexpected has happened - we want to unmap this
+ // snapshot, but it's on the wrong slot. We can't unmap an active
+ // partition. If this is not really a snapshot, skip the unmap
+ // step.
+ auto& dm = DeviceMapper::Instance();
+ if (dm.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
+ LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
+ << " for source partition; removing without unmap.";
+ should_unmap = false;
+ }
+ }
bool partition_ok = true;
if (should_unmap && !UnmapPartitionWithSnapshot(lock, name)) {
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 25500b5..6ed0129 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -2021,6 +2021,34 @@
ASSERT_TRUE(IsPartitionUnchanged("sys_b"));
}
+TEST_F(SnapshotUpdateTest, CancelOnTargetSlot) {
+ AddOperationForPartitions();
+
+ // Execute the update from B->A.
+ test_device->set_slot_suffix("_b");
+ ASSERT_TRUE(sm->BeginUpdate());
+ ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+ std::string path;
+ ASSERT_TRUE(CreateLogicalPartition(
+ CreateLogicalPartitionParams{
+ .block_device = fake_super,
+ .metadata_slot = 0,
+ .partition_name = "sys_a",
+ .timeout_ms = 1s,
+ .partition_opener = opener_.get(),
+ },
+ &path));
+
+ // Hold sys_a open so it can't be unmapped.
+ unique_fd fd(open(path.c_str(), O_RDONLY));
+
+ // Switch back to "A", make sure we can cancel. Instead of unmapping sys_a
+ // we should simply delete the old snapshots.
+ test_device->set_slot_suffix("_a");
+ ASSERT_TRUE(sm->BeginUpdate());
+}
+
class FlashAfterUpdateTest : public SnapshotUpdateTest,
public WithParamInterface<std::tuple<uint32_t, bool>> {
public:
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index a44de84..5eb2003 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -48,6 +48,17 @@
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 @@
// 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 f1fcb70..5ef9e29 100644
--- a/fs_mgr/libsnapshot/snapuserd.cpp
+++ b/fs_mgr/libsnapshot/snapuserd.cpp
@@ -230,7 +230,7 @@
// 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();
@@ -422,7 +422,7 @@
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++;
@@ -468,6 +468,12 @@
<< "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
diff --git a/fs_mgr/libsnapshot/snapuserd.h b/fs_mgr/libsnapshot/snapuserd.h
index 6ce64d8..9335364 100644
--- a/fs_mgr/libsnapshot/snapuserd.h
+++ b/fs_mgr/libsnapshot/snapuserd.h
@@ -108,7 +108,7 @@
bool ProcessIORequest();
int ReadData(sector_t sector, size_t size);
int ReadUnalignedSector(sector_t sector, size_t size,
- std::map<sector_t, const CowOperation*>::iterator& it);
+ std::vector<std::pair<sector_t, const CowOperation*>>::iterator& it);
// Processing COW operations
bool ProcessCowOp(const CowOperation* cow_op);
@@ -164,16 +164,21 @@
bool InitializeWorkers();
std::shared_ptr<Snapuserd> GetSharedPtr() { return shared_from_this(); }
- std::map<sector_t, const CowOperation*>& GetChunkMap() { return chunk_map_; }
+ 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 ReadMetadata();
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); }
@@ -197,15 +202,9 @@
// 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_;
+ // chunk_vec stores the pseudo mapping of sector
+ // to COW operations.
+ std::vector<std::pair<sector_t, const CowOperation*>> chunk_vec_;
std::mutex lock_;
diff --git a/fs_mgr/libsnapshot/snapuserd_worker.cpp b/fs_mgr/libsnapshot/snapuserd_worker.cpp
index 16f47fe..1002569 100644
--- a/fs_mgr/libsnapshot/snapuserd_worker.cpp
+++ b/fs_mgr/libsnapshot/snapuserd_worker.cpp
@@ -185,12 +185,13 @@
return false;
}
-int WorkerThread::ReadUnalignedSector(sector_t sector, size_t size,
- std::map<sector_t, const CowOperation*>::iterator& it) {
+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->second;
+ << " Aligned sector: " << it->first;
if (!ProcessCowOp(it->second)) {
SNAP_LOG(ERROR) << "ReadUnalignedSector: " << sector << " failed of size: " << size;
@@ -223,7 +224,8 @@
*
*/
int WorkerThread::ReadData(sector_t sector, size_t size) {
- std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ 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
@@ -234,10 +236,16 @@
* 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 = 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;
}
@@ -380,7 +388,7 @@
int WorkerThread::GetNumberOfMergedOps(void* merged_buffer, void* unmerged_buffer, loff_t offset,
int unmerged_exceptions) {
int merged_ops_cur_iter = 0;
- std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ 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_) {
@@ -395,7 +403,13 @@
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)];
+ 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);
@@ -510,14 +524,18 @@
return true;
}
- std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ 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);
- CHECK(chunk_map.find(header->sector) == chunk_map.end());
+ 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);
@@ -550,7 +568,7 @@
size_t remaining_size = header->len;
loff_t offset = 0;
sector_t sector = header->sector;
- std::map<sector_t, const CowOperation*>& chunk_map = snapuserd_->GetChunkMap();
+ std::vector<std::pair<sector_t, const CowOperation*>>& chunk_vec = snapuserd_->GetChunkVec();
do {
size_t read_size = std::min(PAYLOAD_SIZE, remaining_size);
@@ -568,8 +586,10 @@
ConstructKernelCowHeader();
SNAP_LOG(DEBUG) << "Kernel header constructed";
} else {
- if (!offset && (read_size == BLOCK_SZ) &&
- chunk_map.find(header->sector) == chunk_map.end()) {
+ 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;
diff --git a/fs_mgr/libstorage_literals/Android.bp b/fs_mgr/libstorage_literals/Android.bp
index 635ca49..5b07168 100644
--- a/fs_mgr/libstorage_literals/Android.bp
+++ b/fs_mgr/libstorage_literals/Android.bp
@@ -8,4 +8,9 @@
host_supported: true,
recovery_available: true,
export_include_dirs: ["."],
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
}
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 49e8085..95e814b 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -40,8 +40,6 @@
"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 f9c0cdd..8792c83 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 @@
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 @@
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/property_service.cpp b/init/property_service.cpp
index 382f430..73ef97a 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -1165,14 +1165,40 @@
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() {
ImportKernelCmdline([&](const std::string& key, const std::string& 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."sv)) {
- InitPropertySet("ro.kernel." + key, value);
- } else if (key == "qemu") {
+ } 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);
}
});
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index 0d9f2c7..0f3763c 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -354,3 +354,17 @@
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 d69c038..e9497a8 100644
--- a/libcutils/fs_config.cpp
+++ b/libcutils/fs_config.cpp
@@ -86,7 +86,7 @@
{ 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/include/private/android_filesystem_config.h b/libcutils/include/private/android_filesystem_config.h
index b4fe2e6..7489281 100644
--- a/libcutils/include/private/android_filesystem_config.h
+++ b/libcutils/include/private/android_filesystem_config.h
@@ -127,6 +127,7 @@
#define AID_EXT_DATA_RW 1078 /* GID for app-private data directories on external storage */
#define AID_EXT_OBB_RW 1079 /* GID for OBB directories on external storage */
#define AID_CONTEXT_HUB 1080 /* GID for access to the Context Hub */
+#define AID_VIRTMANAGER 1081 /* VirtManager daemon */
/* Changes to this file must be made in AOSP, *not* in internal branches. */
#define AID_SHELL 2000 /* adb and debug shell user */
diff --git a/libcutils/rust/cutils.h b/libcutils/rust/cutils.h
new file mode 100644
index 0000000..9b78af6
--- /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/libmodprobe/OWNERS b/libmodprobe/OWNERS
index e6b5bba..a6796cb 100644
--- a/libmodprobe/OWNERS
+++ b/libmodprobe/OWNERS
@@ -1 +1,2 @@
-smuckle@google.com
+dvander@google.com
+willmcvicker@google.com
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 11b3da7..9a30ead 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -821,6 +821,9 @@
mkdir /data/ss 0700 system system encryption=Require
mkdir /data/system 0775 system system encryption=Require
+ mkdir /data/system/environ 0700 system system
+ # b/183861600 attempt to fix selinux label before running derive_classpath service
+ restorecon /data/system/environ
mkdir /data/system/dropbox 0700 system system
mkdir /data/system/heapdump 0700 system system
mkdir /data/system/users 0775 system system
@@ -884,10 +887,6 @@
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
-
# Special-case /data/media/obb per b/64566063
mkdir /data/media 0770 media_rw media_rw encryption=None
exec - media_rw media_rw -- /system/bin/chattr +F /data/media
@@ -900,9 +899,20 @@
# Set SELinux security contexts on upgrade or policy update.
restorecon --recursive --skip-ce /data
+ # Define and export *CLASSPATH variables
+ # 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
+
# 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
@@ -944,6 +954,7 @@
# 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
@@ -952,6 +963,7 @@
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
@@ -960,6 +972,7 @@
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
@@ -1134,6 +1147,11 @@
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/rootdir/init.usb.rc b/rootdir/init.usb.rc
index 27b05ec..0730cce 100644
--- a/rootdir/init.usb.rc
+++ b/rootdir/init.usb.rc
@@ -19,6 +19,9 @@
updatable
seclabel u:r:adbd:s0
+on property:vendor.sys.usb.adb.disabled=*
+ setprop sys.usb.adb.disabled ${vendor.sys.usb.adb.disabled}
+
# Set default value on sys.usb.configfs early in boot sequence. It will be
# overridden in `on boot` action of init.hardware.rc.
on init
diff --git a/trusty/libtrusty/tipc-test/tipc_test.c b/trusty/libtrusty/tipc-test/tipc_test.c
index 94aedd7..29c6f93 100644
--- a/trusty/libtrusty/tipc-test/tipc_test.c
+++ b/trusty/libtrusty/tipc-test/tipc_test.c
@@ -914,7 +914,7 @@
}
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);