Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * fsgsbase_restore.c, test ptrace vs fsgsbase
0004  * Copyright (c) 2020 Andy Lutomirski
0005  *
0006  * This test case simulates a tracer redirecting tracee execution to
0007  * a function and then restoring tracee state using PTRACE_GETREGS and
0008  * PTRACE_SETREGS.  This is similar to what gdb does when doing
0009  * 'p func()'.  The catch is that this test has the called function
0010  * modify a segment register.  This makes sure that ptrace correctly
0011  * restores segment state when using PTRACE_SETREGS.
0012  *
0013  * This is not part of fsgsbase.c, because that test is 64-bit only.
0014  */
0015 
0016 #define _GNU_SOURCE
0017 #include <stdio.h>
0018 #include <stdlib.h>
0019 #include <stdbool.h>
0020 #include <string.h>
0021 #include <sys/syscall.h>
0022 #include <unistd.h>
0023 #include <err.h>
0024 #include <sys/user.h>
0025 #include <asm/prctl.h>
0026 #include <sys/prctl.h>
0027 #include <asm/ldt.h>
0028 #include <sys/mman.h>
0029 #include <stddef.h>
0030 #include <sys/ptrace.h>
0031 #include <sys/wait.h>
0032 #include <stdint.h>
0033 
0034 #define EXPECTED_VALUE 0x1337f00d
0035 
0036 #ifdef __x86_64__
0037 # define SEG "%gs"
0038 #else
0039 # define SEG "%fs"
0040 #endif
0041 
0042 static unsigned int dereference_seg_base(void)
0043 {
0044     int ret;
0045     asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret));
0046     return ret;
0047 }
0048 
0049 static void init_seg(void)
0050 {
0051     unsigned int *target = mmap(
0052         NULL, sizeof(unsigned int),
0053         PROT_READ | PROT_WRITE,
0054         MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
0055     if (target == MAP_FAILED)
0056         err(1, "mmap");
0057 
0058     *target = EXPECTED_VALUE;
0059 
0060     printf("\tsegment base address = 0x%lx\n", (unsigned long)target);
0061 
0062     struct user_desc desc = {
0063         .entry_number    = 0,
0064         .base_addr       = (unsigned int)(uintptr_t)target,
0065         .limit           = sizeof(unsigned int) - 1,
0066         .seg_32bit       = 1,
0067         .contents        = 0, /* Data, grow-up */
0068         .read_exec_only  = 0,
0069         .limit_in_pages  = 0,
0070         .seg_not_present = 0,
0071         .useable         = 0
0072     };
0073     if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) {
0074         printf("\tusing LDT slot 0\n");
0075         asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7));
0076     } else {
0077         /* No modify_ldt for us (configured out, perhaps) */
0078 
0079         struct user_desc *low_desc = mmap(
0080             NULL, sizeof(desc),
0081             PROT_READ | PROT_WRITE,
0082             MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0);
0083         memcpy(low_desc, &desc, sizeof(desc));
0084 
0085         low_desc->entry_number = -1;
0086 
0087         /* 32-bit set_thread_area */
0088         long ret;
0089         asm volatile ("int $0x80"
0090                   : "=a" (ret), "+m" (*low_desc)
0091                   : "a" (243), "b" (low_desc)
0092 #ifdef __x86_64__
0093                   : "r8", "r9", "r10", "r11"
0094 #endif
0095             );
0096         memcpy(&desc, low_desc, sizeof(desc));
0097         munmap(low_desc, sizeof(desc));
0098 
0099         if (ret != 0) {
0100             printf("[NOTE]\tcould not create a segment -- can't test anything\n");
0101             exit(0);
0102         }
0103         printf("\tusing GDT slot %d\n", desc.entry_number);
0104 
0105         unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3);
0106         asm volatile ("mov %0, %" SEG :: "rm" (sel));
0107     }
0108 }
0109 
0110 static void tracee_zap_segment(void)
0111 {
0112     /*
0113      * The tracer will redirect execution here.  This is meant to
0114      * work like gdb's 'p func()' feature.  The tricky bit is that
0115      * we modify a segment register in order to make sure that ptrace
0116      * can correctly restore segment registers.
0117      */
0118     printf("\tTracee: in tracee_zap_segment()\n");
0119 
0120     /*
0121      * Write a nonzero selector with base zero to the segment register.
0122      * Using a null selector would defeat the test on AMD pre-Zen2
0123      * CPUs, as such CPUs don't clear the base when loading a null
0124      * selector.
0125      */
0126     unsigned short sel;
0127     asm volatile ("mov %%ss, %0\n\t"
0128               "mov %0, %" SEG
0129               : "=rm" (sel));
0130 
0131     pid_t pid = getpid(), tid = syscall(SYS_gettid);
0132 
0133     printf("\tTracee is going back to sleep\n");
0134     syscall(SYS_tgkill, pid, tid, SIGSTOP);
0135 
0136     /* Should not get here. */
0137     while (true) {
0138         printf("[FAIL]\tTracee hit unreachable code\n");
0139         pause();
0140     }
0141 }
0142 
0143 int main()
0144 {
0145     printf("\tSetting up a segment\n");
0146     init_seg();
0147 
0148     unsigned int val = dereference_seg_base();
0149     if (val != EXPECTED_VALUE) {
0150         printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
0151         return 1;
0152     }
0153     printf("[OK]\tThe segment points to the right place.\n");
0154 
0155     pid_t chld = fork();
0156     if (chld < 0)
0157         err(1, "fork");
0158 
0159     if (chld == 0) {
0160         prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0);
0161 
0162         if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0)
0163             err(1, "PTRACE_TRACEME");
0164 
0165         pid_t pid = getpid(), tid = syscall(SYS_gettid);
0166 
0167         printf("\tTracee will take a nap until signaled\n");
0168         syscall(SYS_tgkill, pid, tid, SIGSTOP);
0169 
0170         printf("\tTracee was resumed.  Will re-check segment.\n");
0171 
0172         val = dereference_seg_base();
0173         if (val != EXPECTED_VALUE) {
0174             printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE);
0175             exit(1);
0176         }
0177 
0178         printf("[OK]\tThe segment points to the right place.\n");
0179         exit(0);
0180     }
0181 
0182     int status;
0183 
0184     /* Wait for SIGSTOP. */
0185     if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
0186         err(1, "waitpid");
0187 
0188     struct user_regs_struct regs;
0189 
0190     if (ptrace(PTRACE_GETREGS, chld, NULL, &regs) != 0)
0191         err(1, "PTRACE_GETREGS");
0192 
0193 #ifdef __x86_64__
0194     printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base);
0195 #else
0196     printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs);
0197 #endif
0198 
0199     struct user_regs_struct regs2 = regs;
0200 #ifdef __x86_64__
0201     regs2.rip = (unsigned long)tracee_zap_segment;
0202     regs2.rsp -= 128;   /* Don't clobber the redzone. */
0203 #else
0204     regs2.eip = (unsigned long)tracee_zap_segment;
0205 #endif
0206 
0207     printf("\tTracer: redirecting tracee to tracee_zap_segment()\n");
0208     if (ptrace(PTRACE_SETREGS, chld, NULL, &regs2) != 0)
0209         err(1, "PTRACE_GETREGS");
0210     if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0)
0211         err(1, "PTRACE_GETREGS");
0212 
0213     /* Wait for SIGSTOP. */
0214     if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status))
0215         err(1, "waitpid");
0216 
0217     printf("\tTracer: restoring tracee state\n");
0218     if (ptrace(PTRACE_SETREGS, chld, NULL, &regs) != 0)
0219         err(1, "PTRACE_GETREGS");
0220     if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0)
0221         err(1, "PTRACE_GETREGS");
0222 
0223     /* Wait for SIGSTOP. */
0224     if (waitpid(chld, &status, 0) != chld)
0225         err(1, "waitpid");
0226 
0227     if (WIFSIGNALED(status)) {
0228         printf("[FAIL]\tTracee crashed\n");
0229         return 1;
0230     }
0231 
0232     if (!WIFEXITED(status)) {
0233         printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status);
0234         return 1;
0235     }
0236 
0237     int exitcode = WEXITSTATUS(status);
0238     if (exitcode != 0) {
0239         printf("[FAIL]\tTracee reported failure\n");
0240         return 1;
0241     }
0242 
0243     printf("[OK]\tAll is well.\n");
0244     return 0;
0245 }