OpenSolaris_b135/cmd/amt/amt.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * Abstract Machine Test; executes memory access tests to show
 * compliance with Common Criteria object reuse and process address
 * space separation requirements.
 */
#include <errno.h>
#include <fcntl.h>
#include <iso/stdlib_iso.h>
#include <libelf.h>
#include <libintl.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>

#define	NOT_SILENT 0
#define	SILENT_MODE 1

#define	CHILD_SLEEP_PERIOD 2
#define	PARENT_SLEEP_PERIOD 1
#define	SIG_EVENT SIGUSR1

#define	PASS	   0		/* test passed, no SEGV */
#define	FAIL_ZERO  1		/* expected to read zero, didn't */
#define	FAIL_SEGV  2		/* expected good read or write, didn't */
#define	PASS_SEGV  3		/* expected SEGV, got it */
#define	FAIL_ABORT 4		/* test logic error */

#define	PH_VALID   0		/* arg for probe_hole -- do valid memory */
				/* access */
#define	PH_INVALID 1		/* do illegal memory access */

#define	WASTE_PAGES   8		/* a guess at where virgin stack space */
				/* is likely to exist */
#define	STACK_SLOP  256		/* a guess at how far below current end */
				/* of stack I'll find unused space */

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif

extern int _end;	/* first address after the end of initialized data */

static int  data_boundary_test();
static void handler(int);
static int  memory_not_shared_after_use();
static int  memory_allocation_not_shared();
static int  memory_type(const char *);
static void print_message(char *);
static void probe_data_area(void);
static void probe_hole(int);
static void probe_stack(void);
static void probe_text_area(void);
static void segv_action(int, siginfo_t *, void *);
static void set_handler(int);
static int  test_stack_end_of_hole();
static int  text_area_not_writeable();

static int done_memory_grab = 0;
static int silent;
static int handler_exit_code;

/*
 * Main Routine
 */
int
main(int argc, char *argv[])
{
	int fail_count = 0;
	int status = 0;
	int bitsize;

	/* Internationalization */
	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	silent = NOT_SILENT;

	if (argc == 2) {
		/* Pull out argument provided		 */
		/* -s	silent mode, no status or error messages. */
		if (strncmp(argv[1], "-s", 4) == 0)
			silent = SILENT_MODE;
		else {
			/* Wrong argument */
			(void) fprintf(stderr, gettext(
			    "Wrong argument, USAGE: amt [-s]\n"));
			exit(EXIT_FAILURE);
		}
	} else if (argc != 1) {
		/* Illegal number of arguments. */
		(void) fprintf(stderr, gettext(
		    "Wrong usage, USAGE: amt [-s]\n"));
		exit(EXIT_FAILURE);
	}
	bitsize = memory_type(argv[0]);

	if (silent == NOT_SILENT)
		(void) printf(gettext(
		    "\n\nAMT Test Program -- %d bit application\n"
		    "================\n"), bitsize);
	/*
	 * test_stack_end_of_hole must be the first test, or the stack
	 * is of an unknown size.
	 */
	if ((status = test_stack_end_of_hole()) == EXIT_FAILURE) {
		/* Normal fail */
		fail_count++;
		print_message(gettext("TEST 1 FAILED\n"));
	} else if (status == FAIL_ABORT) {
		/* Test logic failure */
		fail_count++;
		print_message(gettext("FAIL: Logic error in test\n"));
	} else if (status == EXIT_SUCCESS)
		print_message(gettext("TEST 1 PASSED\n"));

	/* Carry out test 2 */
	if (data_boundary_test() != EXIT_SUCCESS) {
		fail_count++;
		print_message(gettext("TEST 2 FAILED\n"));
	} else
		print_message(gettext("TEST 2 PASSED\n"));

	/* Carry out test 3 */
	if (text_area_not_writeable() != EXIT_SUCCESS) {
		fail_count++;
		print_message(gettext("TEST 3 FAILED\n"));
	} else
		print_message(gettext("TEST 3 PASSED\n"));

	/* Carry out test 4 */
	if (memory_not_shared_after_use() != EXIT_SUCCESS) {
		fail_count++;
		print_message(gettext("TEST 4 FAILED\n"));
	} else
		print_message(gettext("TEST 4 PASSED\n"));

	/* Carry out test 5 */
	if (memory_allocation_not_shared() != EXIT_SUCCESS) {
		fail_count++;
		print_message(gettext("TEST 5 FAILED\n"));
	} else
		print_message(gettext("TEST 5 PASSED\n"));

	if (silent == NOT_SILENT) {
		if (fail_count > 0)
			(void) printf(gettext("\n %d TESTS FAILED\n\n"),
			    fail_count);
		else
			(void) printf(gettext("\nTESTS SUCCEEDED\n\n"));
	}
	return (fail_count);
}

