[PATCH] D77283: scudo: Add support for diagnosing memory errors when memory tagging is enabled.

Mitch Phillips via Phabricator via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 2 14:06:40 PDT 2020


hctim added inline comments.


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:30
+// the error_info data structure. Up to three possible causes are returned in
+// the reports array, in decreasing order of probability. The remaining elements
+// of reports are zero-initialized.
----------------
Sorry, 'remaining elements' here meaning any `scudo_error_report` struct that's unused?


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:37
+//
+// ptr is the fault address. On aarch64 this is available in the system register
+// FAR_ELx, or far_context.far in an upcoming release of the Linux kernel. This
----------------
Why `ptr` then? Why not `fault_address`?


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:45
+// obtained by calling the function __scudo_get_stack_depot_addr() in the
+// crashing process. The size of the stack depot is available by calling the
+// function __scudo_get_stack_depot_size().
----------------
"in the crashing process, generally ahead-of-time (at libc init, for example)."


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:55
+// The more memory available via this pointer, the more likely it is that the
+// function will be able to analyze a crash correctly.
+//
----------------
Can you make a recommendation on how much memory we need? Should that be defined by the platform, or by Scudo (via. a callback)? I assume we need at least 3 allocations (one to the left and one to the right) to make a reasonable guess, but how many do you see us having? A constant 16? Is it some function on the sizeof the allocation?

Our top primary size class on Android is 64KiB + 16B. Assuming a static 16 allocations (8 on either side) for the crash handler to do its thing, that's a megabyte per crash. Not too bad.

Once we support secondary tagged allocations though (assuming we intend on doing this), we'd have to rethink this, no? Would it be worth having a callback of `__scudo_get_surrounding_metadata()` that, for some fault address, returns the metadata (header + any deallocation data left over) for some number of surrounding allocations alone?


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:59
+// via memory. Each byte of this array corresponds to a region of memory of size
+// equal to the architecturally defined memory tag granule size (16 on aarch64).
+//
----------------
Maybe add a comment where this is normally obtained from?


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:61
+//
+// memory_addr is the address of memory in the crashing process's address space.
+//
----------------
"the starting address"


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:65
+// pointer.
+void __scudo_get_error_info(struct scudo_error_info *error_info, uintptr_t ptr,
+                            const char *stack_depot, const char *region_info,
----------------
Meta note - I'd also add an explicit disclaimer here that this is not intended to work across API level boundaries (version_of_scudo_allocator === version_of_scudo_crash_handler).


================
Comment at: compiler-rt/lib/scudo/standalone/include/scudo/interface.h:70
+
+enum scudo_error_type {
+  UNKNOWN,
----------------
We now have two ways that scudo reports errors. For UaF/overflow, as per this, you get the report from `__scudo_get_error_info`. For internally detected errors (invalid free, double free), you still get an abort message. We should probably unify the behaviour here, because:

 1. The metadata we collect about tags/threads/traces is useful in debugging double frees, etc.
 2. Having different exit conditions is harder to keep track of. I can easily see a double-free that ends up doing some load/store with incorrect tags before it hits the internal trap, and subsequently we report it incorrectly (i.e. a double free now looks like an UNKNOWN or BUFFER_OVERFLOW, etc.).


================
Comment at: compiler-rt/lib/scudo/standalone/stack_depot.h:42
+  HybridMutex ring_end_mu;
+  u32 ring_end;
+
----------------
I know that this is zero-initialized by the linker in its current inclusion in the allocator class, but it's a bit roundabout to detemine this. Can you explicitly initialize these varaiables?


================
Comment at: compiler-rt/lib/scudo/standalone/stack_depot.h:44
+
+  static const uptr kTabBits = 16;
+  static const uptr kTabSize = 1 << kTabBits;
----------------
I worry that these values aren't configurable. We might want to have a larger stack depot on some devices, if:

  # Scudo ends up using a stack depot for a MTE-similar feature on another architecture.
  # Someone ends up using stack trace collection for non-MTE scenarios (I mean, it's a cheap way to do heap profiling).
  # We have different devices that have more/less sensitivity to memory overhead.

Should we make this templated?




================
Comment at: compiler-rt/lib/scudo/standalone/stack_depot.h:50
+  static const uptr kRingBits = 19;
+  static const uptr kRingSize = 1 << kRingBits;
+  static const uptr kRingMask = kRingSize - 1;
----------------
```
#ifdef __LP64__
  static const uptr kRingSize = kRingBits * 8;
#else
  static const uptr kRingSize = kRingBits * 4;
#endif
```

... as we can ask for trace collection on 32-bit?


================
Comment at: compiler-rt/lib/scudo/standalone/stack_depot.h:64
+    u64 entry = ring[ring_pos];
+    u64 id = (u64(end - begin) << 33) | (u64(hash) << 1) | 1;
+    if (entry == id)
----------------
Can we please add some more documentation to this class? I get that this is saving some unique-ID to prevent us from saving the same allocation trace twice, but it would be nice if that was immediately obvious.


================
Comment at: compiler-rt/lib/scudo/standalone/stack_depot.h:85
+    u64 hash_with_tag_bit = (u64(hash) << 1) | 1;
+    if ((entry & 0x1ffffffff) != hash_with_tag_bit)
+      return false;
----------------
Same thing here. What makes up this cookie should be more obvious IMHO. I get the top 31 bits are roughly a UID for the stack trace, the middle 32 bits are hash (and thus these two combined provide some hash-collision resistance), and the bottom bit is to uniquely identifiy cookies as the FP-address shouldn't have the LSB set (except for in thumb...), but putting this somewhere and typedefing this u64 seems like a good idea.


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D77283/new/

https://reviews.llvm.org/D77283





More information about the llvm-commits mailing list