Add CheckedMachAddressRange and its test.

TEST=util_test CheckedMachAddressRange.*
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/513453002
This commit is contained in:
Mark Mentovai 2014-08-28 13:47:28 -04:00
parent ff26ea6db9
commit 04842e0ca4
4 changed files with 481 additions and 0 deletions

View File

@ -0,0 +1,88 @@
// Copyright 2014 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/mac/checked_mach_address_range.h"
#include "base/logging.h"
#include "base/numerics/safe_conversions.h"
#include "util/mac/process_reader.h"
namespace crashpad {
CheckedMachAddressRange::CheckedMachAddressRange()
: range_32_(0, 0), is_64_bit_(false), range_ok_(true) {
}
CheckedMachAddressRange::CheckedMachAddressRange(
const ProcessReader* process_reader,
mach_vm_address_t base,
mach_vm_size_t size) {
SetRange(process_reader, base, size);
}
void CheckedMachAddressRange::SetRange(const ProcessReader* process_reader,
mach_vm_address_t base,
mach_vm_size_t size) {
is_64_bit_ = process_reader->Is64Bit();
if (is_64_bit_) {
range_64_.SetRange(base, size);
range_ok_ = true;
} else {
range_32_.SetRange(base, size);
range_ok_ = base::IsValueInRangeForNumericType<uint32_t>(base) &&
base::IsValueInRangeForNumericType<uint32_t>(size);
}
}
mach_vm_address_t CheckedMachAddressRange::Base() const {
return is_64_bit_ ? range_64_.base() : range_32_.base();
}
mach_vm_size_t CheckedMachAddressRange::Size() const {
return is_64_bit_ ? range_64_.size() : range_32_.size();
}
mach_vm_address_t CheckedMachAddressRange::End() const {
return is_64_bit_ ? range_64_.end() : range_32_.end();
}
bool CheckedMachAddressRange::IsValid() const {
return range_ok_ && (is_64_bit_ ? range_64_.IsValid() : range_32_.IsValid());
}
bool CheckedMachAddressRange::ContainsValue(mach_vm_address_t value) const {
DCHECK(range_ok_);
if (is_64_bit_) {
return range_64_.ContainsValue(value);
}
if (!base::IsValueInRangeForNumericType<uint32_t>(value)) {
return false;
}
return range_32_.ContainsValue(value);
}
bool CheckedMachAddressRange::ContainsRange(
const CheckedMachAddressRange& that) const {
DCHECK_EQ(is_64_bit_, that.is_64_bit_);
DCHECK(range_ok_);
DCHECK(that.range_ok_);
return is_64_bit_ ? range_64_.ContainsRange(that.range_64_)
: range_32_.ContainsRange(that.range_32_);
}
} // namespace crashpad

View File

