162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * fsgsbase_restore.c, test ptrace vs fsgsbase 462306a36Sopenharmony_ci * Copyright (c) 2020 Andy Lutomirski 562306a36Sopenharmony_ci * 662306a36Sopenharmony_ci * This test case simulates a tracer redirecting tracee execution to 762306a36Sopenharmony_ci * a function and then restoring tracee state using PTRACE_GETREGS and 862306a36Sopenharmony_ci * PTRACE_SETREGS. This is similar to what gdb does when doing 962306a36Sopenharmony_ci * 'p func()'. The catch is that this test has the called function 1062306a36Sopenharmony_ci * modify a segment register. This makes sure that ptrace correctly 1162306a36Sopenharmony_ci * restores segment state when using PTRACE_SETREGS. 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * This is not part of fsgsbase.c, because that test is 64-bit only. 1462306a36Sopenharmony_ci */ 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define _GNU_SOURCE 1762306a36Sopenharmony_ci#include <stdio.h> 1862306a36Sopenharmony_ci#include <stdlib.h> 1962306a36Sopenharmony_ci#include <stdbool.h> 2062306a36Sopenharmony_ci#include <string.h> 2162306a36Sopenharmony_ci#include <sys/syscall.h> 2262306a36Sopenharmony_ci#include <unistd.h> 2362306a36Sopenharmony_ci#include <err.h> 2462306a36Sopenharmony_ci#include <sys/user.h> 2562306a36Sopenharmony_ci#include <asm/prctl.h> 2662306a36Sopenharmony_ci#include <sys/prctl.h> 2762306a36Sopenharmony_ci#include <asm/ldt.h> 2862306a36Sopenharmony_ci#include <sys/mman.h> 2962306a36Sopenharmony_ci#include <stddef.h> 3062306a36Sopenharmony_ci#include <sys/ptrace.h> 3162306a36Sopenharmony_ci#include <sys/wait.h> 3262306a36Sopenharmony_ci#include <stdint.h> 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci#define EXPECTED_VALUE 0x1337f00d 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci#ifdef __x86_64__ 3762306a36Sopenharmony_ci# define SEG "%gs" 3862306a36Sopenharmony_ci#else 3962306a36Sopenharmony_ci# define SEG "%fs" 4062306a36Sopenharmony_ci#endif 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic unsigned int dereference_seg_base(void) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci int ret; 4562306a36Sopenharmony_ci asm volatile ("mov %" SEG ":(0), %0" : "=rm" (ret)); 4662306a36Sopenharmony_ci return ret; 4762306a36Sopenharmony_ci} 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic void init_seg(void) 5062306a36Sopenharmony_ci{ 5162306a36Sopenharmony_ci unsigned int *target = mmap( 5262306a36Sopenharmony_ci NULL, sizeof(unsigned int), 5362306a36Sopenharmony_ci PROT_READ | PROT_WRITE, 5462306a36Sopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); 5562306a36Sopenharmony_ci if (target == MAP_FAILED) 5662306a36Sopenharmony_ci err(1, "mmap"); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci *target = EXPECTED_VALUE; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci printf("\tsegment base address = 0x%lx\n", (unsigned long)target); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci struct user_desc desc = { 6362306a36Sopenharmony_ci .entry_number = 0, 6462306a36Sopenharmony_ci .base_addr = (unsigned int)(uintptr_t)target, 6562306a36Sopenharmony_ci .limit = sizeof(unsigned int) - 1, 6662306a36Sopenharmony_ci .seg_32bit = 1, 6762306a36Sopenharmony_ci .contents = 0, /* Data, grow-up */ 6862306a36Sopenharmony_ci .read_exec_only = 0, 6962306a36Sopenharmony_ci .limit_in_pages = 0, 7062306a36Sopenharmony_ci .seg_not_present = 0, 7162306a36Sopenharmony_ci .useable = 0 7262306a36Sopenharmony_ci }; 7362306a36Sopenharmony_ci if (syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)) == 0) { 7462306a36Sopenharmony_ci printf("\tusing LDT slot 0\n"); 7562306a36Sopenharmony_ci asm volatile ("mov %0, %" SEG :: "rm" ((unsigned short)0x7)); 7662306a36Sopenharmony_ci } else { 7762306a36Sopenharmony_ci /* No modify_ldt for us (configured out, perhaps) */ 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci struct user_desc *low_desc = mmap( 8062306a36Sopenharmony_ci NULL, sizeof(desc), 8162306a36Sopenharmony_ci PROT_READ | PROT_WRITE, 8262306a36Sopenharmony_ci MAP_PRIVATE | MAP_ANONYMOUS | MAP_32BIT, -1, 0); 8362306a36Sopenharmony_ci memcpy(low_desc, &desc, sizeof(desc)); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci low_desc->entry_number = -1; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci /* 32-bit set_thread_area */ 8862306a36Sopenharmony_ci long ret; 8962306a36Sopenharmony_ci asm volatile ("int $0x80" 9062306a36Sopenharmony_ci : "=a" (ret), "+m" (*low_desc) 9162306a36Sopenharmony_ci : "a" (243), "b" (low_desc) 9262306a36Sopenharmony_ci#ifdef __x86_64__ 9362306a36Sopenharmony_ci : "r8", "r9", "r10", "r11" 9462306a36Sopenharmony_ci#endif 9562306a36Sopenharmony_ci ); 9662306a36Sopenharmony_ci memcpy(&desc, low_desc, sizeof(desc)); 9762306a36Sopenharmony_ci munmap(low_desc, sizeof(desc)); 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (ret != 0) { 10062306a36Sopenharmony_ci printf("[NOTE]\tcould not create a segment -- can't test anything\n"); 10162306a36Sopenharmony_ci exit(0); 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci printf("\tusing GDT slot %d\n", desc.entry_number); 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci unsigned short sel = (unsigned short)((desc.entry_number << 3) | 0x3); 10662306a36Sopenharmony_ci asm volatile ("mov %0, %" SEG :: "rm" (sel)); 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic void tracee_zap_segment(void) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * The tracer will redirect execution here. This is meant to 11462306a36Sopenharmony_ci * work like gdb's 'p func()' feature. The tricky bit is that 11562306a36Sopenharmony_ci * we modify a segment register in order to make sure that ptrace 11662306a36Sopenharmony_ci * can correctly restore segment registers. 11762306a36Sopenharmony_ci */ 11862306a36Sopenharmony_ci printf("\tTracee: in tracee_zap_segment()\n"); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci /* 12162306a36Sopenharmony_ci * Write a nonzero selector with base zero to the segment register. 12262306a36Sopenharmony_ci * Using a null selector would defeat the test on AMD pre-Zen2 12362306a36Sopenharmony_ci * CPUs, as such CPUs don't clear the base when loading a null 12462306a36Sopenharmony_ci * selector. 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_ci unsigned short sel; 12762306a36Sopenharmony_ci asm volatile ("mov %%ss, %0\n\t" 12862306a36Sopenharmony_ci "mov %0, %" SEG 12962306a36Sopenharmony_ci : "=rm" (sel)); 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci pid_t pid = getpid(), tid = syscall(SYS_gettid); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci printf("\tTracee is going back to sleep\n"); 13462306a36Sopenharmony_ci syscall(SYS_tgkill, pid, tid, SIGSTOP); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* Should not get here. */ 13762306a36Sopenharmony_ci while (true) { 13862306a36Sopenharmony_ci printf("[FAIL]\tTracee hit unreachable code\n"); 13962306a36Sopenharmony_ci pause(); 14062306a36Sopenharmony_ci } 14162306a36Sopenharmony_ci} 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ciint main() 14462306a36Sopenharmony_ci{ 14562306a36Sopenharmony_ci printf("\tSetting up a segment\n"); 14662306a36Sopenharmony_ci init_seg(); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci unsigned int val = dereference_seg_base(); 14962306a36Sopenharmony_ci if (val != EXPECTED_VALUE) { 15062306a36Sopenharmony_ci printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE); 15162306a36Sopenharmony_ci return 1; 15262306a36Sopenharmony_ci } 15362306a36Sopenharmony_ci printf("[OK]\tThe segment points to the right place.\n"); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci pid_t chld = fork(); 15662306a36Sopenharmony_ci if (chld < 0) 15762306a36Sopenharmony_ci err(1, "fork"); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (chld == 0) { 16062306a36Sopenharmony_ci prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0, 0); 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci if (ptrace(PTRACE_TRACEME, 0, 0, 0) != 0) 16362306a36Sopenharmony_ci err(1, "PTRACE_TRACEME"); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci pid_t pid = getpid(), tid = syscall(SYS_gettid); 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci printf("\tTracee will take a nap until signaled\n"); 16862306a36Sopenharmony_ci syscall(SYS_tgkill, pid, tid, SIGSTOP); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci printf("\tTracee was resumed. Will re-check segment.\n"); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci val = dereference_seg_base(); 17362306a36Sopenharmony_ci if (val != EXPECTED_VALUE) { 17462306a36Sopenharmony_ci printf("[FAIL]\tseg[0] == %x; should be %x\n", val, EXPECTED_VALUE); 17562306a36Sopenharmony_ci exit(1); 17662306a36Sopenharmony_ci } 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci printf("[OK]\tThe segment points to the right place.\n"); 17962306a36Sopenharmony_ci exit(0); 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci int status; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ci /* Wait for SIGSTOP. */ 18562306a36Sopenharmony_ci if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) 18662306a36Sopenharmony_ci err(1, "waitpid"); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci struct user_regs_struct regs; 18962306a36Sopenharmony_ci 19062306a36Sopenharmony_ci if (ptrace(PTRACE_GETREGS, chld, NULL, ®s) != 0) 19162306a36Sopenharmony_ci err(1, "PTRACE_GETREGS"); 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci#ifdef __x86_64__ 19462306a36Sopenharmony_ci printf("\tChild GS=0x%lx, GSBASE=0x%lx\n", (unsigned long)regs.gs, (unsigned long)regs.gs_base); 19562306a36Sopenharmony_ci#else 19662306a36Sopenharmony_ci printf("\tChild FS=0x%lx\n", (unsigned long)regs.xfs); 19762306a36Sopenharmony_ci#endif 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci struct user_regs_struct regs2 = regs; 20062306a36Sopenharmony_ci#ifdef __x86_64__ 20162306a36Sopenharmony_ci regs2.rip = (unsigned long)tracee_zap_segment; 20262306a36Sopenharmony_ci regs2.rsp -= 128; /* Don't clobber the redzone. */ 20362306a36Sopenharmony_ci#else 20462306a36Sopenharmony_ci regs2.eip = (unsigned long)tracee_zap_segment; 20562306a36Sopenharmony_ci#endif 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci printf("\tTracer: redirecting tracee to tracee_zap_segment()\n"); 20862306a36Sopenharmony_ci if (ptrace(PTRACE_SETREGS, chld, NULL, ®s2) != 0) 20962306a36Sopenharmony_ci err(1, "PTRACE_GETREGS"); 21062306a36Sopenharmony_ci if (ptrace(PTRACE_CONT, chld, NULL, NULL) != 0) 21162306a36Sopenharmony_ci err(1, "PTRACE_GETREGS"); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* Wait for SIGSTOP. */ 21462306a36Sopenharmony_ci if (waitpid(chld, &status, 0) != chld || !WIFSTOPPED(status)) 21562306a36Sopenharmony_ci err(1, "waitpid"); 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci printf("\tTracer: restoring tracee state\n"); 21862306a36Sopenharmony_ci if (ptrace(PTRACE_SETREGS, chld, NULL, ®s) != 0) 21962306a36Sopenharmony_ci err(1, "PTRACE_GETREGS"); 22062306a36Sopenharmony_ci if (ptrace(PTRACE_DETACH, chld, NULL, NULL) != 0) 22162306a36Sopenharmony_ci err(1, "PTRACE_GETREGS"); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci /* Wait for SIGSTOP. */ 22462306a36Sopenharmony_ci if (waitpid(chld, &status, 0) != chld) 22562306a36Sopenharmony_ci err(1, "waitpid"); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci if (WIFSIGNALED(status)) { 22862306a36Sopenharmony_ci printf("[FAIL]\tTracee crashed\n"); 22962306a36Sopenharmony_ci return 1; 23062306a36Sopenharmony_ci } 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci if (!WIFEXITED(status)) { 23362306a36Sopenharmony_ci printf("[FAIL]\tTracee stopped for an unexpected reason: %d\n", status); 23462306a36Sopenharmony_ci return 1; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci 23762306a36Sopenharmony_ci int exitcode = WEXITSTATUS(status); 23862306a36Sopenharmony_ci if (exitcode != 0) { 23962306a36Sopenharmony_ci printf("[FAIL]\tTracee reported failure\n"); 24062306a36Sopenharmony_ci return 1; 24162306a36Sopenharmony_ci } 24262306a36Sopenharmony_ci 24362306a36Sopenharmony_ci printf("[OK]\tAll is well.\n"); 24462306a36Sopenharmony_ci return 0; 24562306a36Sopenharmony_ci} 246