libbugetlbfs: Test case for powerpc huge_ptep_set_wrprotect() bug
David Gibson
david at gibson.dropbear.id.au
Mon Jul 7 17:19:55 EST 2008
Until very recently (in fact, even now in mainline) powerpc kernels
had a bug in huge_ptep_set_wrprotect() which meant the 'huge' flag was
not passed down to pte_update() and hpte_need_flush(). This meant the
hash ptes for hugepages would not be correctly flushed on fork(),
allowing the parent to pollute the child's mapping after the fork().
This patch adds a testcase to libhugetlbfs for this behaviour, also
doing some other checking of the COW semantics over a fork().
Signed-off-by: David Gibson <david at gibson.dropbear.id.au>
Index: libhugetlbfs/tests/Makefile
===================================================================
--- libhugetlbfs.orig/tests/Makefile 2008-07-03 13:27:18.000000000 +1000
+++ libhugetlbfs/tests/Makefile 2008-07-03 15:27:05.000000000 +1000
@@ -1,7 +1,7 @@
PREFIX = /usr/local
LIB_TESTS = gethugepagesize test_root find_path unlinked_fd misalign \
- readback truncate shared private empty_mounts meminfo_nohuge \
+ readback truncate shared private fork-cow empty_mounts meminfo_nohuge \
ptrace-write-hugepage icache-hygiene slbpacaflush \
chunk-overcommit mprotect alloc-instantiate-race mlock \
truncate_reserve_wraparound truncate_sigbus_versus_oom \
Index: libhugetlbfs/tests/fork-cow.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ libhugetlbfs/tests/fork-cow.c 2008-07-07 16:40:17.000000000 +1000
@@ -0,0 +1,159 @@
+/*
+ * libhugetlbfs - Easy use of Linux hugepages
+ * Copyright (C) 2008 David Gibson, IBM Corporation.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/shm.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <hugetlbfs.h>
+#include "hugetests.h"
+
+/*
+ * Test rationale:
+ *
+ * This checks copy-on-write semantics, specifically the semantics of
+ * a MAP_PRIVATE mapping across a fork(). Some versions of the
+ * powerpc kernel had a bug in huge_ptep_set_wrprotect() which would
+ * fail to flush the hash table after setting the write protect bit in
+ * the parent's page tables, thus allowing the parent to pollute the
+ * child's mapping.
+ */
+
+#define RANDOM_CONSTANT 0x1234ABCD
+#define OTHER_CONSTANT 0xfeef5678
+
+int main(int argc, char ** argv)
+{
+ int fd, ret, status;
+ void *syncarea;
+ volatile unsigned int *p;
+ volatile unsigned int *trigger, *child_readback;
+ unsigned int parent_readback;
+ long hpage_size;
+ pid_t pid;
+
+ test_init(argc, argv);
+
+ if (argc != 1)
+ CONFIG("Usage: fork-cow\n");
+
+ /* Get a shared normal page for synchronization */
+ verbose_printf("Mapping synchronization area..");
+ syncarea = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE,
+ MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+ if (syncarea == MAP_FAILED)
+ FAIL("mmap() sync area: %s", strerror(errno));
+ verbose_printf("done\n");
+
+ trigger = syncarea;
+ *trigger = 0;
+
+ child_readback = trigger + 1;
+ *child_readback = 0;
+
+ hpage_size = check_hugepagesize();
+
+ fd = hugetlbfs_unlinked_fd();
+ if (fd < 0)
+ CONFIG("hugetlbfs_unlinked_fd() failed: %s\n",
+ strerror(errno));
+
+ verbose_printf("Mapping hugepage area...");
+ p = mmap(NULL, hpage_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (p == MAP_FAILED)
+ FAIL("mmap(): %s", strerror(errno));
+ verbose_printf("mapped at %p\n", p);
+
+ /* Touch the page for write in parent */
+ verbose_printf("Parent writes pre-fork...");
+ *p = RANDOM_CONSTANT;
+ verbose_printf("%x\n", RANDOM_CONSTANT);
+
+ if ((pid = fork()) < 0)
+ FAIL("fork(): %s", strerror(errno));
+
+ if (pid != 0) {
+ /* Parent */
+ verbose_printf("Parent writes post-fork...");
+ *p = ~RANDOM_CONSTANT;
+ verbose_printf("%x\n", ~RANDOM_CONSTANT);
+
+ *trigger = 1;
+
+ while (*trigger != 2)
+ ;
+
+ verbose_printf("Parent reads..");
+ parent_readback = *p;
+ verbose_printf("%x\n", parent_readback);
+
+ *trigger = 3;
+ } else {
+ /* Child */
+ verbose_printf("Child starts..\n");
+
+ while (*trigger != 1)
+ ;
+
+ verbose_printf("Child reads...");
+ *child_readback = *p;
+ verbose_printf("%x\n", *child_readback);
+
+ verbose_printf("Child writes...");
+ *p = OTHER_CONSTANT;
+ verbose_printf("%x\n", OTHER_CONSTANT);
+
+ *trigger = 2;
+
+ while (*trigger != 3)
+ ;
+
+ verbose_printf("Child exits...\n");
+ exit(0);
+ }
+
+ verbose_printf("child_readback = 0x%x, parent_readback = 0x%x\n",
+ *child_readback, parent_readback);
+
+ if (*child_readback != RANDOM_CONSTANT)
+ FAIL("Child read back 0x%x instead of 0x%x",
+ *child_readback, RANDOM_CONSTANT);
+ if (parent_readback != ~RANDOM_CONSTANT)
+ FAIL("Parent read back 0x%x instead of 0x%x",
+ parent_readback, RANDOM_CONSTANT);
+
+ ret = waitpid(pid, &status, 0);
+ if (ret < 0)
+ FAIL("waitpid(): %s", strerror(errno));
+ if (WEXITSTATUS(status) != 0)
+ FAIL("Child failed: %d", WEXITSTATUS(status));
+ if (WIFSIGNALED(status))
+ FAIL("Child recived signal %s", strsignal(WTERMSIG(status)));
+
+ PASS();
+}
Index: libhugetlbfs/tests/run_tests.sh
===================================================================
--- libhugetlbfs.orig/tests/run_tests.sh 2008-07-03 15:15:48.000000000 +1000
+++ libhugetlbfs/tests/run_tests.sh 2008-07-03 15:27:06.000000000 +1000
@@ -207,6 +207,7 @@ functional_tests () {
# Tests requiring an active mount and hugepage COW
run_test private
+ run_test fork-cow
run_test direct
run_test malloc
preload_test HUGETLB_MORECORE=yes malloc
--
David Gibson | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_
| _way_ _around_!
http://www.ozlabs.org/~dgibson
More information about the Linuxppc-dev
mailing list