Requesting clarification of some HWASAN behaviours.

Hello,

I'm working on implementing hwasan instrumentation in GCC, and have just
started discussing my current work-in-progress on the gcc-patches
mailing list.
(Matthew Malcomson - [Patch 0/X] [WIP][RFC][libsanitizer] Introduce HWASAN to GCC -- the email
that Kostya saw and added people to).

I've gotten about as basic a user-space implementation as possible
(using the interceptor ABI) up and running, and would appreciate it if I
could get some clarification on some other parts of hwasan and it's use
so I can look into those.

As you may guess by my current task I'm most interested in features that
require some compiler instrumentation.

Most of the questions I have are just double checking the impression I
have from skimming the LLVM source code, but I would very much
appreciate any extra clarification people think would be helpful.

Evgenii's recent reply on the GCC mailing list was very useful, so I
figure there could be a lot of information I don't quite know to ask for
that could be a great help.
(Evgenii Stepanov via - Re: [Patch 0/X] [WIP][RFC][libsanitizer] Introduce HWASAN to GCC)

The questions I know I have are:

1) What code-generation differences are there between kernel and
    userspace hwasan?
    From what I can see in the llvm source code there is
    - No C++ exceptions, globals, or module constructor & init functions
    - Kernel has match-all of 0xFF by default.
    - Tagging code differences due to 0xFF in kernel address top byte.
    - Shadow memory is located in a different place.
    Is there anything else I should know around hwasan in the kernel?

2) I believe compiling while ignoring the "short-granule" feature would
    not add any incompatibilities with other code. Is this correct?

3) Am I right in thinking that longjmp and setjmp are only handled in
    the platform ABI? (I couldn't see any interceptors for them).

Thanks,
Matthew

Hello,

I'm working on implementing hwasan instrumentation in GCC, and have just
started discussing my current work-in-progress on the gcc-patches
mailing list.
(Matthew Malcomson - [Patch 0/X] [WIP][RFC][libsanitizer] Introduce HWASAN to GCC -- the email
that Kostya saw and added people to).

I must say I really like your plan to reuse parts of HWASan codegen
for MTE in that change. In LLVM, we did hwasan before we had a good
idea of what MTE ISA would look like, and they ended up mostly
independent. I'm considering eventually refactoring HWASan to use more
of MTE codegen, but it does not look like this work would be
justifiable by the performance or code size gains it might bring.

I've gotten about as basic a user-space implementation as possible
(using the interceptor ABI) up and running, and would appreciate it if I
could get some clarification on some other parts of hwasan and it's use
so I can look into those.

As you may guess by my current task I'm most interested in features that
require some compiler instrumentation.

Most of the questions I have are just double checking the impression I
have from skimming the LLVM source code, but I would very much
appreciate any extra clarification people think would be helpful.

Evgenii's recent reply on the GCC mailing list was very useful, so I
figure there could be a lot of information I don't quite know to ask for
that could be a great help.
(Evgenii Stepanov via - Re: [Patch 0/X] [WIP][RFC][libsanitizer] Introduce HWASAN to GCC)

The questions I know I have are:

1) What code-generation differences are there between kernel and
    userspace hwasan?
    From what I can see in the llvm source code there is
    - No C++ exceptions, globals, or module constructor & init functions
    - Kernel has match-all of 0xFF by default.
    - Tagging code differences due to 0xFF in kernel address top byte.
    - Shadow memory is located in a different place.
    Is there anything else I should know around hwasan in the kernel?

That's about all I can remember.

2) I believe compiling while ignoring the "short-granule" feature would
    not add any incompatibilities with other code. Is this correct?

As long as short granules are disabled in the runtime library, too.
And no other code in the same process is compiled using short granules
for stack instrumentation. Basically, a short-granule-unaware tag
check will see a short granule as a tag mismatch.That is because with
short granules, the actual tag is stored in the last byte of the
allocation, and the size of the allocation - in the memory tag.

