1 : // Copyright 2015 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/refinery/detectors/lfh_entry_detector.h"
16 :
17 : #include <vector>
18 :
19 : #include "gtest/gtest.h"
20 : #include "syzygy/refinery/unittest_util.h"
21 : #include "syzygy/refinery/detectors/unittest_util.h"
22 :
23 m : namespace refinery {
24 :
25 m : namespace {
26 :
27 m : class LFHEntryDetectorTest : public testing::LFHDetectorTest {
28 m : protected:
29 : // TODO(siggi): This code is 32 bit heap specific - amend this for 64 bit
30 : // heap support.
31 m : void ResetTestData(size_t byte_size) {
32 : // Set with 0x80 as that signals "lfh entry" at certain byte positions.
33 m : test_data_.assign(byte_size, 0x80);
34 m : }
35 :
36 m : void WriteSubseg(size_t byte_offset, uintptr_t subseg_code) {
37 m : ASSERT_LT(byte_offset + sizeof(subseg_code), test_data_.size());
38 m : void* dst_addr = &test_data_.at(byte_offset);
39 m : subseg_code ^= (testing::ToAddress(dst_addr) >> 3);
40 :
41 m : ::memcpy(dst_addr, &subseg_code, sizeof(subseg_code));
42 m : }
43 :
44 m : void DetectTestData(LFHEntryDetector::LFHEntryRuns* found_runs) {
45 m : ASSERT_TRUE(found_runs);
46 :
47 m : LFHEntryDetector detector;
48 m : ASSERT_TRUE(detector.Init(repo().get(), bit_source()));
49 m : ASSERT_TRUE(detector.Detect(
50 m : AddressRange(testing::ToAddress(&test_data_.at(0)), test_data_.size()),
51 m : found_runs));
52 m : }
53 :
54 m : private:
55 m : std::vector<uint8_t> test_data_;
56 m : };
57 :
58 m : } // namespace
59 :
60 m : TEST_F(LFHEntryDetectorTest, InitSuccess) {
61 m : LFHEntryDetector detector;
62 :
63 m : ASSERT_TRUE(detector.Init(repo().get(), bit_source()));
64 m : ASSERT_TRUE(detector.entry_type());
65 m : }
66 :
67 m : TEST_F(LFHEntryDetectorTest, FailsOnEmptyTypeRepo) {
68 m : LFHEntryDetector detector;
69 :
70 m : scoped_refptr<TypeRepository> empty_type_repo = new TypeRepository;
71 m : ASSERT_FALSE(detector.Init(empty_type_repo.get(), bit_source()));
72 m : ASSERT_FALSE(detector.entry_type());
73 m : }
74 :
75 m : TEST_F(LFHEntryDetectorTest, Detect) {
76 m : if (testing::IsAppVerifierActive()) {
77 m : LOG(WARNING) << "LFHEntryDetectorTest.Detect is incompatible with AV.";
78 m : return;
79 m : }
80 :
81 m : LFHEntryDetector detector;
82 :
83 m : ASSERT_TRUE(detector.Init(repo().get(), bit_source()));
84 :
85 m : const size_t kBlockSize = 17;
86 : // Allocate blocks until we get an LFH bucket.
87 m : Address bucket = AllocateLFHBucket(kBlockSize);
88 m : if (bucket == 0) {
89 m : LOG(ERROR) << "Couldn't find an LFH bucket - is AppVerifier enabled?";
90 m : return;
91 m : }
92 :
93 : // Form a range covering the LFH bucket start and perform detection on it.
94 m : AddressRange range(bucket - 256, 1024);
95 m : LFHEntryDetector::LFHEntryRuns found_runs;
96 m : ASSERT_TRUE(detector.Detect(range, &found_runs));
97 :
98 m : ASSERT_LE(1, found_runs.size());
99 :
100 m : bool suitable_size_found = false;
101 m : for (const auto& found_run : found_runs) {
102 m : ASSERT_NE(0U, found_run.entries_found);
103 m : ASSERT_LE(found_run.entry_distance_bytes * (found_run.entries_found - 1),
104 m : found_run.last_entry - found_run.first_entry);
105 m : ASSERT_NE(0U, found_run.size_votes);
106 m : ASSERT_GT(found_run.entries_found, found_run.size_votes);
107 :
108 : // Technically it's possible for the subsegment mask to be zero, but this
109 : // at least tests that it's set with a 1/2^32 odds of flaking.
110 m : ASSERT_NE(0ULL, found_run.subsegment_code);
111 :
112 m : const size_t kEntrySize = 8;
113 m : if (found_run.entry_distance_bytes > kBlockSize + kEntrySize)
114 m : suitable_size_found = true;
115 :
116 m : AddressRange found_span(found_run.first_entry,
117 m : found_run.last_entry - found_run.first_entry);
118 m : ASSERT_TRUE(found_span.IsValid());
119 : // All found spans should be contained within the range we constrain the
120 : // search to.
121 m : ASSERT_TRUE(range.Contains(found_span));
122 m : }
123 :
124 m : ASSERT_TRUE(suitable_size_found);
125 m : }
126 :
127 m : TEST_F(LFHEntryDetectorTest, VotingPicksMinimumDistance) {
128 : // Make some test data.
129 m : ResetTestData(1024);
130 :
131 m : const uintptr_t kSubsegCode = 0xCAFEBABE;
132 m : WriteSubseg(16 * 1, kSubsegCode);
133 m : WriteSubseg(16 * 2, kSubsegCode);
134 m : WriteSubseg(16 * 4, kSubsegCode);
135 :
136 m : LFHEntryDetector::LFHEntryRuns found_runs;
137 m : ASSERT_NO_FATAL_FAILURE(DetectTestData(&found_runs));
138 :
139 m : ASSERT_EQ(1U, found_runs.size());
140 m : EXPECT_EQ(kSubsegCode, found_runs[0].subsegment_code);
141 : // The smaller size should have been selected.
142 m : EXPECT_EQ(16, found_runs[0].entry_distance_bytes);
143 :
144 m : ResetTestData(1024);
145 :
146 : // Now try starting with the larger span.
147 m : WriteSubseg(16 * 1, kSubsegCode);
148 m : WriteSubseg(16 * 3, kSubsegCode);
149 m : WriteSubseg(16 * 4, kSubsegCode);
150 :
151 m : ASSERT_NO_FATAL_FAILURE(DetectTestData(&found_runs));
152 m : ASSERT_EQ(1U, found_runs.size());
153 m : EXPECT_EQ(kSubsegCode, found_runs[0].subsegment_code);
154 : // The smaller size should have been selected.
155 m : EXPECT_EQ(16, found_runs[0].entry_distance_bytes);
156 m : }
157 :
158 m : } // namespace refinery
|