DFSan uses TLS to pass metadata of arguments and return values. When an instrumented function accesses the TLS, if a signal callback happens, and the callback calls other instrumented functions with updating the same TLS, the TLS is in an inconsistent state after the callback ends. This may cause either under-tainting or over-tainting. This fix follows MSan's workaround. https://github.com/llvm/llvm-project/commit/cb22c67a21e4b5e1ade65141117a70be318be072 It simply resets TLS at restore. This prevents from over-tainting. Although under-tainting may still happen, a taint flow can be found eventually if we run a DFSan-instrumented program multiple times. The alternative option is saving the entire TLS. However the TLS storage takes 2k bytes, and signal calls could be nested. So it does not seem worth. This diff fixes sigaction. A following diff will be fixing signal.
Details
- Reviewers
morehouse - Commits
- rGe1a4322f8136: [dfsan] Clean TLS after sigaction callbacks
Diff Detail
- Repository
- rG LLVM Github Monorepo
Event Timeline
compiler-rt/lib/dfsan/dfsan.h | ||
---|---|---|
79 | ||
96 | RAII seems like overkill since Backup() does nothing and we don't have any early returns in the functions we use it from. How about a simple function call instead? | |
compiler-rt/lib/dfsan/dfsan_custom.cpp | ||
816 | ||
829 | Can these params actually be labeled? Usually labels come in via TLS or extra args. (But I am not sure how signals work with DFSan) | |
844 | sizeof(*si)? | |
898–931 | I think we need to block signals before taking this lock. sigaction needs to be async-signal safe. | |
905 | Any reason not to simply use SA_SIGINFO (and other standard SIG_IGN, SIG_DFL constants below)? | |
909 | Is this cast necessary? | |
915 | Is this cast necessary? | |
926 | I think we need logic here to differentiate sa_sigaction and sa_handler instead of blindly casting to sa_sigaction. | |
compiler-rt/test/dfsan/sigaction.c | ||
9 | Is it worth testing legacy mode at all? I think the relevant code paths are the same. | |
33 | ||
38 | Can remove this line if we do psa = {}; above. | |
41 | We should also test that the correct oldact is returned. | |
compiler-rt/test/dfsan/sigaction_stress_test.c | ||
3 | Is it worth testing legacy mode at all? I think the relevant code paths are the same. | |
41–42 |
compiler-rt/lib/dfsan/dfsan.h | ||
---|---|---|
78 | Typo: headlers -> handlers The typo morehouse raised also appears in change description: accesse -> accesses |
compiler-rt/lib/dfsan/dfsan_custom.cpp | ||
---|---|---|
829 | Thank you for catching this. Right. This works only for *si and *uc. For others, their TLS need to be set. Will be changing. | |
898–931 | Yeah. Such a case seems possible. Does blocking signals work like this? sigset_t original; sigset_t blocked; sigemptyset(&blocked); // is it fine to block all signals or needs to allow some, for example, SIGPROF and SIGALRM? sigprocmask(SIG_BLOCK, &blocked, &original); { lock, /// } sigprocmask(SIG_SETMASK, &original, nullptr); MSan's code is likely to have the similar issue. @eugenis: Does MSan's case need a similar update? |
compiler-rt/lib/dfsan/dfsan_custom.cpp | ||
---|---|---|
898–931 | s/sigemptyset/sigfillset I would also recommend creating an RAII SignalSpinMutexLock that blocks signals prior to locking. |
addressed comments
compiler-rt/lib/dfsan/dfsan.h | ||
---|---|---|
78 | Thanks | |
96 | I feel this one is more about making the code easy to read, and in case we need to make SignalAction and SignalHandler with multiple return points. it still works. | |
compiler-rt/lib/dfsan/dfsan_custom.cpp | ||
816 | rephrased after using a RAII async-safe lock. | |
898–931 | introduced SignalSpinLocker. Named it as SignalSpinLocker but not SignalSpinMutexLock because mutex is moved to the class's private static member, and after that the class looks like a lock. | |
905 | Using them avoids casting from handle_t to uptr. But since this function does not use __sanitizer::* consistently. replaced. | |
909 | removed. they were requred if pnew_act was declared as __sanitizer::action_type. | |
926 | Thank you for catching this. It worked before because the undefined behavior in linux: both sa_sigaction and sa_handler are defined as a union, and at runtime I guess linux always applies the 3 arguments that sa_sigaction needs, so it works for both sa_sigaction and sa_handler because sa_handler can safely use the first argument... | |
compiler-rt/test/dfsan/sigaction.c | ||
41 | This test case is for testing callbacks work by SIGHUP since that custom.cpp test cannot be kiiled. |
compiler-rt/lib/dfsan/dfsan.h | ||
---|---|---|
90 | Since this is only used in dfsan_custom.cpp, should we move it there? | |
96 | Then let's remove Backup since it's unused. And also rename it to something more accurate, e.g. ClearThreadLocalState | |
compiler-rt/lib/dfsan/dfsan_custom.cpp | ||
815 | I don't see any atomics in the implementation. | |
861–863 | ||
924 | I think these uptr casts are unnecessary. | |
compiler-rt/test/dfsan/custom.cpp | ||
817–818 |
Typo: headlers -> handlers
The typo morehouse raised also appears in change description: accesse -> accesses