Skip to content

Commit ac30592

Browse files
committed
Addressed comments in the runtime
- Added kasan_shadow_runtime.h for the hook declarations - Added offset in report hook - Added shadow update validation and error reporting - Removed unused ASan compatibility stubs and unused helpers - free() wrapper extended with quarantine queue - memset/memcpy used instead of raw loops - Improved comments and README
1 parent aeed76f commit ac30592

5 files changed

Lines changed: 279 additions & 158 deletions

File tree

arm-software/embedded/samples/src/cpp-baremetal-semihosting-kasan/Makefile

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,16 @@ print(f"KASAN_SHADOW_SIZE=0x{shadow_size:x} " \
4848
f"KASAN_RAM_SIZE=0x{ram_size:x}")')
4949
$(foreach var,$(KASAN_LAYOUT),$(eval $(var)))
5050

51+
# Number of freed heap blocks to keep poisoned before returning the oldest one
52+
# to the allocator. Larger values improve recent use-after-free detection but
53+
# retain more heap memory.
54+
KASAN_HEAP_QUARANTINE_SIZE=4
55+
5156
KASAN_DEFINES=\
5257
-DKASAN_SHADOW_SCALE=$(KASAN_SHADOW_SCALE) \
53-
-DKASAN_SHADOW_OFFSET=$(KASAN_SHADOW_OFFSET)
58+
-DKASAN_SHADOW_OFFSET=$(KASAN_SHADOW_OFFSET) \
59+
-DKASAN_HEAP_QUARANTINE_SIZE=$(KASAN_HEAP_QUARANTINE_SIZE)
60+
5461
KASAN_MEMORY_MAP=\
5562
-Wl,--defsym=__boot_flash=$(BOOT_FLASH_ORIGIN) \
5663
-Wl,--defsym=__boot_flash_size=$(BOOT_FLASH_SIZE) \

arm-software/embedded/samples/src/cpp-baremetal-semihosting-kasan/README.md