3) Am I right in thinking that longjmp and setjmp are only handled in
    the platform ABI? (I couldn't see any interceptors for them).

Yes. I think this is a simple omission coming from the fact that the
interceptor ABI have not really seen a lot of use outside of
compiler-rt tests.

Hello again,

I have been thinking more about the GCC implementation of hwasan and
found a few more questions that I would really appreciate help with.

Hi,

Hello again,

I have been thinking more about the GCC implementation of hwasan and
found a few more questions that I would really appreciate help with.

---
I've noticed a match-all condition in the compiler inline
instrumentation, but can't see where it's used in the libhwasan function
call checks. Is that a planned behaviour or am I just missing the part
in the code where this happens?

From reading the git history I'm guessing it's not in the library since
the feature was introduced for the kernel specifically and the kernel
doesn't use the library ... or is that wild speculation on my part?

Yes, I think this is exactly right.

---
Would it be OK to add `report_load{size}` functions to the library? I
notice that LLVM emits an inline-assembler `brk` into the IR for the
inline tag-mismatch report.

I'm a little uncomfortable putting architecture specific assembly code
into the mid-end of GCC (even though the entire HWASAN is AArch64
specific in GCC) and would like to put some "just report" functions into
libhwasan (in the same manner that libasan has
`__asan_report_(load|store){1,2,4,8,16,_n}{,_noabort}` functions).

Would that be OK? It's a simple patch that I already have locally. I
guess tag-mismatch reporting would then contain an extra function in the
stack report?

Does __hwasan_tag_mismatch / __hwasan_tag_mismatch_stub work for you?
The first one has a non-standard ABI, but it can save register state
at the point of the fault in the user code.

---
As I understand it, when the mainline kernel gets patched to accept
tagged pointers in syscalls, the relaxed ABI will be behind a `prctl`
call rather than being generically turned on. I guess this means that
Android will eventually have the same requirement?

If/when that happens, would the initial call to `prctl` be put in
libhwasan, or would something like that be done in Bionic? (n.b. this
call needs to be done for every program since the setting doesn't
propagate across fork). I have a patch that adds the relevant `prctl`
call to what `__hwasan_init` does in libhwasan. I do this because I'm
using a glibc unmodified for hwasan.

I agree, __hwasan_init is the right place for this.

---
I've noticed code around maintaining a thread-specific ring-buffer of
the stack frames that have been used (recording the PC and SP) -- it
looks like this is in order to give better messages on tag-mismatch, is
that correct?

Yes, for stack errors. We emit the tag offset of each local variable
in DWARF debug info, and the ring buffer contains PC, SP and the base
tag for recent stack frames. The code in PrintStackAllocations
combines this info to find possible source(s) of a mismatching pointer
tag.

---
Would the addition of `longjmp` and `setjmp` to the "interceptor ABI"
build of libhwasan be OK? I understand that LLVM pretty much only use
that ABI for testing, but I'm working without a glibc that supports the
"platform ABI".

Sure, patches are welcome.

Hello,

When looking into the way that hwasan handles C++ exceptions I found
myself wondering about the need for landing pad cleanups (as documented
in the code
https://github.com/llvm-mirror/compiler-rt/blob/69445f095c22aac2388f939bedebf224a6efcdaf/lib/hwasan/hwasan_exceptions.cpp#L51
).

The comment above that code says:
// We only untag frames without a landing pad because landing pads are
// responsible for untagging the stack themselves if they resume.

I think the code as implemented untags all frames as it goes past
whether or not they have a landing pad, and am hoping people can check
or correct my understanding.

It seems that personality routine will eventually return
_URC_CONTINUE_UNWIND if this stack frame is to be unwound past (and
hence the frame will eventually get untagged). If the frame has landing
pads then that just means the personality routine will be called
multiple times for this frame before returning that value.

I took a look by testing code generated with a clang patched to avoid
adding instrumentation to landing pads and it seems that the personality
wrapper does indeed clear stack frames with a landing pad in the basic
case.

Is there an edge case where the personality wrapper is known to not be
sufficient? If not would removing that extra instrumentation sound
reasonable?

The test I ran was to generate code with a clang patched with the diff
below, and run it in a debugger.
I'm attaching the annotated gdb session to demonstrate my reasoning. I
checked that the shadow memory is not untagged in a landing pad and that
after _Unwind_Resume the personality wrapper is run again, eventually
returning _URC_CONTINUE_UNWIND and untagging the shadow memory.

diff --git a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
index df7606d..ca97d72 100644
--- a/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
@@ -1110,8 +1110,7 @@ bool HWAddressSanitizer::sanitizeFunction(Function
&F) {
             continue;
           }

- if (isa<ReturnInst>(Inst) || isa<ResumeInst>(Inst) ||
- isa<CleanupReturnInst>(Inst))
+ if (isa<ReturnInst>(Inst) || isa<CleanupReturnInst>(Inst))
           RetVec.push_back(&INST);

         if (auto *DDI = dyn_cast<DbgDeclareInst>(&Inst))

gdb-session.vsh (12.3 KB)

HI Matthew,

I think you may be correct. It makes sense that an unwinder would need to visit the frame that called _Unwind_Resume again when unwinding. And when it does, there will be no landing pad associated with the _Unwind_Resume call, so the unwinder will be called with _URC_CONTINUE_UNWIND and the stack frame will be cleared. I verified that with your change to the LLVM pass and this change to the hwasan test suite:

diff --git a/compiler-rt/test/hwasan/TestCases/try-catch.cpp b/compiler-rt/test/hwasan/TestCases/try-catch.cpp
index 8e35a9dd2a5…4d186bf17a6 100644
— a/compiler-rt/test/hwasan/TestCases/try-catch.cpp
+++ b/compiler-rt/test/hwasan/TestCases/try-catch.cpp
@@ -23,6 +23,11 @@ void h() {

attribute((noinline))
void g() {

  • // Make sure that we need to resume when unwinding past this function.
  • struct S {
  • ~S() { optimization_barrier((void *)this); }
  • } s;