Skip to content

Commit cdfd4cf

Browse files
authored
[win/asan] Search both higher and lower in AllocateTrampolineRegion (#114212)
There may not always be available virtual memory at higher addresses than the target function. Therefore, search also lower addresses while ensuring that we stay within the accessible memory range. Additionally, add more ReportError calls to make the reasons for interception failure more clear.
1 parent 46ccefb commit cdfd4cf

File tree

1 file changed

+114
-38
lines changed

1 file changed

+114
-38
lines changed

compiler-rt/lib/interception/interception_win.cpp

Lines changed: 114 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,12 @@ static uptr GetMmapGranularity() {
187187
return si.dwAllocationGranularity;
188188
}
189189

190+
UNUSED static uptr RoundDownTo(uptr size, uptr boundary) {
191+
return size & ~(boundary - 1);
192+
}
193+
190194
UNUSED static uptr RoundUpTo(uptr size, uptr boundary) {
191-
return (size + boundary - 1) & ~(boundary - 1);
195+
return RoundDownTo(size + boundary - 1, boundary);
192196
}
193197

194198
// FIXME: internal_str* and internal_mem* functions should be moved from the
@@ -285,8 +289,11 @@ static void WriteJumpInstruction(uptr from, uptr target) {
285289

286290
static void WriteShortJumpInstruction(uptr from, uptr target) {
287291
sptr offset = target - from - kShortJumpInstructionLength;
288-
if (offset < -128 || offset > 127)
292+
if (offset < -128 || offset > 127) {
293+
ReportError("interception_win: cannot write short jmp from %p to %p\n",
294+
(void *)from, (void *)target);
289295
InterceptionFailed();
296+
}
290297
*(u8*)from = 0xEB;
291298
*(u8*)(from + 1) = (u8)offset;
292299
}
@@ -340,32 +347,78 @@ struct TrampolineMemoryRegion {
340347
uptr max_size;
341348
};
342349

343-
UNUSED static const uptr kTrampolineScanLimitRange = 1ull << 31; // 2 gig
350+
UNUSED static const uptr kTrampolineRangeLimit = 1ull << 31; // 2 gig
344351
static const int kMaxTrampolineRegion = 1024;
345352
static TrampolineMemoryRegion TrampolineRegions[kMaxTrampolineRegion];
346353

347-
static void *AllocateTrampolineRegion(uptr image_address, size_t granularity) {
348-
#if SANITIZER_WINDOWS64
349-
uptr address = image_address;
350-
uptr scanned = 0;
351-
while (scanned < kTrampolineScanLimitRange) {
354+
static void *AllocateTrampolineRegion(uptr min_addr, uptr max_addr,
355+
uptr func_addr, size_t granularity) {
356+
# if SANITIZER_WINDOWS64
357+
// Clamp {min,max}_addr to the accessible address space.
358+
SYSTEM_INFO system_info;
359+
::GetSystemInfo(&system_info);
360+
uptr min_virtual_addr =
361+
RoundUpTo((uptr)system_info.lpMinimumApplicationAddress, granularity);
362+
uptr max_virtual_addr =
363+
RoundDownTo((uptr)system_info.lpMaximumApplicationAddress, granularity);
364+
if (min_addr < min_virtual_addr)
365+
min_addr = min_virtual_addr;
366+
if (max_addr > max_virtual_addr)
367+
max_addr = max_virtual_addr;
368+
369+
// This loop probes the virtual address space to find free memory in the
370+
// [min_addr, max_addr] interval. The search starts from func_addr and
371+
// proceeds "outwards" towards the interval bounds using two probes, lo_addr
372+
// and hi_addr, for addresses lower/higher than func_addr. At each step, it
373+
// considers the probe closest to func_addr. If that address is not free, the
374+
// probe is advanced (lower or higher depending on the probe) to the next
375+
// memory block and the search continues.
376+
uptr lo_addr = RoundDownTo(func_addr, granularity);
377+
uptr hi_addr = RoundUpTo(func_addr, granularity);
378+
while (lo_addr >= min_addr || hi_addr <= max_addr) {
379+
// Consider the in-range address closest to func_addr.
380+
uptr addr;
381+
if (lo_addr < min_addr)
382+
addr = hi_addr;
383+
else if (hi_addr > max_addr)
384+
addr = lo_addr;
385+
else
386+
addr = (hi_addr - func_addr < func_addr - lo_addr) ? hi_addr : lo_addr;
387+
352388
MEMORY_BASIC_INFORMATION info;
353-
if (!::VirtualQuery((void*)address, &info, sizeof(info)))
389+
if (!::VirtualQuery((void *)addr, &info, sizeof(info))) {
390+
ReportError(
391+
"interception_win: VirtualQuery in AllocateTrampolineRegion failed "
392+
"for %p\n",
393+
(void *)addr);
354394
return nullptr;
395+
}
355396

356-
// Check whether a region can be allocated at |address|.
397+
// Check whether a region can be allocated at |addr|.
357398
if (info.State == MEM_FREE && info.RegionSize >= granularity) {
358-
void *page = ::VirtualAlloc((void*)RoundUpTo(address, granularity),
359-
granularity,
360-
MEM_RESERVE | MEM_COMMIT,
361-
PAGE_EXECUTE_READWRITE);
399+
void *page =
400+
::VirtualAlloc((void *)addr, granularity, MEM_RESERVE | MEM_COMMIT,
401+
PAGE_EXECUTE_READWRITE);
402+
if (page == nullptr)
403+
ReportError(
404+
"interception_win: VirtualAlloc in AllocateTrampolineRegion failed "
405+
"for %p\n",
406+
(void *)addr);
362407
return page;
363408
}
364409

365-
// Move to the next region.
366-
address = (uptr)info.BaseAddress + info.RegionSize;
367-
scanned += info.RegionSize;
410+
if (addr == lo_addr)
411+
lo_addr =
412+
RoundDownTo((uptr)info.AllocationBase - granularity, granularity);
413+
if (addr == hi_addr)
414+
hi_addr =
415+
RoundUpTo((uptr)info.BaseAddress + info.RegionSize, granularity);
368416
}
417+
418+
ReportError(
419+
"interception_win: AllocateTrampolineRegion failed to find free memory; "
420+
"min_addr: %p, max_addr: %p, func_addr: %p, granularity: %zu\n",
421+
(void *)min_addr, (void *)max_addr, granularity);
369422
return nullptr;
370423
#else
371424
return ::VirtualAlloc(nullptr,
@@ -387,37 +440,50 @@ void TestOnlyReleaseTrampolineRegions() {
387440
}
388441

389442
static uptr AllocateMemoryForTrampoline(uptr func_address, size_t size) {
390-
uptr image_address = func_address;
443+
# if SANITIZER_WINDOWS64
444+
uptr min_addr = func_address - kTrampolineRangeLimit;
445+
uptr max_addr = func_address + kTrampolineRangeLimit - size;
391446

392-
#if SANITIZER_WINDOWS64
393-
// Allocate memory after the module (DLL or EXE file), but within 2GB
394-
// of the start of the module so that any address within the module can be
395-
// referenced with PC-relative operands.
447+
// Allocate memory within 2GB of the module (DLL or EXE file) so that any
448+
// address within the module can be referenced with PC-relative operands.
396449
// This allows us to not just jump to the trampoline with a PC-relative
397450
// offset, but to relocate any instructions that we copy to the trampoline
398451
// which have references to the original module. If we can't find the base
399452
// address of the module (e.g. if func_address is in mmap'ed memory), just
400-
// use func_address as is.
453+
// stay within 2GB of func_address.
401454
HMODULE module;
402455
if (::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
403456
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
404457
(LPCWSTR)func_address, &module)) {
405458
MODULEINFO module_info;
406459
if (::GetModuleInformation(::GetCurrentProcess(), module,
407460
&module_info, sizeof(module_info))) {
408-
image_address = (uptr)module_info.lpBaseOfDll;
461+
min_addr = (uptr)module_info.lpBaseOfDll + module_info.SizeOfImage -
462+
kTrampolineRangeLimit;
463+
max_addr = (uptr)module_info.lpBaseOfDll + kTrampolineRangeLimit - size;
409464
}
410465
}
411-
#endif
412466

413-
// Find a region within 2G with enough space to allocate |size| bytes.
467+
// Check for overflow.
468+
if (min_addr > func_address)
469+
min_addr = 0;
470+
if (max_addr < func_address)
471+
max_addr = ~(uptr)0;
472+
# else
473+
uptr min_addr = 0;
474+
uptr max_addr = ~min_addr;
475+
# endif
476+
477+
// Find a region within [min_addr,max_addr] with enough space to allocate
478+
// |size| bytes.
414479
TrampolineMemoryRegion *region = nullptr;
415480
for (size_t bucket = 0; bucket < kMaxTrampolineRegion; ++bucket) {
416481
TrampolineMemoryRegion* current = &TrampolineRegions[bucket];
417482
if (current->content == 0) {
418483
// No valid region found, allocate a new region.
419484
size_t bucket_size = GetMmapGranularity();
420-
void *content = AllocateTrampolineRegion(image_address, bucket_size);
485+
void *content = AllocateTrampolineRegion(min_addr, max_addr, func_address,
486+
bucket_size);
421487
if (content == nullptr)
422488
return 0U;
423489

@@ -427,13 +493,9 @@ static uptr AllocateMemoryForTrampoline(uptr func_address, size_t size) {
427493
region = current;
428494
break;
429495
} else if (current->max_size - current->allocated_size > size) {
430-
#if SANITIZER_WINDOWS64
431-
// In 64-bits, the memory space must be allocated within 2G boundary.
432-
uptr next_address = current->content + current->allocated_size;
433-
if (next_address < image_address ||
434-
next_address - image_address >= 0x7FFF0000)
435-
continue;
436-
#endif
496+
uptr next_address = current->content + current->allocated_size;
497+
if (next_address < min_addr || next_address > max_addr)
498+
continue;
437499
// The space can be allocated in the current region.
438500
region = current;
439501
break;
@@ -872,8 +934,14 @@ static bool CopyInstructions(uptr to, uptr from, size_t size) {
872934
// this will be untrue if relocated_offset \notin [-2**31, 2**31)
873935
s64 delta = to - from;
874936
s64 relocated_offset = *(s32 *)(to + cursor + rel_offset) - delta;
875-
if (-0x8000'0000ll > relocated_offset || relocated_offset > 0x7FFF'FFFFll)
937+
if (-0x8000'0000ll > relocated_offset ||
938+
relocated_offset > 0x7FFF'FFFFll) {
939+
ReportError(
940+
"interception_win: CopyInstructions relocated_offset %lld outside "
941+
"32-bit range\n",
942+
(long long)relocated_offset);
876943
return false;
944+
}
877945
# else
878946
// on 32-bit, the relative offset will always be correct
879947
s32 delta = to - from;
@@ -1167,19 +1235,27 @@ uptr InternalGetProcAddress(void *module, const char *func_name) {
11671235
// exported directory.
11681236
char function_name[256];
11691237
size_t funtion_name_length = _strlen(func);
1170-
if (funtion_name_length >= sizeof(function_name) - 1)
1238+
if (funtion_name_length >= sizeof(function_name) - 1) {
1239+
ReportError("interception_win: func too long: '%s'\n", func);
11711240
InterceptionFailed();
1241+
}
11721242

11731243
_memcpy(function_name, func, funtion_name_length);
11741244
function_name[funtion_name_length] = '\0';
11751245
char* separator = _strchr(function_name, '.');
1176-
if (!separator)
1246+
if (!separator) {
1247+
ReportError("interception_win: no separator in '%s'\n",
1248+
function_name);
11771249
InterceptionFailed();
1250+
}
11781251
*separator = '\0';
11791252

11801253
void* redirected_module = GetModuleHandleA(function_name);
1181-
if (!redirected_module)
1254+
if (!redirected_module) {
1255+
ReportError("interception_win: GetModuleHandleA failed for '%s'\n",
1256+
function_name);
11821257
InterceptionFailed();
1258+
}
11831259
return InternalGetProcAddress(redirected_module, separator + 1);
11841260
}
11851261

0 commit comments

Comments
 (0)