Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions .github/workflows/ci/valgrind-ct.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Constant-time taint gate (ctgrind / Valgrind), Linux FORCE_SCALAR legs.
# Architecture-generic: keys off FPC_TARGET, so the same script serves the
# linux-x64-scalar (x86_64-linux) and linux-arm64-scalar (aarch64-linux) jobs.
#
# Runs AFTER that job's standard build step, so the CryptoLib / HashLib /
# SimpleBase packages are already compiled (with CRYPTOLIB_FORCE_SCALAR) into
# their lib/<target> unit dirs. We compile CTValgrind against those prebuilt
# .ppu (no from-source rebuild), poison each primitive's secret, and assert:
# * every constant-time SUBJECT runs clean under Memcheck, and
# * every known-leaky CONTROL makes Memcheck report an error (a non-firing
# control means the detector is not sensitive -> the run is INVALID, not a pass).
#
# Opt out with MAKE_RUN_CT_VALGRIND=false. Needs root apt (GitHub runners have it)
# for valgrind + libc6-dbg (glibc debug symbols, required for Memcheck's loader
# redirect on a dynamically linked binary).

set -euo pipefail

source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/shared/common.sh"
ci_init_paths
ci_export_toolchain_path

if [ "${MAKE_RUN_CT_VALGRIND:-true}" != "true" ]; then
echo "MAKE_RUN_CT_VALGRIND != true - skipping the constant-time Valgrind gate."
exit 0
fi

: "${FPC_TARGET:?FPC_TARGET is required (e.g. x86_64-linux)}"
CPU="${FPC_TARGET%-*}"
OS="${FPC_TARGET#*-}"

CT_LAZ="$REPO_ROOT/CryptoLib.ConstantTime/Lazarus"
CT_CORE="$REPO_ROOT/CryptoLib.ConstantTime/src/Core"
SUPP="$CT_LAZ/ct.supp"

echo "==> installing valgrind + glibc debug symbols"
sudo apt-get update
sudo apt-get install -y valgrind libc6-dbg gcc

echo "==> building the taint shim (ct_poison.o)"
gcc -O2 -c "$CT_CORE/ct_poison.c" -o "$CT_LAZ/ct_poison.o"

# Locate the prebuilt package unit dirs (each package outputs to <pkg>/lib/<target>).
# Discover by a known .ppu so we do not hard-code layout that varies per package.
find_units_dir() {
local match f
f="$(find "$REPO_ROOT" "$(dirname "$REPO_ROOT")" "$HOME" -type f -path "$1" 2>/dev/null | head -1 || true)"
[ -n "$f" ] && dirname "$f"
}
CRYPTO_UNITS="$(find_units_dir "*/lib/$FPC_TARGET/ClpAesEngine.ppu")"
HASH_UNITS="$(find_units_dir "*HashLib*/*$FPC_TARGET/*.ppu")"
SB_UNITS="$(find_units_dir "*SimpleBase*/*$FPC_TARGET/*.ppu")"

for pair in "CryptoLib:$CRYPTO_UNITS" "HashLib:$HASH_UNITS" "SimpleBase:$SB_UNITS"; do
name="${pair%%:*}"; dir="${pair#*:}"
if [ -z "$dir" ] || [ ! -d "$dir" ]; then
echo "::error::could not locate prebuilt $name units for $FPC_TARGET (was the build step run first?)"
exit 1
fi
echo " $name units: $dir"
done

echo "==> compiling CTValgrind against the prebuilt scalar packages"
BUILD_DIR="$(mktemp -d)"
( cd "$CT_LAZ" && fpc "-T$OS" "-P$CPU" -MDelphi -O3 \
-dCRYPTOLIB_FORCE_SCALAR -dHASHLIB_FORCE_SCALAR \
-Fu"$CRYPTO_UNITS" -Fu"$HASH_UNITS" -Fu"$SB_UNITS" -Fu"$CT_CORE" \
-Fl"$CT_LAZ" -FU"$BUILD_DIR" -oCTValgrind \
CTValgrind.lpr )
BIN="$CT_LAZ/CTValgrind"
chmod +x "$BIN"

# ct.supp starts empty (masks nothing). Only pass it if it has real entries.
VG=(valgrind --error-exitcode=1 --track-origins=yes)
if [ -s "$SUPP" ] && grep -qvE '^\s*(#|$)' "$SUPP"; then
VG+=(--suppressions="$SUPP")
fi

FAIL=0
run_target() { # <target> <clean|fire>
local t="$1" expect="$2" log="/tmp/vg_$1.log" ec=0
"${VG[@]}" "$BIN" "$t" >"$log" 2>&1 || ec=$?
if [ "$expect" = "clean" ]; then
if [ "$ec" -eq 0 ]; then
echo " PASS subject $t: clean under Memcheck"
else
echo "::error::subject $t reported a secret-dependent access (constant-time violation, OR unsuppressed RTL noise)"
grep -E "Conditional jump|uninitialised|depends on|ERROR SUMMARY" "$log" | head -20 || true
echo " (if these frames are FPC RTL - fpc_*/SYSTEM_*/libc startup - add them to ct.supp;"
echo " regenerate with: valgrind --gen-suppressions=all $BIN $t)"
FAIL=1
fi
else
if [ "$ec" -ne 0 ]; then
echo " PASS control $t: fired (Memcheck reported the expected secret-dependent access)"
else
echo "::error::control $t did NOT fire - the detector is not sensitive, this run is INVALID"
FAIL=1
fi
fi
}

