Initial commit

This commit is contained in:
doyle 2023-08-28 23:41:55 +10:00
commit d2e3722772
5 changed files with 486 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
Build

248
asan_example.cpp Normal file
View File

@ -0,0 +1,248 @@
#include <sanitizer/asan_interface.h>
#include <stdint.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#define IsPowerOfTwo(value, pot) ((((size_t)value) & (((size_t)pot) - 1)) == 0)
#define AsanAlignment 8
#define PrintByteSpacing(space) \
if (index) \
printf(space); \
if (index && (index % 8 == 0)) \
printf("| ")
static void PrintByteArray(char const *array, size_t array_size)
{
printf(" Byte Array ");
for (size_t index = 0 ; index < array_size; index++) {
PrintByteSpacing(" ");
printf("%02zu", index);
}
printf("\n");
}
static void PrintPoisonedBytes(int step, char const *array, size_t array_size)
{
printf("%d. __asan_address_is_poisoned ", step);
for (size_t index = 0 ; index < array_size; index++) {
PrintByteSpacing(" ");
printf("%c", __asan_address_is_poisoned(array + index) ? 'x' : ' ');
}
printf("\n");
}
static void PrintPoisonMemoryIndex(int step, char const *array, size_t array_size, size_t poison_index)
{
printf("%d. __asan_poison_memory_region ", step);
for (size_t index = 0; index < array_size; index++) {
PrintByteSpacing(" ");
printf("%c ", index == poison_index ? 'x' : ' ');
}
printf("\n");
}
static void PrintPoisonMemoryRegion(int step, char const *array, size_t array_size, size_t poison_start_index, size_t poison_end_index)
{
printf("%d. __asan_poison_memory_region ", step);
for (size_t index = 0; index < array_size; index++) {
PrintByteSpacing(" ");
printf("%c ", index >= poison_start_index && index <= poison_end_index ? 'x' : ' ');
}
printf("\n");
}
int main()
{
printf(
"# ASAN Manual Poisoning\n"
"\n"
"## TLDR\n"
"\n"
"`__asan_poison_memory_region(ptr, size)`\n"
"\n"
"Poisons the byte region `[ptr, AlignDown(ptr+size, 8))`\n"
"\n"
"`__asan_unpoison_memory_region(ptr, size)`\n"
"\n"
"Unpoisons the byte region `[AlignDown(ptr, 8), ptr+size)`\n"
"\n"
"Use the provided macros that are conditionally enabled if ASAN is\n"
"defined from `<sanitizer/asan_interface.h>`.\n"
"\n"
"```\n"
"ASAN_POISON_MEMORY_REGION(addr, size)\n"
"ASAN_UNPOISON_MEMORY_REGION(addr, size)\n"
"```\n"
"\n"
"If in doubt, use `__asan_address_is_poisoned` to sanity check the\n"
"ranges requested to be un/poisoned to avoid potential gaps in\n"
"marked-up memory that may lead to undetected read/writes.\n"
"\n"
"## Overview\n"
"\n"
"ASAN provides a way to manually markup ranges of bytes to\n"
"prohibit or permit reads to those addresses. There's a short\n"
"foot-note in Google's "
"[AddressSanitizerManualPoisoning](https://github.com/google/"
"sanitizers/wiki/AddressSanitizerManualPoisoning)\n"
"documentation that states:\n"
"\n"
"```\n"
"If you have a custom allocation arena, the typical workflow would be\n"
"to poison the entire arena first, and then unpoison allocated chunks\n"
"of memory leaving poisoned redzones between them. The allocated\n"
"chunks should start with 8-aligned addresses.\n"
"```\n"
"\n"
"This repository runs some simple tests to clarify the behaviour of\n"
"the API on un/aligned addresses at various sizes without having\n"
"to dig into source code or read the [ASAN paper](https://static."
"googleusercontent.com/media/research.google.com/en/pubs/archive/"
"37752.pdf).\n"
"\n"
"We use a stack-allocated 16 byte array and test un/poisoning\n"
"various ranges of bytes from different alignments to clarify the\n"
"poisoning behaviour of the API.\n"
"\n"
"This reveals that calling the API haphazardly, unaligned or\n"
"straddling boundaries can lead to gaps in poisoned memory and hide\n"
"potential leaks (as also demonstrated in [Manual ASAN poisoning and\n"
"alignment](https://github.com/mcgov/asan_alignment_example)).\n"
"\n"
"## References\n"
"\n"
"- [Manual ASAN poisoning and alignment](https://github.com/mcgov/asan_alignment_example) example by `mcgov`\n"
"- [Address Sanitizer: A Fast Address Sanity Checker](https://static.googleusercontent.com/media/research.google.com/en/pubs/archive/37752.pdf)\n"
"- [sanitizer/asan_interface.h](https://github.com/llvm-mirror/compiler-rt/blob/master/include/sanitizer/asan_interface.h)\n"
"\n"
"## Raw Test Results\n"
"\n"
"Here we poison a sliding window of 7 bytes to demonstrate that ASAN\n"
"poisoning will only poison the byte region if the region meets an 8\n"
"byte aligned address. It will only poison bytes up to the boundary,\n"
"any bytes that straddle the boundary that do not hit the next 8 byte\n"
"boundary are not poisoned.\n"
"\n");
uint32_t const ASAN_ALIGNMENT = 8;
uint32_t const REGION_WINDOW = 7;
char array[16] = {};
printf("```\n");
for (size_t poison_index = 0; poison_index < sizeof(array) - (REGION_WINDOW - 1); poison_index++) {
assert(IsPowerOfTwo(array, ASAN_ALIGNMENT));
// NOTE: Print byte array ==================================================================
PrintByteArray(array, sizeof(array));
// NOTE: Poison the array ==================================================================
__asan_poison_memory_region(&array[poison_index], REGION_WINDOW);
PrintPoisonMemoryRegion(1 /*step*/, array, sizeof(array), poison_index, poison_index + (REGION_WINDOW - 1));
// NOTE: Print the poison-ed bytes =========================================================
PrintPoisonedBytes(2 /*step*/, array, sizeof(array));
// NOTE: Cleanup ===========================================================================
__asan_unpoison_memory_region(array, sizeof(array));
printf("\n");
}
printf("```\n");
printf(
"Now we demonstrate that unpoisoning 1 byte in the 8 byte window\n"
"will unpoison all bytes prior to it up until the previous 8 byte \n"
"boundary.\n"
"\n");
printf("```\n");
for (size_t unpoison_index = 0; unpoison_index < sizeof(array); unpoison_index++) {
assert(IsPowerOfTwo(array, ASAN_ALIGNMENT));
// NOTE: Print byte array ==================================================================
PrintByteArray(array, sizeof(array));
// NOTE: Poison the array ==================================================================
__asan_poison_memory_region(array, sizeof(array));
PrintPoisonMemoryRegion(1 /*step*/, array, sizeof(array), 0, sizeof(array) - 1);
// NOTE: Print the poison-ed bytes =========================================================
PrintPoisonedBytes(2 /*step*/, array, sizeof(array));
// NOTE: Unpoison byte at each position separately =========================================
printf("3. __asan_unpoison_memory_region ");
__asan_unpoison_memory_region(array + unpoison_index, 1);
for (size_t index = 0 ; index < sizeof(array); index++) {
PrintByteSpacing(" ");
printf("%c", index == unpoison_index ? 'x' : ' ');
}
printf("\n");
// NOTE: Print the poison-ed bytes =========================================================
printf("4. __asan_address_is_poisoned ");
for (size_t index = 0 ; index < sizeof(array); index++) {
PrintByteSpacing(" ");
printf("%c", __asan_address_is_poisoned(array + index) ? 'x' : ' ');
}
printf("\n\n");
// NOTE: Cleanup ===========================================================================
__asan_unpoison_memory_region(array, sizeof(array));
}
printf("```\n");
printf(
"Unpoisoning across 8 byte boundaries may lead to undesired\n"
"behaviour, with all bytes on the left side of the boundary being\n"
"unpoisoned\n\n");
printf("```\n");
{
// NOTE: Print byte array ==================================================================
char array[16] = {};
PrintByteArray(array, sizeof(array));
// NOTE: Poison the array ==================================================================
__asan_poison_memory_region(array, sizeof(array));
PrintPoisonMemoryRegion(1 /*step*/, array, sizeof(array), 0, sizeof(array) - 1);
// NOTE: Print the poison-ed bytes =========================================================
PrintPoisonedBytes(2 /*step*/, array, sizeof(array));
// NOTE: Unpoison across the 8 byte boundary ===============================================
printf("3. __asan_unpoison_memory_region ");
uint32_t const start_poison_index = 7;
uint32_t const bytes_to_unpoison = 2;
uint32_t const end_poison_index = start_poison_index + (bytes_to_unpoison - 1);
__asan_unpoison_memory_region(array + start_poison_index, bytes_to_unpoison);
for (size_t index = 0 ; index < sizeof(array); index++) {
PrintByteSpacing(" ");
printf("%c", index >= start_poison_index && index <= end_poison_index ? 'x' : ' ');
}
printf("\n");
// NOTE: Print the poison-ed bytes =========================================================
PrintPoisonedBytes(4 /*step*/, array, sizeof(array));
}
printf("```\n");
return 0;
}

