Bug 522088 and bug 507924: Ensure that the value used for frame poisoning is a pointer to an inaccessible page of virtual memory.

This commit is contained in:
Zack Weinberg
2009-11-17 11:17:20 -08:00
parent aff8f0e48c
commit 456c0f1c2a
3 changed files with 683 additions and 26 deletions

View File

@@ -50,8 +50,8 @@
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "prmem.h"
#ifndef DEBUG_TRACEMALLOC_PRESARENA
#include "prinit.h"
#include "prlog.h"
// Even on 32-bit systems, we allocate objects from the frame arena
// that require 8-byte alignment. The cast to PRUword is needed
@@ -61,37 +61,165 @@
#define PL_ARENA_CONST_ALIGN_MASK ((PRUword(1) << ALIGN_SHIFT) - 1)
#include "plarena.h"
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
# include <sys/mman.h>
# ifndef MAP_ANON
# ifdef MAP_ANONYMOUS
# define MAP_ANON MAP_ANONYMOUS
# else
# error "Don't know how to get anonymous memory"
# endif
# endif
#endif
#ifndef DEBUG_TRACEMALLOC_PRESARENA
// Size to use for PLArena block allocations.
static const size_t ARENA_PAGE_SIZE = 4096;
// Freed memory is filled with a poison value, which is believed to
// form a pointer to an always-unmapped region of the address space on
// all platforms of interest. The low 12 bits of this number are
// chosen to fall in the middle of the typical 4096-byte page, and
// make the address odd.
//
// With the possible exception of PPC64, current 64-bit CPUs permit
// only a subset (2^48 to 2^56, depending) of the full virtual address
// space to be used. x86-64 has the inaccessible region in the
// *middle* of the address space, whereas all others are believed to
// have it at the highest addresses. Use an address in this region if
// we possibly can; if the hardware doesn't let anyone use it, we
// needn't worry about the OS.
//
// TODO: Confirm that this value is a pointer to an always-unmapped
// address space region on (at least) Win32, Win64, WinCE, ARM Linux,
// MacOSX, and add #ifdefs below as necessary. (Bug 507294.)
// Freed memory is filled with a poison value, which we arrange to
// form a pointer either to an always-unmapped region of the address
// space, or to a page that has been reserved and rendered
// inaccessible via OS primitives. See tests/TestPoisonArea.cpp for
// extensive discussion of the requirements for this page. The code
// from here to 'class FreeList' needs to be kept in sync with that
// file.
#ifdef _WIN32
static void *
ReserveRegion(PRUword region, PRUword size)
{
return VirtualAlloc((void *)region, size, MEM_RESERVE, PAGE_NOACCESS);
}
static void
ReleaseRegion(void *region, PRUword size)
{
VirtualFree(region, size, MEM_RELEASE);
}
static bool
ProbeRegion(PRUword region, PRUword size)
{
SYSTEM_INFO sinfo;
GetSystemInfo(&sinfo);
if (region >= (PRUword)sinfo.lpMaximumApplicationAddress &&
region + size >= (PRUword)sinfo.lpMaximumApplicationAddress) {
return true;
} else {
return false;
}
}
static PRUword
GetDesiredRegionSize()
{
SYSTEM_INFO sinfo;
GetSystemInfo(&sinfo);
return sinfo.dwAllocationGranularity;
}
#define RESERVE_FAILED 0
#else // Unix
static void *
ReserveRegion(PRUword region, PRUword size)
{
return mmap((void *)region, size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
static void
ReleaseRegion(void *region, PRUword size)
{
munmap(region, size);
}
static bool
ProbeRegion(PRUword region, PRUword size)
{
if (madvise((void *)region, size, MADV_NORMAL)) {
return true;
} else {
return false;
}
}
static PRUword
GetDesiredRegionSize()
{
return sysconf(_SC_PAGESIZE);
}
#define RESERVE_FAILED MAP_FAILED
#endif // system dependencies
static PRUword ARENA_POISON;
static PRCallOnceType ARENA_POISON_guard;
PR_STATIC_ASSERT(sizeof(PRUword) == 4 || sizeof(PRUword) == 8);
PR_STATIC_ASSERT(sizeof(PRUword) == sizeof(void *));
static PRStatus
ARENA_POISON_init()
{
PRUword rgnsize = GetDesiredRegionSize();
if (sizeof(PRUword) == 8) {
// Use the hardware-inaccessible region.
// We have to avoid 64-bit constants and shifts by 32 bits, since this
// code is compiled in 32-bit mode, although it is never executed there.
ARENA_POISON =
(((PRUword(0x7FFFFFFFu) << 31) << 1 | PRUword(0xF0DEAFFFu))
& ~(rgnsize-1))
+ rgnsize/2 - 1;
return PR_SUCCESS;
} else {
// Probe 1024 pages (four megabytes, typically) in both directions from
// the baseline address before giving up.
PRUword candidate = (0xF0DEAFFF & ~(rgnsize-1));
PRUword step = rgnsize;
int direction = +1;
PRUword limit = candidate + 1024*rgnsize;
while (candidate < limit) {
void *result = ReserveRegion(candidate, rgnsize);
if (result == (void *)candidate) {
// success - inaccessible page allocated
ARENA_POISON = candidate + rgnsize/2 - 1;
return PR_SUCCESS;
} else {
if (result != RESERVE_FAILED)
ReleaseRegion(result, rgnsize);
if (ProbeRegion(candidate, rgnsize)) {
// success - selected page cannot be usable memory
ARENA_POISON = candidate + rgnsize/2 - 1;
return PR_SUCCESS;
}
}
candidate += step*direction;
step = step + rgnsize;
direction = -direction;
}
NS_RUNTIMEABORT("no usable poison region identified");
return PR_FAILURE;
}
}
#if defined(__x86_64__) || defined(_M_AMD64)
const PRUword ARENA_POISON = 0x7FFFFFFFF0DEA7FF;
#else
// This evaluates to 0xF0DE_A7FF when PRUword is 32 bits long, but to
// 0xFFFF_FFFF_F0DE_A7FF when it's 64 bits.
const PRUword ARENA_POISON = (~PRUword(0x0FFFFF00) | PRUword(0x0DEA700));
#endif
// All keys to this hash table fit in 32 bits (see below) so we do not
// bother actually hashing them.
namespace {
class FreeList : public PLDHashEntryHdr
{
public:
@@ -119,6 +247,8 @@ protected:
friend class nsTHashtable<FreeList>;
};
}
struct nsPresArena::State {
nsTHashtable<FreeList> mFreeLists;
PLArenaPool mPool;
@@ -127,6 +257,7 @@ struct nsPresArena::State {
{
mFreeLists.Init();
PL_INIT_ARENA_POOL(&mPool, "PresArena", ARENA_PAGE_SIZE);
PR_CallOnce(&ARENA_POISON_guard, ARENA_POISON_init);
}
~State()