Skip to content

Commit 5cd35ed

Browse files
committedMar 23, 2018
[HWASan] Port HWASan to Linux x86-64 (compiler-rt)
Summary: Porting HWASan to Linux x86-64, first of the three patches, compiler-rt part. The approach is similar to ARM case, trap signal is used to communicate memory tag check failure. int3 instruction is used to generate a signal, access parameters are stored in nop [eax + offset] instruction immediately following the int3 one Had to add HWASan init on malloc because, due to much less interceptors defined (most other sanitizers intercept much more and get initalized via one of those interceptors or don't care about malloc), HWASan was not initialized yet when libstdc++ was trying to allocate memory for its own fixed-size heap, which led to CHECK-fail in AllocateFromLocalPool. Also added the CHECK() failure handler with more detailed message and stack reporting. Reviewers: eugenis Subscribers: kubamracek, dberris, mgorny, kristof.beyls, delcypher, #sanitizers, llvm-commits Differential Revision: https://reviews.llvm.org/D44705 llvm-svn: 328385
1 parent 5317f2e commit 5cd35ed

File tree

6 files changed

+96
-43
lines changed

6 files changed

+96
-43
lines changed
 

‎compiler-rt/cmake/config-ix.cmake

+1-1
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ else()
204204
set(ALL_LSAN_SUPPORTED_ARCH ${X86} ${X86_64} ${MIPS64} ${ARM64} ${ARM32} ${PPC64})
205205
endif()
206206
set(ALL_MSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${PPC64})
207-
set(ALL_HWASAN_SUPPORTED_ARCH ${ARM64})
207+
set(ALL_HWASAN_SUPPORTED_ARCH ${X86_64} ${ARM64})
208208
set(ALL_PROFILE_SUPPORTED_ARCH ${X86} ${X86_64} ${ARM32} ${ARM64} ${PPC64}
209209
${MIPS32} ${MIPS64} ${S390X})
210210
set(ALL_TSAN_SUPPORTED_ARCH ${X86_64} ${MIPS64} ${ARM64} ${PPC64})

‎compiler-rt/lib/hwasan/hwasan.cc

+31-8
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ void PrintWarning(uptr pc, uptr bp) {
143143
ReportInvalidAccess(&stack, 0);
144144
}
145145

146+
static void HWAsanCheckFailed(const char *file, int line, const char *cond,
147+
u64 v1, u64 v2) {
148+
Report("HWAddressSanitizer CHECK failed: %s:%d \"%s\" (0x%zx, 0x%zx)\n", file,
149+
line, cond, (uptr)v1, (uptr)v2);
150+
PRINT_CURRENT_STACK_CHECK();
151+
Die();
152+
}
153+
146154
} // namespace __hwasan
147155

