diff --git a/defconfig b/defconfig
index 0574f1d..2d9c092 100644
--- a/defconfig
+++ b/defconfig
@@ -78,3 +78,9 @@ CONFIG_KEY_DH_OPERATIONS=y
 # Test key management socket.
 # This is not required for SELinux operation itself.
 CONFIG_NET_KEY=m
+
+# Test perf events.
+# This is not required for SELinux operation itself.
+CONFIG_HAVE_PERF_EVENTS=y
+CONFIG_PERF_EVENTS=y
+CONFIG_TRACEPOINTS=y
diff --git a/policy/Makefile b/policy/Makefile
index dda2e16..3f60991 100644
--- a/policy/Makefile
+++ b/policy/Makefile
@@ -101,6 +101,10 @@ ifeq ($(shell grep -q module_load $(POLDEV)/include/support/all_perms.spt && ech
 TARGETS+=test_module_load.te
 endif
 
+ifeq ($(shell grep -q perf_event $(POLDEV)/include/support/all_perms.spt && echo true),true)
+TARGETS += test_perf_event.te
+endif
+
 ifeq (x$(DISTRO),$(filter x$(DISTRO),xRHEL4 xRHEL5 xRHEL6))
 TARGETS:=$(filter-out test_overlayfs.te test_mqueue.te test_ibpkey.te, $(TARGETS))
 endif
diff --git a/policy/test_perf_event.te b/policy/test_perf_event.te
new file mode 100644
index 0000000..67250a4
--- /dev/null
+++ b/policy/test_perf_event.te
@@ -0,0 +1,89 @@
+#
+######### perf_event policy module ##########
+#
+attribute perfdomain;
+
+################# Allow perf_event { * } ##########################
+type test_perf_t;
+domain_type(test_perf_t)
+unconfined_runs_test(test_perf_t)
+typeattribute test_perf_t testdomain;
+typeattribute test_perf_t perfdomain;
+
+allow test_perf_t self:capability { sys_admin };
+allow test_perf_t self:perf_event { open cpu kernel tracepoint read write };
+
+################# Deny capability { sys_admin } ##########################
+type test_perf_no_admin_t;
+domain_type(test_perf_no_admin_t)
+unconfined_runs_test(test_perf_no_admin_t)
+typeattribute test_perf_no_admin_t testdomain;
+typeattribute test_perf_no_admin_t perfdomain;
+
+allow test_perf_no_admin_t self:perf_event { open cpu kernel tracepoint read write };
+
+################# Deny perf_event { open } ##########################
+type test_perf_no_open_t;
+domain_type(test_perf_no_open_t)
+unconfined_runs_test(test_perf_no_open_t)
+typeattribute test_perf_no_open_t testdomain;
+typeattribute test_perf_no_open_t perfdomain;
+
+allow test_perf_no_open_t self:capability { sys_admin };
+allow test_perf_no_open_t self:perf_event { cpu kernel tracepoint read write };
+
+################# Deny perf_event { cpu } ##########################
+type test_perf_no_cpu_t;
+domain_type(test_perf_no_cpu_t)
+unconfined_runs_test(test_perf_no_cpu_t)
+typeattribute test_perf_no_cpu_t testdomain;
+typeattribute test_perf_no_cpu_t perfdomain;
+
+allow test_perf_no_cpu_t self:capability { sys_admin };
+allow test_perf_no_cpu_t self:perf_event { open kernel tracepoint read write };
+
+################# Deny perf_event { kernel } ##########################
+type test_perf_no_kernel_t;
+domain_type(test_perf_no_kernel_t)
+unconfined_runs_test(test_perf_no_kernel_t)
+typeattribute test_perf_no_kernel_t testdomain;
+typeattribute test_perf_no_kernel_t perfdomain;
+
+allow test_perf_no_kernel_t self:capability { sys_admin };
+allow test_perf_no_kernel_t self:perf_event { open cpu tracepoint read write };
+
+################# Deny perf_event { tracepoint } ##########################
+type test_perf_no_tracepoint_t;
+domain_type(test_perf_no_tracepoint_t)
+unconfined_runs_test(test_perf_no_tracepoint_t)
+typeattribute test_perf_no_tracepoint_t testdomain;
+typeattribute test_perf_no_tracepoint_t perfdomain;
+
+allow test_perf_no_tracepoint_t self:capability { sys_admin };
+allow test_perf_no_tracepoint_t self:perf_event { open cpu kernel read write };
+
+################# Deny perf_event { read } ##########################
+type test_perf_no_read_t;
+domain_type(test_perf_no_read_t)
+unconfined_runs_test(test_perf_no_read_t)
+typeattribute test_perf_no_read_t testdomain;
+typeattribute test_perf_no_read_t perfdomain;
+
+allow test_perf_no_read_t self:capability { sys_admin };
+allow test_perf_no_read_t self:perf_event { open cpu kernel tracepoint write };
+
+################# Deny perf_event { write } ##########################
+type test_perf_no_write_t;
+domain_type(test_perf_no_write_t)
+unconfined_runs_test(test_perf_no_write_t)
+typeattribute test_perf_no_write_t testdomain;
+typeattribute test_perf_no_write_t perfdomain;
+
+allow test_perf_no_write_t self:capability { sys_admin };
+allow test_perf_no_write_t self:perf_event { open cpu kernel tracepoint read };
+
+#
+########### Allow these domains to be entered from sysadm domain ############
+#
+miscfiles_domain_entry_test_files(perfdomain)
+userdom_sysadm_entry_spec_domtrans_to(perfdomain)
diff --git a/tests/Makefile b/tests/Makefile
index 0d33fbf..8f66996 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -79,6 +79,10 @@ SUBDIRS+=module_load
 endif
 endif
 
+ifeq ($(shell grep -q perf_event $(POLDEV)/include/support/all_perms.spt && echo true),true)
+SUBDIRS += perf_event
+endif
+
 ifeq ($(DISTRO),RHEL4)
     SUBDIRS:=$(filter-out bounds dyntrace dyntrans inet_socket mmap nnp_nosuid overlay unix_socket, $(SUBDIRS))
 endif
diff --git a/tests/perf_event/.gitignore b/tests/perf_event/.gitignore
new file mode 100644
index 0000000..8c2f931
--- /dev/null
+++ b/tests/perf_event/.gitignore
@@ -0,0 +1 @@
+perf_event
diff --git a/tests/perf_event/Makefile b/tests/perf_event/Makefile
new file mode 100644
index 0000000..988424c
--- /dev/null
+++ b/tests/perf_event/Makefile
@@ -0,0 +1,7 @@
+TARGETS = perf_event
+LDLIBS += -lselinux
+
+all: $(TARGETS)
+
+clean:
+	rm -f $(TARGETS)
diff --git a/tests/perf_event/perf_event.c b/tests/perf_event/perf_event.c
new file mode 100644
index 0000000..8983f02
--- /dev/null
+++ b/tests/perf_event/perf_event.c
@@ -0,0 +1,178 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <asm/unistd.h>
+#include <sys/mman.h>
+#include <sys/ioctl.h>
+#include <linux/perf_event.h>
+#include <selinux/selinux.h>
+
+enum {
+	PERF_FILE_MMAP,
+	PERF_FILE,
+	PERF_MMAP
+} read_type;
+
+static void print_usage(char *progname)
+{
+	fprintf(stderr,
+		"usage:  %s [-f|-m] [-v]\n"
+		"Where:\n\t"
+		"-f  Read perf_event info using read(2)\n\t"
+		"-m  Read perf_event info using mmap(2)\n\t"
+		"    Default is to use read(2) and mmap(2)\n\t"
+		"-v  Print information\n", progname);
+	exit(-1);
+}
+
+static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
+			    int cpu, int group_fd, unsigned long flags)
+{
+	return syscall(__NR_perf_event_open, hw_event, pid, cpu,
+		       group_fd, flags);
+}
+
+int main(int argc, char **argv)
+{
+	int opt, result, page_size, mmap_size, fd;
+	long long count;
+	bool verbose = false;
+	char *context;
+	void *base;
+	struct perf_event_attr pe_attr;
+	struct perf_event_mmap_page *pe_page;
+
+	read_type = PERF_FILE_MMAP;
+
+	while ((opt = getopt(argc, argv, "fmv")) != -1) {
+		switch (opt) {
+		case 'f':
+			read_type = PERF_FILE;
+			break;
+		case 'm':
+			read_type = PERF_MMAP;
+			break;
+		case 'v':
+			verbose = true;
+			break;
+		default:
+			print_usage(argv[0]);
+		}
+	}
+
+	if (verbose) {
+		result = getcon(&context);
+		if (result < 0) {
+			fprintf(stderr, "Failed to obtain process context\n");
+			exit(-1);
+		}
+		printf("Process context:\n\t%s\n", context);
+		free(context);
+	}
+
+	/* Test perf_event { open cpu kernel tracepoint } */
+	memset(&pe_attr, 0, sizeof(struct perf_event_attr));
+	pe_attr.type = PERF_TYPE_HARDWARE | PERF_TYPE_TRACEPOINT;
+	pe_attr.size = sizeof(struct perf_event_attr);
+	pe_attr.config = PERF_COUNT_HW_INSTRUCTIONS;
+	pe_attr.disabled = 1;
+	pe_attr.exclude_hv = 1;
+
+	fd = perf_event_open(&pe_attr, -1, 1, -1, 0);
+	if (fd < 0) {
+		fprintf(stderr, "Failed perf_event_open(): %s\n",
+			strerror(errno));
+		if (errno == EACCES)
+			exit(1);
+		else
+			exit(-1);
+	}
+
+	/* Test perf_event { write }; */
+	result = ioctl(fd, PERF_EVENT_IOC_RESET, 0);
+	if (result < 0) {
+		fprintf(stderr, "Failed ioctl(PERF_EVENT_IOC_RESET): %s\n",
+			strerror(errno));
+		if (errno == EACCES)
+			result = 2;
+		goto err;
+	}
+
+	result = ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
+	if (result < 0) {
+		fprintf(stderr, "Failed ioctl(PERF_EVENT_IOC_ENABLE): %s\n",
+			strerror(errno));
+		if (errno == EACCES)
+			result = 3;
+		goto err;
+	}
+
+	result = ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+	if (result < 0) {
+		fprintf(stderr, "Failed ioctl(PERF_EVENT_IOC_DISABLE): %s\n",
+			strerror(errno));
+		if (errno == EACCES)
+			result = 4;
+		goto err;
+	}
+
+	/* Test mmap(2) perf_event { read } */
+	if (read_type == PERF_MMAP || read_type == PERF_FILE_MMAP) {
+		page_size = sysconf(_SC_PAGESIZE);
+		if (page_size < 0) {
+			fprintf(stderr, "Failed sysconf(_SC_PAGESIZE): %s\n",
+				strerror(errno));
+			if (errno == EACCES)
+				result = 5;
+			else
+				result = -1;
+			goto err;
+		}
+		mmap_size = page_size * 2;
+
+		base = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
+			    MAP_SHARED, fd, 0);
+		if (base == MAP_FAILED) {
+			fprintf(stderr, "Failed mmap(): %s\n", strerror(errno));
+			if (errno == EACCES)
+				result = 6;
+			else
+				result = -1;
+			goto err;
+		}
+
+		if (verbose) {
+			pe_page = base;
+			printf("perf mmap(2) return value: %lld\n",
+			       pe_page->offset);
+		}
+
+		munmap(base, mmap_size);
+	}
+
+	/* Test read(2) perf_event { read } */
+	if (read_type == PERF_FILE || read_type == PERF_FILE_MMAP) {
+		result = read(fd, &count, sizeof(long long));
+		if (result < 0) {
+			fprintf(stderr, "Failed read(): %s\n", strerror(errno));
+			if (errno == EACCES)
+				result = 7;
+			goto err;
+		}
+
+		if (verbose)
+			printf("perf read(2) return value: %lld\n", count);
+
+		close(fd);
+	}
+
+	return 0;
+
+err:
+	close(fd);
+	return result;
+}
diff --git a/tests/perf_event/test b/tests/perf_event/test
new file mode 100755
index 0000000..1c2e4a9
--- /dev/null
+++ b/tests/perf_event/test
@@ -0,0 +1,93 @@
+#!/usr/bin/perl
+use Test::More;
+
+BEGIN {
+    $basedir = $0;
+    $basedir =~ s|(.*)/[^/]*|$1|;
+
+    $test_count = 8;
+    $sys_admin  = 0;
+
+    # allow info to be shown during tests
+    $v = $ARGV[0];
+    if ($v) {
+        if ( $v ne "-v" ) {
+            plan skip_all => "Invalid option (use -v)";
+        }
+    }
+    else {
+        $v = " ";
+    }
+
+    $level = `cat /proc/sys/kernel/perf_event_paranoid`;
+    chomp($level);
+    if ( $level >= 2 ) {    # These tests require CAP_SYS_ADMIN
+        $test_count += 1;
+        $sys_admin = 1;
+    }
+
+    if ( $v eq "-v" ) {
+        print "Paranoid level: $level\n";
+        if ( $level < 0 ) {
+            print "\tNot paranoid\n";
+        }
+        elsif ( $level eq 0 ) {
+            print "\tDisallow raw tracepoint/ftrace without CAP_SYS_ADMIN\n";
+        }
+        elsif ( $level eq 1 ) {
+            print "\tDisallow CPU event access without CAP_SYS_ADMIN\n";
+        }
+        elsif ( $level eq 2 ) {
+            print "\tDisallow kernel profiling without CAP_SYS_ADMIN\n";
+        }
+        else {
+            print "\tUndefined level\n";
+        }
+    }
+
+    plan tests => $test_count;
+}
+
+# perf_event { open cpu kernel tracepoint read write };
+print "Test perf_event\n";
+$result = system "runcon -t test_perf_t $basedir/perf_event $v";
+ok( $result eq 0 );
+
+if ($sys_admin) {
+
+    # Deny capability { sys_admin } - EACCES perf_event_open(2)
+    $result =
+      system "runcon -t test_perf_no_admin_t $basedir/perf_event $v 2>&1";
+    ok( $result >> 8 eq 1 );
+}
+
+# Deny perf_event { open } - EACCES perf_event_open(2)
+$result = system "runcon -t test_perf_no_open_t $basedir/perf_event $v 2>&1";
+ok( $result >> 8 eq 1 );
+
+# Deny perf_event { cpu } - EACCES perf_event_open(2)
+$result = system "runcon -t test_perf_no_cpu_t $basedir/perf_event $v 2>&1";
+ok( $result >> 8 eq 1 );
+
+# Deny perf_event { kernel } - EACCES perf_event_open(2)
+$result = system "runcon -t test_perf_no_kernel_t $basedir/perf_event $v 2>&1";
+ok( $result >> 8 eq 1 );
+
+# Deny perf_event { tracepoint } - EACCES perf_event_open(2)
+$result =
+  system "runcon -t test_perf_no_tracepoint_t $basedir/perf_event $v 2>&1";
+ok( $result >> 8 eq 1 );
+
+# Deny perf_event { read } - EACCES mmap(2)
+$result = system "runcon -t test_perf_no_read_t $basedir/perf_event -m $v 2>&1";
+ok( $result >> 8 eq 6 );
+
+# Deny perf_event { read } - EACCES read(2)
+$result = system "runcon -t test_perf_no_read_t $basedir/perf_event -f $v 2>&1";
+ok( $result >> 8 eq 7 );
+
+# Deny perf_event { write } - EACCES ioctl(2) write
+$result = system "runcon -t test_perf_no_write_t $basedir/perf_event $v 2>&1";
+ok( $result >> 8 eq 2 );
+
+exit;
