crashpad/util/linux/process_memory.cc
Mark Mentovai a5d81370be linux: Use pread64() instead of pread() in ProcessMemory
This fixes ProcessMemory for 32-bit processes. All ProcessMemory tests
were failing on 32-bit ARM on Android like this:

[ RUN      ] ProcessMemory.ReadSelf
[17345:17345:20170407,172222.579687:ERROR process_memory.cc:55] pread: Invalid argument (22)
../../../../util/linux/process_memory_test.cc:73: Failure
Value of: memory.Read(address, region_size_, result.get())
  Actual: false
Expected: true
[  FAILED  ] ProcessMemory.ReadSelf (5 ms)

Contemporary Linux doesn’t provide a pread() system call, it provides
pread64(), which operates on off64_t. pread() is a user-space wrapper
that accepts off_t. See Android 7.1.1
bionic/libc/bionic/legacy_32_bit_support.cpp pread().

Note that off_t is a signed type. With a 32-bit off_t, when the
“offset” parameter to pread() has its high bit set, it will be
sign-extended into the 64-bit off64_t, and when interpreted as a memory
address by virtue of being used as an offset into /proc/pid/mem, the
value will take on an incorrect meaning. In fact, the kernel will reject
it outright for its negativity. See linux-4.9.20/fs/read_write.c
[sys_]pread64().

Since ProcessMemory accepts its address parameter as a LinuxVMAddress,
which is wisely a uint64_t, it converts to off64_t properly, retaining
its original value.

Note, however, that the pread64() mechanism evidently cannot read memory
in the high half of a process’ address space even when pread64() is used
throughout. Most importantly, the (pos < 0) check in the kernel will be
tripped. Less importantly, the conversion of our unsigned LinuxVMAddress
to pread64’s signed off64_t, with the high bit set, is not defined. This
is not an immediate practical problem. With the exception of possible
shared pages mapped from kernel space (I only see this for the vsyscall
page on x86_64), Linux restricts 64-bit user process’ address space to
at least the lower half of the addressable range, with the high bit
clear. (The limit of the user address space is
linux-4.9.20/arch/x86/include/asm/processor.h TASK_SIZE_MAX =
0x7ffffffff000 for x86_64 and
linux-4.9.20/arch/arm64/include/asm/memory.h TASK_SIZE_64 =
0x1000000000000 at maximum for arm64.)

The 32-bit off_t may be a surprise, because
third_party/mini_chromium/mini_chromium/build/common.gypi sets
_FILE_OFFSET_BITS=64. Altough this macro is considered in the NDK’s
“unified headers”, in the classic NDK, this macro is never consulted.
Instead, off_t is always “long”, and pread() always gets the
compatibility shim in Bionic.

Bug: crashpad:30
Change-Id: Id00c882a3d521a46ef3fc0060d03ea0ab9493175
Reviewed-on: https://chromium-review.googlesource.com/472048
Reviewed-by: Joshua Peraza <jperaza@chromium.org>
2017-04-08 02:41:15 +00:00

125 lines
3.4 KiB
C++

// Copyright 2017 The Crashpad Authors. All rights reserved.
//
// 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 "util/linux/process_memory.h"
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
namespace crashpad {
ProcessMemory::ProcessMemory() : mem_fd_(), pid_(-1) {}
ProcessMemory::~ProcessMemory() {}
bool ProcessMemory::Initialize(pid_t pid) {
pid_ = pid;
char path[32];
snprintf(path, sizeof(path), "/proc/%d/mem", pid_);
mem_fd_.reset(HANDLE_EINTR(open(path, O_RDONLY | O_NOCTTY | O_CLOEXEC)));
if (!mem_fd_.is_valid()) {
PLOG(ERROR) << "open";
return false;
}
return true;
}
bool ProcessMemory::Read(LinuxVMAddress address,
size_t size,
void* buffer) const {
DCHECK(mem_fd_.is_valid());
char* buffer_c = static_cast<char*>(buffer);
while (size > 0) {
ssize_t bytes_read =
HANDLE_EINTR(pread64(mem_fd_.get(), buffer_c, size, address));
if (bytes_read < 0) {
PLOG(ERROR) << "pread64";
return false;
}
if (bytes_read == 0) {
LOG(ERROR) << "unexpected eof";
return false;
}
DCHECK_LE(static_cast<size_t>(bytes_read), size);
size -= bytes_read;
address += bytes_read;
buffer_c += bytes_read;
}
return true;
}
bool ProcessMemory::ReadCString(LinuxVMAddress address,
std::string* string) const {
return ReadCStringInternal(address, false, 0, string);
}
bool ProcessMemory::ReadCStringSizeLimited(LinuxVMAddress address,
size_t size,
std::string* string) const {
return ReadCStringInternal(address, true, size, string);
}
bool ProcessMemory::ReadCStringInternal(LinuxVMAddress address,
bool has_size,
size_t size,
std::string* string) const {
DCHECK(mem_fd_.is_valid());
string->clear();
char buffer[4096];
do {
size_t read_size;
if (has_size) {
read_size = std::min(sizeof(buffer), size);
} else {
read_size = sizeof(buffer);
}
ssize_t bytes_read;
bytes_read =
HANDLE_EINTR(pread64(mem_fd_.get(), buffer, read_size, address));
if (bytes_read < 0) {
PLOG(ERROR) << "pread64";
return false;
}
if (bytes_read == 0) {
break;
}
DCHECK_LE(static_cast<size_t>(bytes_read), read_size);
char* nul = static_cast<char*>(memchr(buffer, '\0', bytes_read));
if (nul != nullptr) {
string->append(buffer, nul - buffer);
return true;
}
string->append(buffer, bytes_read);
address += bytes_read;
size -= bytes_read;
} while (!has_size || size > 0);
LOG(ERROR) << "unterminated string";
return false;
}
} // namespace crashpad