/*
 * Test the data boundaries. First test inside the data area at the boundary
 * of the "hole" area. Second test inside the data area at the text area
 * boundary. Both should pass.
 */
static int
data_boundary_test()
{
	int exit_status = EXIT_SUCCESS;
	pid_t pid;
	int status;

	print_message(gettext("\n\nTest 2- Data Side Boundary Test.\n"));

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* Am I my child? */
		set_handler(SIGSEGV);

		/* probe_data_area() does exit() */
		probe_data_area();
	}
	/* still parent */
	(void) wait(&status);
	status = WEXITSTATUS(status);

	if (status == PASS)
		print_message(gettext(
		    "PASS: Successful read/write in data area.\n"));
	else if (status == FAIL_SEGV) {
		print_message(gettext(
		    "FAIL: Caught a segmentation fault while "
		    "attempting to write to the data area.\n"));
		exit_status = EXIT_FAILURE;
	} else {
		(void) printf(gettext("Test program failure: %d\n"),
		    status);
		exit_status = EXIT_FAILURE;
	}
	return (exit_status);
}

static void
probe_data_area()
{
	int *p;
	/* LINTED */
	volatile int p1;
	void *address;

	/* set handler status */
	handler_exit_code = FAIL_SEGV;

	/*
	 * Get an address in the data area, near to the "hole".
	 * sbrk returns prior address value; rather than calculating
	 * the sbrk result, sbrk is called twice, so address points
	 * to the new end of data
	 */
	(void) sbrk(PAGESIZE);
	address = sbrk(0);
	/*
	 * Get to the inside edge of a page boundary
	 * two integer words short of a new page
	 */
	p = ((int *)P2ROUNDUP((uintptr_t)address, PAGESIZE)) - 2;

	/* Try writing to it, shouldn't cause a segmentation fault. */
	*p = 9999;

	/* Should be able to read back with no problems. */
	p1 = *p;

	/*
	 * Get an address near the text area boundary, but in the data
	 * area.  _etext rounded up a page isn't correct since the
	 * initialized data area isn't writeable.
	 *
	 * Future versions should consider handling initialized data
	 * separately -- writing to initialized data should generate
	 * a fault.
	 */
	p = &_end;

	/* Try writing to it, should succeed. */
	*p = 9898;

	/* Should be able to read back with no problems. */
	p1 = *p;

	exit(EXIT_SUCCESS);
}

/*
 * Test that we cannot write to the text area. An attempt to write to
 * the text area will result in a segmentation fault. So if we catch it,
 * test has succeed, else it has failed.
 */