echo "==> running the gate"
run_target x25519 clean
run_target aes-bitsliced clean
run_target ghash-basic clean
run_target aes-ttable fire
run_target ghash-4k fire

if [ "$FAIL" -ne 0 ]; then
echo "GATE: FAIL - see errors above."
exit 1
fi
echo "GATE: PASS - all controls fired and all subjects stayed clean."
23 changes: 23 additions & 0 deletions .github/workflows/make.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ env:
# When true, make.pas builds and runs console benchmarks after the test suite.
# Override per job below, like MAKE_BUILD_BACKEND.
MAKE_RUN_BENCHMARK: false
# When true (default), the linux-x64-scalar job runs the ctgrind/Valgrind
# constant-time gate after its build (subjects clean, leaky controls fire).
# Set false to skip it. Read only by that job's valgrind-ct.sh step.
MAKE_RUN_CT_VALGRIND: true
# When true, make.pas builds (never runs) LCL/GUI projects. Only meaningful under
# the lazbuild backend - the fpc backend has no widgetset and always skips them.
# Set false to opt out of the per-platform GUI widgetset toolchain (GTK2/X11 on
Expand Down Expand Up @@ -142,6 +146,16 @@ jobs:
MAKE_DEFINES: 'CRYPTOLIB_FORCE_SCALAR HASHLIB_FORCE_SCALAR'
run: bash .github/workflows/ci/native-build.sh

# Instrumented constant-time gate: compile CTValgrind against the packages
# the Build step just produced (scalar) and run the ctgrind/Valgrind leg -
# subjects must be clean, the known-leaky controls must fire. Opt out with
# MAKE_RUN_CT_VALGRIND=false.
- name: Constant-time Valgrind gate
shell: bash
env:
FPC_TARGET: ${{ fromJSON(needs.setup.outputs.target_map)['linux-x64-scalar'].fpc_target }}
run: bash .github/workflows/ci/valgrind-ct.sh

linux-arm64-scalar:
name: "Native: Linux AArch64 (FORCE_SCALAR)"
runs-on: ${{ fromJSON(needs.setup.outputs.target_map)['linux-arm64-scalar'].runner }}
Expand All @@ -162,6 +176,15 @@ jobs:
MAKE_DEFINES: 'CRYPTOLIB_FORCE_SCALAR HASHLIB_FORCE_SCALAR'
run: bash .github/workflows/ci/native-build.sh

# Same constant-time taint gate as the x86_64 leg, on native AArch64 - the
# script is architecture-generic (keys off FPC_TARGET). Opt out with
# MAKE_RUN_CT_VALGRIND=false.
- name: Constant-time Valgrind gate
shell: bash
env:
FPC_TARGET: ${{ fromJSON(needs.setup.outputs.target_map)['linux-arm64-scalar'].fpc_target }}
run: bash .github/workflows/ci/valgrind-ct.sh

windows-x64:
name: "Native: Windows x86_64"
runs-on: ${{ fromJSON(needs.setup.outputs.target_map)['windows-x64'].runner }}
Expand Down
83 changes: 83 additions & 0 deletions CryptoLib.ConstantTime/Lazarus/CTLeakDetect.lpi
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="12"/>
<PathDelim Value="\"/>
<General>
<Flags>
<MainUnitHasCreateFormStatements Value="False"/>
<MainUnitHasTitleStatement Value="False"/>
<MainUnitHasScaledStatement Value="False"/>
<CompatibilityMode Value="True"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<Title Value="CTLeakDetect"/>
<UseAppBundle Value="False"/>
<ResourceType Value="res"/>
</General>
<BuildModes Count="1">
<Item1 Name="Default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
</PublishOptions>
<RunParams>
<FormatVersion Value="2"/>
<Modes Count="1">
<Mode0 Name="default"/>
</Modes>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="CryptoLib4PascalPackage"/>
</Item1>
</RequiredPackages>
<Units Count="4">
<Unit0>
<Filename Value="CTLeakDetect.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
<Unit1>
<Filename Value="..\src\Core\CtClock.pas"/>
<IsPartOfProject Value="True"/>
</Unit1>
<Unit2>
<Filename Value="..\src\Core\CtDudect.pas"/>
<IsPartOfProject Value="True"/>
</Unit2>
<Unit3>
<Filename Value="..\src\Core\CtSubjects.pas"/>
<IsPartOfProject Value="True"/>
</Unit3>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="CTLeakDetect"/>
</Target>
<SearchPaths>
<IncludeFiles Value="$(ProjOutDir)"/>
<OtherUnitFiles Value="..\src\Core"/>
<UnitOutputDirectory Value="lib\ctleak\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<CodeGeneration>
<Optimizations>
<OptimizationLevel Value="3"/>
</Optimizations>
<TargetCPU Value="x86_64"/>
<TargetOS Value="win64"/>
</CodeGeneration>
<Linking>
<Debugging>
<GenerateDebugInfo Value="False"/>
<UseLineInfoUnit Value="False"/>
<UseHeaptrc Value="False"/>
</Debugging>
</Linking>
<Other>
<CustomOptions Value="-dCRYPTOLIB_FORCE_SCALAR"/>
</Other>
</CompilerOptions>
</CONFIG>
Loading