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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user