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: [email protected]
Reporter: [email protected]
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.