Lines changed: 46 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,9 @@ report an error if the accessed address is poisoned. See Clang's
1010
[AddressSanitizer documentation](https://clang.llvm.org/docs/AddressSanitizer.html)
1111
for more detail on the sanitizer model.
1212

13-
The Makefile reserves the last 2 KiB of RAM for shadow memory. It uses Python
14-
to calculate the shadow start, end, size, and offset from the RAM origin, total
15-
RAM size, and KASan shadow scale. It passes the calculated offset to Clang's
16-
`-asan-mapping-offset` and the KASan runtime, and passes the full memory map to
17-
the linker with `-Wl,--defsym`, so the application RAM maps into the reserved
18-
shadow region without moving the normal RAM origin. The remaining RAM is
19-
exposed to the selected C library for data, heap, and stack.
13+
The Makefile reserves the last 2 KB of RAM for shadow memory. It calculates the
14+
shadow memory layout based on provided target memory map. The remaining RAM is
15+
exposed to the C library for data, heap, and stack.
2016

2117
## Build and run
2218

@@ -31,58 +27,70 @@ exposed to the selected C library for data, heap, and stack.
3127

3228
## Customizing the runtime
3329

30+
### Shadow memory
31+
3432
The shadow memory runtime is sensitive to the target memory map. If the RAM
3533
origin or RAM size changes, override `RAM_ORIGIN` or `HW_RAM_SIZE` when
3634
invoking `make`, for example:
37-
`make run RAM_ORIGIN=<origin> HW_RAM_SIZE=<size>`. The Makefile
38-
recalculates `KASAN_SHADOW_OFFSET` from those values. If the shadow placement
39-
policy changes, update the Makefile calculation and `KASAN_MEMORY_MAP` symbols
40-
together, because Clang's instrumentation, the runtime, and the linker must all
41-
use the same `shadow = (address >> 3) + offset` mapping. Keep the Makefile
42-
memory-map variables as the source of truth for which application regions are
43-
actually covered.
44-
45-
Tune heap checking by changing `KASAN_HEAP_REDZONE_SIZE` and the quarantine
46-
policy. The sample keeps freed blocks poisoned forever so use-after-free is
47-
reliable in a demo; a practical runtime should use a bounded quarantine or
48-
return old blocks to the allocator once the diagnostic value is no longer worth
49-
the RAM cost. Keep the runtime and allocator wrappers unsanitized, otherwise the
50-
reporting path can recursively instrument itself.
51-
52-
The reporting path avoids `printf` and formats messages through weak output
35+
`make run RAM_ORIGIN=<origin> HW_RAM_SIZE=<size>`.
36+
The Makefile calculates `KASAN_SHADOW_OFFSET` from those values.
37+
38+
### Heap
39+
40+
The sample keeps recently freed blocks in a bounded quarantine queue so
41+
use-after-free remains detectable. When the quarantine is full, the oldest
42+
poisoned block is returned to the real allocator.
43+
44+
Use `make KASAN_HEAP_QUARANTINE_SIZE=<n>` to change the
45+
number of retained freed blocks, or `0` to disable quarantine and free blocks
46+
immediately.
47+
48+
### Redirecting messages
49+
50+
Error reporting avoids `printf` and formats messages through weak output
5351
hooks. The default `kasan_rt_putc` implementation uses libc `putc`, while
54-
`kasan_rt_puts` and `kasan_rt_putaddr` build on top of it. A target can redirect
55-
reports by defining its own non-weak hooks, usually just `kasan_rt_putc`:
52+
`kasan_rt_puts`, `kasan_rt_putaddr`, and `kasan_rt_putsize` build on top of it.
53+
54+
A target can redirect reports by defining its own non-weak hooks, usually just
55+
`kasan_rt_putc`:
5656

5757
```cpp
58+
#include "kasan_shadow_runtime.h"
59+
5860
extern "C" void kasan_rt_putc(char c) {
5961
// Write c to UART, ITM/SWO, retained RAM, or another target-specific sink.
6062
}
6163
```
6264
63-
The runtime provides weak report handlers, `kasan_rt_report_access` and
64-
`kasan_rt_report_alloc_error`. The default handlers print a report and abort.
65-
This sample overrides them in `hello.cpp` with unsanitized handlers (to avoid
66-
infinite recursion) that print `(recovered)` and return, so every demo can run
67-
in one process:
65+
### Error reporting and recovery
66+
67+
The runtime provides weak report handlers: `kasan_rt_report_access`,
68+
`kasan_rt_report_alloc_error`, and `kasan_rt_report_shadow_update_error`. The
69+
default handlers print a report and abort.
70+
71+
This sample overrides the access and allocator handlers in `hello.cpp` to print
72+
`(recovered)` and return, so every demo can run in one process:
6873
6974
```cpp
75+
#include "kasan_shadow_runtime.h"
76+
77+
// Handlers must be unsanitized to avoid infinite recursion
7078
extern "C" __attribute__((no_sanitize("kernel-address"))) void
71-
kasan_rt_report_access(const char *kind, uintptr_t addr, uintptr_t size) {
79+
kasan_rt_report_access(const char *kind, uintptr_t addr, size_t size,
80+
size_t offset) {
7281
// Report the access and return to recover, or abort to stop.
7382
}
7483
```
7584

76-
C code does not need the `extern "C"` wrapper. Keep these hooks small and avoid
77-
allocating memory from them, because they run after KASan has found memory
78-
corruption. Returning from a report handler allows the invalid access to
79-
continue, so recovery is useful for diagnostics but is not a safe production
80-
policy. Add symbolization only if the target has enough storage and the extra
81-
report detail is worth the code-size cost.
85+
Avoid allocating memory in these hooks, because they run after KASan has found
86+
memory corruption.
87+
88+
Returning from a report handler allows the invalid access to continue: recovery
89+
is useful for diagnostics but is not a safe in production.
8290

8391
## Limitations
8492

85-
Dynamic stack allocations remain disabled with
93+
Dynamic stack allocations are disabled with
8694
`-mllvm -asan-instrument-dynamic-allocas=0` to keep the runtime small.
8795

8896
The reserved shadow memory covers the application RAM range only. Stack, heap,

arm-software/embedded/samples/src/cpp-baremetal-semihosting-kasan/hello.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,22 @@
88
#include <stdio.h>
99
#include <stdlib.h>
1010

11+
#include "kasan_shadow_runtime.h"
12+
1113
// Override the default aborting report hooks so this sample can recover and
1214
// continue through all demo tests in one run.
1315

14-
extern "C" void kasan_rt_puts(const char *s);
15-
extern "C" void kasan_rt_putaddr(uintptr_t addr);
16-
1716
extern "C" __attribute__((no_sanitize("kernel-address"))) void
18-
kasan_rt_report_access(const char *kind, uintptr_t addr, uintptr_t size) {
17+
kasan_rt_report_access(const char *kind, uintptr_t addr, size_t size,
18+
size_t offset) {
1919
kasan_rt_puts("KASAN: invalid ");
2020
kasan_rt_puts(kind);
2121
kasan_rt_puts(" at ");
2222
kasan_rt_putaddr(addr);
2323
kasan_rt_puts(", size ");
24-
kasan_rt_putaddr(size);
24+
kasan_rt_putsize(size);
25+
kasan_rt_puts(", offset ");
26+
kasan_rt_putsize(offset);
2527
kasan_rt_puts(" (recovered)\n");
2628
}
2729

0 commit comments

Comments
 (0)