@ -0,0 +1,134 @@
// Copyright 2014 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.
#ifndef CRASHPAD_UTIL_MAC_CHECKED_MACH_ADDRESS_RANGE_H_
#define CRASHPAD_UTIL_MAC_CHECKED_MACH_ADDRESS_RANGE_H_
#include <mach/mach.h>
#include "util/numeric/checked_range.h"
namespace crashpad {
class ProcessReader;
//! \brief Ensures that a range, composed of a base and a size, does not
//! overflow the pointer type of the process it describes a range in.
//!
//! This class checks bases of type `mach_vm_address_t` and sizes of type
//! `mach_vm_address_t` against a process whose pointer type can be determined
//! from its ProcessReader.
//!
//! Aside from varying the overall range on the basis of a process pointer type
//! width, this class functions very similarly to CheckedRange.
class CheckedMachAddressRange {
public:
//! \brief Initializes a default range.
//!
//! The default range has base 0, size 0, and appears to be from a 32-bit
//! process.
CheckedMachAddressRange();
//! \brief Initializes a range.
//!
//! See SetRange().
CheckedMachAddressRange(const ProcessReader* process_reader,
mach_vm_address_t base,
mach_vm_size_t size);
//! \brief Sets a ranges fields.
//!
//! \param[in] process_reader The ProcessReader that can read the process that
//! \a base is a pointer to.
//! \param[in] base The ranges base address.
//! \param[in] size The ranges size.
void SetRange(const ProcessReader* process_reader,
mach_vm_address_t base,
mach_vm_size_t size);
//! \brief The ranges base address.
mach_vm_address_t Base() const;
//! \brief The ranges size.
mach_vm_size_t Size() const;
//! \brief The ranges end address (its base address plus its size).
mach_vm_address_t End() const;
//! \brief Returns the validity of the address range.
//!
//! \return `true` if the address range is valid, `false` otherwise.
//!
//! An address range is valid if its size can be converted to the address
//! ranges data type without data loss, and if its end (base plus size) can
//! be computed without overflowing its data type.
bool IsValid() const;
//! \brief Returns whether the address range contains another address.
//!
//! \param[in] value The (possibly) contained address.
//!
//! \return `true` if the address range contains \a value, `false` otherwise.
//!
//! An address range contains a value if the value is greater than or equal to
//! its base address, and less than its end address (base address plus size).
//!
//! This method must only be called if IsValid() would return `true`.
bool ContainsValue(const mach_vm_address_t value) const;
//! \brief Returns whether the address range contains another address range.
//!
//! \param[in] that The (possibly) contained address range.
//!
//! \return `true` if `this` address range, the containing address range,
//! contains \a that, the contained address range. `false` otherwise.
//!
//! An address range contains another address range when the contained address
//! ranges base is greater than or equal to the containing address ranges
//! base, and the contained address ranges end is less than or equal to the
//! containing address ranges end.
//!
//! This method should only be called on two CheckedMachAddressRange objects
//! sharing the same ProcessReader.
//!
//! This method must only be called if IsValid() would return `true` for both
//! CheckedMachAddressRange objects involved.
bool ContainsRange(const CheckedMachAddressRange& that) const;
private:
// The field of the union that is expressed is determined by is_64_bit_.
union {
CheckedRange<uint32_t> range_32_;
CheckedRange<uint64_t> range_64_;
};
// Determines which field of the union is expressed.
bool is_64_bit_;
// Whether the base and size were valid for their data type when set. This is
// always true when is_64_bit_ is true because the underlying data types are
// 64 bits wide and there is no possibility for range and size to overflow.
// When is_64_bit_ is false, range_ok_ will be false if SetRange() was passed
// a base or size that overflowed the underlying 32-bit data type. This field
// is necessary because the interface exposes mach_vm_address_t and
// mach_vm_size_t uniformly, but these types are too wide for the underlying
// pointer and size types in 32-bit processes.
bool range_ok_;
DISALLOW_COPY_AND_ASSIGN(CheckedMachAddressRange);
};
} // namespace crashpad
#endif // CRASHPAD_UTIL_MAC_CHECKED_MACH_ADDRESS_RANGE_H_

View File