static int
text_area_not_writeable()
{
	int exit_status = EXIT_SUCCESS;
	pid_t pid;
	int status;

	print_message(gettext(
	    "\n\nTest 3- Text Area Not Writeable\n"
	    "Verify that a write to the text space does not cause "
	    "a write to the executable\n"
	    "file from which it came, or to another process which "
	    "shares that text.\n"));

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* Am I my child? */
		set_handler(SIGSEGV);

		/* probe_text_area() does exit() */
		probe_text_area();
	}
	/* still parent */
	(void) wait(&status);
	status = WEXITSTATUS(status);

	if (status == PASS) {
		print_message(gettext(
		    "FAIL: We did not cause a segmentation fault.\n"));
		exit_status = EXIT_FAILURE;
	} else if (status == FAIL_SEGV) {
		print_message(gettext(
		    "PASS: Caught the segmentation fault, "
		    "meaning we can't write to text area.\n"));
	} else {
		(void) printf(gettext(
		    "Test program failure: %d\n"), status);
		exit_status = EXIT_FAILURE;
	}
	return (exit_status);
}

/*
 * write to text area, trigger a SEGV
 */
static void
probe_text_area()
{
	handler_exit_code = FAIL_SEGV;
	*(caddr_t)probe_text_area = 0xff;
	exit(EXIT_FAILURE);
}

/*
 * Test that when we set some values and fork a process, when the child
 * writes to these inherited values, the parents copies are not changed.
 */
static int
memory_not_shared_after_use()
{
	pid_t pid;
	int x = 1000;
	int exit_status = EXIT_SUCCESS;

	print_message(gettext("\n\nTest 4- Memory Not Shared After Write\n"
	    "Verify that anonymous memory initially shared by two "
	    "processes (e.g. after a\n"
	    "fork) is not shared after either process writes "
	    "to it.\n"));

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* I am the child. */
		/*
		 * Change child value; this should not change
		 * parent value.
		 */
		x = 2000;

		/* Wait for parent to test value */
		(void) sleep(CHILD_SLEEP_PERIOD);

		exit(EXIT_SUCCESS);
	}
	/* Wait for child to do its stuff. */
	(void) sleep(PARENT_SLEEP_PERIOD);

	if (x == 1000)
		exit_status = EXIT_SUCCESS;
	else
		exit_status = EXIT_FAILURE;

	return (exit_status);
}

/*
 * If we fork a process and then allocate some memory in that process,
 * we should not see any memory changes in the parent.
 */
static int
memory_allocation_not_shared()
{
	pid_t pid;
	pid_t parent_pid;
	int exit_status = 0;
	caddr_t address;
	caddr_t hole_start;
	caddr_t hole_after;
	void (*old_handler) ();

	print_message(gettext(
	    "\n\nTest 5- Memory Allocation is Not Shared\n"
	    "Verify that newly allocated memory in one of two "
	    "processes created by forking\n"
	    "does not result in newly allocated memory in the other.\n"));

	/* Save Size of data area and 1st block address of "hole" */
	hole_start = (caddr_t)sbrk(0);

	if (silent == NOT_SILENT)
		(void) printf(gettext(
		    "Parent address of hole before child change: %08X\n"),
		    hole_start);

	/* Set handler for signal SIG_EVENT (define at start) */
	old_handler = signal(SIG_EVENT, &handler);
	if (old_handler == SIG_ERR) {
		print_message(gettext(
		    "Can't establish signal handler, test failed\n"));
		return (EXIT_FAILURE);
	}

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* We are the child. */
		address = sbrk(0);
		if (silent == NOT_SILENT)
			(void) printf(gettext(
			    "Child end of hole before change:  %08X\n"),
			    address);

		if (brk((address+PAGESIZE)) != 0) {
			print_message(gettext(
			    "Can't change start of hole address.\n"));
			exit(EXIT_FAILURE);
		}

		address = sbrk(0);
		if (silent == NOT_SILENT)
			(void) printf(gettext(
			    "Child end of hole after change: %08X\n"),
			    address);

		/* Tell the parent we're done. */
		parent_pid = getppid();
		if (sigsend(P_PID, parent_pid, SIG_EVENT) != 0) {
			print_message(gettext("Can't send signal to parent, "
			    "test failed\n"));
			exit(EXIT_FAILURE);
		}

		/* Sleep before exiting to allow parent to finish processing. */
		(void) sleep(CHILD_SLEEP_PERIOD);
		exit(EXIT_SUCCESS);
	}
	/* Wait for child to do its work. */
	(void) sleep(PARENT_SLEEP_PERIOD);

	if (done_memory_grab != 1) {
		print_message(gettext(
		    "Child failed to do memory alterations, "
		    "exiting\n"));
		return (EXIT_FAILURE);
	}

	hole_after = sbrk(0);
	if (silent == NOT_SILENT)
		(void) printf(gettext(
		    "Parent address of hole after child change: "
		    "%08X\n"), hole_after);

	/* Test size of hole and data region. */
	if (hole_start == hole_after)
		print_message(gettext(
		    "PASS: Hole is same size in parent.\n"));
	else {
		print_message(gettext(
		    "FAIL: Hole is a different size.\n"));
		exit_status = EXIT_FAILURE;
	}

	/* Wait for child to finish. */
	(void) wait(0);

	if (signal(SIG_EVENT, old_handler) == SIG_ERR) {
		print_message(gettext("Couldn't put back old signal handler, "
		    "test failed.\n"));
		return (EXIT_FAILURE);
	}
	return (exit_status);
}

