As userspace on UML/!MMU also need to configure %fs register when it is
running to correctly access thread structure, host syscalls implemented
in os-Linux drivers may be puzzled when they are called.  Thus it has to
configure %fs register via arch_prctl(SET_FS) on every host syscalls.

Signed-off-by: Hajime Tazaki <thehaj...@gmail.com>
Signed-off-by: Ricardo Koller <ricar...@google.com>
---
 arch/um/include/shared/os.h |  3 ++
 arch/um/os-Linux/main.c     |  5 ++++
 arch/um/os-Linux/process.c  | 11 ++++++++
 arch/um/os-Linux/start_up.c | 47 +++++++++++++++++++++++++++++++
 arch/um/os-Linux/time.c     |  3 +-
 arch/x86/um/do_syscall_64.c | 35 +++++++++++++++++++++++
 arch/x86/um/syscalls_64.c   | 55 +++++++++++++++++++++++++++++++++++++
 7 files changed, 158 insertions(+), 1 deletion(-)

diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index f6d3f3d7eade..ba95e882f7e6 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -191,6 +191,7 @@ extern void check_host_supports_tls(int *supports_tls, int 
*tls_min);
 extern void get_host_cpu_features(
        void (*flags_helper_func)(char *line),
        void (*cache_helper_func)(char *line));
+extern int os_has_fsgsbase(void);
 
 /* mem.c */
 extern int create_mem_file(unsigned long long len);
@@ -225,6 +226,8 @@ extern int os_unmap_memory(void *addr, int len);
 extern int os_drop_memory(void *addr, int length);
 extern int can_drop_memory(void);
 extern int os_mincore(void *addr, unsigned long len);
+extern long long host_fs;
+extern int os_arch_prctl(int pid, int option, unsigned long *arg);
 
 /* execvp.c */
 extern int execvp_noalloc(char *buf, const char *file, char *const argv[]);
diff --git a/arch/um/os-Linux/main.c b/arch/um/os-Linux/main.c
index f98ff79cdbf7..8e5c7361bac1 100644
--- a/arch/um/os-Linux/main.c
+++ b/arch/um/os-Linux/main.c
@@ -16,6 +16,7 @@
 #include <kern_util.h>
 #include <os.h>
 #include <um_malloc.h>
+#include <asm/prctl.h> /* XXX This should get the constants from libc */
 #include "internal.h"
 
 #define PGD_BOUND (4 * 1024 * 1024)
@@ -142,6 +143,10 @@ int __init main(int argc, char **argv, char **envp)
        change_sig(SIGPIPE, 0);
        ret = linux_main(argc, argv);
 
+#ifndef CONFIG_MMU
+       os_arch_prctl(0, ARCH_SET_FS, (void *)host_fs);
+#endif
+
        /*
         * Disable SIGPROF - I have no idea why libc doesn't do this or turn
         * off the profiling time, but UML dies with a SIGPROF just before
diff --git a/arch/um/os-Linux/process.c b/arch/um/os-Linux/process.c
index f08bb20d95ec..0be8f4ac5f70 100644
--- a/arch/um/os-Linux/process.c
+++ b/arch/um/os-Linux/process.c
@@ -296,3 +296,14 @@ void init_new_thread_signals(void)
        set_handler(SIGIO);
        signal(SIGWINCH, SIG_IGN);
 }
+
+#ifndef CONFIG_MMU
+
+#include <unistd.h>
+#include <sys/syscall.h>   /* For SYS_xxx definitions */
+
+int os_arch_prctl(int pid, int option, unsigned long *arg2)
+{
+       return syscall(SYS_arch_prctl, option, arg2);
+}
+#endif
diff --git a/arch/um/os-Linux/start_up.c b/arch/um/os-Linux/start_up.c
index 93fc82c01aba..80dce242e67a 100644
--- a/arch/um/os-Linux/start_up.c
+++ b/arch/um/os-Linux/start_up.c
@@ -12,6 +12,7 @@
 #include <sched.h>
 #include <signal.h>
 #include <string.h>
