Created attachment 151170 [details] 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.
I'm sorry. The attached file was a working example (compare the assembly). Please refer to the code in the ticket itself. Here is the output from "valgrind --tool=memcheck -v ./ test": ==97692== Memcheck, a memory error detector ==97692== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==97692== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info ==97692== Command: ./a.out ==97692== ==97692== Invalid write of size 8 ==97692== at 0x109254: eval(unsigned char*) (in /home/ubuntu/vb/test/a.out) ==97692== by 0x109454: main (in /home/ubuntu/vb/test/a.out) ==97692== Address 0x4037000 is in a --- anonymous segment ==97692== ==> offset in memory: 0 ==> offset in memory: 67334144 ==97692== ==97692== HEAP SUMMARY: ==97692== in use at exit: 0 bytes in 0 blocks ==97692== total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==97692== ==97692== All heap blocks were freed -- no leaks are possible ==97692== ==97692== For lists of detected and suppressed errors, rerun with: -s ==97692== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 0 from 0)
Created attachment 151171 [details] Minimal reproducible example (failing) USE THIS TO TEST
Using the below should solve the problem: valgrind --vex-iropt-register-updates=allregs-at-mem-access ./test See https://valgrind.org/docs/manual/manual-core.html#manual-core.signals for more info.
(In reply to Philippe Waroquiers from comment #3) > Using the below should solve the problem: > valgrind --vex-iropt-register-updates=allregs-at-mem-access ./test > > See https://valgrind.org/docs/manual/manual-core.html#manual-core.signals > for more info. That works indeed. Can't believe I missed that. Thank you for that hint.