From d2e3722772696a4edd5585dd0ef2d866578116b1 Mon Sep 17 00:00:00 2001 From: doyle Date: Mon, 28 Aug 2023 23:41:55 +1000 Subject: [PATCH] Initial commit --- .gitignore | 1 + asan_example.cpp | 248 +++++++++++++++++++++++++++++++++++++++++++++++ build.bat | 8 ++ build.sh | 9 ++ readme.md | 220 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 486 insertions(+) create mode 100644 .gitignore create mode 100644 asan_example.cpp create mode 100644 build.bat create mode 100644 build.sh create mode 100644 readme.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b45a037 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +Build diff --git a/asan_example.cpp b/asan_example.cpp new file mode 100644 index 0000000..10d45d4 --- /dev/null +++ b/asan_example.cpp @@ -0,0 +1,248 @@ + +#include +#include +#include +#include +#include + +#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 ``.\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; +} diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..27e45e8 --- /dev/null +++ b/build.bat @@ -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 diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..3a75ee5 --- /dev/null +++ b/build.sh @@ -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 + + diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..3af6d42 --- /dev/null +++ b/readme.md @@ -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 ``. + +``` +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 +```