static void
print_message(char *message)
{
	if (silent == NOT_SILENT)
		(void) printf("%s", message);
}

static int
test_stack_end_of_hole()
{
	pid_t pid;
	int status;
	int exit_status = EXIT_SUCCESS;

	print_message(gettext("\n\nTest 1- stack Side Boundary Test\n"));

	/* sub test 1:  the space the stack grows into is zero */

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* Am I my child? */
		set_handler(SIGSEGV);

		/* probe_stack() does exit */
		probe_stack();
	}
	/* still parent */
	(void) wait(&status);
	status = WEXITSTATUS(status);

	if (status == FAIL_ZERO) {
		print_message(gettext("Fail with non-zero read.\n"));
		exit_status = EXIT_FAILURE;
	} else if (status != PASS) {
		print_message(gettext("Test program failure\n"));
		exit_status = EXIT_FAILURE;
	}
	/* sub test 2:  the space in hole is not readable */

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* Am I my child? */
		set_handler(SIGSEGV);

		/* probe_hole does exit */
		probe_hole(PH_INVALID);
	}
	/* still parent */
	(void) wait(&status);
	status = WEXITSTATUS(status);

	if (status == FAIL_SEGV) {
		print_message(
		    gettext("Fail (SEGV expected, not received).\n"));
		exit_status = EXIT_FAILURE;
	} else if (status != PASS_SEGV) {
		print_message(gettext("Test program failure.\n"));
		exit_status = EXIT_FAILURE;
	}

	/* sub test 3:  the space in new page below hole is zero */

	if ((pid = fork()) == -1) {
		print_message(gettext("Fork failed\n"));
		return (EXIT_FAILURE);
	} else if (pid == 0) { /* Am I my child? */
		set_handler(SIGSEGV);

		/* probe_hole does exit */
		probe_hole(PH_VALID);
	}
	/* still parent */
	(void) wait(&status);
	status = WEXITSTATUS(status);

	if (status == FAIL_SEGV) {
		print_message(gettext("Fail (got SEGV).\n"));
		exit_status = EXIT_FAILURE;
	} else if (status != PASS) {
		print_message(gettext("Test program failure.\n"));
		exit_status = EXIT_FAILURE;
	}
	return (exit_status);
}


/*
 * set_handler
 */
static void
set_handler(int sig)
{
	struct sigaction act;

	act.sa_handler = NULL;
	act.sa_flags = SA_SIGINFO;
	act.sa_sigaction = segv_action;
	(void) sigemptyset(&(act.sa_mask));

	if (sigaction(sig, &act, NULL) < 0) {
		if (silent == NOT_SILENT) {
			(void) fprintf(stderr, gettext(
			    "sigaction() returned error: %s\n"),
			    strerror(errno));
		}
		exit(EXIT_FAILURE);
	}
}