148156
// Interface.
@@ -160,6 +168,9 @@ void __hwasan_init() {
160168
CacheBinaryName();
161169
InitializeFlags();
162170

171+
// Install tool-specific callbacks in sanitizer_common.
172+
SetCheckFailedCallback(HWAsanCheckFailed);
173+
163174
__sanitizer_set_report_path(common_flags()->log_path);
164175

165176
InitializeInterceptors();
@@ -240,11 +251,23 @@ void __sanitizer_unaligned_store64(uu64 *p, u64 x) {
240251

241252
template<unsigned X>
242253
__attribute__((always_inline))
243-
static void SigTrap() {
254+
static void SigTrap(uptr p) {
244255
#if defined(__aarch64__)
245-
asm("brk %0\n\t" ::"n"(X));
246-
#elif defined(__x86_64__) || defined(__i386__)
247-
asm("ud2\n\t");
256+
(void)p;
257+
// 0x900 is added to do not interfere with the kernel use of lower values of
258+
// brk immediate.
259+
// FIXME: Add a constraint to put the pointer into x0, the same as x86 branch.
260+
asm("brk %0\n\t" ::"n"(0x900 + X));
261+
#elif defined(__x86_64__)
262+
// INT3 + NOP DWORD ptr [EAX + X] to pass X to our signal handler, 5 bytes
263+
// total. The pointer is passed via rdi.
264+
// 0x40 is added as a safeguard, to help distinguish our trap from others and
265+
// to avoid 0 offsets in the command (otherwise it'll be reduced to a
266+
// different nop command, the three bytes one).
267+
asm volatile(
268+
"int3\n"
269+
"nopl %c0(%%rax)\n"
270+
:: "n"(0x40 + X), "D"(p));
248271
#else
249272
// FIXME: not always sigill.
250273
__builtin_trap();
@@ -261,8 +284,8 @@ __attribute__((always_inline, nodebug)) static void CheckAddress(uptr p) {
261284
uptr ptr_raw = p & ~kAddressTagMask;
262285
tag_t mem_tag = *(tag_t *)MEM_TO_SHADOW(ptr_raw);
263286
if (UNLIKELY(ptr_tag != mem_tag)) {
264-
SigTrap<0x900 + 0x20 * (EA == ErrorAction::Recover) +
265-
0x10 * (AT == AccessType::Store) + LogSize>();
287+
SigTrap<0x20 * (EA == ErrorAction::Recover) +
288+
0x10 * (AT == AccessType::Store) + LogSize>(p);
266289
if (EA == ErrorAction::Abort) __builtin_unreachable();
267290
}
268291
}
@@ -277,8 +300,8 @@ __attribute__((always_inline, nodebug)) static void CheckAddressSized(uptr p,
277300
tag_t *shadow_last = (tag_t *)MEM_TO_SHADOW(ptr_raw + sz - 1);
278301
for (tag_t *t = shadow_first; t <= shadow_last; ++t)
279302
if (UNLIKELY(ptr_tag != *t)) {
280-
SigTrap<0x900 + 0x20 * (EA == ErrorAction::Recover) +
281-
0x10 * (AT == AccessType::Store) + 0xf>();
303+
SigTrap<0x20 * (EA == ErrorAction::Recover) +
304+
0x10 * (AT == AccessType::Store) + 0xf>(p);
282305
if (EA == ErrorAction::Abort) __builtin_unreachable();
283306
}
284307
}

‎compiler-rt/lib/hwasan/hwasan.h

+9
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ const int STACK_TRACE_TAG_POISON = StackTrace::TAG_CUSTOM + 1;
137137
GetStackTrace(&stack, kStackTraceMax, pc, bp, nullptr, \
138138
common_flags()->fast_unwind_on_fatal)
139139

140+
#define GET_FATAL_STACK_TRACE_HERE \
141+
GET_FATAL_STACK_TRACE_PC_BP(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME())
142+
143+
#define PRINT_CURRENT_STACK_CHECK() \
144+
{ \
145+
GET_FATAL_STACK_TRACE_HERE; \
146+
stack.Print(); \
147+
}
148+
140149
class ScopedThreadLocalStateBackup {
141150
public:
142151
ScopedThreadLocalStateBackup() { Backup(); }

‎compiler-rt/lib/hwasan/hwasan_allocator.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,8 @@ struct HwasanMapUnmapCallback {
7070
}
7171
};
7272

73-
#if !defined(__aarch64__)
74-
#error unsupported platform
73+
#if !defined(__aarch64__) && !defined(__x86_64__)
74+
#error Unsupported platform
7575
#endif
7676

7777
static const uptr kMaxAllowedMallocSize = 2UL << 30; // 2G

‎compiler-rt/lib/hwasan/hwasan_interceptors.cc

+2
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,8 @@ INTERCEPTOR(void *, realloc, void *ptr, SIZE_T size) {
258258

259259
INTERCEPTOR(void *, malloc, SIZE_T size) {
260260
GET_MALLOC_STACK_TRACE;
261+
if (UNLIKELY(!hwasan_init_is_running))
262+
ENSURE_HWASAN_INITED();
261263
if (UNLIKELY(!hwasan_inited))
262264
// Hack: dlsym calls malloc before REAL(malloc) is retrieved from dlsym.
263265
return AllocateFromLocalPool(size);

‎compiler-rt/lib/hwasan/hwasan_linux.cc

+51-32
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ static void ProtectGap(uptr addr, uptr size) {
6969

7070
Report(
7171
"ERROR: Failed to protect the shadow gap. "
72-
"ASan cannot proceed correctly. ABORTING.\n");
72+
"HWASan cannot proceed correctly. ABORTING.\n");
7373
DumpProcessMap();
7474
Die();
7575
}
7676

7777
// LowMem covers as much of the first 4GB as possible.
78-
const uptr kLowMemEnd = 1UL<<32;
78+
const uptr kLowMemEnd = 1UL << 32;
7979
const uptr kLowShadowEnd = kLowMemEnd >> kShadowScale;
8080
const uptr kLowShadowStart = kLowShadowEnd >> kShadowScale;
8181
static uptr kHighShadowStart;
@@ -85,7 +85,6 @@ static uptr kHighMemStart;
8585
bool InitShadow() {
8686
const uptr maxVirtualAddress = GetMaxUserVirtualAddress();
8787

88-
8988
// HighMem covers the upper part of the address space.
9089
kHighShadowEnd = (maxVirtualAddress >> kShadowScale) + 1;
9190
kHighShadowStart = Max(kLowMemEnd, kHighShadowEnd >> kShadowScale);
@@ -186,50 +185,65 @@ struct AccessInfo {
186185
bool recover;
187186
};
188187

189-
#if defined(__aarch64__)
190188
static AccessInfo GetAccessInfo(siginfo_t *info, ucontext_t *uc) {
191-
// Access type is encoded in BRK immediate as 0x9XY,
192-
// where X&1 is 1 for store, 0 for load,
193-
// and X&2 is 1 if the error is recoverable.
194-
// Valid values of Y are 0 to 4, which are interpreted as log2(access_size),
195-
// and 0xF, which means that access size is stored in X1 register.
196-
// Access address is always in X0 register.
197-
AccessInfo ai;
189+
// Access type is passed in a platform dependent way (see below) and encoded
190+
// as 0xXY, where X&1 is 1 for store, 0 for load, and X&2 is 1 if the error is
191+
// recoverable. Valid values of Y are 0 to 4, which are interpreted as
192+
// log2(access_size), and 0xF, which means that access size is passed via
193+
// platform dependent register (see below).
194+
#if defined(__aarch64__)
195+
// Access type is encoded in BRK immediate as 0x900 + 0xXY. For Y == 0xF,
196+
// access size is stored in X1 register. Access address is always in X0
197+
// register.
198198
uptr pc = (uptr)info->si_addr;
199-
unsigned code = ((*(u32 *)pc) >> 5) & 0xffff;
199+
const unsigned code = ((*(u32 *)pc) >> 5) & 0xffff;
200200
if ((code & 0xff00) != 0x900)
201-
return AccessInfo{0, 0, false, false}; // Not ours.
202-
bool is_store = code & 0x10;
203-
bool recover = code & 0x20;
204-
unsigned size_log = code & 0xf;
201+
return AccessInfo{}; // Not ours.
202+
203+
const bool is_store = code & 0x10;
204+
const bool recover = code & 0x20;
205+
const const uptr addr = uc->uc_mcontext.regs[0];
206+
const unsigned size_log = code & 0xf;
205207
if (size_log > 4 && size_log != 0xf)
206-
return AccessInfo{0, 0, false, false}; // Not ours.
207-
208-
ai.is_store = is_store;
209-
ai.is_load = !is_store;
210-
ai.addr = uc->uc_mcontext.regs[0];
211-
if (size_log == 0xf)
212-
ai.size = uc->uc_mcontext.regs[1];
213-
else
214-
ai.size = 1U << size_log;
215-
ai.recover = recover;
216-
return ai;
217-
}
208+
return AccessInfo{}; // Not ours.
209+
const uptr size = size_log == 0xf ? uc->uc_mcontext.regs[1] : 1U << size_log;
210+
211+
#elif defined(__x86_64__)
212+
// Access type is encoded in the instruction following INT3 as
213+
// NOP DWORD ptr [EAX + 0x40 + 0xXY]. For Y == 0xF, access size is stored in
214+
// RSI register. Access address is always in RDI register.
215+
uptr pc = (uptr)uc->uc_mcontext.gregs[REG_RIP];
216+
uint8_t *nop = (uint8_t*)pc;
217+
if (*nop != 0x0f || *(nop + 1) != 0x1f || *(nop + 2) != 0x40 ||
218+
*(nop + 3) < 0x40)
219+
return AccessInfo{}; // Not ours.
220+
const unsigned code = *(nop + 3);
221+
222+
const bool is_store = code & 0x10;
223+
const bool recover = code & 0x20;
224+
const uptr addr = uc->uc_mcontext.gregs[REG_RDI];
225+
const unsigned size_log = code & 0xf;
226+
if (size_log > 4 && size_log != 0xf)
227+
return AccessInfo{}; // Not ours.
228+
const uptr size =
229+
size_log == 0xf ? uc->uc_mcontext.gregs[REG_RSI] : 1U << size_log;
230+
218231
#else
219-
static AccessInfo GetAccessInfo(siginfo_t *info, ucontext_t *uc) {
220-
return AccessInfo{0, 0, false, false};
221-
}
232+
# error Unsupported architecture
222233
#endif
223234

235+
return AccessInfo{addr, size, is_store, !is_store, recover};
236+
}
237+
224238
static bool HwasanOnSIGTRAP(int signo, siginfo_t *info, ucontext_t *uc) {
225-
SignalContext sig{info, uc};
226239
AccessInfo ai = GetAccessInfo(info, uc);
227240
if (!ai.is_store && !ai.is_load)
228241
return false;
229242

230243
InternalScopedBuffer<BufferedStackTrace> stack_buffer(1);
231244
BufferedStackTrace *stack = stack_buffer.data();
232245
stack->Reset();
246+
SignalContext sig{info, uc};
233247
GetStackTrace(stack, kStackTraceMax, sig.pc, sig.bp, uc,
234248
common_flags()->fast_unwind_on_fatal);
235249

@@ -239,7 +253,12 @@ static bool HwasanOnSIGTRAP(int signo, siginfo_t *info, ucontext_t *uc) {
239253
if (flags()->halt_on_error || !ai.recover)
240254
Die();
241255

256+
#if defined(__aarch64__)
242257
uc->uc_mcontext.pc += 4;
258+
#elif defined(__x86_64__)
259+
#else
260+
# error Unsupported architecture
261+
#endif
243262
return true;
244263
}
245264

0 commit comments

Comments
 (0)
Please sign in to comment.