Bug 457619 - Instructions are not consistently executed after returning from a SIGSEGV signal handler
Summary: Instructions are not consistently executed after returning from a SIGSEGV sig...
Status: RESOLVED NOT A BUG
Alias: None
Product: valgrind
Classification: Developer tools
Component: memcheck (other bugs)
Version First Reported In: 3.18.1
Platform: Ubuntu Linux
: NOR crash
Target Milestone: ---
Assignee: Julian Seward
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-08-08 10:16 UTC by tanning-skulls0f
Modified: 2022-08-13 18:16 UTC (History)
2 users (show)

See Also:
Latest Commit:
Version Fixed/Implemented In:
Sentry Crash Report:


Attachments
Minimal reproducible example (1.53 KB, text/plain)
2022-08-08 10:16 UTC, tanning-skulls0f
Details
Minimal reproducible example (failing) (1.53 KB, text/plain)
2022-08-08 10:27 UTC, tanning-skulls0f
Details

Note You need to log in before you can comment on or make changes to this bug.
Description tanning-skulls0f 2022-08-08 10:16:17 UTC
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.
Comment 1 tanning-skulls0f 2022-08-08 10:25:52 UTC
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)
Comment 2 tanning-skulls0f 2022-08-08 10:27:59 UTC
Created attachment 151171 [details]
Minimal reproducible example (failing)

USE THIS TO TEST
Comment 3 Philippe Waroquiers 2022-08-13 12:11:17 UTC
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.
Comment 4 tanning-skulls0f 2022-08-13 18:16:19 UTC
(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.