+#include <longjmp.h>
 #include <sys/mman.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -278,6 +279,49 @@ void  __init get_host_cpu_features(
        }
 }
 
+/**
+ * get_host_cpu_features() return true with X86_FEATURE_FSGSBASE even
+ * if the kernel is older and disabled using fsgsbase instruction.
+ * thus detection is based on whether SIGILL is raised or not.
+ */
+static jmp_buf jmpbuf;
+static int has_fsgsbase;
+
+static void sigill(int sig, siginfo_t *si, void *ctx_void)
+{
+       longjmp(jmpbuf, 1);
+}
+
+static int __init check_fsgsbase(void)
+{
+       unsigned long fsbase;
+       struct sigaction sa;
+
+       /* Probe FSGSBASE */
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_sigaction = sigill;
+       sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
+       sigemptyset(&sa.sa_mask);
+       if (sigaction(SIGILL, &sa, 0))
+               os_warn("sigaction");
+
+       os_info("Checking FSGSBASE instructions...");
+       if (setjmp(jmpbuf) == 0) {
+               asm volatile("rdfsbase %0" : "=r" (fsbase) :: "memory");
+               has_fsgsbase = 1;
+               os_info("OK\n");
+       } else {
+               has_fsgsbase = 0;
+               os_info("disabled\n");
+       }
+
+       return 0;
+}
+
+int os_has_fsgsbase(void)
+{
+       return has_fsgsbase;
+}
 
 void __init os_early_checks(void)
 {
@@ -293,6 +337,9 @@ void __init os_early_checks(void)
         */
        check_tmpexec();
 
+       /* probe fsgsbase instruction */
+       check_fsgsbase();
+
        pid = start_ptraced_child();
        if (init_pid_registers(pid))
                fatal("Failed to initialize default registers");
diff --git a/arch/um/os-Linux/time.c b/arch/um/os-Linux/time.c
index 4d5591d96d8c..1ed14c6b67c4 100644
--- a/arch/um/os-Linux/time.c
+++ b/arch/um/os-Linux/time.c
@@ -89,7 +89,8 @@ long long os_nsecs(void)
 {
        struct timespec ts;
 
-       clock_gettime(CLOCK_MONOTONIC,&ts);
+       clock_gettime(CLOCK_MONOTONIC, &ts);
+
        return timespec_to_ns(&ts);
 }
 
diff --git a/arch/x86/um/do_syscall_64.c b/arch/x86/um/do_syscall_64.c
index 7af6e881ad58..594248f7319c 100644
--- a/arch/x86/um/do_syscall_64.c
+++ b/arch/x86/um/do_syscall_64.c
@@ -2,12 +2,38 @@
 
 #include <linux/kernel.h>
 #include <linux/ptrace.h>
+#include <asm/fsgsbase.h>
+#include <asm/prctl.h>
 #include <kern_util.h>
 #include <sysdep/syscalls.h>
 #include <os.h>
 
 #ifndef CONFIG_MMU
 
+static int os_x86_arch_prctl(int pid, int option, unsigned long *arg2)
+{
+       if (os_has_fsgsbase()) {
+               switch (option) {
+               case ARCH_SET_FS:
+                       wrfsbase(*arg2);
+                       break;
+               case ARCH_SET_GS:
+                       wrgsbase(*arg2);
+                       break;
+               case ARCH_GET_FS:
+                       *arg2 = rdfsbase();
+                       break;
+               case ARCH_GET_GS:
+                       *arg2 = rdgsbase();
+                       break;
+               }
+               return 0;
+       } else
+               return os_arch_prctl(pid, option, arg2);
+
+       return 0;
+}
+
 __visible void do_syscall_64(struct pt_regs *regs)
 {
        int syscall;
@@ -19,6 +45,9 @@ __visible void do_syscall_64(struct pt_regs *regs)
                 syscall, (unsigned long)current,
                 (unsigned long)sys_call_table[syscall]);
 
+       /* set fs register to the original host one */
+       os_x86_arch_prctl(0, ARCH_SET_FS, (void *)host_fs);
+
        if (likely(syscall < NR_syscalls)) {
                PT_REGS_SET_SYSCALL_RETURN(regs,
                                EXECUTE_SYSCALL(syscall, regs));
@@ -29,6 +58,11 @@ __visible void do_syscall_64(struct pt_regs *regs)
 
        PT_REGS_SYSCALL_RET(regs) = regs->regs.gp[HOST_AX];
 
+       /* restore back fs register to userspace configured one */
+       os_x86_arch_prctl(0, ARCH_SET_FS,
+                     (void *)(current->thread.regs.regs.gp[FS_BASE
+                                                    / sizeof(unsigned long)]));
+
        /* force do_signal() --> is_syscall() */
        set_thread_flag(TIF_SIGPENDING);
        interrupt_end();
@@ -39,4 +73,5 @@ __visible void do_syscall_64(struct pt_regs *regs)
                        current_thread_info()->aux_fp_regs);
        }
 }
