aboutsummaryrefslogtreecommitdiff
path: root/driver/sanitizer_hooks_with_pc.cpp
blob: bb3ec5e15dede1e28e31193d531ccee5af1249fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// Copyright 2021 Code Intelligence GmbH
//
// 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 "sanitizer_hooks_with_pc.h"

#include <cstddef>
#include <cstdint>

// libFuzzer's compare hooks obtain the caller's address from the compiler
// builtin __builtin_return_adress. Since Java code will invoke the hooks always
// from the same native function, this builtin would always return the same
// value. Internally, the libFuzzer hooks call through to the always inlined
// HandleCmp and thus can't be mimicked without patching libFuzzer.
//
// We solve this problem via an inline assembly trampoline construction that
// translates a runtime argument `fake_pc` in the range [0, 512) into a call to
// a hook with a fake return address whose lower 9 bits are `fake_pc` up to a
// constant shift. This is achieved by pushing a return address pointing into
// 512 ret instructions at offset `fake_pc` onto the stack and then jumping
// directly to the address of the hook.
//
// Note: We only set the lowest 9 bits of the return address since only these
// bits are used by the libFuzzer value profiling mode for integer compares, see
// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerTracePC.cpp#L390
// as well as
// https://github.com/llvm/llvm-project/blob/704d92607d26e696daba596b72cb70effe79a872/compiler-rt/lib/fuzzer/FuzzerValueBitMap.h#L34
// ValueProfileMap.AddValue() truncates its argument to 16 bits and shifts the
// PC to the left by log_2(128)=7, which means that only the lowest 16 - 7 bits
// of the return address matter. String compare hooks use the lowest 12 bits,
// but take the return address as an argument and thus don't require the
// indirection through a trampoline.

#define REPEAT_8(a) a a a a a a a a

#define REPEAT_512(a) REPEAT_8(REPEAT_8(REPEAT_8(a)))

// The first four registers to pass arguments in according to the
// platform-specific x64 calling convention.
#ifdef _WIN64
#define REG_1 "rcx"
#define REG_2 "rdx"
#define REG_3 "r8"
#define REG_4 "r9"
#else
#define REG_1 "rdi"
#define REG_2 "rsi"
#define REG_3 "rdx"
#define REG_4 "rcx"
#endif

// Call the function at address `func` with arguments `arg1` and `arg2` while
// ensuring that the return address is `fake_pc` up to a globally constant
// offset.
__attribute__((noinline)) void trampoline(uint64_t arg1, uint64_t arg2,
                                          void *func, uint16_t fake_pc) {
  // arg1 and arg2 have to be forwarded according to the x64 calling convention.
  // We also fix func and fake_pc to their registers so that we can safely use
  // rax below.
  [[maybe_unused]] register uint64_t arg1_loc asm(REG_1) = arg1;
  [[maybe_unused]] register uint64_t arg2_loc asm(REG_2) = arg2;
  [[maybe_unused]] register void *func_loc asm(REG_3) = func;
  [[maybe_unused]] register uint64_t fake_pc_loc asm(REG_4) = fake_pc;
  asm volatile goto(
      // Load RIP-relative address of the end of this function.
      "lea %l[end_of_function](%%rip), %%rax \n\t"
      "push %%rax \n\t"
      // Load RIP-relative address of the ret sled into rax.
      "lea ret_sled(%%rip), %%rax \n\t"
      // Add the offset of the fake_pc-th ret.
      "add %[fake_pc], %%rax \n\t"
      // Push the fake return address pointing to that ret. The hook will return
      // to it and then immediately return to the end of this function.
      "push %%rax \n\t"
      // Call func with the fake return address on the stack.
      // Function arguments arg1 and arg2 are passed unchanged in the registers
      // RDI and RSI as governed by the x64 calling convention.
      "jmp *%[func] \n\t"
      // Append a sled of 2^9=512 ret instructions.
      "ret_sled: \n\t" REPEAT_512("ret \n\t")
      :
      : "r"(arg1_loc),
        "r"(arg2_loc), [func] "r"(func_loc), [fake_pc] "r"(fake_pc_loc)
      : "memory"
      : end_of_function);

end_of_function:
  return;
}

