https://bugs.kde.org/show_bug.cgi?id=457619
Bug ID: 457619 Summary: Instructions are not consistently executed after returning from a SIGSEGV signal handler Product: valgrind Version: 3.18.1 Platform: Ubuntu Packages OS: Linux Status: REPORTED Severity: crash Priority: NOR Component: memcheck Assignee: jsew...@acm.org Reporter: tanning-skull...@icloud.com Target Milestone: --- Created attachment 151170 --> https://bugs.kde.org/attachment.cgi?id=151170&action=edit Minimal reproducible example SUMMARY Valgrind's memcheck does not properly and consistently execute instructions after returning from a SIGSEGV signal handler STEPS TO REPRODUCE 1. Compile the following minimal reproducible example with gcc (Code is also attached): e.g. "gcc -g test.c -o test" ==================== #include <cstdlib> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <sys/mman.h> #include <unistd.h> __attribute__((naked)) void eval(uint8_t *mem) { // First argument is in rdi asm(".intel_syntax noprefix"); asm("mov rax,0"); asm("mov QWORD PTR [rdi+rax*1],0x0"); asm("mov rax,0"); // <-- remove this and it works asm("mov QWORD PTR [rdi+rax*1],0x0"); asm("ret"); asm(".att_syntax prefix"); } size_t getOSMemoryPageSize() noexcept { return (size_t)sysconf(_SC_PAGE_SIZE); } uint8_t *memory = nullptr; void allocMemory() { memory = (uint8_t *)mmap(nullptr, getOSMemoryPageSize(), PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); } void unprotectMemory() { mprotect(memory, getOSMemoryPageSize(), PROT_READ | PROT_WRITE); } void signalHandler(int32_t const signalId, siginfo_t *const si, void *const ptr) { long const offsetInMemory = (uint8_t *)si->si_addr - memory; char msg[1000]; int len = sprintf(msg, "==> offset in memory: %lu\n", offsetInMemory); write(2, msg, (size_t)len); if (offsetInMemory < 0 || offsetInMemory >= getOSMemoryPageSize()) { _exit(1); } // mprotect is signal safe on GNU libc unprotectMemory(); } int main(void) { // Allocate protected (non-readable, non-writable) memory allocMemory(); // Set up a signal handler for SIGSEGV struct sigaction sa = {}; sa.sa_sigaction = signalHandler; sa.sa_flags = SA_SIGINFO | SA_NODEFER; sigfillset(&sa.sa_mask); sigaction(SIGSEGV, &sa, nullptr); // Execute an asm function that touches the protected memory, which triggers the signal handler where the protection of the memory is subsequently removed (it is set as readable and writable). After the signal handler returns, the failed instruction is retried and can now successfully execute the memory writes eval(memory); } ==================== 2. Execute normally ("./test") and then with memcheck ("valgrind --tool=memcheck ./test") OBSERVED RESULT Program prints the following when executed normally: "==> offset in memory: 0" Program prints the following when executed with memcheck: "==> offset in memory: 0 ==> offset in memory: 67334144" EXPECTED RESULT Program prints the following both when executed normally and when executed with memcheck "==> offset in memory: 0" SOFTWARE/OS VERSIONS gcc-10 (Ubuntu 10.3.0-15ubuntu1) 10.3.0 Ubuntu 22.04 LTS valgrind-3.18.1 uname -r: 5.15.0-1015-aws ADDITIONAL INFORMATION When the program is executed under vgdb, it can be seen that the rax register has a wrong value after the signal handler returns (namely 67334144, see outputs). Additionally, using the gdb command "i r rax", it can be observed that the second "mov rax,0" instruction does not have any effect on the value stored in the rax register. The following assembly sequence works interestingly: mov rax,0 mov QWORD PTR [rdi+rax*1],0x0 mov QWORD PTR [rdi+rax*1],0x0 ret And this also works: mov QWORD PTR [rdi],0x0 mov rax,0 mov QWORD PTR [rdi+rax*1],0x0 ret NOTE: memcheck produces a warning about an "Invalid write of size 8". This is due to the memory that is accessed being deliberately marked with PROT_NONE (neither readable, writable nor executable). This protection is only removed in the signal handler. -- You are receiving this mail because: You are watching all bug changes.