[vm/service]: Add _readNativeMemory RPC and OS::SafeReadMemory for safe native memory inspection
- Declare OS::SafeReadMemory in os.h - Implement using pread64(/proc/self/mem) on Linux and Android - Add _readNativeMemory VM Service RPC with pre-checks for null and address overflow TEST=runtime/vm/service_test.cc Cq-Include-Trybots: dart/try:vm-asan-linux-release-x64-try,vm-asan-mac-release-arm64-try,vm-asan-win-release-x64-try,vm-dyn-linux-debug-x64-try,vm-dyn-mac-debug-arm64-try,vm-ffi-qemu-linux-release-arm-try,vm-ffi-qemu-linux-release-riscv64-try,vm-linux-debug-arm64-try,vm-linux-debug-ia32-try,vm-linux-debug-simriscv32-try,vm-linux-debug-simriscv64-try,vm-linux-debug-x64-try,vm-linux-debug-x64c-try,vm-linux-release-simarm-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-tsan-linux-release-x64-try,vm-tsan-mac-release-arm64-try,vm-ubsan-linux-release-x64-try,vm-ubsan-mac-release-arm64-try,vm-ubsan-win-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try Change-Id: Id15a82bf478bc4822c08d7fdf0a5c8bfd71a1fe0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505781 Auto-Submit: Nourhan Hasan <nourhan.m.hasan@gmail.com> Reviewed-by: Ben Konyi <bkonyi@google.com> Reviewed-by: Daco Harkes <dacoharkes@google.com>
This commit is contained in:
committed by
Daco Harkes
parent
1b3e9d510e
commit
80f770eb57
@@ -232,10 +232,18 @@ dart/data_uri_spawn_test: Skip # TODO(zra): package:unittest is not in the image
|
||||
dart/spawn_shutdown_test: Skip # OOM crash can bring down the OS.
|
||||
|
||||
[ $system == macos ]
|
||||
cc/Service_ReadNativeMemory_InvalidAddress: Skip
|
||||
cc/Service_ReadNativeMemory_LargeRead: Skip
|
||||
cc/Service_ReadNativeMemory_NullAddress: Skip
|
||||
cc/Service_ReadNativeMemory_ValidAddress: Skip
|
||||
dart/transferable_throws_oom_test: SkipByDesign # Allocating too much memory to cause OOM doesn't work on mac
|
||||
|
||||
[ $system == windows ]
|
||||
cc/CorelibCompilerStats: Skip
|
||||
cc/Service_ReadNativeMemory_InvalidAddress: Skip
|
||||
cc/Service_ReadNativeMemory_LargeRead: Skip
|
||||
cc/Service_ReadNativeMemory_NullAddress: Skip
|
||||
cc/Service_ReadNativeMemory_ValidAddress: Skip
|
||||
dart/disassemble_determinism_test: Slow, Pass # Times out on slower bots.
|
||||
|
||||
[ $system != windows ]
|
||||
|
||||
@@ -69,6 +69,7 @@ enum JSONRpcErrorCode {
|
||||
kFileSystemAlreadyExists = 1001,
|
||||
kFileSystemDoesNotExist = 1002,
|
||||
kFileDoesNotExist = 1003,
|
||||
kNativeMemoryReadError = 1004,
|
||||
};
|
||||
|
||||
// Builds on JSONWriter to provide support for serializing various objects
|
||||
|
||||
@@ -69,6 +69,13 @@ class OS {
|
||||
// determined.
|
||||
static uintptr_t CurrentRSS();
|
||||
|
||||
// Safely reads size bytes from native address into buffer.
|
||||
// Returns true on success, false if the address is invalid.
|
||||
static bool SafeReadMemory(uintptr_t address,
|
||||
uint8_t* buffer,
|
||||
intptr_t size_in_bytes,
|
||||
const char** error);
|
||||
|
||||
// Sleep the currently executing thread for millis ms.
|
||||
static void Sleep(int64_t millis);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <dlfcn.h> // NOLINT
|
||||
#include <elf.h> // NOLINT
|
||||
#include <errno.h> // NOLINT
|
||||
#include <fcntl.h> // NOLINT
|
||||
#include <limits.h> // NOLINT
|
||||
#include <malloc.h> // NOLINT
|
||||
#include <sys/resource.h> // NOLINT
|
||||
@@ -220,6 +221,25 @@ uintptr_t OS::CurrentRSS() {
|
||||
return current_rss_pages * getpagesize();
|
||||
}
|
||||
|
||||
bool OS::SafeReadMemory(uintptr_t address,
|
||||
uint8_t* buffer,
|
||||
intptr_t size_in_bytes,
|
||||
const char** error) {
|
||||
int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
*error = strerror(errno);
|
||||
return false;
|
||||
}
|
||||
ssize_t bytes_read =
|
||||
pread64(fd, buffer, size_in_bytes, static_cast<off64_t>(address));
|
||||
close(fd);
|
||||
if (bytes_read == -1) {
|
||||
*error = strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return bytes_read == static_cast<ssize_t>(size_in_bytes);
|
||||
}
|
||||
|
||||
void OS::Sleep(int64_t millis) {
|
||||
int64_t micros = millis * kMicrosecondsPerMillisecond;
|
||||
SleepMicros(micros);
|
||||
|
||||
@@ -556,6 +556,25 @@ uintptr_t OS::CurrentRSS() {
|
||||
return current_rss_pages * getpagesize();
|
||||
}
|
||||
|
||||
bool OS::SafeReadMemory(uintptr_t address,
|
||||
uint8_t* buffer,
|
||||
intptr_t size_in_bytes,
|
||||
const char** error) {
|
||||
int fd = open("/proc/self/mem", O_RDONLY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
*error = strerror(errno);
|
||||
return false;
|
||||
}
|
||||
ssize_t bytes_read =
|
||||
pread64(fd, buffer, size_in_bytes, static_cast<off64_t>(address));
|
||||
close(fd);
|
||||
if (bytes_read == -1) {
|
||||
*error = strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return bytes_read == static_cast<ssize_t>(size_in_bytes);
|
||||
}
|
||||
|
||||
void OS::Sleep(int64_t millis) {
|
||||
int64_t micros = millis * kMicrosecondsPerMillisecond;
|
||||
SleepMicros(micros);
|
||||
|
||||
@@ -192,6 +192,8 @@ class NoSuchParameter : public MethodParameter {
|
||||
#define NO_ISOLATE_PARAMETER new NoSuchParameter("isolateId")
|
||||
#define RUNNABLE_ISOLATE_PARAMETER new RunnableIsolateParameter("isolateId")
|
||||
#define OBJECT_PARAMETER new IdParameter("objectId", true)
|
||||
#define NATIVE_MEMORY_ADDRESS_PARAMETER new StringParameter("address", true)
|
||||
#define NATIVE_MEMORY_SIZE_PARAMETER new UIntParameter("size", true)
|
||||
|
||||
static bool ValidateUIntParameter(const char* value) {
|
||||
if (value == nullptr) {
|
||||
@@ -6235,6 +6237,124 @@ static void GetDefaultClassesAliases(Thread* thread, JSONStream* js) {
|
||||
#undef DEFINE_ADD_VALUE_F
|
||||
}
|
||||
|
||||
static constexpr const char* kNativeMemoryAddressParam = "address";
|
||||
static constexpr const char* kNativeMemorySizeParam = "size";
|
||||
static constexpr const char* kNativeMemoryBytesKey = "bytes";
|
||||
static constexpr const char* kNativeMemoryTypeKey = "type";
|
||||
|
||||
static void ReadNativeMemoryHelper(JSONStream* js,
|
||||
uintptr_t address,
|
||||
intptr_t size) {
|
||||
if (address == 0) {
|
||||
js->PrintError(kInvalidParams, "null pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (address > (UINTPTR_MAX - static_cast<uintptr_t>(size))) {
|
||||
js->PrintError(kInvalidParams, "address + size overflows address space");
|
||||
return;
|
||||
}
|
||||
|
||||
CAllocUniquePtr<uint8_t> buffer(reinterpret_cast<uint8_t*>(malloc(size)));
|
||||
|
||||
if (buffer.get() == nullptr) {
|
||||
js->PrintError(kInternalError, "failed to allocate buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* read_error = nullptr;
|
||||
|
||||
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
|
||||
bool ok = OS::SafeReadMemory(address, buffer.get(), size, &read_error);
|
||||
#elif defined(DART_HOST_OS_MACOS) || defined(DART_HOST_OS_IOS)
|
||||
bool ok = false; // TODO(thenourhan): implement using mach_vm_read
|
||||
#elif defined(DART_HOST_OS_WINDOWS)
|
||||
bool ok = false; // TODO(thenourhan): implement using ReadProcessMemory
|
||||
#else
|
||||
bool ok = false; // TODO(thenourhan): implement for other platforms
|
||||
#endif
|
||||
|
||||
if (!ok) {
|
||||
const char* error_msg =
|
||||
(read_error != nullptr) ? read_error : "address not readable";
|
||||
js->PrintError(kNativeMemoryReadError, "%s", error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
CStringUniquePtr hex(reinterpret_cast<char*>(malloc(size * 2 + 1)));
|
||||
for (intptr_t i = 0; i < size; i++) {
|
||||
Utils::SNPrint(hex.get() + i * 2, 3, "%02x", buffer.get()[i]);
|
||||
}
|
||||
hex.get()[size * 2] = '\0';
|
||||
|
||||
JSONObject response(js);
|
||||
response.AddProperty(kNativeMemoryTypeKey, "NativeMemory");
|
||||
response.AddPropertyF(kNativeMemoryAddressParam, "0x%" Px "", address);
|
||||
response.AddProperty64(kNativeMemorySizeParam, size);
|
||||
response.AddProperty(kNativeMemoryBytesKey, hex.get());
|
||||
}
|
||||
|
||||
static const MethodParameter* const read_native_memory_params[] = {
|
||||
NATIVE_MEMORY_ADDRESS_PARAMETER,
|
||||
NATIVE_MEMORY_SIZE_PARAMETER,
|
||||
nullptr,
|
||||
};
|
||||
|
||||
// Parameters:
|
||||
// address : string
|
||||
// Hex string without '0x' prefix (e.g. "7f3a00001000").
|
||||
//
|
||||
// size : int
|
||||
// Number of bytes to read. Must be between 1 and 1048576 (1 MB).
|
||||
//
|
||||
// Responses:
|
||||
//
|
||||
// On success:
|
||||
// {
|
||||
// "type": "NativeMemory",
|
||||
// "address": "0x7f3a00001000",
|
||||
// "size": 8,
|
||||
// "bytes": "0102030405060708"
|
||||
// }
|
||||
//
|
||||
// On read failure (unmapped/invalid address):
|
||||
// JSON-RPC error code 1004 with OS error string as details
|
||||
// "Input/output error" (Linux EIO)
|
||||
// "ReadProcessMemory failed (error 299)" (Windows)
|
||||
// "mach_vm_read_overwrite failed" (macOS)
|
||||
//
|
||||
// On invalid params (null pointer, address overflow):
|
||||
// JSON-RPC error code -32602 "Invalid params"
|
||||
//
|
||||
static void ReadNativeMemory(Thread* thread, JSONStream* js) {
|
||||
const char* address_str = js->LookupParam(kNativeMemoryAddressParam);
|
||||
const char* size_str = js->LookupParam(kNativeMemorySizeParam);
|
||||
|
||||
if (address_str == nullptr) {
|
||||
PrintMissingParamError(js, kNativeMemoryAddressParam);
|
||||
return;
|
||||
}
|
||||
if (size_str == nullptr) {
|
||||
PrintMissingParamError(js, kNativeMemorySizeParam);
|
||||
return;
|
||||
}
|
||||
|
||||
uintptr_t address = 0;
|
||||
intptr_t size = 0;
|
||||
|
||||
if (!GetUnsignedIntegerId(address_str, &address, 16)) {
|
||||
PrintInvalidParamError(js, kNativeMemoryAddressParam);
|
||||
return;
|
||||
}
|
||||
if (!GetIntegerId(size_str, &size) || size <= 0 || size > 1 * MB) {
|
||||
PrintInvalidParamError(js, kNativeMemorySizeParam);
|
||||
return;
|
||||
}
|
||||
|
||||
ReadNativeMemoryHelper(js, static_cast<uintptr_t>(address),
|
||||
static_cast<intptr_t>(size));
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
static const ServiceMethodDescriptor service_methods_[] = {
|
||||
{ "_echo", Echo,
|
||||
@@ -6402,6 +6522,7 @@ static const ServiceMethodDescriptor service_methods_[] = {
|
||||
collect_all_garbage_params },
|
||||
{ "_getDefaultClassesAliases", GetDefaultClassesAliases,
|
||||
get_default_classes_aliases_params },
|
||||
{ "_readNativeMemory", ReadNativeMemory, read_native_memory_params },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -692,6 +692,179 @@ ISOLATE_UNIT_TEST_CASE(Service_EmbedderIsolateHandler) {
|
||||
handler.msg());
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(Service_ReadNativeMemory_ValidAddress) {
|
||||
const char* kScript =
|
||||
"@pragma('vm:entry-point', 'set')\n"
|
||||
"var port;\n"
|
||||
"main() {}\n";
|
||||
|
||||
Isolate* isolate = thread->isolate();
|
||||
isolate->set_is_runnable(true);
|
||||
Dart_Handle lib;
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
EXPECT_VALID(result);
|
||||
}
|
||||
|
||||
uint8_t buffer[8] = {0x01, 0xAB, 0x0F, 0xFF, 0xDE, 0xAD, 0xBE, 0xEF};
|
||||
uintptr_t address = reinterpret_cast<uintptr_t>(buffer);
|
||||
|
||||
ServiceTestMessageHandler handler;
|
||||
Dart_Port port_id = PortMap::CreatePort(&handler);
|
||||
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
EXPECT_VALID(port);
|
||||
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
||||
}
|
||||
|
||||
Array& service_msg = Array::Handle();
|
||||
|
||||
// send _readNativeMemory RPC with valid address
|
||||
service_msg = EvalF(lib,
|
||||
"[0, port, '0', '_readNativeMemory', false, "
|
||||
"['address', 'size'], ['%" Px "', '8']]",
|
||||
address);
|
||||
HandleIsolateMessage(isolate, service_msg);
|
||||
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
||||
|
||||
EXPECT_SUBSTRING("\"type\":\"NativeMemory\"", handler.msg());
|
||||
EXPECT_SUBSTRING("\"bytes\":\"01ab0fffdeadbeef\"", handler.msg());
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(Service_ReadNativeMemory_LargeRead) {
|
||||
const char* kScript =
|
||||
"@pragma('vm:entry-point', 'set')\n"
|
||||
"var port;\n"
|
||||
"main() {}\n";
|
||||
|
||||
Isolate* isolate = thread->isolate();
|
||||
isolate->set_is_runnable(true);
|
||||
Dart_Handle lib;
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
EXPECT_VALID(result);
|
||||
}
|
||||
|
||||
ServiceTestMessageHandler handler;
|
||||
Dart_Port port_id = PortMap::CreatePort(&handler);
|
||||
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
EXPECT_VALID(port);
|
||||
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
||||
}
|
||||
|
||||
Array& service_msg = Array::Handle();
|
||||
|
||||
// allocate 1MB
|
||||
const intptr_t kOneMB = 1 * MB;
|
||||
CAllocUniquePtr<uint8_t> large_buffer(
|
||||
reinterpret_cast<uint8_t*>(malloc(kOneMB)));
|
||||
for (intptr_t i = 0; i < kOneMB; i++) {
|
||||
large_buffer.get()[i] = static_cast<uint8_t>(i % 256);
|
||||
}
|
||||
|
||||
uintptr_t address = reinterpret_cast<uintptr_t>(large_buffer.get());
|
||||
|
||||
service_msg = EvalF(lib,
|
||||
"[0, port, '0', '_readNativeMemory', false, "
|
||||
"['address', 'size'], ['%" Px "', '%" Pd "']]",
|
||||
address, kOneMB);
|
||||
HandleIsolateMessage(isolate, service_msg);
|
||||
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
||||
|
||||
EXPECT_SUBSTRING("\"type\":\"NativeMemory\"", handler.msg());
|
||||
EXPECT_SUBSTRING("\"bytes\":", handler.msg());
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(Service_ReadNativeMemory_InvalidAddress) {
|
||||
const char* kScript =
|
||||
"@pragma('vm:entry-point', 'set')\n"
|
||||
"var port;\n"
|
||||
"main() {}\n";
|
||||
|
||||
Isolate* isolate = thread->isolate();
|
||||
isolate->set_is_runnable(true);
|
||||
Dart_Handle lib;
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
EXPECT_VALID(result);
|
||||
}
|
||||
|
||||
ServiceTestMessageHandler handler;
|
||||
Dart_Port port_id = PortMap::CreatePort(&handler);
|
||||
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
EXPECT_VALID(port);
|
||||
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
||||
}
|
||||
|
||||
Array& service_msg = Array::Handle();
|
||||
|
||||
service_msg = EvalF(lib,
|
||||
"[0, port, '0', '_readNativeMemory', false, "
|
||||
"['address', 'size'], ['1000', '8']]");
|
||||
|
||||
HandleIsolateMessage(isolate, service_msg);
|
||||
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
||||
|
||||
EXPECT_SUBSTRING("\"code\":1004", handler.msg());
|
||||
#if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_ANDROID)
|
||||
EXPECT_SUBSTRING("Input\\/output error", handler.msg());
|
||||
#elif defined(DART_HOST_OS_WINDOWS)
|
||||
EXPECT_SUBSTRING("error 299", handler.msg());
|
||||
#endif
|
||||
}
|
||||
|
||||
ISOLATE_UNIT_TEST_CASE(Service_ReadNativeMemory_NullAddress) {
|
||||
const char* kScript =
|
||||
"@pragma('vm:entry-point', 'set')\n"
|
||||
"var port;\n"
|
||||
"main() {}\n";
|
||||
|
||||
Isolate* isolate = thread->isolate();
|
||||
isolate->set_is_runnable(true);
|
||||
Dart_Handle lib;
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
lib = TestCase::LoadTestScript(kScript, nullptr);
|
||||
EXPECT_VALID(lib);
|
||||
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
||||
EXPECT_VALID(result);
|
||||
}
|
||||
|
||||
ServiceTestMessageHandler handler;
|
||||
Dart_Port port_id = PortMap::CreatePort(&handler);
|
||||
Dart_Handle port = Api::NewHandle(thread, SendPort::New(port_id));
|
||||
{
|
||||
TransitionVMToNative transition(thread);
|
||||
EXPECT_VALID(port);
|
||||
EXPECT_VALID(Dart_SetField(lib, NewString("port"), port));
|
||||
}
|
||||
|
||||
Array& service_msg = Array::Handle();
|
||||
|
||||
service_msg = EvalF(lib,
|
||||
"[0, port, '0', '_readNativeMemory', false, "
|
||||
"['address', 'size'], ['0', '8']]");
|
||||
HandleIsolateMessage(isolate, service_msg);
|
||||
EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
|
||||
|
||||
EXPECT_SUBSTRING("\"error\"", handler.msg());
|
||||
EXPECT_SUBSTRING("null pointer", handler.msg());
|
||||
}
|
||||
|
||||
// TODO(zra): Remove when tests are ready to enable.
|
||||
#if !defined(TARGET_ARCH_ARM64)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user