qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Laurent Vivier <laurent@vivier.eu>
To: qemu-devel@nongnu.org
Cc: Peter Maydell <peter.maydell@linaro.org>,
	Laurent Vivier <laurent@vivier.eu>
Subject: [Qemu-devel] [PULL 12/13] page_unprotect(): handle calls to pages that are PAGE_WRITE
Date: Tue, 23 Jan 2018 15:48:06 +0100	[thread overview]
Message-ID: <20180123144807.5618-13-laurent@vivier.eu> (raw)
In-Reply-To: <20180123144807.5618-1-laurent@vivier.eu>

From: Peter Maydell <peter.maydell@linaro.org>

If multiple guest threads in user-mode emulation write to a
page which QEMU has marked read-only because of cached TCG
translations, the threads can race in page_unprotect:

 * threads A & B both try to do a write to a page with code in it at
   the same time (ie which we've made non-writeable, so SEGV)
 * they race into the signal handler with this faulting address
 * thread A happens to get to page_unprotect() first and takes the
   mmap lock, so thread B sits waiting for it to be done
 * A then finds the page, marks it PAGE_WRITE and mprotect()s it writable
 * A can then continue OK (returns from signal handler to retry the
   memory access)
 * ...but when B gets the mmap lock it finds that the page is already
   PAGE_WRITE, and so it exits page_unprotect() via the "not due to
   protected translation" code path, and wrongly delivers the signal
   to the guest rather than just retrying the access

In particular, this meant that trying to run 'javac' in user-mode
emulation would fail with a spurious guest SIGSEGV.

Handle this by making page_unprotect() assume that a call for a page
which is already PAGE_WRITE is due to a race of this sort and return
a "fault handled" indication.

Since this would cause an infinite loop if we ever called
page_unprotect() for some other kind of fault than "write failed due
to bad access permissions", tighten the condition in
handle_cpu_signal() to check the signal number and si_code, and add a
comment so that if somebody does ever find themselves debugging an
infinite loop of faults they have some clue about why.

(The trick for identifying the correct setting for
current_tb_invalidated for thread B (needed to handle the precise-SMC
case) is due to Richard Henderson.  Paolo Bonzini suggested just
relying on si_code rather than trying anything more complicated.)

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Message-Id: <1511879725-9576-3-git-send-email-peter.maydell@linaro.org>
Signed-off-by: Laurent Vivier <laurent@vivier.eu>
---
 accel/tcg/translate-all.c | 50 +++++++++++++++++++++++++++++------------------
 accel/tcg/user-exec.c     | 13 +++++++++++-
 2 files changed, 43 insertions(+), 20 deletions(-)

diff --git a/accel/tcg/translate-all.c b/accel/tcg/translate-all.c
index 7736257085..67795cd78c 100644
--- a/accel/tcg/translate-all.c
+++ b/accel/tcg/translate-all.c
@@ -2181,29 +2181,41 @@ int page_unprotect(target_ulong address, uintptr_t pc)
 
     /* if the page was really writable, then we change its
        protection back to writable */
-    if ((p->flags & PAGE_WRITE_ORG) && !(p->flags & PAGE_WRITE)) {
-        host_start = address & qemu_host_page_mask;
-        host_end = host_start + qemu_host_page_size;
-
-        prot = 0;
+    if (p->flags & PAGE_WRITE_ORG) {
         current_tb_invalidated = false;
-        for (addr = host_start ; addr < host_end ; addr += TARGET_PAGE_SIZE) {
-            p = page_find(addr >> TARGET_PAGE_BITS);
-            p->flags |= PAGE_WRITE;
-            prot |= p->flags;
-
-            /* and since the content will be modified, we must invalidate
-               the corresponding translated code. */
-            current_tb_invalidated |= tb_invalidate_phys_page(addr, pc);
-#ifdef CONFIG_USER_ONLY
-            if (DEBUG_TB_CHECK_GATE) {
-                tb_invalidate_check(addr);
+        if (p->flags & PAGE_WRITE) {
+            /* If the page is actually marked WRITE then assume this is because
+             * this thread raced with another one which got here first and
+             * set the page to PAGE_WRITE and did the TB invalidate for us.
+             */
+#ifdef TARGET_HAS_PRECISE_SMC
+            TranslationBlock *current_tb = tb_find_pc(pc);
+            if (current_tb) {
+                current_tb_invalidated = tb_cflags(current_tb) & CF_INVALID;
             }
 #endif
+        } else {
+            host_start = address & qemu_host_page_mask;
+            host_end = host_start + qemu_host_page_size;
+
+            prot = 0;
+            for (addr = host_start; addr < host_end; addr += TARGET_PAGE_SIZE) {
+                p = page_find(addr >> TARGET_PAGE_BITS);
+                p->flags |= PAGE_WRITE;
+                prot |= p->flags;
+
+                /* and since the content will be modified, we must invalidate
+                   the corresponding translated code. */
+                current_tb_invalidated |= tb_invalidate_phys_page(addr, pc);
+#ifdef CONFIG_USER_ONLY
+                if (DEBUG_TB_CHECK_GATE) {
+                    tb_invalidate_check(addr);
+                }
+#endif
+            }
+            mprotect((void *)g2h(host_start), qemu_host_page_size,
+                     prot & PAGE_BITS);
         }
-        mprotect((void *)g2h(host_start), qemu_host_page_size,
-                 prot & PAGE_BITS);
-
         mmap_unlock();
         /* If current TB was invalidated return to main loop */
         return current_tb_invalidated ? 2 : 1;
diff --git a/accel/tcg/user-exec.c b/accel/tcg/user-exec.c
index e8f26ff0cb..c973752562 100644
--- a/accel/tcg/user-exec.c
+++ b/accel/tcg/user-exec.c
@@ -104,7 +104,18 @@ static inline int handle_cpu_signal(uintptr_t pc, siginfo_t *info,
            pc, address, is_write, *(unsigned long *)old_set);
 #endif
     /* XXX: locking issue */
-    if (is_write && h2g_valid(address)) {
+    /* Note that it is important that we don't call page_unprotect() unless
+     * this is really a "write to nonwriteable page" fault, because
+     * page_unprotect() assumes that if it is called for an access to
+     * a page that's writeable this means we had two threads racing and
+     * another thread got there first and already made the page writeable;
+     * so we will retry the access. If we were to call page_unprotect()
+     * for some other kind of fault that should really be passed to the
+     * guest, we'd end up in an infinite loop of retrying the faulting
+     * access.
+     */
+    if (is_write && info->si_signo == SIGSEGV && info->si_code == SEGV_ACCERR &&
+        h2g_valid(address)) {
         switch (page_unprotect(h2g(address), pc)) {
         case 0:
             /* Fault not caused by a page marked unwritable to protect
-- 
2.14.3

  parent reply	other threads:[~2018-01-23 14:48 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-01-23 14:47 [Qemu-devel] [PULL 00/13] Linux user for 2.12 patches Laurent Vivier
2018-01-23 14:47 ` [Qemu-devel] [PULL 01/13] linux-user: Fix locking order in fork_start() Laurent Vivier
2018-01-23 14:47 ` [Qemu-devel] [PULL 02/13] linux-user: wrap fork() in a start/end exclusive section Laurent Vivier
2018-01-23 14:47 ` [Qemu-devel] [PULL 03/13] linux-user: Fix length calculations in host_to_target_cmsg() Laurent Vivier
2018-01-23 14:47 ` [Qemu-devel] [PULL 04/13] linux-user: Don't use CMSG_ALIGN(sizeof struct cmsghdr) Laurent Vivier
2018-01-23 14:47 ` [Qemu-devel] [PULL 05/13] linux-user: Translate flags argument to dup3 syscall Laurent Vivier
2018-01-23 14:48 ` [Qemu-devel] [PULL 06/13] linux-user/mmap.c: Avoid choosing NULL as start address Laurent Vivier
2018-01-23 14:48 ` [Qemu-devel] [PULL 07/13] linux-user: Fix sched_get/setaffinity conversion Laurent Vivier
2018-01-26 18:23   ` Peter Maydell
2018-01-26 18:33     ` Samuel Thibault
2018-01-23 14:48 ` [Qemu-devel] [PULL 08/13] linux-user: Add AT_SECURE auxval Laurent Vivier
2018-01-23 14:48 ` [Qemu-devel] [PULL 09/13] linux-user: Add getcpu() support Laurent Vivier
2018-01-23 14:48 ` [Qemu-devel] [PULL 10/13] linux-user: remove nmi.c and fw-path-provider.c Laurent Vivier
2018-01-23 14:48 ` [Qemu-devel] [PULL 11/13] linux-user: Propagate siginfo_t through to handle_cpu_signal() Laurent Vivier
2018-01-23 14:48 ` Laurent Vivier [this message]
2018-03-22  1:52   ` [Qemu-devel] [PULL 12/13] page_unprotect(): handle calls to pages that are PAGE_WRITE Laurent Vivier
2018-03-22 10:36     ` Laurent Vivier
2018-03-22 11:05       ` Peter Maydell
2018-03-22 11:07         ` Peter Maydell
2018-03-22 11:13           ` Laurent Vivier
2018-03-22 16:47             ` Laurent Vivier
2018-03-22 11:07         ` Laurent Vivier
2018-03-22 11:10           ` Peter Maydell
2018-01-23 14:48 ` [Qemu-devel] [PULL 13/13] linux-user: implement renameat2 Laurent Vivier
2018-01-23 19:13   ` Palmer Dabbelt
2018-01-23 20:13     ` Laurent Vivier
2018-01-23 20:55       ` Palmer Dabbelt
2018-01-25 11:37 ` [Qemu-devel] [PULL 00/13] Linux user for 2.12 patches Peter Maydell

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20180123144807.5618-13-laurent@vivier.eu \
    --to=laurent@vivier.eu \
    --cc=peter.maydell@linaro.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).