[PATCH] linux user i386 fork: clone the GDT for each thread to, separate TLS

Douglas Crosher posted 1 patch 3 years, 7 months ago
Failed in applying to current master (apply log)
linux-user/syscall.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
[PATCH] linux user i386 fork: clone the GDT for each thread to, separate TLS
Posted by Douglas Crosher 3 years, 7 months ago
The Linux kernel i386 maintains three GDT entries for TLS, and these
are the same three indexes for all threads. The kernel swaps the GDT
entries at these indexes before running each thread so each thread can
have separate TLS.

Current qemu linux user i386 implements a single GDT per process so
shared by all threads. While it does cache the GDT entries per thread
this cache can need to be reloaded from the GDT entry. Other threads
are likely to have overwritten the GDT entry and so the thread TLS
can become inconsistent.

For example, the segment registers are reloaded on a signal return
path, so threaded coding returning from signals breaks.

This patch clones the GDT for each thread, to separate the TLS
entries. It mmaps space from the target for this, just as the initial
GDT is currently allocated. This memory is unmapped on thread exit.

The Linux x86_64 kernel offers a different strategy for TLS, so this
patch is limited to Linux user i386.

If there were some code relying on a global GDT then this patch might 
break that and a correct fix might be more involved and might need to 
add separate per-thread GDT state for just the TLS entries and for just 
Linux user. Would anyone be aware of such uses?

Signed-off-by: Douglas Crosher <dtc-ubuntu@scieneer.com>
---
  linux-user/syscall.c | 15 +++++++++++++++
  1 file changed, 15 insertions(+)

diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 55ac5c3208..099e4f875a 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -6424,6 +6424,15 @@ static int do_fork(CPUArchState *env, unsigned 
int flags, abi_ulong newsp,
              ts->child_tidptr = child_tidptr;
          }

+#if defined(TARGET_I386) && defined(TARGET_ABI32)
+        new_env->gdt.base = target_mmap(0, sizeof(uint64_t) * 
TARGET_GDT_ENTRIES,
+                                        PROT_READ|PROT_WRITE,
+                                        MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
+        new_env->gdt.limit = sizeof(uint64_t) * TARGET_GDT_ENTRIES - 1;
+        memcpy(g2h(new_env->gdt.base), g2h(env->gdt.base),
+               sizeof(uint64_t) * TARGET_GDT_ENTRIES);
+#endif
+
          if (flags & CLONE_SETTLS) {
              cpu_set_tls (new_env, newtls);
          }
@@ -8193,6 +8202,12 @@ static abi_long do_syscall1(void *cpu_env, int 
num, abi_long arg1,
          if (CPU_NEXT(first_cpu)) {
              TaskState *ts = cpu->opaque;

+#if defined(TARGET_I386) && defined(TARGET_ABI32)
+            CPUX86State *env = cpu_env;
+            target_munmap(env->gdt.base, sizeof(uint64_t) * 
TARGET_GDT_ENTRIES);
+            env->gdt.base = 0;
+#endif
+
              object_property_set_bool(OBJECT(cpu), "realized", false, 
NULL);
              object_unref(OBJECT(cpu));
              /*
-- 
2.25.4