/*ARGSUSED*/
static void
segv_action(int which_sig, siginfo_t *t1, void *t2)
{
	exit(handler_exit_code);
}

/*
 * probe_stack
 *
 * Warning -- if you do a printf or fprintf prior to the actual
 * reading from the stack, you've changed the stack to an unknown
 * state.  (stack memory isn't free'd automatically and this function
 * needs to touch virgin stack space.)
 */
static void
probe_stack(void)
{
	unsigned char *end;	/* end of stack */
	unsigned char probe;
	long i;
	int j;
	unsigned char last_fail, *last_fail_address;
	unsigned char mark = 0xAA;	/* roughly the end of stack */
	handler_exit_code = FAIL_SEGV;

	end = &mark;
	/* stack growth is negative */
	end -= (WASTE_PAGES * PAGESIZE) + STACK_SLOP;

	for (i = 0, j = 0; i < PAGESIZE; i++) {
		if ((probe = *end) != 0) {
			j++;
			last_fail = probe;
			last_fail_address = end;
		}
		end--;
	}

	if (j != 0) {
		if (silent == NOT_SILENT)
			(void) fprintf(stderr, gettext(
			    "probe_stack failed. address=0x%08X; "
			    "probe=0x%02X; content = %d\n"),
			    (caddr_t)last_fail_address, last_fail, j);

		exit(FAIL_ZERO);    /* test failed at least once */
	}
	exit(EXIT_SUCCESS);
}

static void
probe_hole(int test_type)
{
	long i;
	/* LINTED */
	volatile unsigned char probe;
	unsigned char *probe_adr;
	void *address;

	address = sbrk(0);  /* current end data + 1 */

	if (address == (void *)-1) {
		print_message(gettext("Test program logic error\n"));
		exit(FAIL_ABORT);
	}
	if (test_type == PH_VALID) {
		/* show that access works inside the  hole */
		handler_exit_code = FAIL_SEGV;

		probe_adr =  (unsigned char *)address - sizeof (char);

		for (i = 0; i < PAGESIZE; i++)
			probe = *probe_adr--;

		exit(EXIT_SUCCESS);
	} else {
		/* show that a trap occurs in the  hole */
		handler_exit_code = PASS_SEGV;

		address = (void *)P2ROUNDUP((uintptr_t)address, PAGESIZE);
		probe_adr = (unsigned char *)address;

		probe = *probe_adr;
		exit(FAIL_SEGV);	/* expected SEGV, didn't get it */
	}
}

/*
 * Catch signal, child to parent
 */
/*ARGSUSED*/
void
handler(int signal)
{
	done_memory_grab = 1;
}
/*
 * memory_type:  Determine whether a given executable file is compiled
 * as 32 or 64 bit.
 *
 * The following code was stolen from isainfo (1)
 */

static int
memory_type(const char *path) {
	char *idarray;
	Elf *elf;
	int d;
	int bits = 0;

	if ((d = open(path, O_RDONLY)) < 0) {
		(void) fprintf(stderr,
		    "cannot open: %s -- %s\n",
		    path, strerror(errno));
		return (bits);
	}

	if (elf_version(EV_CURRENT) == EV_NONE) {
		(void) fprintf(stderr,
		    "internal error: ELF library out of date?\n");
		(void) close(d);
		return (bits);
	}

	elf = elf_begin(d, ELF_C_READ, (Elf *)0);
	if (elf_kind(elf) != ELF_K_ELF) {
		(void) elf_end(elf);
		(void) close(d);
		return (bits);
	}

	idarray = elf_getident(elf, 0);

	if (idarray[EI_CLASS] == ELFCLASS32) {
		bits = 32;
	} else if (idarray[EI_CLASS] == ELFCLASS64) {
		bits = 64;
	}

	(void) elf_end(elf);
	(void) close(d);
	return (bits);
}