+
 #endif
diff --git a/arch/x86/um/syscalls_64.c b/arch/x86/um/syscalls_64.c
index 8abf2a679578..5a1b1b3efab2 100644
--- a/arch/x86/um/syscalls_64.c
+++ b/arch/x86/um/syscalls_64.c
@@ -12,11 +12,24 @@
 #include <asm/prctl.h> /* XXX This should get the constants from libc */
 #include <registers.h>
 #include <os.h>
+#include <asm/thread_info.h>
+#include <asm/mman.h>
+
+/*
+ * The guest libc can change FS, which confuses the host libc.
+ * In fact, changing FS directly is not supported (check
+ * man arch_prctl). So, whenever we make a host syscall,
+ * we should be changing FS to the original FS (not the
+ * one set by the guest libc). This original FS is stored
+ * in host_fs.
+ */
+long long host_fs = -1;
 
 long arch_prctl(struct task_struct *task, int option,
                unsigned long __user *arg2)
 {
        long ret = -EINVAL;
+#ifdef CONFIG_MMU
 
        switch (option) {
        case ARCH_SET_FS:
@@ -38,6 +51,48 @@ long arch_prctl(struct task_struct *task, int option,
        }
 
        return ret;
+#else
+
+       unsigned long *ptr = arg2, tmp;
+
+       switch (option) {
+       case ARCH_SET_FS:
+               if (host_fs == -1)
+                       os_arch_prctl(0, ARCH_GET_FS, (void *)&host_fs);
+               ret = 0;
+               break;
+       case ARCH_SET_GS:
+               ret = 0;
+               break;
+       case ARCH_GET_FS:
+       case ARCH_GET_GS:
+               ptr = &tmp;
+               break;
+       }
+
+       ret = os_arch_prctl(0, option, ptr);
+       if (ret)
+               return ret;
+
+       switch (option) {
+       case ARCH_SET_FS:
+               current->thread.regs.regs.gp[FS_BASE / sizeof(unsigned long)] =
+                       (unsigned long) arg2;
+               break;
+       case ARCH_SET_GS:
+               current->thread.regs.regs.gp[GS_BASE / sizeof(unsigned long)] =
+                       (unsigned long) arg2;
+               break;
+       case ARCH_GET_FS:
+               ret = put_user(current->thread.regs.regs.gp[FS_BASE / 
sizeof(unsigned long)], arg2);
+               break;
+       case ARCH_GET_GS:
+               ret = put_user(current->thread.regs.regs.gp[GS_BASE / 
sizeof(unsigned long)], arg2);
+               break;
+       }
+
+       return ret;
+#endif
 }
 
 SYSCALL_DEFINE2(arch_prctl, int, option, unsigned long, arg2)
-- 
2.43.0


Reply via email to