From b6adc0337ac3479a28f1f580ec6ef634f7bc7396 Mon Sep 17 00:00:00 2001 From: Eric Kilmer Date: Wed, 25 Feb 2026 20:55:01 +0000 Subject: [PATCH] Add ASAN/UBSAN CI testing and fix signed left shift UB Add a sanitizer CI job that builds and tests with AddressSanitizer and UndefinedBehaviorSanitizer using clang on Ubuntu for both stable and HEAD release tracks. Fix three UBSAN signed left shift errors found during sanitizer testing by casting signed values to unsigned before left shifting: - address.hh sign_extend(): left shift of negative value / overflow - slghpatexpress.cc LeftShiftExpression: left shift of negative value Co-Authored-By: Claude Opus 4.6 --- .github/workflows/main.yml | 91 +++++++++++++++++++ ...8-Fix-UBSAN-signed-left-shift-errors.patch | 57 ++++++++++++ ...6-Fix-UBSAN-signed-left-shift-errors.patch | 57 ++++++++++++ src/setup-ghidra-source.cmake | 2 + 4 files changed, 207 insertions(+) create mode 100644 src/patches/HEAD/0008-Fix-UBSAN-signed-left-shift-errors.patch create mode 100644 src/patches/stable/0006-Fix-UBSAN-signed-left-shift-errors.patch diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3543c7a..03259ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -254,3 +254,94 @@ jobs: - name: ccache stats run: ccache -s + + sanitizer: + runs-on: ubuntu-latest + permissions: + contents: read + + env: + CXX: clang++ + ASAN_OPTIONS: strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=1 + UBSAN_OPTIONS: print_stacktrace=1 + + strategy: + fail-fast: false + matrix: + release: [stable, HEAD] + + steps: + - uses: actions/checkout@v6 + + - name: Setup Git User for Applying Patches + run: | + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y ccache + + - name: Generate cache key + shell: cmake -P {0} + run: | + string(TIMESTAMP current_date "%Y-%m-%d-%H;%M;%S" UTC) + file(APPEND "$ENV{GITHUB_ENV}" "CACHE_KEY=sanitizer_${{ matrix.release }}\n") + file(APPEND "$ENV{GITHUB_ENV}" "CACHE_TIMESTAMP=${current_date}\n") + + - name: Update the cache (ccache) + uses: actions/cache@v5 + with: + path: "${{ github.workspace }}/ccache" + key: ${{ env.CACHE_KEY }}_ccache_${{ env.CACHE_TIMESTAMP }} + restore-keys: | + ${{ env.CACHE_KEY }}_ccache_ + + - name: Setup ccache + working-directory: "${{ github.workspace }}" + shell: cmake -P {0} + run: | + file(MAKE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/ccache") + file(APPEND "$ENV{GITHUB_ENV}" "CCACHE_BASEDIR=${CMAKE_CURRENT_SOURCE_DIR}\n") + file(APPEND "$ENV{GITHUB_ENV}" "CCACHE_DIR=${CMAKE_CURRENT_SOURCE_DIR}/ccache\n") + file(APPEND "$ENV{GITHUB_ENV}" "CCACHE_COMPRESS=true\n") + file(APPEND "$ENV{GITHUB_ENV}" "CCACHE_COMPRESSLEVEL=10\n") + file(APPEND "$ENV{GITHUB_ENV}" "CCACHE_MAXSIZE=400M\n") + file(APPEND "$ENV{GITHUB_ENV}" "CMAKE_CXX_COMPILER_LAUNCHER=ccache\n") + file(APPEND "$ENV{GITHUB_ENV}" "CMAKE_C_COMPILER_LAUNCHER=ccache\n") + execute_process(COMMAND ccache -z) + + - name: Configure the project + run: cmake --preset=ci-sanitize + -Dsleigh_RELEASE_TYPE=${{ matrix.release }} + -Dsleigh_BUILD_DOCUMENTATION=OFF + + - name: Build the project + run: cmake + --build build/sanitize + -j 4 + -v + + - name: Test the project + working-directory: build/sanitize + run: ctest -VV + + - name: Run the example + run: cmake + --build build/sanitize + -j 4 + --target sleigh_example_runner + + - name: Run the install target + run: cmake --install build/sanitize + --prefix install + + - name: Smoketest sleigh lift + run: | + ./install/bin/sleigh-lift --version + ./install/bin/sleigh-lift disassemble x86-64.sla 4881ecc00f0000 + ./install/bin/sleigh-lift pcode x86-64.sla 4881ecc00f0000 + + - name: ccache stats + run: ccache -s diff --git a/src/patches/HEAD/0008-Fix-UBSAN-signed-left-shift-errors.patch b/src/patches/HEAD/0008-Fix-UBSAN-signed-left-shift-errors.patch new file mode 100644 index 0000000..d269a2b --- /dev/null +++ b/src/patches/HEAD/0008-Fix-UBSAN-signed-left-shift-errors.patch @@ -0,0 +1,57 @@ +From 8afa7d89a368b358a5493b87804985ed9ac70a2f Mon Sep 17 00:00:00 2001 +From: "github-actions[bot]" + <41898282+github-actions[bot]@users.noreply.github.com> +Date: Wed, 25 Feb 2026 20:41:10 +0000 +Subject: [PATCH] Fix UBSAN signed left shift errors + +Cast signed values to unsigned before left shifting to avoid undefined +behavior when the signed value is negative or when the result cannot be +represented in the signed type. + +Fixes runtime errors detected by UndefinedBehaviorSanitizer: +- address.hh sign_extend(): left shift of negative value +- address.hh sign_extend(): left shift cannot be represented in type 'intb' +- slghpatexpress.cc LeftShiftExpression: left shift of negative value +--- + Ghidra/Features/Decompiler/src/decompile/cpp/address.hh | 2 +- + .../Features/Decompiler/src/decompile/cpp/slghpatexpress.cc | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh +index 45144daf3..0e75c68b8 100644 +--- a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh ++++ b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh +@@ -544,7 +544,7 @@ inline intb sign_extend(intb val,int4 bit) + + { + int4 sa = 8*sizeof(intb) - (bit+1); +- val = (val << sa) >> sa; ++ val = static_cast(static_cast(val) << sa) >> sa; + return val; + } + +diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc +index 941097859..93cce378a 100644 +--- a/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc ++++ b/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc +@@ -969,7 +969,7 @@ intb LeftShiftExpression::getValue(ParserWalker &walker) const + { + intb leftval = getLeft()->getValue(walker); + intb rightval = getRight()->getValue(walker); +- return leftval << rightval; ++ return static_cast(static_cast(leftval) << rightval); + } + + intb LeftShiftExpression::getSubValue(const vector &replace,int4 &listpos) const +@@ -977,7 +977,7 @@ intb LeftShiftExpression::getSubValue(const vector &replace,int4 &listpos) + { + intb leftval = getLeft()->getSubValue(replace,listpos); // Must be left first + intb rightval = getRight()->getSubValue(replace,listpos); +- return leftval << rightval; ++ return static_cast(static_cast(leftval) << rightval); + } + + void LeftShiftExpression::encode(Encoder &encoder) const +-- +2.51.1 + diff --git a/src/patches/stable/0006-Fix-UBSAN-signed-left-shift-errors.patch b/src/patches/stable/0006-Fix-UBSAN-signed-left-shift-errors.patch new file mode 100644 index 0000000..91b4d8c --- /dev/null +++ b/src/patches/stable/0006-Fix-UBSAN-signed-left-shift-errors.patch @@ -0,0 +1,57 @@ +From 8afa7d89a368b358a5493b87804985ed9ac70a2f Mon Sep 17 00:00:00 2001 +From: "github-actions[bot]" + <41898282+github-actions[bot]@users.noreply.github.com> +Date: Wed, 25 Feb 2026 20:41:10 +0000 +Subject: [PATCH] Fix UBSAN signed left shift errors + +Cast signed values to unsigned before left shifting to avoid undefined +behavior when the signed value is negative or when the result cannot be +represented in the signed type. + +Fixes runtime errors detected by UndefinedBehaviorSanitizer: +- address.hh sign_extend(): left shift of negative value +- address.hh sign_extend(): left shift cannot be represented in type 'intb' +- slghpatexpress.cc LeftShiftExpression: left shift of negative value +--- + Ghidra/Features/Decompiler/src/decompile/cpp/address.hh | 2 +- + .../Features/Decompiler/src/decompile/cpp/slghpatexpress.cc | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh +index 45144daf3..0e75c68b8 100644 +--- a/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh ++++ b/Ghidra/Features/Decompiler/src/decompile/cpp/address.hh +@@ -544,7 +544,7 @@ inline intb sign_extend(intb val,int4 bit) + + { + int4 sa = 8*sizeof(intb) - (bit+1); +- val = (val << sa) >> sa; ++ val = static_cast(static_cast(val) << sa) >> sa; + return val; + } + +diff --git a/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc b/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc +index 941097859..93cce378a 100644 +--- a/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc ++++ b/Ghidra/Features/Decompiler/src/decompile/cpp/slghpatexpress.cc +@@ -969,7 +969,7 @@ intb LeftShiftExpression::getValue(ParserWalker &walker) const + { + intb leftval = getLeft()->getValue(walker); + intb rightval = getRight()->getValue(walker); +- return leftval << rightval; ++ return static_cast(static_cast(leftval) << rightval); + } + + intb LeftShiftExpression::getSubValue(const vector &replace,int4 &listpos) const +@@ -977,7 +977,7 @@ intb LeftShiftExpression::getSubValue(const vector &replace,int4 &listpos) + { + intb leftval = getLeft()->getSubValue(replace,listpos); // Must be left first + intb rightval = getRight()->getSubValue(replace,listpos); +- return leftval << rightval; ++ return static_cast(static_cast(leftval) << rightval); + } + + void LeftShiftExpression::encode(Encoder &encoder) const +-- +2.51.1 + diff --git a/src/setup-ghidra-source.cmake b/src/setup-ghidra-source.cmake index 5dcd98d..cc1e2ac 100644 --- a/src/setup-ghidra-source.cmake +++ b/src/setup-ghidra-source.cmake @@ -43,6 +43,7 @@ set(ghidra_patches "${CMAKE_CURRENT_LIST_DIR}/patches/stable/0003-Ignore-floating-point-test-due-to-compilation-differ.patch" "${CMAKE_CURRENT_LIST_DIR}/patches/stable/0004-Allow-positive-or-negative-NAN-in-decompiler-floatin.patch" "${CMAKE_CURRENT_LIST_DIR}/patches/stable/0005-decompiler-Fix-strict-weak-ordering-TypePartialEnum.patch" + "${CMAKE_CURRENT_LIST_DIR}/patches/stable/0006-Fix-UBSAN-signed-left-shift-errors.patch" ) # Ghidra pinned commits used for pinning last known working HEAD commit @@ -65,6 +66,7 @@ if("${sleigh_RELEASE_TYPE}" STREQUAL "HEAD") "${CMAKE_CURRENT_LIST_DIR}/patches/HEAD/0005-decompiler-Fix-strict-weak-ordering-TypePartialEnum.patch" "${CMAKE_CURRENT_LIST_DIR}/patches/HEAD/0006-decompiler-Fix-strict-weak-ordering-PullRecord.patch" "${CMAKE_CURRENT_LIST_DIR}/patches/HEAD/0007-decompiler-Fix-strict-weak-ordering-compareFinalOrde.patch" + "${CMAKE_CURRENT_LIST_DIR}/patches/HEAD/0008-Fix-UBSAN-signed-left-shift-errors.patch" ) string(SUBSTRING "${ghidra_git_tag}" 0 7 ghidra_short_commit) else()