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 aa06237084
commit 3c349b25a6
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()

View File

@ -47,8 +47,19 @@ DIRS += \
chrome \
$(NULL)
# We can't use CPP_UNIT_TESTS for TestPoisonArea because libxpcom
# (which it does not need) isn't available at this point in the build.
BARE_UNIT_TESTS = \
TestPoisonArea.cpp \
$(NULL)
CPPSRCS += $(BARE_UNIT_TESTS)
SIMPLE_PROGRAMS += $(BARE_UNIT_TESTS:.cpp=$(BIN_SUFFIX))
include $(topsrcdir)/config/rules.mk
DEFINES += -D_IMPL_NS_LAYOUT
_TEST_FILES = \
@ -180,3 +191,9 @@ endif
libs:: $(_TEST_FILES)
$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
check::
@$(EXIT_ON_ERROR) \
for f in $(subst .cpp,,$(BARE_UNIT_TESTS)); do \
$(RUN_TEST_PROGRAM) $(DIST)/bin/$$f; \
done

View File

@ -0,0 +1,509 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is frame poisoning tests.
*
* The Initial Developer of the Original Code is the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Zachary Weinberg <zweinberg@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK *****
*/
/* Code in this file needs to be kept in sync with code in nsPresArena.cpp.
*
* We want to use a fixed address for frame poisoning so that it is readily
* identifiable in crash dumps. Whether such an address is available
* without any special setup depends on the system configuration.
*
* All current 64-bit CPUs (with the possible exception of PowerPC64)
* reserve the vast majority of the virtual address space for future
* hardware extensions; valid addresses must be below some break point
* between 2**48 and 2**54, depending on exactly which chip you have. Some
* chips (notably amd64) also allow the use of the *highest* 2**48 -- 2**54
* addresses. Thus, if user space pointers are 64 bits wide, we can just
* use an address outside this range, and no more is required. To
* accommodate the chips that allow very high addresses to be valid, the
* value chosen is close to 2**63 (that is, in the middle of the space).
*
* In most cases, a purely 32-bit operating system must reserve some
* fraction of the address space for its own use. Contemporary 32-bit OSes
* tend to take the high gigabyte or so (0xC000_0000 on up). If we can
* prove that high addresses are reserved to the kernel, we can use an
* address in that region. Unfortunately, not all 32-bit OSes do this;
* OSX 10.4 might not, and it is unclear what mobile OSes are like
* (some 32-bit CPUs make it very easy for the kernel to exist in its own
* private address space).
*
* Furthermore, when a 32-bit user space process is running on a 64-bit
* kernel, the operating system has no need to reserve any of the space that
* the process can see, and generally does not do so. This is the scenario
* of greatest concern, since it covers all contemporary OSX iterations
* (10.5+) as well as Windows Vista and 7 on newer amd64 hardware. Linux on
* amd64 is generally run as a pure 64-bit environment, but its 32-bit
* compatibility mode also has this property.
*
* Thus, when user space pointers are 32 bits wide, we need to validate
* our chosen address, and possibly *make* it a good poison address by
* allocating a page around it and marking it inaccessible. The algorithm
* for this is:
*
* 1. Attempt to make the page surrounding the poison address a reserved,
* inaccessible memory region using OS primitives. On Windows, this is
* done with VirtualAlloc(MEM_RESERVE); on Unix, mmap(PROT_NONE).
*
* 2. If mmap/VirtualAlloc failed, there are two possible reasons: either
* the region is reserved to the kernel and no further action is
* required, or there is already usable memory in this area and we have
* to pick a different address. The tricky part is knowing which case
* we have, without attempting to access the region. On Windows, we
* rely on GetSystemInfo()'s reported upper and lower bounds of the
* application memory area. On Unix, there is nothing devoted to the
* purpose, but seeing if madvise() fails is close enough (it *might*
* disrupt someone else's use of the memory region, but not by as much
* as anything else available).
*
* Be aware of these gotchas:
*
* 1. We cannot use mmap() with MAP_FIXED. MAP_FIXED is defined to
* _replace_ any existing mapping in the region, if necessary to satisfy
* the request. Obviously, as we are blindly attempting to acquire a
* page at a constant address, we must not do this, lest we overwrite
* someone else's allocation.
*
* 2. For the same reason, we cannot blindly use mprotect() if mmap() fails.
*
* 3. madvise() may fail when applied to a 'magic' memory region provided as
* a kernel/user interface. Fortunately, the only such case I know about
* is the "vsyscall" area (not to be confused with the "vdso" area) for
* *64*-bit processes on Linux - and we don't even run this code for
* 64-bit processes.
*
* 4. VirtualQuery() does not produce any useful information if
* applied to kernel memory - in fact, it doesn't write its output
* at all. Thus, it is not used here.
*/
// MAP_ANON(YMOUS) is not in any standard, and the C99 PRI* macros are
// not in C++98. Add defines as necessary.
#define __STDC_FORMAT_MACROS
#define _GNU_SOURCE 1
#define _DARWIN_C_SOURCE 1
#include <stddef.h>
#ifndef _WIN32
#include <inttypes.h>
#else
#define PRIxPTR "Ix"
typedef unsigned int uint32_t;
// MSVC defines uintptr_t in <crtdefs.h> which is brought in implicitly
#endif
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.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
#define SIZxPTR ((int)(sizeof(uintptr_t)*2))
/* This program assumes that a whole number of return instructions fit into
* 32 bits, and that 32-bit alignment is sufficient for a branch destination.
*/
#if defined __i386__ || defined __x86_64__ || \
defined _M_IX86 || defined _M_AMD64
#define RETURN_INSTR 0xC3C3C3C3 /* ret; ret; ret; ret */
#elif defined __arm__ || defined _M_ARM
#define RETURN_INSTR 0xE12FFF1E /* bx lr */
// PPC has its own style of CPU-id #defines. There is no Windows for
// PPC as far as I know, so no _M_ variant.
#elif defined _ARCH_PPC || defined _ARCH_PWR || defined _ARCH_PWR2
#define RETURN_INSTR 0x4E800020 /* blr */
#else
#error "Need return instruction for this architecture"
#endif
// Miscellaneous Windows/Unix portability gumph
#ifdef _WIN32
// Uses of this function deliberately leak the string.
static LPSTR
StrW32Error(DWORD errcode)
{
LPSTR errmsg;
#ifndef WINCE
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errcode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR) &errmsg, 0, NULL);
// FormatMessage puts an unwanted newline at the end of the string
size_t n = strlen(errmsg)-1;
while (errmsg[n] == '\r' || errmsg[n] == '\n') n--;
errmsg[n+1] = '\0';
#else
// CE doesn't have FormatMessageA so we just stringify the error code.
// Use LocalAlloc for consistency with the regular Windows code path.
// "code \0" is 6 bytes, and a 32-bit number might need 10 more.
errmsg = (LPSTR)LocalAlloc(LMEM_FIXED, 16);
_snprintf(errmsg, 16, "code %u", errcode);
#endif
return errmsg;
}
#define LastErrMsg() (StrW32Error(GetLastError()))
// Because we use VirtualAlloc in MEM_RESERVE mode, the "page size" we want
// is the allocation granularity.
static SYSTEM_INFO _sinfo;
#undef PAGESIZE
#define PAGESIZE (_sinfo.dwAllocationGranularity)
static void *
ReserveRegion(uintptr_t request, bool accessible)
{
return VirtualAlloc((void *)request, PAGESIZE,
accessible ? MEM_RESERVE|MEM_COMMIT : MEM_RESERVE,
accessible ? PAGE_EXECUTE_READWRITE : PAGE_NOACCESS);
}
static void
ReleaseRegion(void *page)
{
VirtualFree(page, PAGESIZE, MEM_RELEASE);
}
static bool
ProbeRegion(uintptr_t page)
{
if (page >= (uintptr_t)_sinfo.lpMaximumApplicationAddress &&
page + PAGESIZE >= (uintptr_t)_sinfo.lpMaximumApplicationAddress) {
return true;
} else {
return false;
}
}
static bool
MakeRegionExecutable(void *)
{
return false;
}
#undef MAP_FAILED
#define MAP_FAILED 0
#else
#define LastErrMsg() (strerror(errno))
static unsigned long _pagesize;
#define PAGESIZE _pagesize
static void *
ReserveRegion(uintptr_t request, bool accessible)
{
return mmap((void *)request, PAGESIZE,
accessible ? PROT_READ|PROT_WRITE : PROT_NONE,
MAP_PRIVATE|MAP_ANON, -1, 0);
}
static void
ReleaseRegion(void *page)
{
munmap(page, PAGESIZE);
}
static bool
ProbeRegion(uintptr_t page)
{
if (madvise((void *)page, PAGESIZE, MADV_NORMAL)) {
return true;
} else {
return false;
}
}
static int
MakeRegionExecutable(void *page)
{
return mprotect(page, PAGESIZE, PROT_READ|PROT_WRITE|PROT_EXEC);
}
#endif
static uintptr_t
ReservePoisonArea()
{
if (sizeof(uintptr_t) == 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.
uintptr_t result = (((uintptr_t(0x7FFFFFFFu) << 31) << 1 |
uintptr_t(0xF0DEAFFFu)) &
~uintptr_t(PAGESIZE-1));
printf("INFO | poison area assumed at 0x%.*"PRIxPTR"\n", SIZxPTR, result);
return result;
} else {
// Probe 1024 pages (four megabytes, typically) in both directions from
// the baseline address before giving up.
uintptr_t candidate = (0xF0DEAFFF & ~(uintptr_t)(PAGESIZE-1));
uintptr_t step = PAGESIZE;
intptr_t direction = +1;
uintptr_t limit = candidate + 1024*PAGESIZE;
while (candidate < limit) {
void *result = ReserveRegion(candidate, false);
if (result == (void *)candidate) {
// success - inaccessible page allocated
printf("INFO | poison area allocated at 0x%.*"PRIxPTR"\n",
SIZxPTR, (uintptr_t)result);
return candidate;
} else {
if (result != MAP_FAILED)
ReleaseRegion(result);
if (ProbeRegion(candidate)) {
// success - selected page cannot be usable memory
printf("INFO | poison area probed at 0x%.*"PRIxPTR" | %s\n",
SIZxPTR, candidate, LastErrMsg());
return candidate;
}
}
candidate += step*direction;
step = step + PAGESIZE;
direction = -direction;
}
printf("ERROR | no usable poison area found\n");
return 0;
}
}
/* The "positive control" area confirms that we can allocate a page with the
* proper characteristics.
*/
static uintptr_t
ReservePositiveControl()
{
void *result = ReserveRegion(0, false);
if (result == MAP_FAILED) {
printf("ERROR | allocating positive control | %s\n", LastErrMsg());
return 0;
}
printf("INFO | positive control allocated at 0x%.*"PRIxPTR"\n",
SIZxPTR, (uintptr_t)result);
return (uintptr_t)result;
}
/* The "negative control" area confirms that our probe logic does detect a
* page that is readable, writable, or executable.
*/
static uintptr_t
ReserveNegativeControl()
{
void *result = ReserveRegion(0, true);
if (result == MAP_FAILED) {
printf("ERROR | allocating negative control | %s\n", LastErrMsg());
return 0;
}
// Fill the page with return instructions.
uint32_t *p = (uint32_t *)result;
uint32_t *limit = (uint32_t *)(((char *)result) + PAGESIZE);
while (p < limit)
*p++ = RETURN_INSTR;
// Now mark it executable as well as readable and writable.
// (mmap(PROT_EXEC) may fail when applied to anonymous memory.)
if (MakeRegionExecutable(result)) {
printf("ERROR | making negative control executable | %s\n", LastErrMsg());
return 0;
}
printf("INFO | negative control allocated at 0x%.*"PRIxPTR"\n",
SIZxPTR, (uintptr_t)result);
return (uintptr_t)result;
}
/* Test each page. */
static bool
TestPage(const char *pagelabel, uintptr_t pageaddr, int should_succeed)
{
const char *oplabel;
uintptr_t opaddr;
bool failed = false;
for (unsigned int test = 0; test < 3; test++) {
switch (test) {
// The execute test must be done before the write test, because the
// write test will clobber memory at the target address.
case 0: oplabel = "reading"; opaddr = pageaddr + PAGESIZE/2 - 1; break;
case 1: oplabel = "executing"; opaddr = pageaddr + PAGESIZE/2; break;
case 2: oplabel = "writing"; opaddr = pageaddr + PAGESIZE/2 - 1; break;
default: abort();
}
#ifdef _WIN32
__try {
unsigned char scratch;
switch (test) {
case 0: scratch = *(volatile unsigned char *)opaddr; break;
case 1: ((void (*)())opaddr)(); break;
case 2: *(volatile unsigned char *)opaddr = 0; break;
default: abort();
}
// if control reaches this point the probe succeeded
if (should_succeed) {
printf("TEST-PASS | %s %s\n", oplabel, pagelabel);
} else {
printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel);
failed = true;
}
} __except (EXCEPTION_EXECUTE_HANDLER) {
// Unfortunately, there is no equivalent of strsignal().
DWORD code = GetExceptionCode();
if (should_succeed) {
printf("TEST-UNEXPECTED-FAIL | %s %s | exception code %x\n",
oplabel, pagelabel, code);
failed = true;
} else {
printf("TEST-PASS | %s %s | exception code %x\n",
oplabel, pagelabel, code);
}
}
#else
pid_t pid = fork();
if (pid == -1) {
printf("ERROR | %s %s | fork=%s\n", oplabel, pagelabel,
LastErrMsg());
exit(2);
} else if (pid == 0) {
unsigned char scratch;
switch (test) {
case 0: scratch = *(volatile unsigned char *)opaddr; break;
case 1: ((void (*)())opaddr)(); break;
case 2: *(volatile unsigned char *)opaddr = 0; break;
default: abort();
}
_exit(0);
} else {
int status;
if (waitpid(pid, &status, 0) != pid) {
printf("ERROR | %s %s | wait=%s\n", oplabel, pagelabel,
LastErrMsg());
exit(2);
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
if (should_succeed) {
printf("TEST-PASS | %s %s\n", oplabel, pagelabel);
} else {
printf("TEST-UNEXPECTED-FAIL | %s %s\n", oplabel, pagelabel);
failed = true;
}
} else if (WIFEXITED(status)) {
printf("ERROR | %s %s | unexpected exit code %d\n",
oplabel, pagelabel, WEXITSTATUS(status));
exit(2);
} else if (WIFSIGNALED(status)) {
if (should_succeed) {
printf("TEST-UNEXPECTED-FAIL | %s %s | %s\n",
oplabel, pagelabel, strsignal(WTERMSIG(status)));
failed = true;
} else {
printf("TEST-PASS | %s %s | %s\n",
oplabel, pagelabel, strsignal(WTERMSIG(status)));
}
} else {
printf("ERROR | %s %s | unexpected exit status %d\n",
oplabel, pagelabel, status);
exit(2);
}
}
#endif
}
return failed;
}
int
main()
{
#ifdef _WIN32
GetSystemInfo(&_sinfo);
#else
_pagesize = sysconf(_SC_PAGESIZE);
#endif
uintptr_t ncontrol = ReserveNegativeControl();
uintptr_t pcontrol = ReservePositiveControl();
uintptr_t poison = ReservePoisonArea();
if (!ncontrol || !pcontrol || !poison)
return 2;
bool failed = false;
failed |= TestPage("negative control", ncontrol, 1);
failed |= TestPage("positive control", pcontrol, 0);
failed |= TestPage("poison area", poison, 0);
return failed ? 1 : 0;
}