8
build.bat Normal file
View File

@ -0,0 +1,8 @@
@echo off
set script_dir_backslash=%~dp0
set script_dir=%script_dir_backslash:~0,-1%
if not exist %script_dir%\Build mkdir %script_dir%\Build
pushd %script_dir%\Build
cl %script_dir%\asan_example.cpp -Zi -fsanitize=address -nologo -link || exit /b 1
popd

9
build.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/bash
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
mkdir -p ${script_dir}/Build
pushd ${script_dir}/Build
gcc ${script_dir}/asan_example.cpp -g -fsanitize=address -o asan_example
popd

220
readme.md Normal file
View File

@ -0,0 +1,220 @@
# ASAN Manual Poisoning
## TLDR
`__asan_poison_memory_region(ptr, size)`
Poisons the byte region `[ptr, AlignDown(ptr+size, 8))`
`__asan_unpoison_memory_region(ptr, size)`
Unpoisons the byte region `[AlignDown(ptr, 8), ptr+size)`
Use the provided macros that are conditionally enabled if ASAN is
defined from `<sanitizer/asan_interface.h>`.
```
ASAN_POISON_MEMORY_REGION(addr, size)
ASAN_UNPOISON_MEMORY_REGION(addr, size)
```
If in doubt, use `__asan_address_is_poisoned` to sanity check the
ranges requested to be un/poisoned to avoid potential gaps in
marked-up memory that may lead to undetected read/writes.
## Overview
ASAN provides a way to manually markup ranges of bytes to
prohibit or permit reads to those addresses. There's a short
foot-note in Google's [AddressSanitizerManualPoisoning](https://github.com/google/sanitizers/wiki/AddressSanitizerManualPoisoning)
documentation that states:
```
If you have a custom allocation arena, the typical workflow would be
to poison the entire arena first, and then unpoison allocated chunks
of memory leaving poisoned redzones between them. The allocated
chunks should start with 8-aligned addresses.
```
This repository runs some simple tests to clarify the behaviour of
the API on un/aligned addresses at various sizes without having
to dig into source code or read the [ASAN paper](https://static.googleusercontent.com/media/research.google.com/en/pubs/archive/37752.pdf).
We use a stack-allocated 16 byte array and test un/poisoning
various ranges of bytes from different alignments to clarify the
poisoning behaviour of the API.
This reveals that calling the API haphazardly, unaligned or
straddling boundaries can lead to gaps in poisoned memory and hide
potential leaks (as also demonstrated in [Manual ASAN poisoning and
alignment](https://github.com/mcgov/asan_alignment_example) example
by `mcgov`.
## References
- [Manual ASAN poisoning and alignment](https://github.com/mcgov/asan_alignment_example) example by `mcgov`
- [Address Sanitizer: A Fast Address Sanity Checker](https://static.googleusercontent.com/media/research.google.com/en/pubs/archive/37752.pdf)
- [sanitizer/asan_interface.h](https://github.com/llvm-mirror/compiler-rt/blob/master/include/sanitizer/asan_interface.h)
## Raw Test Results
Here we demonstrate that ASAN poison-ing will only poison the
byte region if the region meets an 8 byte boundary. It will only
poison bytes upto the 8 byte boundary, any bytes that straddle
the boundary that do not hit the next 8 byte boundary are not
poison-ed.
```
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x |
2. __asan_address_is_poisoned |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x |
2. __asan_address_is_poisoned x x x x x x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x | x
2. __asan_address_is_poisoned x x x x x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x | x x
2. __asan_address_is_poisoned x x x x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x | x x x
2. __asan_address_is_poisoned x x x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x | x x x x
2. __asan_address_is_poisoned x x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x | x x x x x
2. __asan_address_is_poisoned x x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x | x x x x x x
2. __asan_address_is_poisoned x |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region | x x x x x x x
2. __asan_address_is_poisoned |
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region | x x x x x x x
2. __asan_address_is_poisoned | x x x x x x x
```
Now we demonstrate that unpoisoning 1 byte in the 8 byte window
will unpoison all bytes prior to it up until the previous 8 byte
boundary.
```
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x x x x x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x x x x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x x x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned x | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x |
4. __asan_address_is_poisoned | x x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x | x
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region | x
4. __asan_address_is_poisoned x x x x x x x x |
```
Unpoisoning across 8 byte boundaries may lead to undesired
behaviour, with all bytes on the left side of the boundary being
unpoisoned
```
Byte Array 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15
1. __asan_poison_memory_region x x x x x x x x | x x x x x x x x
2. __asan_address_is_poisoned x x x x x x x x | x x x x x x x x
3. __asan_unpoison_memory_region x | x
4. __asan_address_is_poisoned | x x x x x x x
```