This is an archive of the discontinued LLVM Phabricator instance.

[HWASAN] Instrument globals
Needs ReviewPublic

Authored by evgeny777 on Jan 14 2019, 10:22 AM.

Details

Summary

This patch enables access checks for global variables in HWASAN.
The algorithm is fairly simple:

For each global variable (GV) we can instrument

  • Replace it with one aligned to 1 << kDefaultShadowScale
  • Generate tag based on global name: hash_value(GV->getName())
  • Replace all uses of pointer to this variable with tagged pointer, i.e:
i64 *@foo -> i64* bitcast (i8* getelementptr (i8, i8* bitcast (i64* @foo to i8*), i64 N) to i64*)
where N is a tag shifted by 56 bits

Patch adds calls to __hwasan_register_globals and __hwasan_unregister_globals to tag and untag globals memory.

Everything else is handled HWASAN function pass after that.

Diff Detail

Event Timeline

hash_value(GV->getName())

This would break interoperability with non-instrumented translation units and DSOs.

AFAIK the proper implementation of global tagging needs to involve the dynamic loader, which can emit tagged pointers into GOTs. The compiler would need to route every access to a global through GOT, too, even for internal / non-preemptible globals.

I wonder if the same could be achieved with a clever use of ifunc?

lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
1275

This "all globals in one" approach breaks -Wl,-gc-sections, which is rather bad for binary size. You need to do the whole comdat + !associated thing, same as ASan.

even for internal / non-preemptible globals.

Why so? The only case I can imagine is ThinLTO promotion, but in such case instrumented code is imported as well

I wonder if the same could be achieved with a clever use of ifunc?

Do you mean generating ifunc for each instrumented global and tagging a pointer there?

even for internal / non-preemptible globals.

Why so? The only case I can imagine is ThinLTO promotion, but in such case instrumented code is imported as well

OK, not internal, but external non-preemptible for sure because they can be accessed directly from outside, and then the only source of truth for the variable's tag would be the GOT entry.

Internal variables can use a different approach, maybe based on a hash of the name (if there is one!) like in this patch.

I wonder if the same could be achieved with a clever use of ifunc?

Do you mean generating ifunc for each instrumented global and tagging a pointer there?

Yes, but I'm having doubts about how well this case (data-ifunc) is supported in the toolchain(s). If at all.

This comment was removed by evgeny777.

I wonder if the same could be achieved with a clever use of ifunc?

Do you mean generating ifunc for each instrumented global and tagging a pointer there?

I noticed that HWASAN forces compilation of position independent object files (this happens in SanitizerArgs.cpp). This means
that uninstrumented objects/libraries will also likely be position independent, beacuse linking PIE vs non-PIE is problematic

That said we can probably use ordinary ifuncs like it's already done with __hwasan_shadow. The lld linker can't handle this
at the moment because it can't create R_*_IRELATIVE relocs in .rela.dyn section, but GNU linkers can.

Your thoughts?

BTW, does it make sense to make this patch work for static variables first?

pcc added a comment.Jan 17 2019, 6:41 PM

An alternative idea to ifunc is to compute a tag for each global at compile time and store it in the global's virtual address in the symbol table. Although the tags wouldn't be randomized per run, maybe this would be enough.

You can do this by transforming the globals from:

@foo = global i32 123

to:

@foo.data = private global {i32, [12 x i8]} {i32 123, [12 x i8] zeroinitializer}
@foo = alias inttoptr(add(ptrtoint(@foo.data), 0x4200000000000000))) ; tag is 0x42

To tag the globals at load time, you can create a section of (tagged address, size) pairs. The runtime would tag the range (address, address + size) with the tag (address >> 56). The tagged address could use a 64-bit relative relocation (R_AARCH64_PREL64 on AArch64 or R_X86_64_PC64 on x86_64) to avoid needing the section to be dynamically relocated.

One possible downside is that the tagged virtual addresses in the symbol table could confuse tools (e.g. objdump), but we might be able to live with it.

evgeny777 updated this revision to Diff 196456.EditedApr 24 2019, 8:29 AM

Ok, I finally have some time to work on this again
Changes

  • External globals are instrumented via alias creation (suggested by @pcc). Compiler forces access to them via GOT even if they're dso_local
  • Internal globals are instrumented by means of GEP pointer tagging (same as before)
  • Interposable globals are not instrumented, but marked with special attribute, so they're accessed via GOT
  • Hidden globals are not instrumented. They can't be accessed via GOT, because this will make such variables preemptive and not making them preemptive causes relocation overflow at link time
  • Pointers to globals are stored in __hwasan_globals section. The pointer to section contents is passed to registration function.

Testing

To test the patch(es) I used RPI3 board with slightly modified Linux kernel (__user pointer untagging in syscalls). I ran two different test suites

  • LLVM/clang compiled with -DLLVM_BUILD_LLVM_DYLIB=ON and -DLLVM_LINK_LLVM_DYLIB=ON. Toolchain was deployed on board and lit tests were run. Looks good so far
  • LLVM test suite tests. Those triggered a number of TAG mismatch errors, one of them in hbd.test:
// decomp.cpp:
Exp *stack[8];
Exp **stkptr;
// .....
int decompileblock(Classfile *c, method_info_ptr mi) {
  // ...
  stkptr = stack;
  // ... pushunop is called later in this function ...
}
// d6-arith.cpp
int pushunop(Classfile *c) /* push unary operation, popping operand e.g. lneg */
{
  Exp *e1 = *(stkptr-1);
  // ....
}

There are quite a few other read overflows, for example in lencod and ldecod. I haven't investigated them thoroughly, but may be this would make sense if decide to proceed with this patch.

Issues

  • Linking with ld.bfd requires -Wl,--no-relax. I haven't investigated this yet (ld.lld works fine).
  • Linking uninstrumented code with weak or common symbols with instrumented code containing strong versions of those symbols will not work. This probably can be fixed at least for AArch64 by means of untagging symbol addresses on the linker side.
kcc added a reviewer: hctim.May 6 2019, 6:29 PM

sorry for the delay, I'll take a look later this week

pcc added inline comments.May 8 2019, 1:31 PM
lib/Transforms/Instrumentation/HWAddressSanitizer.cpp
1297

Could this use a relative relocation so that we aren't paying the cost of a dynamic relocation for each global?

Could this use a relative relocation so that we aren't paying the cost of a dynamic relocation for each global?

@pcc Sorry I missed your comment

I think this is not possible to do for shared objects, because GVs could be preempted. Still this looks possible for executables where I can set
visibility of instrumented globals to hidden. I'll try this out

pcc added a comment.May 17 2019, 6:50 PM

Could this use a relative relocation so that we aren't paying the cost of a dynamic relocation for each global?

@pcc Sorry I missed your comment

I think this is not possible to do for shared objects, because GVs could be preempted.

I think it should be possible if you make the relocation point to the object itself (which will have local linkage) and not the alias that you're creating. Of course you'll need to add the tag to the address in the same way as when you create the alias.

Still this looks possible for executables where I can set
visibility of instrumented globals to hidden. I'll try this out

I think it should be possible if you make the relocation point to the object itself (which will have local linkage) and not the alias that you're creating.

It doesn't seem to work. Dynamic loader doesn't know anything about aliases - alias and aliasee can point to different memory locations if aliasee is preempted.
Also let's say I have external declaration of variable foo. How do I know if foo is instrumented in another TU or not. If not then I think, I can't tag it's address