| From 1b9abd1755ad947d7c9913e92e7837b533124c90 Mon Sep 17 00:00:00 2001 |
| From: Andy Lutomirski <luto@kernel.org> |
| Date: Wed, 26 Aug 2020 10:00:46 -0700 |
| Subject: selftests/x86/fsgsbase: Test PTRACE_PEEKUSER for GSBASE with invalid LDT GS |
| |
| From: Andy Lutomirski <luto@kernel.org> |
| |
| commit 1b9abd1755ad947d7c9913e92e7837b533124c90 upstream. |
| |
| This tests commit: |
| |
| 8ab49526b53d ("x86/fsgsbase/64: Fix NULL deref in 86_fsgsbase_read_task") |
| |
| Unpatched kernels will OOPS. |
| |
| Signed-off-by: Andy Lutomirski <luto@kernel.org> |
| Signed-off-by: Ingo Molnar <mingo@kernel.org> |
| Cc: stable@vger.kernel.org |
| Link: https://lore.kernel.org/r/c618ae86d1f757e01b1a8e79869f553cb88acf9a.1598461151.git.luto@kernel.org |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| tools/testing/selftests/x86/fsgsbase.c | 65 +++++++++++++++++++++++++++++++++ |
| 1 file changed, 65 insertions(+) |
| |
| --- a/tools/testing/selftests/x86/fsgsbase.c |
| +++ b/tools/testing/selftests/x86/fsgsbase.c |
| @@ -443,6 +443,68 @@ static void test_unexpected_base(void) |
| |
| #define USER_REGS_OFFSET(r) offsetof(struct user_regs_struct, r) |
| |
| +static void test_ptrace_write_gs_read_base(void) |
| +{ |
| + int status; |
| + pid_t child = fork(); |
| + |
| + if (child < 0) |
| + err(1, "fork"); |
| + |
| + if (child == 0) { |
| + printf("[RUN]\tPTRACE_POKE GS, read GSBASE back\n"); |
| + |
| + printf("[RUN]\tARCH_SET_GS to 1\n"); |
| + if (syscall(SYS_arch_prctl, ARCH_SET_GS, 1) != 0) |
| + err(1, "ARCH_SET_GS"); |
| + |
| + if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) |
| + err(1, "PTRACE_TRACEME"); |
| + |
| + raise(SIGTRAP); |
| + _exit(0); |
| + } |
| + |
| + wait(&status); |
| + |
| + if (WSTOPSIG(status) == SIGTRAP) { |
| + unsigned long base; |
| + unsigned long gs_offset = USER_REGS_OFFSET(gs); |
| + unsigned long base_offset = USER_REGS_OFFSET(gs_base); |
| + |
| + /* Read the initial base. It should be 1. */ |
| + base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL); |
| + if (base == 1) { |
| + printf("[OK]\tGSBASE started at 1\n"); |
| + } else { |
| + nerrs++; |
| + printf("[FAIL]\tGSBASE started at 0x%lx\n", base); |
| + } |
| + |
| + printf("[RUN]\tSet GS = 0x7, read GSBASE\n"); |
| + |
| + /* Poke an LDT selector into GS. */ |
| + if (ptrace(PTRACE_POKEUSER, child, gs_offset, 0x7) != 0) |
| + err(1, "PTRACE_POKEUSER"); |
| + |
| + /* And read the base. */ |
| + base = ptrace(PTRACE_PEEKUSER, child, base_offset, NULL); |
| + |
| + if (base == 0 || base == 1) { |
| + printf("[OK]\tGSBASE reads as 0x%lx with invalid GS\n", base); |
| + } else { |
| + nerrs++; |
| + printf("[FAIL]\tGSBASE=0x%lx (should be 0 or 1)\n", base); |
| + } |
| + } |
| + |
| + ptrace(PTRACE_CONT, child, NULL, NULL); |
| + |
| + wait(&status); |
| + if (!WIFEXITED(status)) |
| + printf("[WARN]\tChild didn't exit cleanly.\n"); |
| +} |
| + |
| static void test_ptrace_write_gsbase(void) |
| { |
| int status; |
| @@ -529,6 +591,9 @@ int main() |
| shared_scratch = mmap(NULL, 4096, PROT_READ | PROT_WRITE, |
| MAP_ANONYMOUS | MAP_SHARED, -1, 0); |
| |
| + /* Do these tests before we have an LDT. */ |
| + test_ptrace_write_gs_read_base(); |
| + |
| /* Probe FSGSBASE */ |
| sethandler(SIGILL, sigill, 0); |
| if (sigsetjmp(jmpbuf, 1) == 0) { |