Skip to content

runtime: signal handling: runtime bypasses TSAN/MSAN sigaction interceptors #17753

Closed
@bcmills

Description

@bcmills

go version devel +09bb643 Wed Nov 2 20:49:58 2016 +0000 linux/amd64

What did you do?

$ cat tsan8.go
package main

/*
#cgo CFLAGS: -fsanitize=thread
#cgo LDFLAGS: -fsanitize=thread

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct sigaction prev_sa;

void forwardSignal(int signo, siginfo_t *info, void *context) {
	// One of sa_sigaction and/or sa_handler
	if ((prev_sa.sa_flags&SA_SIGINFO) != 0) {
		prev_sa.sa_sigaction(signo, info, context);
		return;
	}
	if (prev_sa.sa_handler != SIG_IGN && prev_sa.sa_handler != SIG_DFL) {
		prev_sa.sa_handler(signo);
		return;
	}

	fprintf(stderr, "No Go handler to forward to!\n");
	abort();
}

void registerSegvFowarder() {
	struct sigaction sa;
	memset(&sa, 0, sizeof(sa));
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
	sa.sa_sigaction = forwardSignal;

	if (sigaction(SIGSEGV, &sa, &prev_sa) != 0) {
		perror("failed to register SEGV forwarder");
		exit(EXIT_FAILURE);
	}
}
*/
import "C"

func main() {
	C.registerSegvFowarder()

	defer func() {
		recover()
	}()
	var nilp *int
	*nilp = 42
}
$ CC=/usr/lib/llvm-3.8/bin/clang go run tsan8.go

What did you expect to see?

The test should pass (as it does without the -fsanitize=thread flags).

What did you see instead?

$ CC=/usr/lib/llvm-3.8/bin/clang go run tsan8.go
No Go handler to forward to!
SIGABRT: abort
PC=0x7f4b364cfc37 m=0

goroutine 1 [running]:

goroutine 17 [syscall, locked to thread]:
runtime.goexit()
        /usr/lib/google-golang/src/runtime/asm_amd64.s:2086 +0x1

rax    0x0
rbx    0x0
rcx    0xffffffffffffffff
rdx    0x6
rdi    0x7436
rsi    0x7436
rbp    0xb
rsp    0xc420009948
r8     0x7f4b375a1440
r9     0x524e23
r10    0x8
r11    0x206
r12    0xc
r13    0x7f4b375387c0
r14    0xc420009bc0
r15    0xc420009cf0
rip    0x7f4b364cfc37
rflags 0x206
cs     0x33
fs     0x0
gs     0x0
exit status 2

It appears that TSAN and MSAN replace the libc sigaction function with their own versions, and when sigaction is called they return the remembered struct sigaction previously passed to the libc function rather than calling all the way down to the OS.

One possible fix would be to make TSAN and MSAN make a real sigaction syscall to verify the actual handler before returning the cached one.

Another would be to make the Go runtime use the libc sigaction function (instead of making the system call directly from Go) when the sanitizers are in use (e.g. as we do today for mmap via x_cgo_mmap).

Metadata

Metadata

Assignees

No one assigned

    Labels

    FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions