18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * fsgsbase_restore.c, test ptrace vs fsgsbase 48c2ecf20Sopenharmony_ci * Copyright (c) 2020 Andy Lutomirski 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This test case simulates a tracer redirecting tracee execution to 78c2ecf20Sopenharmony_ci * a function and then restoring tracee state using PTRACE_GETREGS and 88c2ecf20Sopenharmony_ci * PTRACE_SETREGS. This is similar to what gdb does when doing 98c2ecf20Sopenharmony_ci * 'p func()'. The catch is that this test has the called function 108c2ecf20Sopenharmony_ci * modify a segment register. This makes sure that ptrace correctly 118c2ecf20Sopenharmony_ci * restores segment state when using PTRACE_SETREGS. 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * This is not part of fsgsbase.c, because that test is 64-bit only. 148c2ecf20Sopenharmony_ci */ 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define _GNU_SOURCE 178c2ecf20Sopenharmony_ci#include <stdio.h> 188c2ecf20Sopenharmony_ci#include <stdlib.h> 198c2ecf20Sopenharmony_ci#include <stdbool.h> 208c2ecf20Sopenharmony_ci#include <string.h> 218c2ecf20Sopenharmony_ci#include <sys/syscall.h> 228c2ecf20Sopenharmony_ci#include <unistd.h> 238c2ecf20Sopenharmony_ci#include <err.h> 248c2ecf20Sopenharmony_ci#include <sys/user.h> 258c2ecf20Sopenharmony_ci#include <asm/prctl.h> 268c2ecf20Sopenharmony_ci#include <sys/prctl.h> 278c2ecf20Sopenharmony_ci#include <asm/ldt.h> 288c2ecf20Sopenharmony_ci#include <sys/mman.h> 298c2ecf20Sopenharmony_ci#include <stddef.h> 308c2ecf20Sopenharmony_ci#include <sys/ptrace.h> 318c2ecf20Sopenharmony_ci#include <sys/wait.h> 328c2ecf20Sopenharmony_ci#include <stdint.h> 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci#define EXPECTED_VALUE 0x1337f00d 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci#ifdef __x86_64__ 378c2ecf20Sopenharmony_ci# define SEG "%gs" 388c2ecf20Sopenharmony_ci#else 398c2ecf20Sopenharmony_ci# define SEG "%fs" 408c2ecf20Sopenharmony_ci#endif 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_cistatic unsigned int dereference_seg_base(void) 438c2ecf20Sopenharmony_ci{ 448c2ecf20Sopenharmony_ci int ret; 458c2ecf20Sopenharmony_ci asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret)); 468c2ecf20Sopenharmony_ci return ret; 478c2ecf20Sopenharmony_ci} 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic void init_seg(void) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci unsigned int *target = mmap( 528c2ecf20Sopenharmony_ci NULL, sizeof(unsigned int), 538c2ecf20Sopenharmony_ci PROT_READ | PROT_WRITE, 548c2ecf20Sopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); 558c2ecf20Sopenharmony_ci if (target == MAP_FAILED) 568c2ecf20Sopenharmony_ci err(1, "mmap"); 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci *target = EXPECTED_VALUE; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci printf("\tsegment base address = 0x%lx\n", (unsigned long)target); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci struct user_desc desc = { 638c2ecf20Sopenharmony_ci .entry_number = 0, 648c2ecf20Sopenharmony_ci .base_addr = (unsigned int)(uintptr_t)target, 658c2ecf20Sopenharmony_ci .limit = sizeof(unsigned int) - 1, 668c2ecf20Sopenharmony_ci .seg_32bit = 1, 678c2ecf20Sopenharmony_ci .contents = 0, /* Data, grow-up */ 688c2ecf20Sopenharmony_ci .read_exec_only = 0, 698c2ecf20Sopenharmony_ci .limit_in_pages = 0, 708c2ecf20Sopenharmony_ci .seg_not_present = 0, 718c2ecf20Sopenharmony_ci .useable = 0 728c2ecf20Sopenharmony_ci }; 738c2ecf20Sopenharmony_ci if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) { 748c2ecf20Sopenharmony_ci printf("\tusing LDT slot 0\n"); 758c2ecf20Sopenharmony_ci asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7)); 768c2ecf20Sopenharmony_ci } else { 778c2ecf20Sopenharmony_ci /* No modify_ldt for us (configured out, perhaps) */ 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci struct user_desc *low_desc = mmap( 808c2ecf20Sopenharmony_ci NULL, sizeof(desc), 818c2ecf20Sopenharmony_ci PROT_READ | PROT_WRITE, 828c2ecf20Sopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); 838c2ecf20Sopenharmony_ci memcpy(low_desc, &desc, sizeof(desc)); 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci low_desc->entry_number = -1; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci /* 32-bit set_thread_area */ 888c2ecf20Sopenharmony_ci long ret; 898c2ecf20Sopenharmony_ci asm volatile ("int $0x80" 908c2ecf20Sopenharmony_ci : "=a" (ret), "+m" (*low_desc) 918c2ecf20Sopenharmony_ci : "a" (243), "b" (low_desc) 928c2ecf20Sopenharmony_ci#ifdef __x86_64__ 938c2ecf20Sopenharmony_ci : "r8", "r9", "r10", "r11" 948c2ecf20Sopenharmony_ci#endif 958c2ecf20Sopenharmony_ci ); 968c2ecf20Sopenharmony_ci memcpy(&desc, low_desc, sizeof(desc)); 978c2ecf20Sopenharmony_ci munmap(low_desc, sizeof(desc)); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci if (ret != 0) { 1008c2ecf20Sopenharmony_ci printf("[NOTE]\tcould not create a segment -- can't test anything\n"); 1018c2ecf20Sopenharmony_ci exit(0); 1028c2ecf20Sopenharmony_ci } 1038c2ecf20Sopenharmony_ci printf("\tusing GDT slot %d\n", desc.entry_number); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3); 1068c2ecf20Sopenharmony_ci asm volatile ("mov %0, %" SEG :: "rm" (sel)); 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic void tracee_zap_segment(void) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci /* 1138c2ecf20Sopenharmony_ci * The tracer will redirect execution here. This is meant to 1148c2ecf20Sopenharmony_ci * work like gdb's 'p func()' feature. The tricky bit is that 1158c2ecf20Sopenharmony_ci * we modify a segment register in order to make sure that ptrace 1168c2ecf20Sopenharmony_ci * can correctly restore segment registers. 1178c2ecf20Sopenharmony_ci */ 1188c2ecf20Sopenharmony_ci printf("\tTracee: in tracee_zap_segment()\n"); 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci /* 1218c2ecf20Sopenharmony_ci * Write a nonzero selector with base zero to the segment register. 1228c2ecf20Sopenharmony_ci * Using a null selector would defeat the test on AMD pre-Zen2 1238c2ecf20Sopenharmony_ci * CPUs, as such CPUs don't clear the base when loading a null 1248c2ecf20Sopenharmony_ci * selector. 1258c2ecf20Sopenharmony_ci */ 1268c2ecf20Sopenharmony_ci unsigned short sel; 1278c2ecf20Sopenharmony_ci asm volatile ("mov %%ss, %0\n\t" 1288c2ecf20Sopenharmony_ci "mov %0, %" SEG 1298c2ecf20Sopenharmony_ci : "=rm" (sel)); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci pid_t pid = getpid(), tid = syscall(SYS_gettid); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci printf("\tTracee is going back to sleep\n"); 1348c2ecf20Sopenharmony_ci syscall(SYS_tgkill, pid, tid, SIGSTOP); 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci /* Should not get here. */ 1378c2ecf20Sopenharmony_ci while (true) { 1388c2ecf20Sopenharmony_ci printf("[FAIL]\tTracee hit unreachable code\n"); 1398c2ecf20Sopenharmony_ci pause(); 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci} 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ciint main() 1448c2ecf20Sopenharmony_ci{ 1458c2ecf20Sopenharmony_ci printf("\tSetting up a segment\n"); 1468c2ecf20Sopenharmony_ci init_seg(); 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci unsigned int val = dereference_seg_base(); 1498c2ecf20Sopenharmony_ci if (val != EXPECTED_VALUE) { 1508c2ecf20Sopenharmony_ci printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE); 1518c2ecf20Sopenharmony_ci return 1; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci printf("[OK]\tThe segment points to the right place.\n"); 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci pid_t chld = fork(); 1568c2ecf20Sopenharmony_ci if (chld < 0) 1578c2ecf20Sopenharmony_ci err(1, "fork"); 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci if (chld == 0) { 1608c2ecf20Sopenharmony_ci prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0); 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) 1638c2ecf20Sopenharmony_ci err(1, "PTRACE_TRACEME"); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci pid_t pid = getpid(), tid = syscall(SYS_gettid); 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci printf("\tTracee will take a nap until signaled\n"); 1688c2ecf20Sopenharmony_ci syscall(SYS_tgkill, pid, tid, SIGSTOP); 1698c2ecf20Sopenharmony_ci 1708c2ecf20Sopenharmony_ci printf("\tTracee was resumed. Will re-check segment.\n"); 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci val = dereference_seg_base(); 1738c2ecf20Sopenharmony_ci if (val != EXPECTED_VALUE) { 1748c2ecf20Sopenharmony_ci printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE); 1758c2ecf20Sopenharmony_ci exit(1); 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci printf("[OK]\tThe segment points to the right place.\n"); 1798c2ecf20Sopenharmony_ci exit(0); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci int status; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci /* Wait for SIGSTOP. */ 1858c2ecf20Sopenharmony_ci if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) 1868c2ecf20Sopenharmony_ci err(1, "waitpid"); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci struct user_regs_struct regs; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci if (ptrace(PTRACE_GETREGS, chld, NULL, ®s) != 0) 1918c2ecf20Sopenharmony_ci err(1, "PTRACE_GETREGS"); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci#ifdef __x86_64__ 1948c2ecf20Sopenharmony_ci printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base); 1958c2ecf20Sopenharmony_ci#else 1968c2ecf20Sopenharmony_ci printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs); 1978c2ecf20Sopenharmony_ci#endif 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci struct user_regs_struct regs2 = regs; 2008c2ecf20Sopenharmony_ci#ifdef __x86_64__ 2018c2ecf20Sopenharmony_ci regs2.rip = (unsigned long)tracee_zap_segment; 2028c2ecf20Sopenharmony_ci regs2.rsp -= 128; /* Don't clobber the redzone. */ 2038c2ecf20Sopenharmony_ci#else 2048c2ecf20Sopenharmony_ci regs2.eip = (unsigned long)tracee_zap_segment; 2058c2ecf20Sopenharmony_ci#endif 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci printf("\tTracer: redirecting tracee to tracee_zap_segment()\n"); 2088c2ecf20Sopenharmony_ci if (ptrace(PTRACE_SETREGS, chld, NULL, ®s2) != 0) 2098c2ecf20Sopenharmony_ci err(1, "PTRACE_GETREGS"); 2108c2ecf20Sopenharmony_ci if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0) 2118c2ecf20Sopenharmony_ci err(1, "PTRACE_GETREGS"); 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_ci /* Wait for SIGSTOP. */ 2148c2ecf20Sopenharmony_ci if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) 2158c2ecf20Sopenharmony_ci err(1, "waitpid"); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci printf("\tTracer: restoring tracee state\n"); 2188c2ecf20Sopenharmony_ci if (ptrace(PTRACE_SETREGS, chld, NULL, ®s) != 0) 2198c2ecf20Sopenharmony_ci err(1, "PTRACE_GETREGS"); 2208c2ecf20Sopenharmony_ci if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0) 2218c2ecf20Sopenharmony_ci err(1, "PTRACE_GETREGS"); 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci /* Wait for SIGSTOP. */ 2248c2ecf20Sopenharmony_ci if (waitpid(chld, &status, 0) != chld) 2258c2ecf20Sopenharmony_ci err(1, "waitpid"); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci if (WIFSIGNALED(status)) { 2288c2ecf20Sopenharmony_ci printf("[FAIL]\tTracee crashed\n"); 2298c2ecf20Sopenharmony_ci return 1; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci if (!WIFEXITED(status)) { 2338c2ecf20Sopenharmony_ci printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status); 2348c2ecf20Sopenharmony_ci return 1; 2358c2ecf20Sopenharmony_ci } 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci int exitcode = WEXITSTATUS(status); 2388c2ecf20Sopenharmony_ci if (exitcode != 0) { 2398c2ecf20Sopenharmony_ci printf("[FAIL]\tTracee reported failure\n"); 2408c2ecf20Sopenharmony_ci return 1; 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci printf("[OK]\tAll is well.\n"); 2448c2ecf20Sopenharmony_ci return 0; 2458c2ecf20Sopenharmony_ci} 246