namespace {
uintptr_t trampoline_offset = 0;
}

void set_trampoline_offset() {
  // Stores the additive inverse of the current return address modulo 0x200u in
  // trampoline_offset.
  trampoline_offset =
      0x200u -
      (reinterpret_cast<uintptr_t>(__builtin_return_address(0)) & 0x1FFu);
}

// Computes the additive shift that needs to be applied to the caller PC by
// caller_pc_to_fake_pc to make caller PC and resulting fake return address
// in their lowest 9 bite. This offset is constant for each binary, but may vary
// based on code generation specifics. By calibrating the trampoline, the fuzzer
// behavior is fully determined by the seed.
void CalibrateTrampoline() {
  trampoline(0, 0, reinterpret_cast<void *>(&set_trampoline_offset), 0);
}

// Masks any address down to its lower 9 bits, adjusting for the trampoline
// shift.
__attribute__((always_inline)) inline uint16_t caller_pc_to_fake_pc(
    const void *caller_pc) {
  return (reinterpret_cast<uintptr_t>(caller_pc) + trampoline_offset) & 0x1FFu;
}

// The original hooks exposed by libFuzzer. All of these get the caller's
// address via __builtin_return_address(0).
extern "C" {
void __sanitizer_cov_trace_cmp4(uint32_t arg1, uint32_t arg2);
void __sanitizer_cov_trace_cmp8(uint64_t arg1, uint64_t arg2);
void __sanitizer_cov_trace_switch(uint64_t val, uint64_t *cases);
void __sanitizer_cov_trace_div4(uint32_t val);
void __sanitizer_cov_trace_div8(uint64_t val);
void __sanitizer_cov_trace_gep(uintptr_t idx);
void __sanitizer_cov_trace_pc_indir(uintptr_t callee);
}
void __sanitizer_cov_trace_cmp4_with_pc(void *caller_pc, uint32_t arg1,
                                        uint32_t arg2) {
  void *trace_cmp4 = reinterpret_cast<void *>(&__sanitizer_cov_trace_cmp4);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(arg1), static_cast<uint64_t>(arg2),
             trace_cmp4, fake_pc);
}

void __sanitizer_cov_trace_cmp8_with_pc(void *caller_pc, uint64_t arg1,
                                        uint64_t arg2) {
  void *trace_cmp8 = reinterpret_cast<void *>(&__sanitizer_cov_trace_cmp8);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(arg1), static_cast<uint64_t>(arg2),
             trace_cmp8, fake_pc);
}

void __sanitizer_cov_trace_switch_with_pc(void *caller_pc, uint64_t val,
                                          uint64_t *cases) {
  void *trace_switch = reinterpret_cast<void *>(&__sanitizer_cov_trace_switch);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(val), reinterpret_cast<uint64_t>(cases),
             trace_switch, fake_pc);
}

void __sanitizer_cov_trace_div4_with_pc(void *caller_pc, uint32_t val) {
  void *trace_div4 = reinterpret_cast<void *>(&__sanitizer_cov_trace_div4);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(val), 0, trace_div4, fake_pc);
}

void __sanitizer_cov_trace_div8_with_pc(void *caller_pc, uint64_t val) {
  void *trace_div8 = reinterpret_cast<void *>(&__sanitizer_cov_trace_div8);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(val), 0, trace_div8, fake_pc);
}

void __sanitizer_cov_trace_gep_with_pc(void *caller_pc, uintptr_t idx) {
  void *trace_gep = reinterpret_cast<void *>(&__sanitizer_cov_trace_gep);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(idx), 0, trace_gep, fake_pc);
}

void __sanitizer_cov_trace_pc_indir_with_pc(void *caller_pc, uintptr_t callee) {
  void *trace_pc_indir =
      reinterpret_cast<void *>(&__sanitizer_cov_trace_pc_indir);
  auto fake_pc = caller_pc_to_fake_pc(caller_pc);
  trampoline(static_cast<uint64_t>(callee), 0, trace_pc_indir, fake_pc);
}