@ -0,0 +1,256 @@
// Copyright 2014 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/mac/checked_mach_address_range.h"
#include <mach/mach.h>
#include <limits>
#include "base/basictypes.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "gtest/gtest.h"
#include "util/mac/process_reader.h"
namespace {
using namespace crashpad;
#if defined(ARCH_CPU_64_BITS)
const bool kValid64Invalid32 = true;
#else
const bool kValid64Invalid32 = false;
#endif
TEST(CheckedMachAddressRange, IsValid) {
const struct TestData {
mach_vm_address_t base;
mach_vm_size_t size;
bool valid;
} kTestData[] = {
{0, 0, true},
{0, 1, true},
{0, 2, true},
{0, 0x7fffffff, true},
{0, 0x80000000, true},
{0, 0xfffffffe, true},
{0, 0xffffffff, true},
{0, 0xffffffffffffffff, kValid64Invalid32},
{1, 0, true},
{1, 1, true},
{1, 2, true},
{1, 0x7fffffff, true},
{1, 0x80000000, true},
{1, 0xfffffffe, true},
{1, 0xffffffff, kValid64Invalid32},
{1, 0xfffffffffffffffe, kValid64Invalid32},
{1, 0xffffffffffffffff, false},
{0x7fffffff, 0, true},
{0x7fffffff, 1, true},
{0x7fffffff, 2, true},
{0x7fffffff, 0x7fffffff, true},
{0x7fffffff, 0x80000000, true},
{0x7fffffff, 0xfffffffe, kValid64Invalid32},
{0x7fffffff, 0xffffffff, kValid64Invalid32},
{0x80000000, 0, true},
{0x80000000, 1, true},
{0x80000000, 2, true},
{0x80000000, 0x7fffffff, true},
{0x80000000, 0x80000000, kValid64Invalid32},
{0x80000000, 0xfffffffe, kValid64Invalid32},
{0x80000000, 0xffffffff, kValid64Invalid32},
{0xfffffffe, 0, true},
{0xfffffffe, 1, true},
{0xfffffffe, 2, kValid64Invalid32},
{0xfffffffe, 0x7fffffff, kValid64Invalid32},
{0xfffffffe, 0x80000000, kValid64Invalid32},
{0xfffffffe, 0xfffffffe, kValid64Invalid32},
{0xfffffffe, 0xffffffff, kValid64Invalid32},
{0xffffffff, 0, true},
{0xffffffff, 1, kValid64Invalid32},
{0xffffffff, 2, kValid64Invalid32},
{0xffffffff, 0x7fffffff, kValid64Invalid32},
{0xffffffff, 0x80000000, kValid64Invalid32},
{0xffffffff, 0xfffffffe, kValid64Invalid32},
{0xffffffff, 0xffffffff, kValid64Invalid32},
{0x7fffffffffffffff, 0, kValid64Invalid32},
{0x7fffffffffffffff, 1, kValid64Invalid32},
{0x7fffffffffffffff, 2, kValid64Invalid32},
{0x7fffffffffffffff, 0x7fffffffffffffff, kValid64Invalid32},
{0x7fffffffffffffff, 0x8000000000000000, kValid64Invalid32},
{0x7fffffffffffffff, 0x8000000000000001, false},
{0x7fffffffffffffff, 0xfffffffffffffffe, false},
{0x7fffffffffffffff, 0xffffffffffffffff, false},
{0x8000000000000000, 0, kValid64Invalid32},
{0x8000000000000000, 1, kValid64Invalid32},
{0x8000000000000000, 2, kValid64Invalid32},
{0x8000000000000000, 0x7fffffffffffffff, kValid64Invalid32},
{0x8000000000000000, 0x8000000000000000, false},
{0x8000000000000000, 0x8000000000000001, false},
{0x8000000000000000, 0xfffffffffffffffe, false},
{0x8000000000000000, 0xffffffffffffffff, false},
{0xfffffffffffffffe, 0, kValid64Invalid32},
{0xfffffffffffffffe, 1, kValid64Invalid32},
{0xfffffffffffffffe, 2, false},
{0xffffffffffffffff, 0, kValid64Invalid32},
{0xffffffffffffffff, 1, false},
};
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& testcase = kTestData[index];
SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx",
index,
testcase.base,
testcase.size));
CheckedMachAddressRange range(
&process_reader, testcase.base, testcase.size);
EXPECT_EQ(testcase.valid, range.IsValid());
}
}
TEST(CheckedMachAddressRange, ContainsValue) {
const struct TestData {
mach_vm_address_t value;
bool valid;
} kTestData[] = {
{0, false},
{1, false},
{0x1fff, false},
{0x2000, true},
{0x2001, true},
{0x2ffe, true},
{0x2fff, true},
{0x3000, false},
{0x3001, false},
{0x7fffffff, false},
{0x80000000, false},
{0x80000001, false},
{0x80001fff, false},
{0x80002000, false},
{0x80002001, false},
{0x80002ffe, false},
{0x80002fff, false},
{0x80003000, false},
{0x80003001, false},
{0xffffcfff, false},
{0xffffdfff, false},
{0xffffefff, false},
{0xffffffff, false},
{0x100000000, false},
{0xffffffffffffffff, false},
};
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
CheckedMachAddressRange parent_range(&process_reader, 0x2000, 0x1000);
ASSERT_TRUE(parent_range.IsValid());
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& testcase = kTestData[index];
SCOPED_TRACE(
base::StringPrintf("index %zu, value 0x%llx", index, testcase.value));
EXPECT_EQ(testcase.valid, parent_range.ContainsValue(testcase.value));
}
CheckedMachAddressRange parent_range_64(&process_reader, 0x100000000, 0x1000);
ASSERT_EQ(kValid64Invalid32, parent_range_64.IsValid());
if (parent_range_64.IsValid()) {
EXPECT_FALSE(parent_range_64.ContainsValue(0xffffffff));
EXPECT_TRUE(parent_range_64.ContainsValue(0x100000000));
EXPECT_TRUE(parent_range_64.ContainsValue(0x100000001));
EXPECT_TRUE(parent_range_64.ContainsValue(0x100000fff));
EXPECT_FALSE(parent_range_64.ContainsValue(0x100001000));
}
}
TEST(CheckedMachAddressRange, ContainsRange) {
const struct TestData {
mach_vm_address_t base;
mach_vm_size_t size;
bool valid;
} kTestData[] = {
{0, 0, false},
{0, 1, false},
{0x2000, 0x1000, true},
{0, 0x2000, false},
{0x3000, 0x1000, false},
{0x1800, 0x1000, false},
{0x2800, 0x1000, false},
{0x2000, 0x800, true},
{0x2800, 0x800, true},
{0x2400, 0x800, true},
{0x2800, 0, true},
{0x2000, 0xffffdfff, false},
{0x2800, 0xffffd7ff, false},
{0x3000, 0xffffcfff, false},
{0xfffffffe, 1, false},
{0xffffffff, 0, false},
{0x1fff, 0, false},
{0x2000, 0, true},
{0x2001, 0, true},
{0x2fff, 0, true},
{0x3000, 0, true},
{0x3001, 0, false},
{0x1fff, 1, false},
{0x2000, 1, true},
{0x2001, 1, true},
{0x2fff, 1, true},
{0x3000, 1, false},
{0x3001, 1, false},
};
ProcessReader process_reader;
ASSERT_TRUE(process_reader.Initialize(mach_task_self()));
CheckedMachAddressRange parent_range(&process_reader, 0x2000, 0x1000);
ASSERT_TRUE(parent_range.IsValid());
for (size_t index = 0; index < arraysize(kTestData); ++index) {
const TestData& testcase = kTestData[index];
SCOPED_TRACE(base::StringPrintf("index %zu, base 0x%llx, size 0x%llx",
index,
testcase.base,
testcase.size));
CheckedMachAddressRange child_range(
&process_reader, testcase.base, testcase.size);
ASSERT_TRUE(child_range.IsValid());
EXPECT_EQ(testcase.valid, parent_range.ContainsRange(child_range));
}
CheckedMachAddressRange parent_range_64(&process_reader, 0x100000000, 0x1000);
ASSERT_EQ(kValid64Invalid32, parent_range_64.IsValid());
if (parent_range_64.IsValid()) {
CheckedMachAddressRange child_range_64(&process_reader, 0xffffffff, 2);
EXPECT_FALSE(parent_range_64.ContainsRange(child_range_64));
child_range_64.SetRange(&process_reader, 0x100000000, 2);
EXPECT_TRUE(parent_range_64.ContainsRange(child_range_64));
child_range_64.SetRange(&process_reader, 0x100000ffe, 2);
EXPECT_TRUE(parent_range_64.ContainsRange(child_range_64));
child_range_64.SetRange(&process_reader, 0x100000fff, 2);
EXPECT_FALSE(parent_range_64.ContainsRange(child_range_64));
}
}
} // namespace

View File

@ -32,6 +32,8 @@
'file/file_writer.h',
'file/string_file_writer.cc',
'file/string_file_writer.h',
'mac/checked_mach_address_range.cc',
'mac/checked_mach_address_range.h',
'mac/launchd.h',
'mac/launchd.mm',
'mac/mac_util.cc',
@ -118,6 +120,7 @@
],
'sources': [
'file/string_file_writer_test.cc',
'mac/checked_mach_address_range_test.cc',
'mac/launchd_test.mm',
'mac/mac_util_test.mm',
'mac/process_reader_test.cc',