From bd47e84ac16ee5a6c87c7604dcf47d96c1c293c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Wed, 27 May 2026 09:14:19 +0200 Subject: [PATCH 01/15] Add ct tooling logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 230 +++ tests/ct_tooling/README.md | 170 ++ tests/ct_tooling/analyze_results.py | 247 +++ tests/ct_tooling/ct_test.sh | 429 +++++ tests/ct_tooling/local_testing_example.sh | 17 + tests/ct_tooling/tools/memsan/CMakeLists.txt | 246 +++ tests/ct_tooling/tools/memsan/README.md | 26 + .../tools/memsan/false_positives/ml_kem.txt | 15 + .../tools/memsan/rng_poison_memsan.c | 11 + tests/ct_tooling/tools/memsan/test_kem.c | 475 +++++ tests/ct_tooling/tools/memsan/test_sig.c | 396 +++++ .../tools/valgrind_varlat/README.md | 99 ++ .../false_positives/ml-kem.txt | 83 + .../valgrind-try-patch-20250805.txt | 1578 +++++++++++++++++ .../valgrind-varlat-patch-20250805.txt | 909 ++++++++++ .../valgrind-varlat-sup-block.txt | 23 + tests/test_helpers.h | 4 + 17 files changed, 4958 insertions(+) create mode 100644 .github/workflows/ct-tooling.yml create mode 100644 tests/ct_tooling/README.md create mode 100644 tests/ct_tooling/analyze_results.py create mode 100755 tests/ct_tooling/ct_test.sh create mode 100755 tests/ct_tooling/local_testing_example.sh create mode 100644 tests/ct_tooling/tools/memsan/CMakeLists.txt create mode 100644 tests/ct_tooling/tools/memsan/README.md create mode 100644 tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt create mode 100644 tests/ct_tooling/tools/memsan/rng_poison_memsan.c create mode 100644 tests/ct_tooling/tools/memsan/test_kem.c create mode 100644 tests/ct_tooling/tools/memsan/test_sig.c create mode 100644 tests/ct_tooling/tools/valgrind_varlat/README.md create mode 100644 tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt create mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt create mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt create mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml new file mode 100644 index 0000000000..def620c8fd --- /dev/null +++ b/.github/workflows/ct-tooling.yml @@ -0,0 +1,230 @@ +name: ct-tooling + +permissions: + contents: read + +on: + workflow_dispatch: + +jobs: + interactive-inputs: + runs-on: ubuntu-latest + permissions: + contents: write + actions: write + steps: + - name: Example Interactive Inputs Step + id: interactive-inputs + uses: boasiHQ/interactive-inputs@v2 + with: + timeout: 300 + title: Select algorithm(s) Valgrind-Varlat CT Testing' + interactive: | + fields: + - label: runtime-options + properties: + description: Choose one or more algorithms to execute valgrind-varlat constant-time testing on + display: Select the algorithm(s) to execute valgrind-varlat constant-time testing on + type: multiselect + choices: + - "BIKE-L1" + - "BIKE-L3" + - "BIKE-L5" + - "Classic-McEliece-348864" + - "Classic-McEliece-348864f" + - "Classic-McEliece-460896" + - "Classic-McEliece-460896f" + - "Classic-McEliece-6688128" + - "Classic-McEliece-6688128f" + - "Classic-McEliece-6960119" + - "Classic-McEliece-6960119f" + - "Classic-McEliece-8192128" + - "Classic-McEliece-8192128f" + - "Kyber512" + - "Kyber768" + - "Kyber1024" + - "ML-KEM-512" + - "ML-KEM-768" + - "ML-KEM-1024" + - "sntrup761" + - "FrodoKEM-640-AES" + - "FrodoKEM-640-SHAKE" + - "FrodoKEM-976-AES" + - "FrodoKEM-976-SHAKE" + - "FrodoKEM-1344-AES" + - "FrodoKEM-1344-SHAKE" + - "ML-DSA-44" + - "ML-DSA-65" + - "ML-DSA-87" + - "Falcon-512" + - "Falcon-1024" + - "Falcon-padded-512" + - "Falcon-padded-1024" + - "SPHINCS+-SHA2-128f-simple" + - "SPHINCS+-SHA2-128s-simple" + - "SPHINCS+-SHA2-192f-simple" + - "SPHINCS+-SHA2-192s-simple" + - "SPHINCS+-SHA2-256f-simple" + - "SPHINCS+-SHA2-256s-simple" + - "SPHINCS+-SHAKE-128f-simple" + - "SPHINCS+-SHAKE-128s-simple" + - "SPHINCS+-SHAKE-192f-simple" + - "SPHINCS+-SHAKE-192s-simple" + - "SPHINCS+-SHAKE-256f-simple" + - "SPHINCS+-SHAKE-256s-simple" + - "MAYO-1" + - "MAYO-2" + - "MAYO-3" + - "MAYO-5" + - "cross-rsdp-128-balanced" + - "cross-rsdp-128-fast" + - "cross-rsdp-128-small" + - "cross-rsdp-192-balanced" + - "cross-rsdp-192-fast" + - "cross-rsdp-192-small" + - "cross-rsdp-256-balanced" + - "cross-rsdp-256-fast" + - "cross-rsdp-256-small" + - "cross-rsdpg-128-balanced" + - "cross-rsdpg-128-fast" + - "cross-rsdpg-128-small" + - "cross-rsdpg-192-balanced" + - "cross-rsdpg-192-fast" + - "cross-rsdpg-192-small" + - "cross-rsdpg-256-balanced" + - "cross-rsdpg-256-fast" + - "cross-rsdpg-256-small" + - "OV-Is" + - "OV-Ip" + - "OV-III" + - "OV-V" + - "OV-Is-pkc" + - "OV-Ip-pkc" + - "OV-III-pkc" + - "OV-V-pkc" + - "OV-Is-pkc-skc" + - "OV-Ip-pkc-skc" + - "OV-III-pkc-skc" + - "OV-V-pkc-skc" + required: true + notifier-slack-enabled: "false" + notifier-discord-enabled: "false" + github-token: ${{ github.token }} + ngrok-authtoken: ${{ secrets.NGROK_AUTHTOKEN }} + outputs: + runtime-options: ${{ steps.interactive-inputs.outputs.runtime-options }} + + valgrind-varlat: + needs: [interactive-inputs] + runs-on: ubuntu-latest + container: + image: openquantumsafe/ci-ubuntu-latest:latest + strategy: + matrix: + compiler: [gcc, clang] + liboqs_build: [generic, auto] + opt_flag: [-O0, -O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-tree-vectorize", "-O3 -fno-tree-vectorize"] + exclude: + - compiler: clang + opt_flag: "-O2 -fno-tree-vectorize" + - compiler: clang + opt_flag: "-O3 -fno-tree-vectorize" + include: + - compiler: clang + liboqs_build: generic + opt_flag: "-O2 -fno-vectorize" + - compiler: clang + liboqs_build: auto + opt_flag: "-O2 -fno-vectorize" + - compiler: clang + liboqs_build: generic + opt_flag: "-O3 -fno-vectorize" + - compiler: clang + liboqs_build: auto + opt_flag: "-O3 -fno-vectorize" + max-parallel: 5 + steps: + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 + + - name: Build valgrind_varlat + shell: bash + run: | + set -eu -o pipefail + INSTALL_PREFIX="$PWD/valgrind_varlat" + + echo "Cloning Valgrind's source code" + git clone git://sourceware.org/git/valgrind.git valgrind_varlat_src> /dev/null 2>&1 || true + cd valgrind_varlat_src + git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa > /dev/null 2>&1 || true + + echo "Applying Kyberslash patch" + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" > /dev/null 2>&1 || true + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" > /dev/null 2>&1 || true + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" > /dev/null 2>&1 || true + + echo "Building Valgrind-Varlat" + ./autogen.sh > /dev/null 2>&1 || true + ./configure --prefix="$INSTALL_PREFIX" > /dev/null 2>&1 || true + make -j"$(nproc)" > /dev/null 2>&1 || true + make install > /dev/null 2>&1 || true + + mv "$INSTALL_PREFIX/bin/valgrind" "$INSTALL_PREFIX/bin/valgrind_varlat" + chmod -R u+rwX "$INSTALL_PREFIX" + export PATH="$INSTALL_PREFIX/bin:$PATH" + echo "PATH: $PATH" + command -v valgrind_varlat + echo "$INSTALL_PREFIX/bin" >> "$GITHUB_PATH" + + - name: Run valgrind_varlat tests + shell: bash + run: | + set -eu -o pipefail + cd "$GITHUB_WORKSPACE/tests/ct_tooling" + chmod +x ct_test.sh + raw_algs="${{ needs.interactive-inputs.outputs.runtime-options }}" + IFS=',' read -r -a algs <<< "$raw_algs" # convert to array + for alg in "${algs[@]}"; do + ./ct_test.sh valgrind-varlat ${{ matrix.compiler }} ${{ matrix.liboqs_build }} ${{ matrix.opt_flag }} "$alg" + done + + - name: Upload valgrind_varlat logs + uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4 + with: + name: valgrind_varlat_${{ matrix.compiler }}_${{ matrix.liboqs_build }}_${{ matrix.opt_flag }}_logs + path: | + tests/ct_tooling/tools/valgrind_varlat/logs/** + + memsan: + needs: [interactive-inputs] + runs-on: ubuntu-latest + container: + image: openquantumsafe/ci-ubuntu-latest:latest + strategy: + matrix: + compiler: [clang] + liboqs_build: [generic, auto] + opt_flag: [-O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-vectorize", "-O3 -fno-vectorize"] + max-parallel: 5 + steps: + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 + + - name: Run memsan tests + shell: bash + run: | + set -eu -o pipefail + cd "$GITHUB_WORKSPACE/tests/ct_tooling" + chmod +x ct_test.sh + raw_algs="${{ needs.interactive-inputs.outputs.runtime-options }}" + IFS=',' read -r -a algs <<< "$raw_algs" # convert to array + for alg in "${algs[@]}"; do + ./ct_test.sh memsan ${{ matrix.compiler }} ${{ matrix.liboqs_build }} ${{ matrix.opt_flag }} "$alg" + done + + - name: Upload memsan logs + uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4 + with: + name: memsan_${{ matrix.compiler }}_${{matrix.liboqs_build}}_${{ matrix.opt_flag }}_logs + path: | + tests/ct_tooling/tools/memsan/logs/** \ No newline at end of file diff --git a/tests/ct_tooling/README.md b/tests/ct_tooling/README.md new file mode 100644 index 0000000000..635166493e --- /dev/null +++ b/tests/ct_tooling/README.md @@ -0,0 +1,170 @@ +# Constant-Time Tooling + +Framework constant-time testing of liboqs across compilers, optimization flags, and `OQS_OPT_TARGET` build modes. + +## Repository Structure + +``` +tests/ct_tooling/ +├── README.md +├── ct_test.sh # Unified shell script handling CT test execution for all tools +├── local_testing_example.sh # Example script for running CT tests locally +├── analyze_results.py # Parse log output and generate summary CSVs and graphs +└── tools/ + ├── memsan/ + │ ├── CMakeLists.txt # MemSan-specific CMake configuration for liboqs/tests + │ ├── rng_poison_memsan.c # RNG poisoning for MemSan testing + │ ├── test_kem.c # MemSan-specific KEM test + │ ├── test_sig.c # MemSan-specific SIG test + │ └── README.md + └── valgrind_varlat/ + ├── false_positives/ # Directory containing false-positives suppression files + │ └── *.supp + ├── valgrind-try-patch-20250805.txt # Valgrind patch file + ├── valgrind-varlat-patch-20250805.txt # Valgrind variable-latency patch + ├── valgrind-varlat-sup-block.txt # Valgrind suppression block + └── README.md +``` + +## Tools + +### 1. Valgrind-Varlat (`valgrind_varlat/`) +- **Purpose**: Uninitialized memory error detection for constant-time analysis using Kyberslash patch for Valgrind +- **Output**: Directory containing all unique suppression blocks for each warning output + +### 2. MemSan (`memsan/`) +- **Purpose**: LLVM-based uninitialized memory error detection for constant-time analysis using MemorySanitizer +- **Output**: Unique `SUMMARY: MemorySanitizer` lines for each warning output + +Both tools are driven by the single unified `ct_test.sh` script located at the root of `tests/ct_tooling/`. The tool to execute is selected via the first argument: + +```bash +./ct_test.sh +``` + +- `tool`: `valgrind-varlat` or `memsan` +- `compiler_version`: clang, clang-20, gcc, gcc-14, ... +- `liboqs_build`: `generic` or `auto` +- `input`: `all`, `kems`, `sigs`, or a specific enabled algorithm variant +- `opt_flags`: All arguments from position 4 up to position N-1 are treated as compiler optimization flags (including multi-flag combinations such as `-O3 -fno-tree-vectorize`). + +Examples: + +```bash +./ct_test.sh valgrind-varlat clang generic -O2 all +./ct_test.sh valgrind-varlat clang-20 auto -O3 -fno-tree-vectorize kems +./ct_test.sh valgrind-varlat gcc-14 generic -O2 -fno-tree-vectorize Kyber768 +./ct_test.sh memsan clang generic -O1 ML-DSA-44 +./ct_test.sh memsan clang-20 auto -O3 all +``` + +The `local_testing_example.sh` script demonstrates how to use `ct_test.sh` to run CT tests locally across a variety of compilers, compiler versions, liboqs target builds, and optimization flags. + +The `ct-tooling-valgrind-varlat.yml` and `ct-tooling-memsan.yml` workflows also use `ct_test.sh` to execute CT tests in CI on user-selected algorithms (using [interactive-inputs](https://github.com/marketplace/actions/interactive-inputs)) when the workflows are manually triggered on GitHub Actions. + +### Configuration +For Valgrind-Varlat configuration, see: [Valgrind-Varlat's README](tools/valgrind_varlat/README.md) +For MemSan configuration, see: [MemSan's README](tools/memsan/README.md) + +## Workflow + +### Running Tests + +Each tool follows the same testing workflow, implemented through two functions in the unified `ct_test.sh` script: + +1. **Building liboqs** +First, the `build()` function builds liboqs with the compiler options desired. In the case of `local_testing_example.sh`, these options are set to: + +- Compiler versions: gcc, gcc-14, clang, and clang-20 +- liboqs build: auto (with optimizations), and generic +- Optimization flags: -O0, -O1, -O2, -O3, -Os, -Ofast, -O2 -fno-tree-vectorize, and -O3 -fno-tree-vectorize + +When `ct_test.sh` is executed on a single algorithm variant, `build()` builds liboqs with the `OQS_MINIMAL_BUILD` option, minimizing the time and resources required for building. For the other input options, liboqs is built entirely. Each build is placed into a dedicated directory at the liboqs root, named using the pattern. + +Note that Valgrind-Varlat tests can be compiled using both gcc and clang, while MemSan is only native to the clang compiler. This leaves the following possible configurations for each of the optimization flags in the case of `local_testing_example.sh`: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Valgrind-VarlatMemSan
gccgcc-14clangclang-20clangclang-20
genericAll opt flagsAll opt flagsAll opt flagsAll opt flagsAll opt flagsAll opt flags
autoAll opt flagsAll opt flagsAll opt flagsAll opt flagsAll opt flagsAll opt flags
+ +Once the script builds each configuration into a build folder, it calls the test execution function (`test()`) on the build folder generated. + +2. **Test execution** +Then, `test()` is tasked with executing the tool's test on selected liboqs algorithms. Each tool has a different process through which it parses the tool's output to keep unique instances of the warnings, which are further detailed in their respective README files: [Valgrind-Varlat's README](tools/valgrind_varlat/README.md) and [MemSan's README](tools/memsan/README.md). + +Both tools enforce a warning cap of 100,000 unique warnings per algorithm run. Once the cap is reached, further warnings are suppressed. All SPHINCS and SLH-DSA signature variants are currently skipped during SIG tests due to the excessive time they require to execute. + +### Output Structure +The workflow organizes test outputs into log files that capture unique warnings for each algorithm. These logs are written inside `tests/ct_tooling/tools//logs/`, categorized into concrete subdirectories based on the compiler and build configuration (`gcc_14_auto`, `clang_generic`, ...), which then contain further subdivisions by optimization levels (`O0`, `O1`, ...) and algorithm types (`kem` or `sig`). The structure is as follows: + +``` +tests/ct_tooling/tools//logs/ +├── clang_generic/ +│ ├── O0/ +│ │ ├── kem/ +│ │ │ ├── kem_summary.txt # Pass/fail summary with compiler info +│ │ │ ├── _.log # Unique warnings for the algorithm +│ │ └── sig/ +│ │ └── ... +│ ├── O1/ +│ └── ... +├── _/ +│ ├── O0/ +│ └── ... +``` + +The summary file for each run includes the compiler path, compiler version, target architecture, and compilation flags used, followed by a pass/fail line for each algorithm tested. + +### Simultaneous testing +Since MemSan requires to replace several files within liboqs/tests, it is not recommended to run both tests at the same time. This would cause Valgrind_Varlat tests to fail because of using MemSan-oriented files. + +### Analyzing Results + +Use `analyze_results.py` to parse the warnings data from the log files and generate graphs describing the results. + +```bash +python3 analyze_results.py \ + --tool \ + --input tests/ct_tooling/tools//logs \ + --output results_ +``` + +**Generates**: +- `KEM_warnings_per_opt_level.csv` - Warning counts per algorithm per optimization +- `SIG_warnings_per_opt_level.csv` - Same for signature schemes +- `*.png` - 4 visualization graphs regarding the total and average number of warnings per KEM and signature \ No newline at end of file diff --git a/tests/ct_tooling/analyze_results.py b/tests/ct_tooling/analyze_results.py new file mode 100644 index 0000000000..1bba74e9ce --- /dev/null +++ b/tests/ct_tooling/analyze_results.py @@ -0,0 +1,247 @@ +# SPDX-License-Identifier: MIT + +#!/usr/bin/env python3 + +""" +Analyze constant-time results across different optimization levels. +Parses summary files to compare warning counts for KEMs and SIGs. +Generates CSV reports and visualization graphs. +""" + +import os +import re +import glob +from collections import defaultdict +import csv +import matplotlib.pyplot as plt +import matplotlib +matplotlib.use('Agg') +import numpy as np +import argparse +import sys + +parser = argparse.ArgumentParser(description='Analyze constant-time test results') +parser.add_argument('--tool', '-t', type=str, help='Constant-time tool used for analysis') +parser.add_argument('--input', '-i', type=str, help='General input directory containing build information') +parser.add_argument('--output', '-o', type=str, default='.', help='Output directory for analysis results') +args = parser.parse_args() + +cwd = os.getcwd() +LOG_BASE_DIR = os.path.abspath(os.path.join(cwd, args.input)) if args.input else cwd +OUTPUT_DIR = os.path.abspath(os.path.join(cwd, args.output)) if args.output else cwd + +def parse_summary_file(filepath): + """ + Pattern to match Valgrind test results: + Example: "Testing KEM: Kyber512 ... FAIL (Valgrind/MemSan warnings)" + " → Found 150 Valgrind warnings" + """ + results = {} + + with open(filepath, 'r') as f: + content = f.read() + + header_re = re.compile(r'^Testing (?:KEM|SIG): (\S+) \.\.\. (?:PASS|FAIL)', re.MULTILINE) + count_re = re.compile(r'(?:→|->)?\s*(?:Found|Reached cap).*?(\d+).*?warnings', re.IGNORECASE) + + matches = list(header_re.finditer(content)) + for idx, m in enumerate(matches): + alg_name = m.group(1) + + # Skip SPHINCS and SLH_DSA algorithms + if 'SPHINCS' in alg_name or 'SLH_DSA' in alg_name: + continue + + start = m.end() + end = matches[idx+1].start() if idx+1 < len(matches) else len(content) + block = content[start:end] + + c = count_re.search(block) + if c: + error_count = int(c.group(1)) + else: + error_count = 0 + + results[alg_name] = error_count + + return results + +def analyze_logs(): + """ + Expected layout: + ///kem/kem_summary_*.txt + ///sig/sig_summary_*.txt + """ + + kem_data = defaultdict(lambda: defaultdict(dict)) # {build: {opt: {alg: warnings}}} + sig_data = defaultdict(lambda: defaultdict(dict)) + + if not os.path.isdir(LOG_BASE_DIR): + print(f"Error: Input directory '{LOG_BASE_DIR}' does not exist.", file=sys.stderr) + sys.exit(1) + + for build_dir in sorted(glob.glob(os.path.join(LOG_BASE_DIR, '*'))): + build_name = os.path.basename(build_dir) + for opt_dir in sorted(glob.glob(os.path.join(build_dir, '*'))): + opt_level = os.path.basename(opt_dir) + for test_type in ['kem', 'sig']: + test_dir = os.path.join(opt_dir, test_type) + if not os.path.isdir(test_dir): + continue + + for summary_file in glob.glob(os.path.join(test_dir, f'{test_type}_summary_*.txt')): + results = parse_summary_file(summary_file) + if test_type == 'kem': + kem_data[build_name][opt_level].update(results) + else: + sig_data[build_name][opt_level].update(results) + + return kem_data, sig_data + +def sort_optimization_levels(opt_levels): + priority = { + 'O0': 0, + 'O1': 1, + 'O2-NOVEC': 2, + 'O2': 3, + 'OS': 4, + 'O3-NOVEC': 5, + 'O3': 6, + 'OFAST': 7 + } + return sorted(opt_levels, key=lambda x: priority.get(x.upper(), 99)) + +def generate_csv_file(data, alg_type, opt_levels, output_dir): + + csv_path = os.path.join(output_dir, f'{alg_type}_warnings_per_opt_level.csv') + with open(csv_path, 'w', newline='') as csvfile: + all_algs = set() + for opt_data in data.values(): + all_algs.update(opt_data.keys()) + all_algs = sorted(all_algs) + + writer = csv.writer(csvfile) + writer.writerow(['Algorithm'] + opt_levels) + + for alg in all_algs: + row = [alg] + for opt in opt_levels: + row.append(data[opt].get(alg, 'N/A')) + writer.writerow(row) + +def heatmap_warnings_per_alg_and_opt_level(data, opt_levels, alg_type, tool, output_dir): + + # Sort algorithms by total warnings (descending). For ties, sort alphabetically ascending + def _norm_name(n): + return re.sub(r'[^0-9a-z]', '', n.lower()) + + all_algs = set() + for opt in opt_levels: + all_algs.update(data.get(opt, {}).keys()) + alg_total = {alg: sum([data.get(opt, {}).get(alg, 0) for opt in opt_levels]) for alg in all_algs} + alg_names = [a for a, _ in sorted(alg_total.items(), key=lambda x: (-x[1], _norm_name(x[0])))] + + matrix = np.zeros((len(alg_names), len(opt_levels)), dtype=int) + for i, alg in enumerate(alg_names): + for j, opt in enumerate(opt_levels): + matrix[i, j] = data.get(opt, {}).get(alg, 0) + + CMAP_BY_TYPE = {'KEM': 'Blues', 'SIG': 'Oranges'} + cmap = CMAP_BY_TYPE.get(alg_type.upper(), 'YlOrRd') + + fig_height = max(8, len(alg_names) * 0.25) + fig, ax = plt.subplots(figsize=(12, fig_height)) + + max_val = int(matrix.max()) if matrix.size else 0 + vmax = max(1, max_val) + + raster = True if len(alg_names) > 200 else False + im = ax.imshow(matrix, aspect='auto', cmap=cmap, vmin=0, vmax=vmax, rasterized=raster) + + ax.set_xticks(np.arange(len(opt_levels))) + ax.set_xticklabels(opt_levels) + plt.setp(ax.get_xticklabels(), rotation=45, ha='right', rotation_mode='anchor') + + ax.set_yticks(np.arange(len(alg_names))) + ax.set_yticklabels(alg_names) + + cbar = plt.colorbar(im, ax=ax) + cbar.set_label('Number of Warnings', rotation=270, labelpad=20, fontsize=12) + + for i in range(len(alg_names)): + for j in range(len(opt_levels)): + ax.text(j, i, int(matrix[i, j]), ha='center', va='center', color='black', fontsize=8) + + ax.set_title(f'{tool.upper()} {alg_type.upper()} Warnings per Algorithm by Optimization Level', + fontsize=14, fontweight='bold', pad=20) + ax.set_xlabel('Optimization Level', fontsize=12) + ax.set_ylabel(f'{alg_type.upper()} Algorithms', fontsize=12) + plt.tight_layout() + + graph_path = os.path.join(output_dir, f"{alg_type.upper()}_total_warnings_per_alg_by_opt_level.png") + plt.savefig(graph_path, dpi=300, bbox_inches='tight') + plt.close() + +def bar_chart_total_warnings_per_opt_level(kem_data, sig_data, opt_levels, tool, output_dir): + + fig, ax = plt.subplots(figsize=(14, 6)) + + kem_totals = [sum(kem_data[opt].values()) for opt in opt_levels] + sig_totals = [sum(sig_data[opt].values()) for opt in opt_levels] + + x = np.arange(len(opt_levels)) + width = 0.35 + ax.bar(x - width/2, kem_totals, width, label='KEM', color='#2b8cbe') + ax.bar(x + width/2, sig_totals, width, label='SIG', color='#f16913') + ax.set_xlabel('Optimization Level', fontsize=12) + ax.set_ylabel('Total Warnings', fontsize=12) + ax.set_title(f'{tool.upper()} Total Warnings by Optimization Level', fontsize=14, fontweight='bold', pad=20) + ax.set_xticks(x) + ax.set_xticklabels(opt_levels, rotation=45, ha='right') + ax.legend() + ax.grid(axis='y', alpha=0.3) + plt.tight_layout() + outpath = os.path.join(output_dir, f'{tool}_total_warnings_kem_vs_sig.png') + plt.savefig(outpath, dpi=300, bbox_inches='tight') + plt.close() + +def line_chart_avg_warnings_per_opt_level(kem_data, sig_data, opt_levels, tool, output_dir): + + kem_avgs = [np.mean(list(kem_data.get(opt, {}).values())) if kem_data.get(opt) else 0 for opt in opt_levels] + sig_avgs = [np.mean(list(sig_data.get(opt, {}).values())) if sig_data.get(opt) else 0 for opt in opt_levels] + + fig, ax = plt.subplots(figsize=(10, 6)) + ax.plot(opt_levels, kem_avgs, marker='o', label='KEM', color='#2b8cbe') + ax.plot(opt_levels, sig_avgs, marker='o', label='SIG', color='#f16913') + ax.set_xlabel('Optimization Level',fontsize=12) + ax.set_ylabel('Average Warnings', fontsize=12) + ax.set_title(f'{tool.upper()} Average Warnings per Optimization Level', fontsize=14, fontweight='bold', pad=20) + ax.legend() + ax.grid(True, alpha=0.3) + plt.tight_layout() + outpath = os.path.join(output_dir, f'{tool}_avg_warnings_kem_vs_sig.png') + plt.savefig(outpath, dpi=300, bbox_inches='tight') + plt.close() + +def generate_reports(kem_data, sig_data): + for build_name in kem_data.keys(): + build_output_dir = os.path.join(OUTPUT_DIR, build_name) + os.makedirs(build_output_dir, exist_ok=True) + + opt_levels = sort_optimization_levels(kem_data[build_name].keys()) + + generate_csv_file(kem_data[build_name], 'KEM', opt_levels, build_output_dir) + generate_csv_file(sig_data[build_name], 'SIG', opt_levels, build_output_dir) + + heatmap_warnings_per_alg_and_opt_level(kem_data[build_name], opt_levels, 'KEM', args.tool, build_output_dir) + heatmap_warnings_per_alg_and_opt_level(sig_data[build_name], opt_levels, 'SIG', args.tool, build_output_dir) + + bar_chart_total_warnings_per_opt_level(kem_data[build_name], sig_data[build_name], opt_levels, args.tool, build_output_dir) + line_chart_avg_warnings_per_opt_level(kem_data[build_name], sig_data[build_name], opt_levels, args.tool, build_output_dir) + +def main(): + kem_data, sig_data = analyze_logs() + generate_reports(kem_data, sig_data) + +if __name__ == "__main__": + main() diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh new file mode 100755 index 0000000000..647bba2068 --- /dev/null +++ b/tests/ct_tooling/ct_test.sh @@ -0,0 +1,429 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LIBOQS_DIR="$(realpath "$SCRIPT_DIR/../..")" + +notify() { + printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" +} + +build() { + local TOOL=$1 + local COMP_V=$2 + local LIBOQS_BUILD=$3 + local OPT_FLAG=$4 + local BUILD_DIR=$5 + local INPUT=$6 + + # Remove build dir if OQS_MINIMAL_BUILD or input changed + if [ -f "$BUILD_DIR/CMakeCache.txt" ]; then + grep -q "OQS_MINIMAL_BUILD" "$BUILD_DIR/CMakeCache.txt" && rm -rf "$BUILD_DIR" + fi + + # Handle a minimal liboqs build only if a single algorithm is passed as input, otherwise build the complete library + local MINIMAL_BUILD_ARG=() + if [[ -n "$INPUT" && "$INPUT" != "all" && "$INPUT" != "kems" && "$INPUT" != "sigs" ]]; then + MINIMAL_BUILD_ARG=(-DOQS_MINIMAL_BUILD="$INPUT") + fi + + mkdir -p "$BUILD_DIR" + cd "$BUILD_DIR" + local CMAKE_ARGS=("-S .." "-G Ninja" "${MINIMAL_BUILD_ARG[@]}" "-DCMAKE_C_COMPILER=$COMP_V" "-DOQS_OPT_TARGET=$LIBOQS_BUILD" "-DCMAKE_BUILD_TYPE=Debug" "-DOQS_USE_OPENSSL=OFF" "-DOQS_DIST_BUILD=OFF") + + case "$TOOL" in + valgrind-varlat) + + cmake "${CMAKE_ARGS[@]}" \ + -DCMAKE_C_FLAGS="$OPT_FLAG" \ + -DOQS_ENABLE_TEST_CONSTANT_TIME=ON > /dev/null 2>&1 + cmake --build . -j$(nproc) > /dev/null 2>&1 + ;; + memsan) + + # Generate suppression flags for all suppression files containing false positives + SUP_DIR="$SCRIPT_DIR/tools/memsan/false_positives" + SUP_FLAGS=() + for f in "$SUP_DIR"/*.txt; do + [ -f "$f" ] || continue + SUP_FLAGS+=( "-fsanitize-ignorelist=$f" ) + done + + # Create backup files of the original tests files + mv "$LIBOQS_DIR/tests/CMakeLists.txt" "$LIBOQS_DIR/tests/CMakeLists.txt.bak" + mv "$LIBOQS_DIR/tests/test_kem.c" "$LIBOQS_DIR/tests/test_kem.c.bak" + mv "$LIBOQS_DIR/tests/test_sig.c" "$LIBOQS_DIR/tests/test_sig.c.bak" + + # Replace original tests/CMakeLists.txt, test_kem.c, and test_sig.txt for their "MemSan poisoned" version + cp "$SCRIPT_DIR/tools/memsan/CMakeLists.txt" "$LIBOQS_DIR/tests/CMakeLists.txt" + cp "$SCRIPT_DIR/tools/memsan/test_kem.c" "$LIBOQS_DIR/tests/test_kem.c" + cp "$SCRIPT_DIR/tools/memsan/test_sig.c" "$LIBOQS_DIR/tests/test_sig.c" + cp "$SCRIPT_DIR/tools/memsan/rng_poison_memsan.c" "$LIBOQS_DIR/tests/rng_poison_memsan.c" + + cmake "${CMAKE_ARGS[@]}" \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ + -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 + cmake --build . -j$(nproc) > /dev/null 2>&1 + + # Restore the original test files with the backups + mv "$LIBOQS_DIR/tests/CMakeLists.txt.bak" "$LIBOQS_DIR/tests/CMakeLists.txt" + mv "$LIBOQS_DIR/tests/test_kem.c.bak" "$LIBOQS_DIR/tests/test_kem.c" + mv "$LIBOQS_DIR/tests/test_sig.c.bak" "$LIBOQS_DIR/tests/test_sig.c" + rm "$LIBOQS_DIR/tests/rng_poison_memsan.c" + ;; + *) + echo "Unknown tool: $TOOL"; return 1 + ;; + esac +} + +cleanup() { + mv "$LIBOQS_DIR/tests/CMakeLists.txt.bak" "$LIBOQS_DIR/tests/CMakeLists.txt" 2>/dev/null || true + mv "$LIBOQS_DIR/tests/test_kem.c.bak" "$LIBOQS_DIR/tests/test_kem.c" 2>/dev/null || true + mv "$LIBOQS_DIR/tests/test_sig.c.bak" "$LIBOQS_DIR/tests/test_sig.c" 2>/dev/null || true + rm "$LIBOQS_DIR/tests/rng_poison_memsan.c" 2>/dev/null || true +} + +trap cleanup EXIT INT + +test() { + local TOOL=$1 + local BUILD_DIR=$2 + local TEST_TYPE=$3 + local COMP_V="${4//-/_}" + local TARGET=$5 + local ALGORITHM=$6 + local SCRIPT_DIR=$7 + + if [[ "$TEST_TYPE" == "kem" ]]; then + TEST_BINARY="test_kem" + UPPER_TYPE="KEM" + elif [[ "$TEST_TYPE" == "sig" ]]; then + TEST_BINARY="test_sig" + UPPER_TYPE="SIG" + fi + + # Skip SPHINCS and SLH-DSA for SIG tests + if [[ "$ALGORITHM" == *SPHINCS* || "$ALGORITHM" == *SLH_DSA* ]]; then + echo "Skipping ${UPPER_TYPE} ${ALGORITHM}" + return 0 + fi + + LOG_DIR="${SCRIPT_DIR}/tools/${TOOL//-/_}/logs/${COMP_V}_${TARGET}" + mkdir -p "$LOG_DIR" + CURRENT_RUN_DIR="$LOG_DIR/$SANITIZED_OPT_FLAG" + mkdir -p "$CURRENT_RUN_DIR" + OUTPUT_DIR="$CURRENT_RUN_DIR/$TEST_TYPE" + mkdir -p "$OUTPUT_DIR" + SUMMARY_FILE="$OUTPUT_DIR/${TEST_TYPE}_summary.txt" + + COMPILER_PATH=$(grep -E '^CMAKE_C_COMPILER:.*=' "$BUILD_DIR"/CMakeCache.txt | head -n1 | cut -d'=' -f2- | tr -d '\r') + COMPILER_VERSION=$("$COMPILER_PATH" --version 2>&1 | head -n1) + ARCH="$(uname -m)" + COMP_FLAGS=$(grep "CMAKE_C_FLAGS:" "$BUILD_DIR/CMakeCache.txt" | cut -d'=' -f2-) + MAX_WARNINGS=100000 + + # Check if this is first algorithm (header only once) + if [[ ! -s "$SUMMARY_FILE" ]]; then + { + echo "========================================" + echo "Compiled with: $COMP_FLAGS" + echo "Compiler version: ${COMPILER_VERSION}" + echo "Architecture: ${ARCH}" + echo "========================================" + echo "" + } > "$SUMMARY_FILE" + else + echo "" >> "$SUMMARY_FILE" + fi + + PASS_COUNT=0 + FAIL_COUNT=0 + LOG_FILE="$OUTPUT_DIR/${ALGORITHM}_${TIMESTAMP}.log" + echo -n "Testing $ALGORITHM ... " | tee -a "$SUMMARY_FILE" + + cd "$LIBOQS_DIR" + + # Execute CT tests based on the tool selected + case "$TOOL" in + valgrind-varlat) + # Generate suppression flags for all suppression files containing false positives + SUP_DIR="$SCRIPT_DIR/tools/valgrind_varlat/false_positives" + SUP_FLAGS=() + for f in "$SUP_DIR"/*.txt; do + [ -f "$f" ] || continue + SUP_FLAGS+=( "--suppressions=$f" ) + done + + VALGRIND_OPTS=( + valgrind_varlat + --tool=memcheck + --gen-suppressions=all + "${SUP_FLAGS[@]}" # Include all suppression files + --error-exitcode=123 + --max-stackframe=20480000 + --num-callers=20 + --variable-latency-errors=yes # Enable the KyberSlash patch + ) + + : > "$LOG_FILE" ; : > "$LOG_FILE.hashes"; : > "$LOG_FILE.count" + + "${VALGRIND_OPTS[@]}" "$BUILD_DIR"/tests/$TEST_BINARY "$ALGORITHM" 2>&1 | awk \ + -v log_file="$LOG_FILE" \ + -v tmp_file="$LOG_FILE.tmp" \ + -v hash_file="$LOG_FILE.hashes" \ + -v count_file="$LOG_FILE.count" \ + -v max_warnings="$MAX_WARNINGS" ' + # Extract unique suppression blocks from Valgrind output + BEGIN { + unique_warnings_count = 0; + in_block = 0; # Whether we are inside a { ... } block + block = ""; # Current block content (including braces) + suppress = 0; # reached max_warnings + + # Preload known hashes if present + while ((getline line < hash_file) > 0) { + gsub(/\r$/, "", line); + if (length(line) > 0) { + seen[line] = 1; + } + } + close(hash_file); + } + + { + if (suppress) { + if (in_block) { + if ($0 ~ /^\}$/) { in_block = 0 } + } else if ($0 ~ /^\{$/) { + in_block = 1 + } + next + } + + if (in_block) { + block = block $0 "\n"; + + # When } is encountered, it is the end of block: compute hash via tmp file + if ($0 ~ /^\}$/) { + print block > tmp_file; close(tmp_file); + cmd = "sha256sum \"" tmp_file "\""; + cmd | getline line; close(cmd); + hash = line; sub(/ .*/, "", hash); + + # If the hash is new, store it in seen[] and increase the count + if (!(hash in seen)) { + print block >> log_file; close(log_file); + print "" >> log_file; # spacer line between blocks + print hash >> hash_file; close(hash_file); + seen[hash] = 1; + unique_warnings_count++; + + if (unique_warnings_count >= max_warnings) { + suppress = 1; + } + } + + in_block = 0; + block = ""; + } + next + } + + # When { is detected, start a new block + if ($0 ~ /^\{$/) { + in_block = 1; + block = $0 "\n"; + } + } + + END { + print unique_warnings_count > count_file; close(count_file); + } + ' + EXIT_CODE=${PIPESTATUS[0]} + ERROR_COUNT=$(cat "$LOG_FILE.count" 2>/dev/null); ERROR_COUNT=${ERROR_COUNT:-0} + rm -f "$OUTPUT_DIR"/*.hashes "$OUTPUT_DIR"/*log.tmp + ;; + + memsan) + touch "$LOG_FILE" + + "$BUILD_DIR"/tests/$TEST_BINARY "$ALGORITHM" 2>&1 | awk \ + -v log_file="$LOG_FILE" \ + -v max_warnings="$MAX_WARNINGS" ' + /^SUMMARY: MemorySanitizer:/ { + # memcmp/bcmp warnings are known false positives but -fsanitize-ignorelist has no effect because they are not compiled by clang + # Skip their warnings here instead. + if ($0 ~ / in (memcmp|bcmp)$/) next + + # Check if this exact SUMMARY was already logged and store it if not + cmd = "grep -Fxq \"" $0 "\" " log_file + if (system(cmd) != 0) { + warnings++ + print >> log_file + fflush(log_file) + } + + if (warnings >= max_warnings) { + print warnings > log_file ".count" + print "TERMINATED: Exceeded " max_warnings " warnings" >> log_file + fflush(log_file) + terminated = 1 + exit 1 + } + } + # Count and store the number of unique warnings obtained and store it + END { + if (!terminated && warnings > 0) { + print warnings > log_file ".count" + } else if (!terminated) { + print 0 > log_file ".count" + } + } + ' + EXIT_CODE=$? + ERROR_COUNT=$(cat "$LOG_FILE.count" 2>/dev/null); ERROR_COUNT=${ERROR_COUNT:-0} + ;; + *) + echo "Unknown tool: $TOOL"; return 1 + ;; + esac + + if [ "$ERROR_COUNT" -eq "$MAX_WARNINGS" ]; then + echo "FAIL" | tee -a "$SUMMARY_FILE" + echo " → Found $ERROR_COUNT warnings (warning cap reached — further warnings suppressed)" \ + | tee -a "$SUMMARY_FILE" + ((++FAIL_COUNT)) + + elif [ "$ERROR_COUNT" -gt 0 ]; then + echo "FAIL" | tee -a "$SUMMARY_FILE" + echo " → Found $ERROR_COUNT warnings" \ + | tee -a "$SUMMARY_FILE" + ((++FAIL_COUNT)) + + elif [ $EXIT_CODE -ne 0 ]; then + echo "FAIL (Exit code: $EXIT_CODE)" | tee -a "$SUMMARY_FILE" + ((++FAIL_COUNT)) + + else + echo "PASS" | tee -a "$SUMMARY_FILE" + ((++PASS_COUNT)) + + fi + + rm -f "$OUTPUT_DIR"/*.count +} + +get_available_algs() { + local ALG_TYPE="$1" + local LIBOQS_DIR="$2" + + (cd "$LIBOQS_DIR" && python3 -c "import sys +sys.path.insert(0, 'tests') +import helpers +for alg in helpers.available_"$ALG_TYPE"s_by_name(): + print(alg)") +} + +# Read inputs from arguments +if [ "$#" -lt 4 ]; then + echo "Usage: $0 " + echo "Example: $0 clang-20 generic -O2 -fno-tree-vectorize all" + exit 1 +fi + +TOOL="$1" +COMPILER="$2" +TARGET="$3" + +# The last argument is the input (all/kems/sigs/) +INPUT="${!#}" + +# Collect all optimization flags between position 4 and the last-1 argument +if [ "$#" -gt 4 ]; then + NUM_OPT_ARGS=$(( $# - 4 )) + OPT_FLAG="${@:4:$NUM_OPT_ARGS}" +else + OPT_FLAG="" +fi + +SANITIZED_OPT_FLAG=$(printf "%s" "$OPT_FLAG" | tr ' ' '_' | tr -d '-' | tr -c '[:alnum:]_' '_') +if [ -z "$SANITIZED_OPT_FLAG" ]; then + SANITIZED_OPT_FLAG=default +fi + +BUILD_NAME="${TOOL//-/_}_${COMPILER//-/_}_${TARGET}_${SANITIZED_OPT_FLAG}" +BUILD_DIR="$LIBOQS_DIR/build_$BUILD_NAME" + +# Export build dir for tests/helpers.py to find generated headers +export OQS_BUILD_DIR="$BUILD_DIR" +KEMS=$(get_available_algs kem "$LIBOQS_DIR") +SIGS=$(get_available_algs sig "$LIBOQS_DIR") + +# Convert user-facing algorithm name to CMake OQS_MINIMAL_BUILD key +# Some OQS API names don't match the CMake variable suffix directly (e.g. sntrup761 maps to OQS_ENABLE_KEM_ntruprime_sntrup761, OV-Is to OQS_ENABLE_SIG_uov_ov_Is) +# Detect the type (KEM/SIG), then look up the correct suffix from alg_support.cmake +BUILD_INPUT="$INPUT" +ALG_TYPE="" +echo "$KEMS" | grep -Fxq "$INPUT" && ALG_TYPE="KEM" +echo "$SIGS" | grep -Fxq "$INPUT" && ALG_TYPE="SIG" + +if [[ -n "$ALG_TYPE" ]]; then + NORMALIZED_INPUT="$(printf '%s' "$INPUT" | tr '[:upper:]-' '[:lower:]_')" + CMAKE_SUFFIX=$(grep -oE "OQS_ENABLE_${ALG_TYPE}_[a-zA-Z0-9_]+" "$LIBOQS_DIR/.CMake/alg_support.cmake" \ + | sed "s/OQS_ENABLE_${ALG_TYPE}_//" \ + | grep -vE "(_avx2|_avx|_aesni|_x86_64|_aarch64|_icicle_cuda|_cuda)$" \ + | grep -iE "_${NORMALIZED_INPUT}$|^${NORMALIZED_INPUT}$" | head -1) + BUILD_INPUT="${ALG_TYPE}_${CMAKE_SUFFIX:-$NORMALIZED_INPUT}" +fi + +# Build liboqs with the specified compilation parameters +notify "Preparing liboqs build (compiler=${COMPILER}, target=${TARGET}, flags=${OPT_FLAG})" +build "$TOOL" "$COMPILER" "$TARGET" "$OPT_FLAG" "$BUILD_DIR" "$BUILD_INPUT" + +cd "$LIBOQS_DIR" + +TIMESTAMP="$(date '+%Y%m%d_%H%M%S')" +notify "Setting up ${TOOL} CT testing" + +RUN_KEMS=() +RUN_SIGS=() + +case "$INPUT" in + all) + RUN_KEMS=($KEMS) + RUN_SIGS=($SIGS) + ;; + kems) + RUN_KEMS=($KEMS) + ;; + sigs) + RUN_SIGS=($SIGS) + ;; + *) + if echo "$KEMS" | grep -Fxq "$INPUT"; then + RUN_KEMS=("$INPUT") + elif echo "$SIGS" | grep -Fxq "$INPUT"; then + RUN_SIGS=("$INPUT") + else + echo "Enter a valid input: all/kems/sigs/" + exit 1 + fi + ;; +esac + +for KEM in "${RUN_KEMS[@]}"; do + test "$TOOL" "$BUILD_DIR" kem "$COMPILER" "$TARGET" "$KEM" "$SCRIPT_DIR" +done + +for SIG in "${RUN_SIGS[@]}"; do + test "$TOOL" "$BUILD_DIR" sig "$COMPILER" "$TARGET" "$SIG" "$SCRIPT_DIR" +done + +notify "Finished ${TOOL} CT testing" +echo "" \ No newline at end of file diff --git a/tests/ct_tooling/local_testing_example.sh b/tests/ct_tooling/local_testing_example.sh new file mode 100755 index 0000000000..80b8b5a2cb --- /dev/null +++ b/tests/ct_tooling/local_testing_example.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-License-Identifier: MIT + +# Iterate through the compilation options provided by the framework to execute constant time tests +# Adjust valgrind-varlat/memsan accordingly (note that gcc uses -fno-tree-vectorize while clang uses -fno-vectorize) + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +for compiler_version in gcc gcc-14 clang clang-20; do + for liboqs_build in generic auto; do + for opt_flag in -O0 -O1 -O2 -O3 -Os -Ofast "-O2 -fno-tree-vectorize" "-O3 -fno-tree-vectorize"; do + ./ct_test.sh valgrind-varlat "$compiler_version" "$liboqs_build" "$opt_flag" all + done + done +done \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/CMakeLists.txt b/tests/ct_tooling/tools/memsan/CMakeLists.txt new file mode 100644 index 0000000000..e6dbc23f3f --- /dev/null +++ b/tests/ct_tooling/tools/memsan/CMakeLists.txt @@ -0,0 +1,246 @@ +# SPDX-License-Identifier: MIT + +option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR + CMAKE_C_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wbad-function-cast) +endif() +if(CMAKE_C_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wcast-qual) + add_compile_options(-Wnarrowing) + add_compile_options(-Wconversion) +endif() +if (MINGW OR MSYS OR CYGWIN) + add_definitions(-D__USE_MINGW_ANSI_STDIO=1) + add_compile_options(-Wno-unknown-pragmas) + add_compile_options(-Wno-unused-parameter) +endif() +if(WIN32 AND NOT (MINGW OR MSYS OR CYGWIN)) + # ignore warning for test apps + add_compile_options(/wd4996 /wd4244) +endif() + +if(OQS_USE_OPENSSL) + include_directories(${OPENSSL_INCLUDE_DIR}) +endif() + +if(NOT WIN32) + set(LIBM m) +endif() + +# List oqs-internal after oqs so that oqs has linking precedence. +if(${OQS_USE_OPENSSL}) + find_package(OpenSSL 1.1.1 REQUIRED) + set(TEST_DEPS oqs oqs-internal ${LIBM} OpenSSL::Crypto) +else() + set(TEST_DEPS oqs oqs-internal ${LIBM}) +endif() + +if(OQS_USE_PTHREADS) + set(TEST_DEPS ${TEST_DEPS} Threads::Threads) +endif() + +if(NOT WIN32) + execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/git_commit.sh OUTPUT_VARIABLE GIT_COMMIT) + add_definitions(-DOQS_COMPILE_GIT_COMMIT="${GIT_COMMIT}") + + add_executable(test_aes test_aes.c) + target_link_libraries(test_aes PRIVATE ${TEST_DEPS}) + + add_executable(test_hash test_hash.c) + target_link_libraries(test_hash PRIVATE ${TEST_DEPS}) + + add_executable(test_sha3 test_sha3.c) + target_link_libraries(test_sha3 PRIVATE ${TEST_DEPS}) + + add_executable(speed_common speed_common.c) + target_link_libraries(speed_common PRIVATE ${TEST_DEPS}) + + set(UNIX_TESTS test_aes test_hash test_sha3 speed_common) + + set(PYTHON3_EXEC python3) +else() + set(PYTHON3_EXEC python) +endif() + +# KEM API tests +add_executable(example_kem example_kem.c) +target_link_libraries(example_kem PRIVATE ${TEST_DEPS}) + +add_executable(kat_kem kat_kem.c test_helpers.c) +target_link_libraries(kat_kem PRIVATE ${TEST_DEPS}) +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) + target_link_options(kat_kem PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(kat_kem PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(test_kem test_kem.c test_helpers.c rng_poison_memsan.c) +target_link_libraries(test_kem PRIVATE ${TEST_DEPS}) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) + target_link_options(test_kem PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(test_kem PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(test_kem_mem test_kem_mem.c test_helpers.c) +target_link_libraries(test_kem_mem PRIVATE ${TEST_DEPS}) +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) + target_link_options(test_kem_mem PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(test_kem_mem PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(speed_kem speed_kem.c) +target_link_libraries(speed_kem PRIVATE ${TEST_DEPS}) + +set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem) + +# SIG API tests +add_executable(example_sig example_sig.c) +target_link_libraries(example_sig PRIVATE ${TEST_DEPS}) + +if(OQS_BUILD_FUZZ_TESTS AND '${CMAKE_C_COMPILER_ID}' STREQUAL 'Clang') + add_executable(fuzz_test_sig fuzz_test_sig.c) + target_link_libraries(fuzz_test_sig PRIVATE ${TEST_DEPS}) + set_target_properties(fuzz_test_sig PROPERTIES + COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}" + LINK_FLAGS "${FUZZING_LINK_FLAGS}" + ) + add_executable(fuzz_test_kem fuzz_test_kem.c) + target_link_libraries(fuzz_test_kem PRIVATE ${TEST_DEPS}) + set_target_properties(fuzz_test_kem PROPERTIES + COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}" + LINK_FLAGS "${FUZZING_LINK_FLAGS}" + ) +endif() + +# Stateful SIG API tests +add_executable(example_sig_stfl example_sig_stfl.c) +target_link_libraries(example_sig_stfl PRIVATE ${TEST_DEPS}) + +add_executable(kat_sig kat_sig.c test_helpers.c) +target_link_libraries(kat_sig PRIVATE ${TEST_DEPS}) +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) + target_link_options(kat_sig PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(kat_sig PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(kat_sig_stfl kat_sig_stfl.c test_helpers.c) +target_link_libraries(kat_sig_stfl PRIVATE ${TEST_DEPS}) +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(CMAKE_CROSSCOMPILING) + target_link_options(kat_sig_stfl PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(kat_sig_stfl PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(test_sig test_sig.c test_helpers.c rng_poison_memsan.c) +target_link_libraries(test_sig PRIVATE ${TEST_DEPS}) +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(CMAKE_CROSSCOMPILING) + target_link_options(test_sig PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(test_sig PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(test_sig_mem test_sig_mem.c) +target_link_libraries(test_sig_mem PRIVATE ${TEST_DEPS}) + +add_executable(speed_sig speed_sig.c) +target_link_libraries(speed_sig PRIVATE ${TEST_DEPS}) + + +set(SIG_TESTS example_sig kat_sig test_sig test_sig_mem speed_sig vectors_sig) + +# SIG_STFL API tests +add_executable(test_sig_stfl test_sig_stfl.c test_helpers.c) +if((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_C_COMPILER_ID STREQUAL "GNU")) + target_link_libraries(test_sig_stfl PRIVATE ${TEST_DEPS} Threads::Threads) +else () + target_link_libraries(test_sig_stfl PRIVATE ${TEST_DEPS}) +endif() +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(CMAKE_CROSSCOMPILING) + target_link_options(test_sig_stfl PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(test_sig_stfl PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +add_executable(speed_sig_stfl speed_sig_stfl.c) +target_link_libraries(speed_sig_stfl PRIVATE ${TEST_DEPS}) + +set(SIG_STFL_TESTS kat_sig_stfl test_sig_stfl speed_sig_stfl) + +add_executable(dump_alg_info dump_alg_info.c) +target_link_libraries(dump_alg_info PRIVATE ${TEST_DEPS}) + +# Intermediate values vector tests +add_executable(vectors_sig vectors_sig.c) +target_link_libraries(vectors_sig PRIVATE ${TEST_DEPS}) + +add_executable(vectors_kem vectors_kem.c) +target_link_libraries(vectors_kem PRIVATE ${TEST_DEPS}) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) + # workaround for Windows .dll + if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) + target_link_options(vectors_kem PRIVATE -Wl,--allow-multiple-definition) + else() + target_link_options(vectors_kem PRIVATE "/FORCE:MULTIPLE") + endif() +endif() + +# Enable Valgrind-based timing side-channel analysis for test_kem and test_sig +if(OQS_ENABLE_TEST_CONSTANT_TIME AND NOT OQS_DEBUG_BUILD) + message(WARNING "OQS_ENABLE_TEST_CONSTANT_TIME is incompatible with CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}.") + set(OQS_ENABLE_TEST_CONSTANT_TIME OFF) +endif() + +# Record compile options -- from target speed_kem - don't set any options only for speed_kem! +get_property(OQS_COMPILE_OPTIONS TARGET speed_kem PROPERTY COMPILE_OPTIONS) +add_definitions(-DOQS_COMPILE_OPTIONS="[${OQS_COMPILE_OPTIONS}]") + +if (CMAKE_GENERATOR MATCHES "Visual Studio") + # With Visual studio the output of tests go into a folder with the configuration option. Force it to the same folder as if + # generating with Ninja + set_target_properties( + dump_alg_info ${KEM_TESTS} ${SIG_TESTS} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/tests" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/tests") +endif() + +# TODO: Get CMake to find python. +# and set PATH variable in Windows +# for DLL builds. +add_custom_target( + run_tests + # skip long KAT tests + COMMAND ${CMAKE_COMMAND} -E env OQS_BUILD_DIR=${CMAKE_BINARY_DIR} ${PYTHON3_EXEC} -m pytest --verbose --numprocesses=auto --ignore=scripts/copy_from_upstream/repos --ignore=tests/test_kat_all.py + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + DEPENDS oqs dump_alg_info ${KEM_TESTS} ${SIG_TESTS} ${SIG_STFL_TESTS} ${UNIX_TESTS} + USES_TERMINAL) \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/README.md b/tests/ct_tooling/tools/memsan/README.md new file mode 100644 index 0000000000..047f5d966d --- /dev/null +++ b/tests/ct_tooling/tools/memsan/README.md @@ -0,0 +1,26 @@ +# MemSan + +This directory contains the files required to execute MemSan's tooling for liboqs constant-time testing. + +MemSan handles false-positive warnings by storing specific functions into `.txt` files within the `false_positives/` directory. These files are passed as parameters of the `-fsanitize-ignorelist` flag during compilation, successfully disregarding those warnings that are cathegorized as not constant-time issues after review. + +## Compiling liboqs with MemSan +MemSan is inherently included with the clang compiler, so no requirement besides installing clang is needed. However, it does require certain workarounds to mark memory as uninitialized when building liboqs. Nonetheless, this process is directly implemented by using the `build()` function within the `ct_test.sh` script. + +The `rng_poison_msan.c` file is used to overwrite the original `OQS_randombytes()` and mark secret variables as uninitialized. Note that the actual value is filled with a non-zero buffer (0xA5) to prevent masking of bugs, as well as eliminating any random noise in the heap memory. + +For MemSan liboqs testing, it is necessary to compile liboqs with new versions of `tests/CMakeLists.txt`, `tests/test_kem.c`, `tests_sig.c`, which can be found under the repository ct-tools/memsan. These new versions allow for memory "poisoning" during the "randombytes" function in `CMakeLists.txt`, and memory "unpoisioning" of public keys in `test_kem.c` and `test_sig.c`. + +Therefore, `build()` replaces the original files with the "poisoned" ones during compilation, so that MemSan testing can successfully take place. Once liboqs compilation is ready, the script replaces the original files with a backup that was temporarily stored so that liboqs is unchanged after constant-time testing with MemSan is finished. + +## Algorithms Testing +Because of how many warnings are output, it is not feasible to store all the warnings in terms of memory and runtime. Therefore, the `test()` function in `ct_test.sh` handles MemSan's output as follows: +- It captures the first SUMMARY line of each warning, which contains key details (file, line, issue type), and stores these in log files. +- Only unique SUMMARY lines are retained, avoiding duplication from repeated warnings during execution. + +The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. + +## Dependencies +Remember to install the required dependencies before testing: + +`sudo apt install -y clang clang-tools` \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt b/tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt new file mode 100644 index 0000000000..6b1fb56592 --- /dev/null +++ b/tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt @@ -0,0 +1,15 @@ +fun:PQCP_MLKEM_NATIVE_MLKEM*_C_poly_decompress_d* +fun:PQCP_MLKEM_NATIVE_MLKEM*_C_poly_reduce +fun:PQCP_MLKEM_NATIVE_MLKEM*_X86_64_poly_frommsg +fun:mlk_ct_memcmp +fun:mlk_ct_cmov_zero +fun:mlk_rej_uniform_scalar +fun:mlk_load32_littleendian +fun:PQCP_MLKEM_NATIVE_MLKEM*_C_poly_frommsg +fun:mlk_value_barrier_* +fun:PQCP_MLKEM_NATIVE_MLKEM*_X86_64_rej_uniform_avx2 +fun:PQCP_MLKEM_NATIVE_MLKEM*_X86_64_enc_derand +fun:mlk_ct_cmask_neg_i16 +fun:mlk_scalar_signed_to_unsigned_q +fun:mlk_ct_cmask_nonzero_u* +fun:mlk_ct_sel_int* \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/rng_poison_memsan.c b/tests/ct_tooling/tools/memsan/rng_poison_memsan.c new file mode 100644 index 0000000000..45a31c4aac --- /dev/null +++ b/tests/ct_tooling/tools/memsan/rng_poison_memsan.c @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +void OQS_randombytes(uint8_t *r, size_t len) { + memset(r, 0xA5, len); // Fill with pattern 0xA5 + __msan_poison(r, len); +} \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/test_kem.c b/tests/ct_tooling/tools/memsan/test_kem.c new file mode 100644 index 0000000000..eb4a76e6cf --- /dev/null +++ b/tests/ct_tooling/tools/memsan/test_kem.c @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: MIT + +#include +#include +#include +#if defined(_WIN32) +#include +#define strcasecmp _stricmp +#else +#include +#endif + +#include +#include +#if OQS_USE_PTHREADS +#include +#endif + +#ifdef OQS_ENABLE_KEM_ML_KEM +#define MLKEM_SECRET_LEN 32 +#endif + +#include "system_info.c" +#include "test_helpers.h" + +#include // Include MemSan for CT testing + +#ifdef OQS_ENABLE_KEM_ML_KEM +/* mlkem rejection key testcase */ +static bool mlkem_rej_testcase(OQS_KEM *kem, uint8_t *ciphertext, uint8_t *secret_key) { + // sanity checks + if ((kem == NULL) || (ciphertext == NULL) || (secret_key == NULL)) { + fprintf(stderr, "ERROR: inputs NULL!\n"); + return false; + } + // Only run tests for ML-KEM + if (!(strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_512) == 0 || + strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_768) == 0 || + strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_1024) == 0)) { + return true; + } + // Buffer to hold z and c. z is always 32 bytes + uint8_t *buff_z_c = NULL; + bool retval = false; + OQS_STATUS rc; + int rv; + size_t length_z_c = 32 + kem->length_ciphertext; + buff_z_c = OQS_MEM_malloc(length_z_c); + if (buff_z_c == NULL) { + fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); + return false; + } + // Scenario 1: Test rejection key by corrupting the secret key + secret_key[0] += 1; + uint8_t shared_secret_r[MLKEM_SECRET_LEN]; // expected output + uint8_t shared_secret_d[MLKEM_SECRET_LEN]; // calculated output + memcpy(buff_z_c, &secret_key[kem->length_secret_key - 32], 32); + memcpy(&buff_z_c[MLKEM_SECRET_LEN], ciphertext, kem->length_ciphertext); + // Calculate expected secret in case of corrupted cipher: shake256(z || c) + OQS_SHA3_shake256(shared_secret_r, MLKEM_SECRET_LEN, buff_z_c, length_z_c); + rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_decaps failed for rejection testcase scenario 1\n"); + goto cleanup; + } + OQS_TEST_CT_DECLASSIFY(shared_secret_d, MLKEM_SECRET_LEN); + OQS_TEST_CT_DECLASSIFY(shared_secret_r, MLKEM_SECRET_LEN); + rv = memcmp(shared_secret_d, shared_secret_r, MLKEM_SECRET_LEN); + if (rv != 0) { + fprintf(stderr, "ERROR: shared secrets are not equal for rejection key in decapsulation scenario 1\n"); + OQS_print_hex_string("shared_secret_d", shared_secret_d, MLKEM_SECRET_LEN); + OQS_print_hex_string("shared_secret_r", shared_secret_r, MLKEM_SECRET_LEN); + goto cleanup; + } + secret_key[0] -= 1; // Restore private key + OQS_MEM_cleanse(buff_z_c, length_z_c); // Reset buffer + + // Scenario 2: Test rejection key by corrupting the ciphertext + ciphertext[0] += 1; + memcpy(buff_z_c, &secret_key[kem->length_secret_key - 32], 32); + memcpy(&buff_z_c[MLKEM_SECRET_LEN], ciphertext, kem->length_ciphertext); + + // Calculate expected secret in case of corrupted cipher: shake256(z || c) + OQS_SHA3_shake256(shared_secret_r, MLKEM_SECRET_LEN, buff_z_c, length_z_c); + rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_decaps failed for rejection testcase scenario 2\n"); + goto cleanup; + } + OQS_TEST_CT_DECLASSIFY(shared_secret_d, MLKEM_SECRET_LEN); + OQS_TEST_CT_DECLASSIFY(shared_secret_r, MLKEM_SECRET_LEN); + rv = memcmp(shared_secret_d, shared_secret_r, MLKEM_SECRET_LEN); + if (rv != 0) { + fprintf(stderr, "ERROR: shared secrets are not equal for rejection key in decapsulation scenario 2\n"); + OQS_print_hex_string("shared_secret_d", shared_secret_d, MLKEM_SECRET_LEN); + OQS_print_hex_string("shared_secret_r", shared_secret_r, MLKEM_SECRET_LEN); + goto cleanup; + } + ciphertext[0] -= 1; // Restore ciphertext + retval = true; +cleanup: + if (buff_z_c) { + OQS_MEM_secure_free(buff_z_c, length_z_c); + } + return retval; +} +#endif //OQS_ENABLE_KEM_ML_KEM + +typedef struct magic_s { + uint8_t val[31]; +} magic_t; + +static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { + + OQS_KEM *kem = NULL; + uint8_t *public_key = NULL; + uint8_t *secret_key = NULL; + uint8_t *ciphertext = NULL; + uint8_t *shared_secret_e = NULL; + uint8_t *shared_secret_d = NULL; + uint8_t *keypair_seed = NULL; + uint8_t *encaps_seed = NULL; + OQS_STATUS rc, ret = OQS_ERROR; + int rv; + + //The magic numbers are random values. + //The length of the magic number was chosen to be 31 to break alignment + magic_t magic; + OQS_randombytes(magic.val, sizeof(magic_t)); + + kem = OQS_KEM_new(method_name); + if (kem == NULL) { + fprintf(stderr, "ERROR: OQS_KEM_new failed\n"); + goto err; + } + + if (!derand) { + printf("================================================================================\n"); + printf("sample computation for KEM %s\n", kem->method_name); + printf("Version source: %s\n", kem->alg_version); + printf("================================================================================\n"); + } + + /* pk, sk, ciphertext, shared secret, and shared secret cmp are needed + * regardless of derand or not + */ + public_key = OQS_MEM_malloc(kem->length_public_key + 2 * sizeof(magic_t)); + secret_key = OQS_MEM_malloc(kem->length_secret_key + 2 * sizeof(magic_t)); + ciphertext = OQS_MEM_malloc(kem->length_ciphertext + 2 * sizeof(magic_t)); + shared_secret_e = OQS_MEM_malloc(kem->length_shared_secret + 2 * sizeof(magic_t)); + shared_secret_d = OQS_MEM_malloc(kem->length_shared_secret + 2 * sizeof(magic_t)); + if ((public_key == NULL) || (secret_key == NULL) || (ciphertext == NULL) || (shared_secret_e == NULL) || (shared_secret_d == NULL)) { + fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); + goto err; + } + memcpy(public_key, magic.val, sizeof(magic_t)); + memcpy(secret_key, magic.val, sizeof(magic_t)); + memcpy(ciphertext, magic.val, sizeof(magic_t)); + memcpy(shared_secret_e, magic.val, sizeof(magic_t)); + memcpy(shared_secret_d, magic.val, sizeof(magic_t)); + public_key += sizeof(magic_t); + secret_key += sizeof(magic_t); + ciphertext += sizeof(magic_t); + shared_secret_e += sizeof(magic_t); + shared_secret_d += sizeof(magic_t); + memcpy(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); + memcpy(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); + memcpy(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); + memcpy(shared_secret_e + kem->length_shared_secret, magic.val, sizeof(magic_t)); + memcpy(shared_secret_d + kem->length_shared_secret, magic.val, sizeof(magic_t)); + + if (derand) { + /* keypair_seed and encaps_seed are only needed if derand */ + keypair_seed = malloc(kem->length_keypair_seed + 2 * sizeof(magic_t)); + if (!keypair_seed) { + fprintf(stderr, "Failed to allocate %zu bytes for keypair seed\n", + kem->length_keypair_seed); + goto err; + } + encaps_seed = malloc(kem->length_encaps_seed + 2 * sizeof(magic_t)); + if (!encaps_seed) { + fprintf(stderr, "Failed to allocate %zu bytes for encaps seed\n", + kem->length_encaps_seed); + goto err; + } + memcpy(keypair_seed, magic.val, sizeof(magic_t)); + memcpy(encaps_seed, magic.val, sizeof(magic_t)); + keypair_seed += sizeof(magic_t); + encaps_seed += sizeof(magic_t); + memcpy(keypair_seed + kem->length_keypair_seed, magic.val, + sizeof(magic_t)); + memcpy(encaps_seed + kem->length_encaps_seed, magic.val, + sizeof(magic_t)); + } + + + + if (derand) { + // On some systems, getentropy fails if given a zero-length array + if (kem->length_keypair_seed > 0) { + OQS_randombytes(keypair_seed, kem->length_keypair_seed); + } + rc = OQS_KEM_keypair_derand(kem, public_key, secret_key, keypair_seed); + __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (kem->length_keypair_seed == 0) { + // If length_keypair_seed is set to 0 for this KEM scheme, a failure is expected + if (rc != OQS_ERROR) { + fprintf(stderr, "ERROR: OQS_KEM_keypair_derand succeeded but expected a failure\n"); + goto err; + } + printf("OQS_KEM_keypair_derand failed, as expected\n"); + ret = OQS_SUCCESS; + goto cleanup; + } else { + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_keypair_derand failed\n"); + goto err; + } + } + } else { + rc = OQS_KEM_keypair(kem, public_key, secret_key); + __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_keypair failed\n"); + goto err; + } + } + + OQS_TEST_CT_DECLASSIFY(public_key, kem->length_public_key); + if (derand) { + // On some systems, getentropy fails if given a zero-length array + if (kem->length_encaps_seed > 0) { + OQS_randombytes(encaps_seed, kem->length_encaps_seed); + } + rc = OQS_KEM_encaps_derand(kem, ciphertext, shared_secret_e, public_key, encaps_seed); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (kem->length_encaps_seed == 0) { + // If length_encaps_seed is set to 0 for this KEM scheme, a failure is expected + if (rc != OQS_ERROR) { + fprintf(stderr, "ERROR: OQS_KEM_encaps_derand succeeded but expected a failure\n"); + goto err; + } + printf("OQS_KEM_encaps_derand failed, as expected\n"); + ret = OQS_SUCCESS; + goto cleanup; + } else { + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_encaps_derand failed\n"); + goto err; + } + } + } else { + rc = OQS_KEM_encaps(kem, ciphertext, shared_secret_e, public_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_encaps failed\n"); + goto err; + } + } + + OQS_TEST_CT_DECLASSIFY(ciphertext, kem->length_ciphertext); + rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_KEM_decaps failed\n"); + goto err; + } + + OQS_TEST_CT_DECLASSIFY(shared_secret_d, kem->length_shared_secret); + OQS_TEST_CT_DECLASSIFY(shared_secret_e, kem->length_shared_secret); + rv = memcmp(shared_secret_e, shared_secret_d, kem->length_shared_secret); + if (rv != 0) { + fprintf(stderr, "ERROR: shared secrets are not equal\n"); + OQS_print_hex_string("shared_secret_e", shared_secret_e, kem->length_shared_secret); + OQS_print_hex_string("shared_secret_d", shared_secret_d, kem->length_shared_secret); + goto err; + } else { + printf("shared secrets are equal\n"); + } + +#ifdef OQS_ENABLE_KEM_ML_KEM + /* check mlkem rejection testcases. returns true for all other kem algos */ + if (false == mlkem_rej_testcase(kem, ciphertext, secret_key)) { + goto err; + } +#endif + + // test invalid encapsulation (call should either fail or result in invalid shared secret) + OQS_randombytes(ciphertext, kem->length_ciphertext); + OQS_TEST_CT_DECLASSIFY(ciphertext, kem->length_ciphertext); + rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); + OQS_TEST_CT_DECLASSIFY(shared_secret_d, kem->length_shared_secret); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc == OQS_SUCCESS && memcmp(shared_secret_e, shared_secret_d, kem->length_shared_secret) == 0) { + fprintf(stderr, "ERROR: OQS_KEM_decaps succeeded on wrong input\n"); + goto err; + } + +#ifndef OQS_ENABLE_TEST_CONSTANT_TIME + rv = memcmp(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); + rv |= memcmp(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); + rv |= memcmp(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); + rv |= memcmp(shared_secret_e + kem->length_shared_secret, magic.val, sizeof(magic_t)); + rv |= memcmp(shared_secret_d + kem->length_shared_secret, magic.val, sizeof(magic_t)); + rv |= memcmp(public_key - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(secret_key - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(ciphertext - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(shared_secret_e - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(shared_secret_d - sizeof(magic_t), magic.val, sizeof(magic_t)); + if (derand) { + rv |= memcmp(keypair_seed + kem->length_keypair_seed, magic.val, sizeof(magic_t)); + rv |= memcmp(encaps_seed + kem->length_encaps_seed, magic.val, sizeof(magic_t)); + rv |= memcmp(keypair_seed - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(encaps_seed - sizeof(magic_t), magic.val, sizeof(magic_t)); + } + if (rv != 0) { + fprintf(stderr, "ERROR: Magic numbers do not match\n"); + goto err; + } +#endif + + ret = OQS_SUCCESS; + goto cleanup; + +err: + ret = OQS_ERROR; + +cleanup: + if ((secret_key) && (kem != NULL)) { + OQS_MEM_secure_free(secret_key - sizeof(magic_t), kem->length_secret_key + 2 * sizeof(magic_t)); + } + if ((shared_secret_e) && (kem != NULL)) { + OQS_MEM_secure_free(shared_secret_e - sizeof(magic_t), kem->length_shared_secret + 2 * sizeof(magic_t)); + } + if ((shared_secret_d) && (kem != NULL)) { + OQS_MEM_secure_free(shared_secret_d - sizeof(magic_t), kem->length_shared_secret + 2 * sizeof(magic_t)); + } + if (public_key) { + OQS_MEM_insecure_free(public_key - sizeof(magic_t)); + } + if (ciphertext) { + OQS_MEM_insecure_free(ciphertext - sizeof(magic_t)); + } + if ((keypair_seed) && (kem != NULL)) { + OQS_MEM_secure_free(keypair_seed - sizeof(magic_t), kem->length_keypair_seed + 2 * sizeof(magic_t)); + } + if ((encaps_seed) && (kem != NULL)) { + OQS_MEM_secure_free(encaps_seed - sizeof(magic_t), kem->length_encaps_seed + 2 * sizeof(magic_t)); + } + OQS_KEM_free(kem); + + return ret; +} + +#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +static void TEST_KEM_randombytes(uint8_t *random_array, size_t bytes_to_read) { + // We can't make direct calls to the system randombytes on some platforms, + // so we have to swap out the OQS_randombytes provider. + (void)OQS_randombytes_switch_algorithm("system"); + OQS_randombytes(random_array, bytes_to_read); + OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); + + // OQS_TEST_CT_CLASSIFY tells Valgrind's memcheck tool to issue a warning if + // the program branches on any byte that depends on random_array. This helps us + // identify timing side-channels, as these bytes often contain secret data. + OQS_TEST_CT_CLASSIFY(random_array, bytes_to_read); +} +#endif + +#if OQS_USE_PTHREADS +struct thread_data { + char *alg_name; + OQS_STATUS rc; +}; + +void *test_wrapper(void *arg) { + struct thread_data *td = arg; + td->rc = kem_test_correctness(td->alg_name, false); + if (td->rc == OQS_SUCCESS) { + // test derandomized operations + td->rc = kem_test_correctness(td->alg_name, true); + } + OQS_thread_stop(); + return NULL; +} +#endif + +int main(int argc, char **argv) { + OQS_STATUS rc; + OQS_init(); + + printf("Testing KEM algorithms using liboqs version %s\n", OQS_version()); + + if (argc != 2) { + fprintf(stderr, "Usage: test_kem algname\n"); + fprintf(stderr, " algname: "); + for (size_t i = 0; i < OQS_KEM_algs_length; i++) { + if (i > 0) { + fprintf(stderr, ", "); + } + fprintf(stderr, "%s", OQS_KEM_alg_identifier(i)); + } + fprintf(stderr, "\n"); + OQS_destroy(); + return EXIT_FAILURE; + } + + print_system_info(); + + char *alg_name = argv[1]; + if (!OQS_KEM_alg_is_enabled(alg_name)) { + printf("KEM algorithm %s not enabled!\n", alg_name); + OQS_destroy(); + return EXIT_FAILURE; + } + +#ifdef OQS_ENABLE_TEST_CONSTANT_TIME + OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); +#else + rc = OQS_randombytes_switch_algorithm("system"); + if (rc != OQS_SUCCESS) { + printf("Could not generate random data with system RNG\n"); + OQS_destroy(); + return EXIT_FAILURE; + } +#endif + +#if OQS_USE_PTHREADS +#define MAX_LEN_KEM_NAME_ 64 + // don't run Classic McEliece in threads because of large stack usage + char no_thread_kem_patterns[][MAX_LEN_KEM_NAME_] = {"Classic-McEliece", "HQC-256-"}; + int test_in_thread = 1; + for (size_t i = 0 ; i < sizeof(no_thread_kem_patterns) / MAX_LEN_KEM_NAME_; ++i) { + if (strstr(alg_name, no_thread_kem_patterns[i]) != NULL) { + test_in_thread = 0; + break; + } + } + if (test_in_thread) { + pthread_t thread; + struct thread_data td; + td.alg_name = alg_name; + int trc = pthread_create(&thread, NULL, test_wrapper, &td); + if (trc) { + fprintf(stderr, "ERROR: Creating pthread\n"); + OQS_destroy(); + return EXIT_FAILURE; + } + pthread_join(thread, NULL); + rc = td.rc; + } else { + rc = kem_test_correctness(alg_name, false); + if (rc == OQS_SUCCESS) { + // test with derandomized keygen + rc = kem_test_correctness(alg_name, true); + } + } +#else + rc = kem_test_correctness(alg_name, false); + if (rc == OQS_SUCCESS) { + // test with derandomized keygen + rc = kem_test_correctness(alg_name, true); + } +#endif + if (rc != OQS_SUCCESS) { + OQS_destroy(); + return EXIT_FAILURE; + } + OQS_destroy(); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/test_sig.c b/tests/ct_tooling/tools/memsan/test_sig.c new file mode 100644 index 0000000000..253babfcb6 --- /dev/null +++ b/tests/ct_tooling/tools/memsan/test_sig.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: MIT + +#if defined(_WIN32) +#pragma warning(disable : 4244 4293) +#endif + +#include +#include +#include + +#include + +#if OQS_USE_PTHREADS +#include +#endif + +#include "system_info.c" +#include "test_helpers.h" + +#include // Include MemSan for CT testing + +typedef struct magic_s { + uint8_t val[31]; +} magic_t; + +static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_all[2], size_t bitflips[2], bool extended_tests) { + + OQS_SIG *sig = NULL; + uint8_t *public_key = NULL; + uint8_t *secret_key = NULL; + uint8_t *message = NULL; + size_t message_len = 100; + uint8_t ctx[257] = { 0 }; + uint8_t *signature = NULL; + size_t signature_len; + OQS_STATUS rc, ret = OQS_ERROR; + + //The magic numbers are random values. + //The length of the magic number was chosen to be 31 to break alignment + magic_t magic; + OQS_randombytes(magic.val, sizeof(magic_t)); + + printf(extended_tests ? "Extended tests enabled\n" : "Extended tests disabled\n"); + + + sig = OQS_SIG_new(method_name); + if (sig == NULL) { + fprintf(stderr, "ERROR: OQS_SIG_new failed\n"); + goto err; + } + + char bitflips_as_str[2][50]; + for (int i = 0; i < 2; i++) { + if (bitflips_all[i]) { + snprintf(bitflips_as_str[i], sizeof(bitflips_as_str[i]), "all"); + } else { + snprintf(bitflips_as_str[i], sizeof(bitflips_as_str[i]), "%zu random", bitflips[i]); + } + } + + printf("================================================================================\n"); + printf("Sample computation for signature %s\n", sig->method_name); + if (sig->euf_cma) { + printf("Testing EUF-CMA by flipping %s bits of the message\n", bitflips_as_str[0]); + } + if (sig->suf_cma) { + printf("Testing SUF-CMA by flipping %s bits of the signature\n", bitflips_as_str[1]); + } + printf("Version source: %s\n", sig->alg_version); + printf("================================================================================\n"); + + public_key = OQS_MEM_malloc(sig->length_public_key + 2 * sizeof(magic_t)); + secret_key = OQS_MEM_malloc(sig->length_secret_key + 2 * sizeof(magic_t)); + message = OQS_MEM_malloc(message_len + 2 * sizeof(magic_t)); + signature = OQS_MEM_malloc(sig->length_signature + 2 * sizeof(magic_t)); + + if ((public_key == NULL) || (secret_key == NULL) || (message == NULL) || (signature == NULL)) { + fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); + goto err; + } + + //Set the magic numbers before + memcpy(public_key, magic.val, sizeof(magic_t)); + memcpy(secret_key, magic.val, sizeof(magic_t)); + memcpy(message, magic.val, sizeof(magic_t)); + memcpy(signature, magic.val, sizeof(magic_t)); + + public_key += sizeof(magic_t); + secret_key += sizeof(magic_t); + message += sizeof(magic_t); + signature += sizeof(magic_t); + + // and after + memcpy(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); + memcpy(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); + memcpy(message + message_len, magic.val, sizeof(magic_t)); + memcpy(signature + sig->length_signature, magic.val, sizeof(magic_t)); + + OQS_randombytes(message, message_len); + OQS_TEST_CT_DECLASSIFY(message, message_len); + + rc = OQS_SIG_keypair(sig, public_key, secret_key); + __msan_unpoison(public_key, sig->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_keypair failed\n"); + goto err; + } + + rc = OQS_SIG_sign(sig, signature, &signature_len, message, message_len, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_sign failed\n"); + goto err; + } + + /* Commenting so that MemSan constant-time testing does not run on signature verification + + OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); + OQS_TEST_CT_DECLASSIFY(signature, signature_len); + rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); + goto err; + } + + if (extended_tests) { + rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, false, NULL, 0); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + goto err; + } + } */ + + /* testing signing with context, if supported */ + OQS_randombytes(ctx, 257); + if (sig->sig_with_ctx_support) { + size_t ctx_step = 1; + // Only do a small fraction of the context sizes for SLH_DSA for efficiency purposes + if (!strncmp(sig->method_name, "SLH_DSA", 7)) { + ctx_step = 61; // using a prime slightly smaller than a power of 2 to avoid only testing word/block aligned values + } + for (size_t i = 0; i < 256; ++i) { + if (((i % ctx_step == 0) && extended_tests) || i == 255) { + rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, i, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str failed\n"); + goto err; + } + + /* Commenting so that MemSan constant-time testing does not run on signature verification + + OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); + OQS_TEST_CT_DECLASSIFY(signature, signature_len); + rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); + goto err; + } + + if (extended_tests) { + rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + goto err; + } + } */ + } + } + + if (extended_tests) { + rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, 256, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_ERROR) { + fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should only support up to 255 byte contexts\n"); + goto err; + } + } + } else if (extended_tests) { + rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, 1, secret_key); + if (rc != OQS_ERROR) { + fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should fail without support for context strings\n"); + goto err; + } + } + + if (extended_tests) { + rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, NULL, 0, secret_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should always succeed when providing a NULL context string\n"); + goto err; + } + + /* Commenting so that MemSan constant-time testing does not run on signature verification + + OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); + OQS_TEST_CT_DECLASSIFY(signature, signature_len); + rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, NULL, 0, public_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); + goto err; + } + + rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, NULL, 0); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); + if (rc != OQS_SUCCESS) { + goto err; + } */ + } + +#ifndef OQS_ENABLE_TEST_CONSTANT_TIME + /* check magic values */ + int rv = memcmp(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); + rv |= memcmp(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); + rv |= memcmp(message + message_len, magic.val, sizeof(magic_t)); + rv |= memcmp(signature + sig->length_signature, magic.val, sizeof(magic_t)); + rv |= memcmp(public_key - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(secret_key - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(message - sizeof(magic_t), magic.val, sizeof(magic_t)); + rv |= memcmp(signature - sizeof(magic_t), magic.val, sizeof(magic_t)); + if (rv) { + fprintf(stderr, "ERROR: Magic numbers do not match\n"); + goto err; + } +#endif + + printf("verification passes as expected\n"); + ret = OQS_SUCCESS; + goto cleanup; + +err: + ret = OQS_ERROR; + +cleanup: + if ((secret_key) && (sig != NULL)) { + OQS_MEM_secure_free(secret_key - sizeof(magic_t), sig->length_secret_key + 2 * sizeof(magic_t)); + } + if (public_key) { + OQS_MEM_insecure_free(public_key - sizeof(magic_t)); + } + if (message) { + OQS_MEM_insecure_free(message - sizeof(magic_t)); + } + if (signature) { + OQS_MEM_insecure_free(signature - sizeof(magic_t)); + } + OQS_SIG_free(sig); + + return ret; +} + +#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { + // We can't make direct calls to the system randombytes on some platforms, + // so we have to swap out the OQS_randombytes provider. + (void)OQS_randombytes_switch_algorithm("system"); + OQS_randombytes(random_array, bytes_to_read); + OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); + + // OQS_TEST_CT_CLASSIFY tells Valgrind's memcheck tool to issue a warning if + // the program branches on any byte that depends on random_array. This helps us + // identify timing side-channels, as these bytes often contain secret data. + OQS_TEST_CT_CLASSIFY(random_array, bytes_to_read); +} +#endif + +#if OQS_USE_PTHREADS +struct thread_data { + char *alg_name; + bool *bitflips_all; + size_t *bitflips; + bool extended_tests; + OQS_STATUS rc; +}; + +void *test_wrapper(void *arg) { + struct thread_data *td = arg; + td->rc = sig_test_correctness(td->alg_name, td->bitflips_all, td->bitflips, td->extended_tests); + OQS_thread_stop(); + return NULL; +} +#endif + +int main(int argc, char **argv) { + OQS_STATUS rc; + OQS_init(); +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(USE_SANITIZER) + long int extended_tests = 0; +#else + long int extended_tests = 1; +#endif + printf("Testing signature algorithms using liboqs version %s\n", OQS_version()); + + if (argc < 2 || argc > 5) { + fprintf(stderr, "Usage: test_sig algname [bitflips_msg] [bitflips_sig] [extended_tests]\n"); + fprintf(stderr, " algname: "); + for (size_t i = 0; i < OQS_SIG_algs_length; i++) { + if (i > 0) { + fprintf(stderr, ", "); + } + fprintf(stderr, "%s", OQS_SIG_alg_identifier(i)); + } + fprintf(stderr, "\n"); + fprintf(stderr, " extended_tests: run extended correctness tests (with bitflips, full context-string tests)\n"); + fprintf(stderr, " bitflips_msg: the number of random bitflips to perform for each EUF-CMA signature (\"all\" to flip every bit)\n"); + fprintf(stderr, " bitflips_sig: the number of random bitflips to perform for each SUF-CMA signature (\"all\" to flip every bit)\n"); + OQS_destroy(); + return EXIT_FAILURE; + } + + print_system_info(); + + char *alg_name = argv[1]; + if (!OQS_SIG_alg_is_enabled(alg_name)) { + printf("Signature algorithm %s not enabled!\n", alg_name); + OQS_destroy(); + return EXIT_FAILURE; + } + + /* by default, flip 50 random bits of the message and signature (to test EUF-CMA and SUF-CMA, respectively) */ + bool bitflips_all[2] = {false, false}; + size_t bitflips[2] = {50, 50}; + if (argc >= 3) { + if (strcmp(argv[2], "all") == 0) { + bitflips_all[0] = true; + } else { + bitflips[0] = (size_t)strtol(argv[2], NULL, 10); + } + } + if (argc >= 4) { + if (strcmp(argv[3], "all") == 0) { + bitflips_all[1] = true; + } else { + bitflips[1] = (size_t)strtol(argv[3], NULL, 10); + } + } + if (argc == 5) { + extended_tests = strtol(argv[4], NULL, 10); + if (extended_tests != 0 && extended_tests != 1) { + fprintf(stderr, "ERROR: invalid value for extended_tests (must be 0 or 1)\n"); + OQS_destroy(); + return EXIT_FAILURE; + } + } + +#ifdef OQS_ENABLE_TEST_CONSTANT_TIME + OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); +#else + rc = OQS_randombytes_switch_algorithm("system"); + if (rc != OQS_SUCCESS) { + printf("Could not generate random data with system RNG\n"); + OQS_destroy(); + return EXIT_FAILURE; + } +#endif + +#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) +#define MAX_LEN_SIG_NAME_ 64 + // don't run algorithms with large stack usage in threads + char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5"}; + int test_in_thread = 1; + for (size_t i = 0 ; i < sizeof(no_thread_sig_patterns) / MAX_LEN_SIG_NAME_; ++i) { + if ( (strncmp(alg_name, "SLH_DSA", 7) == 0) || (strstr(alg_name, no_thread_sig_patterns[i]) != NULL) ) { + test_in_thread = 0; + break; + } + } + if (test_in_thread) { + pthread_t thread; + struct thread_data td = {.alg_name = alg_name, .bitflips_all = bitflips_all, .bitflips = bitflips, .rc = OQS_ERROR, .extended_tests = (bool)extended_tests}; + int trc = pthread_create(&thread, NULL, test_wrapper, &td); + if (trc) { + fprintf(stderr, "ERROR: Creating pthread\n"); + OQS_destroy(); + return EXIT_FAILURE; + } + pthread_join(thread, NULL); + rc = td.rc; + } else { + rc = sig_test_correctness(alg_name, bitflips_all, bitflips, (bool)extended_tests); + } +#else + rc = sig_test_correctness(alg_name, bitflips_all, bitflips, (bool)extended_tests); +#endif + if (rc != OQS_SUCCESS) { + OQS_destroy(); + return EXIT_FAILURE; + } + OQS_destroy(); + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/tests/ct_tooling/tools/valgrind_varlat/README.md b/tests/ct_tooling/tools/valgrind_varlat/README.md new file mode 100644 index 0000000000..3a0d88d873 --- /dev/null +++ b/tests/ct_tooling/tools/valgrind_varlat/README.md @@ -0,0 +1,99 @@ +# Valgrind-Varlat + +This directory contains the necessary files to execute Valgrind's memcheck tool on liboqs with [Daniel Bernstein's Kyberslash patches](https://kyberslash.cr.yp.to/papers.html) (valgrind-try-patch-20250805.txt and valgrind-varlat-patch-20250805.txt) and another patch including variable latency warnings in the suppression block (valgrind_varlat_sup_block.txt). + +Valgrind-Varlat handles false-positive warnings by storing their suppression block into `.txt` files within the `false_positives/` directory. These files are passed during the tools execution, successfully disregarding those warnings that are cathegorized as not constant-time issues after review. + +## Valgrind-Varlat Install Requirements +In order to successfully execute Valgrind-Varlat's test using the tooling developed in this subrepository follow the next steps: + +- Install valgrind using the official git repository. Go to [Daniel Bernstein's suggested](https://sourceforge.net/p/valgrind/mailman/message/59216875/) [commit](https://sourceware.org/git/?p=valgrind.git;a=commit;h=112f1080b7c21e37dfce0a2e589d0dc7aa115afa) to cleanly apply Kyberslash patches to Valgrind without encountering dependency issues: 112f1080b7c21e37dfce0a2e589d0dc7aa115afa. + +``` +VALGRIND_REPO="https://sourceware.org/git/valgrind.git" +TRY_PATCH= +VARLAT_PATCH= +SUP_BLOCK_PATCH= +INSTALL_DIR="$HOME/valgrind_varlat" + +# Clone the Valgrind repository +git clone "$VALGRIND_REPO" valgrind_varlat +git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa +cd valgrind_varlat +``` + +- Apply Bernstein's patches. + +``` +git apply $TRY_PATCH +git apply $VARLAT_PATCH +``` + +- Apply the suppression block patch. + +``` +git apply $SUP_BLOCK_PATCH +``` + +- Include the resultant version of valgrind into PATH under . + +``` +# Build and install valgrind_varlat +./autogen.sh +./configure --prefix="$INSTALL_DIR" +make -j$(nproc) +sudo make install + +# Rename the executable +sudo mv "$INSTALL_DIR/bin/valgrind" "$INSTALL_DIR/bin/valgrind_varlat" + +# Add valgrind_varlat to PATH +echo "export PATH=\"$INSTALL_DIR/bin:\$PATH\"" >> ~/.bashrc +source ~/.bashrc +``` + +To check whether the installation was successful, you can use the varlat tests provided in the Kyberslash patch. Compile valgrind/memcheck/tests/varlat.c with `gcc -o varlat varlat.c` and execute `valgrind_varlat --tool=memcheck --variable-latency-errors=yes --gen-suppressions=all ./varlat`. If the output ressembles something like the following output, valgrind_varlat was installed successfully: + +``` +==5335== Memcheck, a memory error detector +==5335== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. +==5335== Using Valgrind-3.26.0.GIT and LibVEX; rerun with -h for copyright info +==5335== Command: ./varlat +==5335== +==5335== Variable-latency instruction operand of size 4 is secret/uninitialised +==5335== at 0x4001176: storage_init (in /home/.../valgrind_varlat/memcheck/tests/varlat) +==5335== by 0x40011BB: main (in /home/.../valgrind_varlat/memcheck/tests/varlat) +==5335== +{ + + Memcheck:Value4 + variable-latency: yes + fun:storage_init + fun:main +} +==5335== +==5335== HEAP SUMMARY: +==5335== in use at exit: 0 bytes in 0 blocks +==5335== total heap usage: 1 allocs, 1 frees, 1 bytes allocated +==5335== +==5335== All heap blocks were freed -- no leaks are possible +==5335== +==5335== Use --track-origins=yes to see where uninitialised values come from +==5335== For lists of detected and suppressed errors, rerun with: -s +==5335== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) + +``` + +## Algorithm Testing + +Valgrind-Varlat produces numerous warnings, making complete storage impractical in terms of memory and runtime. The test script therefore processes output as follows: +- It captures each unique suppression block (the {...} content with full warning details) and logs it to a file. +- Only these unique blocks are counted as warnings, avoiding duplication from repeated occurrences during execution. + +The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. + +## Dependencies +Remember to install the prerequisites for building liboqs: + +`sudo apt install -y build-essential cmake ninja-build gcc g++ clang python3 python3-pip valgrind libssl-dev realpath python3 python3-pip` +`pip3 install pytest` diff --git a/tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt b/tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt new file mode 100644 index 0000000000..11007e5702 --- /dev/null +++ b/tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt @@ -0,0 +1,83 @@ +{ + + Memcheck:Cond + ... + fun:mlk_rej_uniform + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Value8 + ... + fun:mlk_rej_uniform + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Cond + ... + fun:PQCP_MLKEM_NATIVE_MLKEM*_dec + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Cond + ... + fun:PQCP_MLKEM_NATIVE_MLKEM*_X86_64_poly_rej_uniform* + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Value8 + ... + fun:PQCP_MLKEM_NATIVE_MLKEM*_X86_64_poly_rej_uniform* + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Cond + ... + fun:mlk_check_sk + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} + +{ + + Memcheck:Value8 + ... + fun:_mm_set_epi64x + ... + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone +} \ No newline at end of file diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt new file mode 100644 index 0000000000..395d41c06b --- /dev/null +++ b/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt @@ -0,0 +1,1578 @@ +diff --git a/coregrind/m_commandline.c b/coregrind/m_commandline.c +index e9fdb9bb4..4e120db0d 100644 +--- a/coregrind/m_commandline.c ++++ b/coregrind/m_commandline.c +@@ -41,7 +41,7 @@ + + /* Add a string to an expandable array of strings. */ + +-static void add_string ( XArray* /* of HChar* */xa, HChar* str ) ++static void add_string ( XArray* /* of HChar* */xa, const HChar* str ) + { + (void) VG_(addToXA)( xa, (void*)(&str) ); + } +@@ -93,8 +93,9 @@ static HChar* read_dot_valgrindrc ( const HChar* dir ) + + // Add args from a string into VG_(args_for_valgrind), splitting the + // string at whitespace and adding each component as a separate arg. ++// If try is set: add --try before each arg. + +-static void add_args_from_string ( HChar* s ) ++static void add_args_from_string ( HChar* s, Bool try ) + { + HChar* tmp; + HChar* cp = s; +@@ -120,6 +121,7 @@ static void add_args_from_string ( HChar* s ) + } + if (out < cp) *out++ = '\0'; + if ( *cp != 0 ) *cp++ = '\0'; // terminate if not the last ++ if (try) add_string( VG_(args_for_valgrind), "--try" ); + add_string( VG_(args_for_valgrind), tmp ); + } + } +@@ -131,6 +133,7 @@ static void add_args_from_string ( HChar* s ) + The resulting arg list is the concatenation of the following: + - contents of ~/.valgrindrc + - contents of $VALGRIND_OPTS ++ - contents of $VALGRIND_TRY_OPTS with --try included + - contents of ./.valgrindrc + - args from the command line + in the stated order. +@@ -157,7 +160,7 @@ static void add_args_from_string ( HChar* s ) + args-for-v are then copied into tmp_xarray. + + if args-for-v does not include --command-line-only=yes: +- contents of ~/.valgrindrc, $VALGRIND_OPTS and ./.valgrindrc ++ contents of ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS and ./.valgrindrc + are copied into VG_(args_for_valgrind). + else + VG_(args_for_valgrind) is made empty. +@@ -223,7 +226,7 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) + add_string( VG_(args_for_client), argv[i] ); + } + +- /* Get extra args from ~/.valgrindrc, $VALGRIND_OPTS and ++ /* Get extra args from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS and + ./.valgrindrc into VG_(args_for_valgrind). */ + if (augment) { + // read_dot_valgrindrc() allocates the return value with +@@ -233,6 +236,8 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) + HChar* f1_clo = home ? read_dot_valgrindrc( home ) : NULL; + HChar* env_clo = VG_(strdup)( "commandline.sua.4", + VG_(getenv)(VALGRIND_OPTS) ); ++ HChar* env_try_clo = VG_(strdup)( "commandline.sua.5", ++ VG_(getenv)(VALGRIND_TRY_OPTS) ); + HChar* f2_clo = NULL; + + // Don't read ./.valgrindrc if "." is the same as "$HOME", else its +@@ -244,9 +249,10 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) + ? NULL : read_dot_valgrindrc(".") ); + } + +- if (f1_clo) add_args_from_string( f1_clo ); +- if (env_clo) add_args_from_string( env_clo ); +- if (f2_clo) add_args_from_string( f2_clo ); ++ if (f1_clo) add_args_from_string( f1_clo, False ); ++ if (env_clo) add_args_from_string( env_clo, False ); ++ if (env_try_clo) add_args_from_string( env_try_clo, True ); ++ if (f2_clo) add_args_from_string( f2_clo, False ); + } + + /* .. and record how many extras we got. */ +diff --git a/coregrind/m_main.c b/coregrind/m_main.c +index f7fd20dba..7a35b7b6d 100644 +--- a/coregrind/m_main.c ++++ b/coregrind/m_main.c +@@ -289,7 +289,8 @@ static void usage_NORETURN ( int need_help ) + " --sym-offsets=yes|no show syms in form 'name+offset'? [no]\n" + " --progress-interval= report progress every \n" + " CPU seconds [0, meaning disabled]\n" +-" --command-line-only=no|yes only use command line options [no]\n\n" ++" --command-line-only=no|yes only use command line options [no]\n" ++" --try skip next option if option is unsupported\n\n" + " Vex options for all Valgrind tools:\n" + " --vex-iropt-verbosity=<0..9> [0]\n" + " --vex-iropt-level=<0..2> [2]\n" +@@ -333,7 +334,7 @@ static void usage_NORETURN ( int need_help ) + + const HChar usage3[] = + "\n" +-" Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc\n" ++" Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc\n" + "\n" + " %s is %s\n" + " Valgrind is Copyright (C) 2000-2024, and GNU GPL'd, by Julian Seward et al.\n" +@@ -429,6 +430,10 @@ struct process_option_state { + VgLogTo xml_to; // Where is XML output to be sent? + Int tmp_log_fd; + Int tmp_xml_fd; ++ ++ /* For cloP: True if immediately preceding option was --try. */ ++ /* For other modes: Irrelevant. */ ++ Bool try; + }; + + static void process_option (Clo_Mode mode, +@@ -438,6 +443,7 @@ static void process_option (Clo_Mode mode, + Int toolname_len = VG_(strlen)(VG_(clo_toolname)); + HChar* colon = arg; + UInt ix = 0; ++ Bool prevtry = pos->try; + + /* Constants for parsing PX control flags. */ + const HChar* pxStrings[5] +@@ -448,6 +454,7 @@ static void process_option (Clo_Mode mode, + VexRegUpdAllregsAtMemAccess, VexRegUpdAllregsAtEachInsn, 0/*inval*/ }; + + VG_(set_Clo_Mode) (mode); ++ pos->try = False; + + // Look for a colon in the option name. + while (*colon && *colon != ':' && *colon != '=') +@@ -921,14 +928,17 @@ static void process_option (Clo_Mode mode, + else if VG_XACT_CLO(arg, "--resync-filter=verbose", + VG_(clo_resync_filter), 2) {} + ++ else if VG_XACT_CLO(arg, "--try", pos->try, True) {} + else if ( VG_(Clo_Mode)() != cloE // tool does not have Early options + && !VG_(Clo_Recognised) () + && (! VG_(needs).command_line_options + || ! VG_TDICT_CALL(tool_process_cmd_line_option, arg) )) { + if (VG_(Clo_Mode)() == cloH) + ; +- else if (VG_(Clo_Mode)() == cloP && !VG_(Clo_Recognised) ()) +- VG_(fmsg_unknown_option)(arg); ++ else if (VG_(Clo_Mode)() == cloP && !VG_(Clo_Recognised) ()) { ++ if (!prevtry) ++ VG_(fmsg_unknown_option)(arg); ++ } + else if (VG_(Clo_Mode)() == cloD && !VG_(Clo_Recognised) ()) + VG_(umsg)("Ignoring dynamic change to unrecognised option %s\n", arg); + } +@@ -962,7 +972,7 @@ static void early_process_cmd_line_options ( /*OUT*/Int* need_help ) + UInt i; + HChar* str; + struct process_option_state pos +- = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1}; ++ = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1, False}; + + vg_assert( VG_(args_for_valgrind) ); + +@@ -1003,7 +1013,7 @@ void main_process_cmd_line_options( void ) + { + Int i; + struct process_option_state pos +- = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1}; ++ = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1, False}; + + /* Check for sane path in ./configure --prefix=... */ + if (VG_LIBDIR[0] != '/') +diff --git a/coregrind/pub_core_libcproc.h b/coregrind/pub_core_libcproc.h +index 488aaf0ca..5b580a474 100644 +--- a/coregrind/pub_core_libcproc.h ++++ b/coregrind/pub_core_libcproc.h +@@ -53,6 +53,9 @@ + is no quoting mechanism. */ + #define VALGRIND_OPTS "VALGRIND_OPTS" + ++/* Same as VALGRIND_OPTS but puts --try before each option. */ ++#define VALGRIND_TRY_OPTS "VALGRIND_TRY_OPTS" ++ + /* The full name of Valgrind's stage1 (launcher) executable. This is + set by stage1 and read by stage2, and is used for recursive + invocations of Valgrind on child processes. +diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml +index 9ab09b51b..7d07d93fc 100644 +--- a/docs/xml/manual-core.xml ++++ b/docs/xml/manual-core.xml +@@ -2839,7 +2839,7 @@ Valgrind itself, then you should use the options + + Setting Default Options + +-Note that Valgrind also reads options from three places: ++Note that Valgrind also reads options from four places: + + + +@@ -2851,6 +2851,13 @@ Valgrind itself, then you should use the options + $VALGRIND_OPTS + + ++ ++ Starting with Valgrind 3.26: The environment variable ++ $VALGRIND_TRY_OPTS ++ (see below for the difference from ++ $VALGRIND_OPTS) ++ ++ + + The file ./.valgrindrc + +@@ -2873,7 +2880,8 @@ your user account. + + + Any tool-specific options put in +-$VALGRIND_OPTS or the ++$VALGRIND_OPTS or ++$VALGRIND_TRY_OPTS or the + .valgrindrc files should be + prefixed with the tool name and a colon. For example, if you + want Memcheck to always do leak checking, you can put the +@@ -2888,6 +2896,51 @@ part, this will cause problems if you select other tools that + don't understand + . + ++Starting with Valgrind 3.26, ++$VALGRIND_TRY_OPTS ++is handled the same way as ++$VALGRIND_OPTS ++except that unsupported options in ++$VALGRIND_TRY_OPTS ++are skipped rather than stopping Valgrind. ++This makes ++$VALGRIND_TRY_OPTS ++suitable for best-effort ++"please use this option if it is available" scripts. ++ ++Starting with Valgrind 3.26, there is also a ++ ++option that, if it immediately precedes an unsupported option, ++will cause the unsupported option ++to be skipped rather than stopping Valgrind. ++The advantage of ++ ++over ++$VALGRIND_TRY_OPTS ++is that, in situations where the order of options is important, ++ ++can be inserted at any point in the option list, ++whereas ++$VALGRIND_TRY_OPTS ++is always handled after ++$VALGRIND_OPTS. ++The disadvantage of ++ ++is that it will stop Valgrind 3.25 and earlier. ++ ++If ++--command-line-only=yes ++appears on the command line ++then all of the above default-option mechanisms are ignored. ++Furthermore, the options ++--command-line-only, ++--profile-heap, ++--core-redzone-size, ++--redzone-size, ++and ++--aspace-minaddr ++are usable only on the command line ++and not via the default-option mechanisms. + + + +diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am +index 91d58b48b..633c036c3 100644 +--- a/memcheck/tests/Makefile.am ++++ b/memcheck/tests/Makefile.am +@@ -472,7 +472,8 @@ EXTRA_DIST = \ + wrapmallocstatic.vgtest wrapmallocstatic.stdout.exp \ + wrapmallocstatic.stderr.exp \ + writev1.stderr.exp writev1.stderr.exp-solaris writev1.vgtest \ +- xml1.stderr.exp xml1.stdout.exp xml1.vgtest xml1.stderr.exp-s390x-mvc ++ xml1.stderr.exp xml1.stdout.exp xml1.vgtest xml1.stderr.exp-s390x-mvc \ ++ xml1_env.stderr.exp xml1_env.stdout.exp xml1_env.vgtest xml1_env.stderr.exp-s390x-mvc + + check_PROGRAMS = \ + accounting \ +diff --git a/memcheck/tests/xml1_env.stderr.exp b/memcheck/tests/xml1_env.stderr.exp +new file mode 100644 +index 000000000..8ff61a531 +--- /dev/null ++++ b/memcheck/tests/xml1_env.stderr.exp +@@ -0,0 +1,440 @@ ++ ++ ++ ++ ++6 ++memcheck ++ ++ ++ ... ++ ... ++ ... ++ ... ++ ++ ++... ++... ++memcheck ++ ++ ++ ... ++ ++ ./xml1 ++ ++ ++ ++ ++ RUNNING ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidRead ++ Invalid read of size N ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is 0 bytes after a block of size 40 alloc'd ++ ++ ++ 0x........ ++ ... ++ malloc ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ UninitCondition ++ Conditional jump or move depends on uninitialised value(s) ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ UninitValue ++ Use of uninitialised value of size N ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidFree ++ Invalid free() / delete / delete[] / realloc() ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is 0 bytes inside a block of size 40 free'd ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidFree ++ Invalid free() / delete / delete[] / realloc() ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is on thread 1's stack ++ in frame #1, created by frame3 (xml1.c:7) ++ ++ ++ ++ 0x........ ++ ... ++ SyscallParam ++ Syscall param exit(status) contains uninitialised byte(s) ++ ++ ++ ++ FINISHED ++ ++ ++ ++... ++ ++ ++ 0x........ ++ ... ++ Leak_DefinitelyLost ++ ++ 396 bytes in 1 blocks are definitely lost in loss record ... of ... ++ 396 ++ 1 ++ ++ ++ ++ 0x........ ++ ... ++ malloc ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++... ++ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ++... ++ ++... ++ ++ +diff --git a/memcheck/tests/xml1_env.stderr.exp-s390x-mvc b/memcheck/tests/xml1_env.stderr.exp-s390x-mvc +new file mode 100644 +index 000000000..e1abac28a +--- /dev/null ++++ b/memcheck/tests/xml1_env.stderr.exp-s390x-mvc +@@ -0,0 +1,436 @@ ++ ++ ++ ++ ++4 ++memcheck ++ ++ ++ ... ++ ... ++ ... ++ ... ++ ++ ++... ++... ++memcheck ++ ++ ++ ... ++ ++ ./xml1 ++ ++ ++ ++ ++ RUNNING ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidRead ++ Invalid read of size 1 ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is 0 bytes after a block of size 40 alloc'd ++ ++ ++ 0x........ ++ ... ++ malloc ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ UninitCondition ++ Conditional jump or move depends on uninitialised value(s) ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ UninitValue ++ Use of uninitialised value of size N ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidFree ++ Invalid free() / delete / delete[] / realloc() ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is 0 bytes inside a block of size 40 free'd ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ 0x........ ++ ... ++ InvalidFree ++ Invalid free() / delete / delete[] / realloc() ++ ++ ++ 0x........ ++ ... ++ free ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ Address 0x........ is on thread 1's stack ++ in frame #1, created by frame3 (xml1.c:7) ++ ++ ++ ++ 0x........ ++ ... ++ SyscallParam ++ Syscall param exit(status) contains uninitialised byte(s) ++ ++ ++ ++ ++ FINISHED ++ ++ ++ ++ ++ 0x........ ++ ... ++ Leak_DefinitelyLost ++ ++ 396 bytes in 1 blocks are definitely lost in loss record ... of ... ++ 396 ++ 1 ++ ++ ++ ++ 0x........ ++ ... ++ malloc ++ ... ++ vg_replace_malloc.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame3 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame2 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ frame1 ++ ... ++ xml1.c ++ ... ++ ++ ++ 0x........ ++ ... ++ main ++ ... ++ xml1.c ++ ... ++ ++ ++ ++ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ... ++ 0x........ ++ ++ ++ ++... ++ ++ ++ +diff --git a/memcheck/tests/xml1_env.stdout.exp b/memcheck/tests/xml1_env.stdout.exp +new file mode 100644 +index 000000000..9397eece6 +--- /dev/null ++++ b/memcheck/tests/xml1_env.stdout.exp +@@ -0,0 +1 @@ ++hello from frame3(). The answer is not 42. +diff --git a/memcheck/tests/xml1_env.vgtest b/memcheck/tests/xml1_env.vgtest +new file mode 100644 +index 000000000..6b6cd8aab +--- /dev/null ++++ b/memcheck/tests/xml1_env.vgtest +@@ -0,0 +1,4 @@ ++prog: xml1 ++vgopts: --command-line-only=no ++env: VALGRIND_TRY_OPTS='--xml=yes --xml-fd=2 --log-file=/dev/null --keep-stacktraces=alloc-then-free' ++stderr_filter: filter_xml +diff --git a/none/tests/Makefile.am b/none/tests/Makefile.am +index 716ce000d..9961b80d8 100644 +--- a/none/tests/Makefile.am ++++ b/none/tests/Makefile.am +@@ -88,6 +88,7 @@ dist_noinst_SCRIPTS = \ + filter_fdleak \ + filter_ioctl_moans \ + filter_none_discards \ ++ filter_optlist \ + filter_stderr \ + filter_timestamp \ + filter_xml \ +@@ -134,6 +135,15 @@ EXTRA_DIST = \ + double_close_range_xml.vgtest \ + double_close_range_sup.stderr.exp double_close_range_sup.vgtest \ + empty-exe.vgtest empty-exe.stderr.exp \ ++ env1.stderr.exp env1.vgtest try1.stderr.exp try1.vgtest \ ++ env2.stderr.exp env2.vgtest try2.stderr.exp try2.vgtest \ ++ env3.stderr.exp env3.vgtest try3.stderr.exp try3.vgtest \ ++ env4.stderr.exp env4.vgtest try4.stderr.exp try4.vgtest \ ++ env5.stderr.exp env5.vgtest \ ++ env6.stderr.exp env6.vgtest \ ++ trytry.stderr.exp trytry.vgtest \ ++ trytry2.stderr.exp trytry2.vgtest \ ++ trytry3.stderr.exp trytry3.vgtest \ + exec-sigmask.vgtest exec-sigmask.stdout.exp \ + exec-sigmask.stdout.exp2 exec-sigmask.stdout.exp3 \ + exec-sigmask.stdout.exp-solaris exec-sigmask.stderr.exp \ +@@ -253,6 +263,10 @@ EXTRA_DIST = \ + threadederrno.vgtest \ + timer_delete.vgtest timer_delete.stderr.exp \ + timestamp.stderr.exp timestamp.vgtest \ ++ timestamp_env.stderr.exp timestamp_env.vgtest \ ++ timestamp_env21.stderr.exp timestamp_env21.vgtest \ ++ timestamp_env22.stderr.exp timestamp_env22.vgtest \ ++ timestamp_envoverride.stderr.exp timestamp_envoverride.vgtest \ + tls.vgtest tls.stderr.exp tls.stdout.exp \ + track-fds-exec-children.vgtest track-fds-exec-children.stderr.exp \ + unit_debuglog.stderr.exp unit_debuglog.vgtest \ +diff --git a/none/tests/cmdline1.stdout.exp b/none/tests/cmdline1.stdout.exp +index 5d3ea0569..3bd3d1afc 100644 +--- a/none/tests/cmdline1.stdout.exp ++++ b/none/tests/cmdline1.stdout.exp +@@ -169,7 +169,7 @@ usage: valgrind [options] prog-and-args + user options for Nulgrind: + (none) + +- Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc ++ Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc + + Nulgrind is Copyright (C) 2000, and GNU GPL'd, by Nicholas Nethercote et al. + Valgrind is Copyright (C) 2000, and GNU GPL'd, by Julian Seward et al. +diff --git a/none/tests/cmdline2.stdout.exp b/none/tests/cmdline2.stdout.exp +index 618c43acb..0ca1ddb34 100644 +--- a/none/tests/cmdline2.stdout.exp ++++ b/none/tests/cmdline2.stdout.exp +@@ -201,6 +201,7 @@ usage: valgrind [options] prog-and-args + --progress-interval= report progress every + CPU seconds [0, meaning disabled] + --command-line-only=no|yes only use command line options [no] ++ --try skip next option if option is unsupported + + Vex options for all Valgrind tools: + --vex-iropt-verbosity=<0..9> [0] +@@ -245,7 +246,7 @@ usage: valgrind [options] prog-and-args + debugging options for Nulgrind: + (none) + +- Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc ++ Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc + + Nulgrind is Copyright (C) 2000, and GNU GPL'd, by Nicholas Nethercote et al. + Valgrind is Copyright (C) 2000, and GNU GPL'd, by Julian Seward et al. +diff --git a/none/tests/env1.stderr.exp b/none/tests/env1.stderr.exp +new file mode 100644 +index 000000000..e69de29bb +diff --git a/none/tests/env1.vgtest b/none/tests/env1.vgtest +new file mode 100644 +index 000000000..10925d78d +--- /dev/null ++++ b/none/tests/env1.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS=--bad-bad-option=yes ++vgopts: -q --command-line-only=no ++prog: ../../tests/true +diff --git a/none/tests/env2.stderr.exp b/none/tests/env2.stderr.exp +new file mode 100644 +index 000000000..139597f9c +--- /dev/null ++++ b/none/tests/env2.stderr.exp +@@ -0,0 +1,2 @@ ++ ++ +diff --git a/none/tests/env2.vgtest b/none/tests/env2.vgtest +new file mode 100644 +index 000000000..102d99e67 +--- /dev/null ++++ b/none/tests/env2.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS=--trace-children=no ++vgopts: --command-line-only=no ++prog: ../../tests/true +diff --git a/none/tests/env3.stderr.exp b/none/tests/env3.stderr.exp +new file mode 100644 +index 000000000..139597f9c +--- /dev/null ++++ b/none/tests/env3.stderr.exp +@@ -0,0 +1,2 @@ ++ ++ +diff --git a/none/tests/env3.vgtest b/none/tests/env3.vgtest +new file mode 100644 +index 000000000..9a43a3ebd +--- /dev/null ++++ b/none/tests/env3.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS=--trace-children=yes ++vgopts: --command-line-only=no ++prog: ../../tests/true +diff --git a/none/tests/env4.stderr.exp b/none/tests/env4.stderr.exp +new file mode 100644 +index 000000000..fb3624035 +--- /dev/null ++++ b/none/tests/env4.stderr.exp +@@ -0,0 +1,3 @@ ++valgrind: Bad option: --trace-children=1 ++valgrind: Invalid boolean value '1' (should be 'yes' or 'no') ++valgrind: Use --help for more information or consult the user manual. +diff --git a/none/tests/env4.vgtest b/none/tests/env4.vgtest +new file mode 100644 +index 000000000..7794bd74b +--- /dev/null ++++ b/none/tests/env4.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS=--trace-children=1 ++vgopts: --command-line-only=no ++prog: ../../tests/true +diff --git a/none/tests/env5.stderr.exp b/none/tests/env5.stderr.exp +new file mode 100644 +index 000000000..139597f9c +--- /dev/null ++++ b/none/tests/env5.stderr.exp +@@ -0,0 +1,2 @@ ++ ++ +diff --git a/none/tests/env5.vgtest b/none/tests/env5.vgtest +new file mode 100644 +index 000000000..5e2ce8a41 +--- /dev/null ++++ b/none/tests/env5.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS=--trace-children=1 ++vgopts: --command-line-only=yes ++prog: ../../tests/true +diff --git a/none/tests/env6.stderr.exp b/none/tests/env6.stderr.exp +new file mode 100644 +index 000000000..59c75a7f5 +--- /dev/null ++++ b/none/tests/env6.stderr.exp +@@ -0,0 +1,12 @@ ++ --try ++ --gen-suppressions=no ++ --try ++ --trace-children=yes ++ --try ++ -q ++ --command-line-only=yes ++ --memcheck:leak-check=no ++ --tool=none ++ --command-line-only=no ++ --verbose ++ --verbose +diff --git a/none/tests/env6.vgtest b/none/tests/env6.vgtest +new file mode 100644 +index 000000000..d45479d9f +--- /dev/null ++++ b/none/tests/env6.vgtest +@@ -0,0 +1,4 @@ ++env: VALGRIND_TRY_OPTS='--gen-suppressions=no --trace-children=yes -q' ++vgopts: --command-line-only=no --verbose --verbose ++prog: ../../tests/true ++stderr_filter: filter_optlist +diff --git a/none/tests/filter_optlist b/none/tests/filter_optlist +new file mode 100755 +index 000000000..39639a198 +--- /dev/null ++++ b/none/tests/filter_optlist +@@ -0,0 +1,11 @@ ++#! /bin/sh ++ ++dir=`dirname $0` ++ ++$dir/filter_stderr | ++awk ' ++ BEGIN { printing = 0 } ++ /^Valgrind options:/ { printing = 1; next } ++ /^ / { if (printing) print; next } ++ { printing = 0 } ++' +diff --git a/none/tests/timestamp_env.stderr.exp b/none/tests/timestamp_env.stderr.exp +new file mode 100644 +index 000000000..00a5083ae +--- /dev/null ++++ b/none/tests/timestamp_env.stderr.exp +@@ -0,0 +1,2 @@ ++00:00:00:XX:YYY ++00:00:00:XX:YYY +diff --git a/none/tests/timestamp_env.vgtest b/none/tests/timestamp_env.vgtest +new file mode 100644 +index 000000000..fe5b20e3b +--- /dev/null ++++ b/none/tests/timestamp_env.vgtest +@@ -0,0 +1,4 @@ ++env: VALGRIND_TRY_OPTS=--time-stamp=yes ++vgopts: --command-line-only=no ++prog: timestamp ++stderr_filter: filter_timestamp +diff --git a/none/tests/timestamp_env21.stderr.exp b/none/tests/timestamp_env21.stderr.exp +new file mode 100644 +index 000000000..00a5083ae +--- /dev/null ++++ b/none/tests/timestamp_env21.stderr.exp +@@ -0,0 +1,2 @@ ++00:00:00:XX:YYY ++00:00:00:XX:YYY +diff --git a/none/tests/timestamp_env21.vgtest b/none/tests/timestamp_env21.vgtest +new file mode 100644 +index 000000000..aa6e76688 +--- /dev/null ++++ b/none/tests/timestamp_env21.vgtest +@@ -0,0 +1,4 @@ ++env: VALGRIND_TRY_OPTS='--time-stamp=yes --trace-children=yes' ++vgopts: --command-line-only=no ++prog: timestamp ++stderr_filter: filter_timestamp +diff --git a/none/tests/timestamp_env22.stderr.exp b/none/tests/timestamp_env22.stderr.exp +new file mode 100644 +index 000000000..00a5083ae +--- /dev/null ++++ b/none/tests/timestamp_env22.stderr.exp +@@ -0,0 +1,2 @@ ++00:00:00:XX:YYY ++00:00:00:XX:YYY +diff --git a/none/tests/timestamp_env22.vgtest b/none/tests/timestamp_env22.vgtest +new file mode 100644 +index 000000000..cb628ac8d +--- /dev/null ++++ b/none/tests/timestamp_env22.vgtest +@@ -0,0 +1,4 @@ ++env: VALGRIND_TRY_OPTS='--show-below-main=no --time-stamp=yes' ++vgopts: --command-line-only=no ++prog: timestamp ++stderr_filter: filter_timestamp +diff --git a/none/tests/timestamp_envoverride.stderr.exp b/none/tests/timestamp_envoverride.stderr.exp +new file mode 100644 +index 000000000..00a5083ae +--- /dev/null ++++ b/none/tests/timestamp_envoverride.stderr.exp +@@ -0,0 +1,2 @@ ++00:00:00:XX:YYY ++00:00:00:XX:YYY +diff --git a/none/tests/timestamp_envoverride.vgtest b/none/tests/timestamp_envoverride.vgtest +new file mode 100644 +index 000000000..c77b52762 +--- /dev/null ++++ b/none/tests/timestamp_envoverride.vgtest +@@ -0,0 +1,4 @@ ++env: VALGRIND_TRY_OPTS=--time-stamp=no ++vgopts: --time-stamp=yes --command-line-only=no ++prog: timestamp ++stderr_filter: filter_timestamp +diff --git a/none/tests/try1.stderr.exp b/none/tests/try1.stderr.exp +new file mode 100644 +index 000000000..e69de29bb +diff --git a/none/tests/try1.vgtest b/none/tests/try1.vgtest +new file mode 100644 +index 000000000..d00f79dfb +--- /dev/null ++++ b/none/tests/try1.vgtest +@@ -0,0 +1,2 @@ ++vgopts: -q --try --bad-bad-option=yes ++prog: ../../tests/true +diff --git a/none/tests/try2.stderr.exp b/none/tests/try2.stderr.exp +new file mode 100644 +index 000000000..139597f9c +--- /dev/null ++++ b/none/tests/try2.stderr.exp +@@ -0,0 +1,2 @@ ++ ++ +diff --git a/none/tests/try2.vgtest b/none/tests/try2.vgtest +new file mode 100644 +index 000000000..ce763273c +--- /dev/null ++++ b/none/tests/try2.vgtest +@@ -0,0 +1,2 @@ ++vgopts: --try --trace-children=no ++prog: ../../tests/true +diff --git a/none/tests/try3.stderr.exp b/none/tests/try3.stderr.exp +new file mode 100644 +index 000000000..139597f9c +--- /dev/null ++++ b/none/tests/try3.stderr.exp +@@ -0,0 +1,2 @@ ++ ++ +diff --git a/none/tests/try3.vgtest b/none/tests/try3.vgtest +new file mode 100644 +index 000000000..ce763273c +--- /dev/null ++++ b/none/tests/try3.vgtest +@@ -0,0 +1,2 @@ ++vgopts: --try --trace-children=no ++prog: ../../tests/true +diff --git a/none/tests/try4.stderr.exp b/none/tests/try4.stderr.exp +new file mode 100644 +index 000000000..fb3624035 +--- /dev/null ++++ b/none/tests/try4.stderr.exp +@@ -0,0 +1,3 @@ ++valgrind: Bad option: --trace-children=1 ++valgrind: Invalid boolean value '1' (should be 'yes' or 'no') ++valgrind: Use --help for more information or consult the user manual. +diff --git a/none/tests/try4.vgtest b/none/tests/try4.vgtest +new file mode 100644 +index 000000000..21bb392e9 +--- /dev/null ++++ b/none/tests/try4.vgtest +@@ -0,0 +1,2 @@ ++vgopts: --try --trace-children=1 ++prog: ../../tests/true +diff --git a/none/tests/trytry.stderr.exp b/none/tests/trytry.stderr.exp +new file mode 100644 +index 000000000..e69de29bb +diff --git a/none/tests/trytry.vgtest b/none/tests/trytry.vgtest +new file mode 100644 +index 000000000..b638148ed +--- /dev/null ++++ b/none/tests/trytry.vgtest +@@ -0,0 +1,2 @@ ++vgopts: -q --try --try --bad-bad-option ++prog: ../../tests/true +diff --git a/none/tests/trytry2.stderr.exp b/none/tests/trytry2.stderr.exp +new file mode 100644 +index 000000000..879e12a28 +--- /dev/null ++++ b/none/tests/trytry2.stderr.exp +@@ -0,0 +1,2 @@ ++valgrind: Unknown option: --bad-bad-option ++valgrind: Use --help for more information or consult the user manual. +diff --git a/none/tests/trytry2.vgtest b/none/tests/trytry2.vgtest +new file mode 100644 +index 000000000..cb5335456 +--- /dev/null ++++ b/none/tests/trytry2.vgtest +@@ -0,0 +1,2 @@ ++vgopts: -q --try --try --bad-bad-option --bad-bad-option ++prog: ../../tests/true +diff --git a/none/tests/trytry3.stderr.exp b/none/tests/trytry3.stderr.exp +new file mode 100644 +index 000000000..4dac4a198 +--- /dev/null ++++ b/none/tests/trytry3.stderr.exp +@@ -0,0 +1,14 @@ ++ --command-line-only=yes ++ --memcheck:leak-check=no ++ --tool=none ++ --try ++ --gen-suppressions=no ++ --try ++ --trace-children=yes ++ --try ++ -q ++ --try ++ --try ++ --bad-bad-option ++ --verbose ++ --verbose +diff --git a/none/tests/trytry3.vgtest b/none/tests/trytry3.vgtest +new file mode 100644 +index 000000000..b065fd0e9 +--- /dev/null ++++ b/none/tests/trytry3.vgtest +@@ -0,0 +1,3 @@ ++vgopts: --try --gen-suppressions=no --try --trace-children=yes --try -q --try --try --bad-bad-option --verbose --verbose ++prog: ../../tests/true ++stderr_filter: filter_optlist diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt new file mode 100644 index 0000000000..de4021f084 --- /dev/null +++ b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt @@ -0,0 +1,909 @@ +diff --git a/.gitignore b/.gitignore +index e48a2ab0e..812e8f11f 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1042,6 +1042,8 @@ + /memcheck/tests/varinfo5so.so + /memcheck/tests/varinfo6 + /memcheck/tests/varinforestrict ++/memcheck/tests/varlat ++/memcheck/tests/varlat2 + /memcheck/tests/vcpu_bz2 + /memcheck/tests/vcpu_fbench + /memcheck/tests/vcpu_fnfns +diff --git a/memcheck/mc_errors.c b/memcheck/mc_errors.c +index a708b3f85..8d163cd90 100644 +--- a/memcheck/mc_errors.c ++++ b/memcheck/mc_errors.c +@@ -94,11 +94,14 @@ struct _MC_Error { + // Use of an undefined value: + // - as a pointer in a load or store + // - as a jump target ++ // - (VARLAT mode) as an operand in a variable-latency instruction + struct { + SizeT szB; // size of value in bytes + // Origin info + UInt otag; // origin tag + ExeContext* origin_ec; // filled in later ++ // VARLAT mode ++ Bool isLatency; // if issue is variable-latency instruction + } Value; + + // Use of an undefined value in a conditional branch or move. +@@ -489,8 +492,13 @@ void MC_(pp_Error) ( const Error* err ) + MC_(any_value_errors) = True; + if (xml) { + emit( " UninitValue\n" ); +- emit( " Use of uninitialised value of size %lu\n", +- extra->Err.Value.szB ); ++ if (extra->Err.Value.isLatency) ++ emit( " Variable-latency instruction operand" ++ " of size %lu is secret/uninitialised\n", ++ extra->Err.Value.szB ); ++ else ++ emit( " Use of uninitialised value of size %lu\n", ++ extra->Err.Value.szB ); + VG_(pp_ExeContext)( VG_(get_error_where)(err) ); + if (extra->Err.Value.origin_ec) + mc_pp_origin( extra->Err.Value.origin_ec, +@@ -498,8 +506,13 @@ void MC_(pp_Error) ( const Error* err ) + } else { + /* Could also show extra->Err.Cond.otag if debugging origin + tracking */ +- emit( "Use of uninitialised value of size %lu\n", +- extra->Err.Value.szB ); ++ if (extra->Err.Value.isLatency) ++ emit( "Variable-latency instruction operand" ++ " of size %lu is secret/uninitialised\n", ++ extra->Err.Value.szB ); ++ else ++ emit( "Use of uninitialised value of size %lu\n", ++ extra->Err.Value.szB ); + VG_(pp_ExeContext)( VG_(get_error_where)(err) ); + if (extra->Err.Value.origin_ec) + mc_pp_origin( extra->Err.Value.origin_ec, +@@ -926,6 +939,20 @@ void MC_(record_value_error) ( ThreadId tid, Int szB, UInt otag ) + extra.Err.Value.szB = szB; + extra.Err.Value.otag = otag; + extra.Err.Value.origin_ec = NULL; /* Filled in later */ ++ extra.Err.Value.isLatency = False; ++ VG_(maybe_record_error)( tid, Err_Value, /*addr*/0, /*s*/NULL, &extra ); ++} ++ ++void MC_(record_varlat_value_error) ( ThreadId tid, Int szB, UInt otag ) ++{ ++ MC_Error extra; ++ tl_assert( MC_(clo_mc_level) >= 2 ); ++ if (otag > 0) ++ tl_assert( MC_(clo_mc_level) == 3 ); ++ extra.Err.Value.szB = szB; ++ extra.Err.Value.otag = otag; ++ extra.Err.Value.origin_ec = NULL; /* Filled in later */ ++ extra.Err.Value.isLatency = True; + VG_(maybe_record_error)( tid, Err_Value, /*addr*/0, /*s*/NULL, &extra ); + } + +diff --git a/memcheck/mc_include.h b/memcheck/mc_include.h +index acc595a74..2578cccb0 100644 +--- a/memcheck/mc_include.h ++++ b/memcheck/mc_include.h +@@ -525,6 +525,10 @@ void MC_(pp_LossRecord)(UInt n_this_record, UInt n_total_records, + rerun with --track-origins=yes might help. */ + extern Bool MC_(any_value_errors); + ++/* Are we running in VARLAT mode, where variable-latency operations ++ on uninitialised (or secret) values are considered as errors ? */ ++extern Bool MC_(clo_variable_latency_errors); ++ + /* Standard functions for error and suppressions as required by the + core/tool iface */ + Bool MC_(eq_Error) ( VgRes res, const Error* e1, const Error* e2 ); +@@ -552,6 +556,7 @@ void MC_(record_address_error) ( ThreadId tid, Addr a, Int szB, + Bool isWrite ); + void MC_(record_cond_error) ( ThreadId tid, UInt otag ); + void MC_(record_value_error) ( ThreadId tid, Int szB, UInt otag ); ++void MC_(record_varlat_value_error) ( ThreadId tid, Int szB, UInt otag ); + void MC_(record_jump_error) ( ThreadId tid, Addr a ); + + void MC_(record_free_error) ( ThreadId tid, Addr a ); +@@ -773,6 +778,10 @@ VG_REGPARM(1) void MC_(helperc_value_check8_fail_w_o) ( UWord ); + VG_REGPARM(1) void MC_(helperc_value_check4_fail_w_o) ( UWord ); + VG_REGPARM(1) void MC_(helperc_value_check1_fail_w_o) ( UWord ); + VG_REGPARM(1) void MC_(helperc_value_check0_fail_w_o) ( UWord ); ++VG_REGPARM(2) void MC_(helperc_value_checkN_varlat_fail_w_o) ( HWord, UWord ); ++VG_REGPARM(1) void MC_(helperc_value_check8_varlat_fail_w_o) ( UWord ); ++VG_REGPARM(1) void MC_(helperc_value_check4_varlat_fail_w_o) ( UWord ); ++VG_REGPARM(1) void MC_(helperc_value_check1_varlat_fail_w_o) ( UWord ); + + /* And call these ones instead to report an uninitialised value error + but with no origin available. */ +@@ -781,6 +790,10 @@ VG_REGPARM(0) void MC_(helperc_value_check8_fail_no_o) ( void ); + VG_REGPARM(0) void MC_(helperc_value_check4_fail_no_o) ( void ); + VG_REGPARM(0) void MC_(helperc_value_check1_fail_no_o) ( void ); + VG_REGPARM(0) void MC_(helperc_value_check0_fail_no_o) ( void ); ++VG_REGPARM(1) void MC_(helperc_value_checkN_varlat_fail_no_o) ( HWord ); ++VG_REGPARM(0) void MC_(helperc_value_check8_varlat_fail_no_o) ( void ); ++VG_REGPARM(0) void MC_(helperc_value_check4_varlat_fail_no_o) ( void ); ++VG_REGPARM(0) void MC_(helperc_value_check1_varlat_fail_no_o) ( void ); + + /* V-bits load/store helpers */ + VG_REGPARM(1) void MC_(helperc_STOREV64be) ( Addr, ULong ); +diff --git a/memcheck/mc_main.c b/memcheck/mc_main.c +index 626d481d2..49f835929 100644 +--- a/memcheck/mc_main.c ++++ b/memcheck/mc_main.c +@@ -5770,6 +5770,27 @@ void MC_(helperc_value_checkN_fail_w_o) ( HWord sz, UWord origin ) { + MC_(record_value_error) ( VG_(get_running_tid)(), (Int)sz, (UInt)origin ); + } + ++VG_REGPARM(1) ++void MC_(helperc_value_check1_varlat_fail_w_o) ( UWord origin ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 1, (UInt)origin ); ++} ++ ++VG_REGPARM(1) ++void MC_(helperc_value_check4_varlat_fail_w_o) ( UWord origin ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 4, (UInt)origin ); ++} ++ ++VG_REGPARM(1) ++void MC_(helperc_value_check8_varlat_fail_w_o) ( UWord origin ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 8, (UInt)origin ); ++} ++ ++VG_REGPARM(2) ++void MC_(helperc_value_checkN_varlat_fail_w_o) ( HWord sz, UWord origin ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), (Int)sz, ++ (UInt)origin ); ++} ++ + /* ... and these when an origin isn't available. */ + + VG_REGPARM(0) +@@ -5797,6 +5818,27 @@ void MC_(helperc_value_checkN_fail_no_o) ( HWord sz ) { + MC_(record_value_error) ( VG_(get_running_tid)(), (Int)sz, 0/*origin*/ ); + } + ++VG_REGPARM(0) ++void MC_(helperc_value_check1_varlat_fail_no_o) ( void ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 1, 0/*origin*/ ); ++} ++ ++VG_REGPARM(0) ++void MC_(helperc_value_check4_varlat_fail_no_o) ( void ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 4, 0/*origin*/ ); ++} ++ ++VG_REGPARM(0) ++void MC_(helperc_value_check8_varlat_fail_no_o) ( void ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 8, 0/*origin*/ ); ++} ++ ++VG_REGPARM(1) ++void MC_(helperc_value_checkN_varlat_fail_no_o) ( HWord sz ) { ++ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), (Int)sz, ++ 0/*origin*/ ); ++} ++ + + /*------------------------------------------------------------*/ + /*--- Metadata get/set functions, for client requests. ---*/ +@@ -6064,6 +6106,7 @@ UInt MC_(clo_leak_check_heuristics) = H2S(LchStdString) + | H2S( LchMultipleInheritance); + Bool MC_(clo_xtree_leak) = False; + const HChar* MC_(clo_xtree_leak_file) = "xtleak.kcg.%p"; ++Bool MC_(clo_variable_latency_errors) = False; + Bool MC_(clo_workaround_gcc296_bugs) = False; + Int MC_(clo_malloc_fill) = -1; + Int MC_(clo_free_fill) = -1; +@@ -6135,6 +6178,8 @@ static Bool mc_process_cmd_line_options(const HChar* arg) + else if VG_USET_CLOM(cloPD, arg, "--leak-check-heuristics", + MC_(parse_leak_heuristics_tokens), + MC_(clo_leak_check_heuristics)) {} ++ else if VG_BOOL_CLOM(cloPD, arg, "--variable-latency-errors", ++ MC_(clo_variable_latency_errors)) {} + else if (VG_BOOL_CLOM(cloPD, arg, "--show-reachable", tmp_show)) { + if (tmp_show) { + MC_(clo_show_leak_kinds) = MC_(all_Reachedness)(); +@@ -6313,6 +6358,7 @@ static void mc_print_usage(void) + " --xtree-leak=no|yes output leak result in xtree format? [no]\n" + " --xtree-leak-file= xtree leak report file [xtleak.kcg.%%p]\n" + " --undef-value-errors=no|yes check for undefined value errors [yes]\n" ++" --variable-latency-errors=no|yes check for variable latency errors [no]\n" + " --track-origins=no|yes show origins of undefined values? [no]\n" + " --partial-loads-ok=no|yes too hard to explain here; see manual [yes]\n" + " --expensive-definedness-checks=no|auto|yes\n" +diff --git a/memcheck/mc_translate.c b/memcheck/mc_translate.c +index b4e499cc0..2d93f2d18 100644 +--- a/memcheck/mc_translate.c ++++ b/memcheck/mc_translate.c +@@ -1593,8 +1593,13 @@ static void setHelperAnns ( MCEnv* mce, IRDirty* di ) { + + This routine does not generate code to check the definedness of + |guard|. The caller is assumed to have taken care of that already. ++ ++ If varlatLatency is set, then this function emits complaints for ++ VARLAT mode rather than for the usual branches etc. + */ +-static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) ++static void complainIfUndefinedOrVariableLatency ( MCEnv* mce, IRAtom* atom, ++ IRExpr *guard, ++ Bool varlatLatency ) + { + IRAtom* vatom; + IRType ty; +@@ -1664,39 +1669,69 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) + break; + case 1: + if (origin) { +- fn = &MC_(helperc_value_check1_fail_w_o); +- nm = "MC_(helperc_value_check1_fail_w_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check1_fail_w_o); ++ nm = "MC_(helperc_value_check1_fail_w_o)"; ++ } else { ++ fn = &MC_(helperc_value_check1_varlat_fail_w_o); ++ nm = "MC_(helperc_value_check1_varlat_fail_w_o)"; ++ } + args = mkIRExprVec_1(origin); + nargs = 1; + } else { +- fn = &MC_(helperc_value_check1_fail_no_o); +- nm = "MC_(helperc_value_check1_fail_no_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check1_fail_no_o); ++ nm = "MC_(helperc_value_check1_fail_no_o)"; ++ } else { ++ fn = &MC_(helperc_value_check1_varlat_fail_no_o); ++ nm = "MC_(helperc_value_check1_varlat_fail_no_o)"; ++ } + args = mkIRExprVec_0(); + nargs = 0; + } + break; + case 4: + if (origin) { +- fn = &MC_(helperc_value_check4_fail_w_o); +- nm = "MC_(helperc_value_check4_fail_w_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check4_fail_w_o); ++ nm = "MC_(helperc_value_check4_fail_w_o)"; ++ } else { ++ fn = &MC_(helperc_value_check4_varlat_fail_w_o); ++ nm = "MC_(helperc_value_check4_varlat_fail_w_o)"; ++ } + args = mkIRExprVec_1(origin); + nargs = 1; + } else { +- fn = &MC_(helperc_value_check4_fail_no_o); +- nm = "MC_(helperc_value_check4_fail_no_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check4_fail_no_o); ++ nm = "MC_(helperc_value_check4_fail_no_o)"; ++ } else { ++ fn = &MC_(helperc_value_check4_varlat_fail_no_o); ++ nm = "MC_(helperc_value_check4_varlat_fail_no_o)"; ++ } + args = mkIRExprVec_0(); + nargs = 0; + } + break; + case 8: + if (origin) { +- fn = &MC_(helperc_value_check8_fail_w_o); +- nm = "MC_(helperc_value_check8_fail_w_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check8_fail_w_o); ++ nm = "MC_(helperc_value_check8_fail_w_o)"; ++ } else { ++ fn = &MC_(helperc_value_check8_varlat_fail_w_o); ++ nm = "MC_(helperc_value_check8_varlat_fail_w_o)"; ++ } + args = mkIRExprVec_1(origin); + nargs = 1; + } else { +- fn = &MC_(helperc_value_check8_fail_no_o); +- nm = "MC_(helperc_value_check8_fail_no_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_check8_fail_no_o); ++ nm = "MC_(helperc_value_check8_fail_no_o)"; ++ } else { ++ fn = &MC_(helperc_value_check8_varlat_fail_no_o); ++ nm = "MC_(helperc_value_check8_varlat_fail_no_o)"; ++ } + args = mkIRExprVec_0(); + nargs = 0; + } +@@ -1704,13 +1739,23 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) + case 2: + case 16: + if (origin) { +- fn = &MC_(helperc_value_checkN_fail_w_o); +- nm = "MC_(helperc_value_checkN_fail_w_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_checkN_fail_w_o); ++ nm = "MC_(helperc_value_checkN_fail_w_o)"; ++ } else { ++ fn = &MC_(helperc_value_checkN_varlat_fail_w_o); ++ nm = "MC_(helperc_value_checkN_varlat_fail_w_o)"; ++ } + args = mkIRExprVec_2( mkIRExpr_HWord( sz ), origin); + nargs = 2; + } else { +- fn = &MC_(helperc_value_checkN_fail_no_o); +- nm = "MC_(helperc_value_checkN_fail_no_o)"; ++ if (!varlatLatency) { ++ fn = &MC_(helperc_value_checkN_fail_no_o); ++ nm = "MC_(helperc_value_checkN_fail_no_o)"; ++ } else { ++ fn = &MC_(helperc_value_checkN_varlat_fail_no_o); ++ nm = "MC_(helperc_value_checkN_varlat_fail_no_o)"; ++ } + args = mkIRExprVec_1( mkIRExpr_HWord( sz ) ); + nargs = 1; + } +@@ -1771,6 +1816,17 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) + } + } + ++static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) ++{ ++ complainIfUndefinedOrVariableLatency(mce, atom, guard, False); ++} ++ ++static void complainIfVariableLatency ( MCEnv* mce, IRAtom* atom ) ++{ ++ if (MC_(clo_variable_latency_errors)) ++ complainIfUndefinedOrVariableLatency(mce, atom, NULL, True); ++} ++ + + /*------------------------------------------------------------*/ + /*--- Shadowing PUTs/GETs, and indexed variants thereof ---*/ +@@ -3511,14 +3567,19 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, + case Iop_Yl2xF64: + case Iop_Yl2xp1F64: + case Iop_AtanF64: +- case Iop_PRemF64: +- case Iop_PRem1F64: + case Iop_QuantizeD64: + /* I32(rm) x F64/D64 x F64/D64 -> F64/D64 */ + return mkLazy3(mce, Ity_I64, vatom1, vatom2, vatom3); ++ case Iop_PRemF64: ++ case Iop_PRem1F64: ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); ++ return mkLazy3(mce, Ity_I64, vatom1, vatom2, vatom3); + case Iop_PRemC3210F64: + case Iop_PRem1C3210F64: + /* I32(rm) x F64 x F64 -> I32 */ ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); + return mkLazy3(mce, Ity_I32, vatom1, vatom2, vatom3); + case Iop_AddF32: + case Iop_SubF32: +@@ -3572,21 +3633,33 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, + case Iop_Add64Fx2: + case Iop_Sub64Fx2: + case Iop_Mul64Fx2: +- case Iop_Div64Fx2: + case Iop_Scale2_64Fx2: + return binary64Fx2_w_rm(mce, vatom1, vatom2, vatom3); + ++ case Iop_Div64Fx2: ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); ++ return binary64Fx2_w_rm(mce, vatom1, vatom2, vatom3); ++ + case Iop_Add32Fx4: + case Iop_Sub32Fx4: + case Iop_Mul32Fx4: +- case Iop_Div32Fx4: + case Iop_Scale2_32Fx4: +- return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); ++ return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); ++ ++ case Iop_Div32Fx4: ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); ++ return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); + + case Iop_Add64Fx4: + case Iop_Sub64Fx4: + case Iop_Mul64Fx4: ++ return binary64Fx4_w_rm(mce, vatom1, vatom2, vatom3); ++ + case Iop_Div64Fx4: ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); + return binary64Fx4_w_rm(mce, vatom1, vatom2, vatom3); + + /* TODO: remaining versions of 16x4 FP ops when more of the half-precision +@@ -3594,12 +3667,16 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, + */ + case Iop_Add16Fx8: + case Iop_Sub16Fx8: +- return binary16Fx8_w_rm(mce, vatom1, vatom2, vatom3); ++ return binary16Fx8_w_rm(mce, vatom1, vatom2, vatom3); + + case Iop_Add32Fx8: + case Iop_Sub32Fx8: + case Iop_Mul32Fx8: ++ return binary32Fx8_w_rm(mce, vatom1, vatom2, vatom3); ++ + case Iop_Div32Fx8: ++ complainIfVariableLatency(mce, atom2); ++ complainIfVariableLatency(mce, atom3); + return binary32Fx8_w_rm(mce, vatom1, vatom2, vatom3); + + case Iop_F32x4_2toQ16x8: +@@ -3913,11 +3990,15 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + + case Iop_I32StoF32x4: + case Iop_F32toI32Sx4: ++ return unary16Fx8_w_rm(mce, vatom1, vatom2); + case Iop_Sqrt16Fx8: ++ complainIfVariableLatency(mce, atom2); + return unary16Fx8_w_rm(mce, vatom1, vatom2); + case Iop_Sqrt32Fx4: ++ complainIfVariableLatency(mce, atom2); + return unary32Fx4_w_rm(mce, vatom1, vatom2); + case Iop_Sqrt64Fx2: ++ complainIfVariableLatency(mce, atom2); + return unary64Fx2_w_rm(mce, vatom1, vatom2); + + case Iop_ShrN8x16: +@@ -4153,6 +4234,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_ModU128: + case Iop_ModS128: + /* I128 x I128 -> I128 */ ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_V128, vatom1, vatom2); + + case Iop_QNarrowBin64Sto32Sx4: +@@ -4184,7 +4267,6 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_Mul64F0x2: + case Iop_Min64F0x2: + case Iop_Max64F0x2: +- case Iop_Div64F0x2: + case Iop_CmpLT64F0x2: + case Iop_CmpLE64F0x2: + case Iop_CmpEQ64F0x2: +@@ -4192,6 +4274,11 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_Add64F0x2: + return binary64F0x2(mce, vatom1, vatom2); + ++ case Iop_Div64F0x2: ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); ++ return binary64F0x2(mce, vatom1, vatom2); ++ + case Iop_Min32Fx4: + case Iop_Max32Fx4: + case Iop_CmpLT32Fx4: +@@ -4220,7 +4307,6 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_Mul32F0x4: + case Iop_Min32F0x4: + case Iop_Max32F0x4: +- case Iop_Div32F0x4: + case Iop_CmpLT32F0x4: + case Iop_CmpLE32F0x4: + case Iop_CmpEQ32F0x4: +@@ -4228,6 +4314,11 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_Add32F0x4: + return binary32F0x4(mce, vatom1, vatom2); + ++ case Iop_Div32F0x4: ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); ++ return binary32F0x4(mce, vatom1, vatom2); ++ + case Iop_QShlNsatSU8x16: + case Iop_QShlNsatUU8x16: + case Iop_QShlNsatSS8x16: +@@ -4531,11 +4622,14 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + case Iop_CosF64: + case Iop_TanF64: + case Iop_2xm1F64: +- case Iop_SqrtF64: + case Iop_RecpExpF64: + /* I32(rm) x I64/F64 -> I64/F64 */ + return mkLazy2(mce, Ity_I64, vatom1, vatom2); + ++ case Iop_SqrtF64: ++ complainIfVariableLatency(mce, atom2); ++ return mkLazy2(mce, Ity_I64, vatom1, vatom2); ++ + case Iop_ShlD64: + case Iop_ShrD64: + case Iop_RoundD64toInt: +@@ -4589,16 +4683,21 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + + case Iop_SqrtF16: + /* I32(rm) x F16 -> F16 */ ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I16, vatom1, vatom2); + + case Iop_RoundF32toInt: +- case Iop_SqrtF32: + case Iop_RecpExpF32: + /* I32(rm) x I32/F32 -> I32/F32 */ + return mkLazy2(mce, Ity_I32, vatom1, vatom2); + ++ case Iop_SqrtF32: ++ complainIfVariableLatency(mce, atom2); ++ return mkLazy2(mce, Ity_I32, vatom1, vatom2); ++ + case Iop_SqrtF128: + /* I32(rm) x F128 -> F128 */ ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I128, vatom1, vatom2); + + case Iop_I32StoF32: +@@ -4687,10 +4786,14 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + + case Iop_DivModU64to32: + case Iop_DivModS64to32: ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I64, vatom1, vatom2); + + case Iop_DivModU128to64: + case Iop_DivModS128to64: ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I128, vatom1, vatom2); + + case Iop_8HLto16: +@@ -4702,6 +4805,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + + case Iop_DivModU64to64: + case Iop_DivModS64to64: { ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + IRAtom* vTmp64 = mkLazy2(mce, Ity_I64, vatom1, vatom2); + return assignNew('V', mce, Ity_I128, + binop(Iop_64HLto128, vTmp64, vTmp64)); +@@ -4717,6 +4822,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + + case Iop_DivModU32to32: + case Iop_DivModS32to32: { ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + IRAtom* vTmp32 = mkLazy2(mce, Ity_I32, vatom1, vatom2); + return assignNew('V', mce, Ity_I64, + binop(Iop_32HLto64, vTmp32, vTmp32)); +@@ -4746,18 +4853,24 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, + } + + case Iop_Sad8Ux4: /* maybe we could do better? ftm, do mkLazy2. */ ++ case Iop_QAdd32S: /* could probably do better */ ++ case Iop_QSub32S: /* could probably do better */ ++ return mkLazy2(mce, Ity_I32, vatom1, vatom2); ++ + case Iop_DivS32: + case Iop_DivU32: + case Iop_DivU32E: + case Iop_DivS32E: +- case Iop_QAdd32S: /* could probably do better */ +- case Iop_QSub32S: /* could probably do better */ ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I32, vatom1, vatom2); + + case Iop_DivS64: + case Iop_DivU64: + case Iop_DivS64E: + case Iop_DivU64E: ++ complainIfVariableLatency(mce, atom1); ++ complainIfVariableLatency(mce, atom2); + return mkLazy2(mce, Ity_I64, vatom1, vatom2); + + case Iop_Add32: +@@ -5163,14 +5276,19 @@ IRExpr* expr2vbits_Unop ( MCEnv* mce, IROp op, IRAtom* atom ) + return unary64Fx2(mce, vatom); + + case Iop_Sqrt64F0x2: ++ complainIfVariableLatency(mce, atom); + return unary64F0x2(mce, vatom); + + case Iop_Sqrt32Fx8: ++ complainIfVariableLatency(mce, atom); ++ return unary32Fx8(mce, vatom); ++ + case Iop_RSqrtEst32Fx8: + case Iop_RecipEst32Fx8: + return unary32Fx8(mce, vatom); + + case Iop_Sqrt64Fx4: ++ complainIfVariableLatency(mce, atom); + return unary64Fx4(mce, vatom); + + case Iop_RecipEst32Fx4: +@@ -5200,6 +5318,9 @@ IRExpr* expr2vbits_Unop ( MCEnv* mce, IROp op, IRAtom* atom ) + return unary32Fx2(mce, vatom); + + case Iop_Sqrt32F0x4: ++ complainIfVariableLatency(mce, atom); ++ return unary32F0x4(mce, vatom); ++ + case Iop_RSqrtEst32F0x4: + case Iop_RecipEst32F0x4: + return unary32F0x4(mce, vatom); +@@ -8044,19 +8165,25 @@ Bool check_or_add ( Pairs* tidyingEnv, IRExpr* guard, void* entry ) + static Bool is_helperc_value_checkN_fail ( const HChar* name ) + { + /* This is expensive because it happens a lot. We are checking to +- see whether |name| is one of the following 8 strings: ++ see whether |name| is one of the following 14 strings: + + MC_(helperc_value_check8_fail_no_o) + MC_(helperc_value_check4_fail_no_o) + MC_(helperc_value_check0_fail_no_o) + MC_(helperc_value_check1_fail_no_o) + MC_(helperc_value_check8_fail_w_o) ++ MC_(helperc_value_check4_fail_w_o) + MC_(helperc_value_check0_fail_w_o) + MC_(helperc_value_check1_fail_w_o) +- MC_(helperc_value_check4_fail_w_o) ++ MC_(helperc_value_check8_varlat_fail_no_o) ++ MC_(helperc_value_check4_varlat_fail_no_o) ++ MC_(helperc_value_check1_varlat_fail_no_o) ++ MC_(helperc_value_check8_varlat_fail_w_o) ++ MC_(helperc_value_check4_varlat_fail_w_o) ++ MC_(helperc_value_check1_varlat_fail_w_o) + + To speed it up, check the common prefix just once, rather than +- all 8 times. ++ all 14 times. + */ + const HChar* prefix = "MC_(helperc_value_check"; + +@@ -8081,7 +8208,13 @@ static Bool is_helperc_value_checkN_fail ( const HChar* name ) + || 0==VG_(strcmp)(name, "8_fail_w_o)") + || 0==VG_(strcmp)(name, "4_fail_w_o)") + || 0==VG_(strcmp)(name, "0_fail_w_o)") +- || 0==VG_(strcmp)(name, "1_fail_w_o)"); ++ || 0==VG_(strcmp)(name, "1_fail_w_o)") ++ || 0==VG_(strcmp)(name, "8_varlat_fail_no_o)") ++ || 0==VG_(strcmp)(name, "4_varlat_fail_no_o)") ++ || 0==VG_(strcmp)(name, "1_varlat_fail_no_o)") ++ || 0==VG_(strcmp)(name, "8_varlat_fail_w_o)") ++ || 0==VG_(strcmp)(name, "4_varlat_fail_w_o)") ++ || 0==VG_(strcmp)(name, "1_varlat_fail_w_o)"); + } + + IRSB* MC_(final_tidy) ( IRSB* sb_in ) +@@ -8147,7 +8280,7 @@ void MC_(do_instrumentation_startup_checks)( void ) + # define CHECK(_expected, _string) \ + tl_assert((_expected) == is_helperc_value_checkN_fail(_string)) + +- /* It should identify these 8, and no others, as targets. */ ++ /* It should identify these 14, and no others, as targets. */ + CHECK(True, "MC_(helperc_value_check8_fail_no_o)"); + CHECK(True, "MC_(helperc_value_check4_fail_no_o)"); + CHECK(True, "MC_(helperc_value_check0_fail_no_o)"); +@@ -8156,6 +8289,12 @@ void MC_(do_instrumentation_startup_checks)( void ) + CHECK(True, "MC_(helperc_value_check0_fail_w_o)"); + CHECK(True, "MC_(helperc_value_check1_fail_w_o)"); + CHECK(True, "MC_(helperc_value_check4_fail_w_o)"); ++ CHECK(True, "MC_(helperc_value_check8_varlat_fail_no_o)"); ++ CHECK(True, "MC_(helperc_value_check4_varlat_fail_no_o)"); ++ CHECK(True, "MC_(helperc_value_check1_varlat_fail_no_o)"); ++ CHECK(True, "MC_(helperc_value_check8_varlat_fail_w_o)"); ++ CHECK(True, "MC_(helperc_value_check1_varlat_fail_w_o)"); ++ CHECK(True, "MC_(helperc_value_check4_varlat_fail_w_o)"); + + /* Ad-hoc selection of other strings gathered via a quick test. */ + CHECK(False, "amd64g_dirtyhelper_CPUID_avx2"); +diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am +index 633c036c3..11f440b2a 100644 +--- a/memcheck/tests/Makefile.am ++++ b/memcheck/tests/Makefile.am +@@ -448,6 +448,14 @@ EXTRA_DIST = \ + varinfo6.vgtest varinfo6.stdout.exp varinfo6.stderr.exp \ + varinfo6.stderr.exp-ppc64 \ + varinforestrict.vgtest varinforestrict.stderr.exp \ ++ varlat.vgtest varlat.stderr.exp \ ++ varlat-env.vgtest varlat-env.stderr.exp \ ++ varlat-no.vgtest varlat-no.stderr.exp \ ++ varlat-yes.vgtest varlat-yes.stderr.exp \ ++ varlat2.vgtest varlat2.stderr.exp \ ++ varlat2-env.vgtest varlat2-env.stderr.exp \ ++ varlat2-no.vgtest varlat2-no.stderr.exp \ ++ varlat2-yes.vgtest varlat2-yes.stderr.exp \ + vcpu_bz2.stdout.exp vcpu_bz2.stderr.exp vcpu_bz2.vgtest \ + vcpu_fbench.stdout.exp vcpu_fbench.stderr.exp vcpu_fbench.vgtest \ + vcpu_fnfns.stdout.exp vcpu_fnfns.stdout.exp-glibc28-amd64 \ +@@ -563,6 +571,7 @@ check_PROGRAMS = \ + varinfo1 varinfo2 varinfo3 varinfo4 \ + varinfo5 varinfo5so.so varinfo6 \ + varinforestrict \ ++ varlat varlat2 \ + vcpu_fbench vcpu_fnfns \ + wcs \ + wcscat \ +@@ -881,6 +890,9 @@ else + endif + varinforestrict_CFLAGS = $(AM_CFLAGS) -O0 -g @FLAG_W_NO_MAYBE_UNINITIALIZED@ + ++varlat_CFLAGS = $(AM_CFLAGS) @FLAG_W_NO_UNINITIALIZED@ ++varlat2_CFLAGS = $(AM_CFLAGS) @FLAG_W_NO_UNINITIALIZED@ ++ + # Build shared object for wrap7 + wrap7_SOURCES = wrap7.c + wrap7_DEPENDENCIES = wrap7so.so +diff --git a/memcheck/tests/varlat-env.stderr.exp b/memcheck/tests/varlat-env.stderr.exp +new file mode 100644 +index 000000000..5393cd2dc +--- /dev/null ++++ b/memcheck/tests/varlat-env.stderr.exp +@@ -0,0 +1,3 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ ... ++ +diff --git a/memcheck/tests/varlat-env.vgtest b/memcheck/tests/varlat-env.vgtest +new file mode 100644 +index 000000000..1c0dffbb5 +--- /dev/null ++++ b/memcheck/tests/varlat-env.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS='--memcheck:variable-latency-errors=yes' ++prog: varlat ++vgopts: -q --command-line-only=no +diff --git a/memcheck/tests/varlat-no.stderr.exp b/memcheck/tests/varlat-no.stderr.exp +new file mode 100644 +index 000000000..e69de29bb +diff --git a/memcheck/tests/varlat-no.vgtest b/memcheck/tests/varlat-no.vgtest +new file mode 100644 +index 000000000..ed5fe4495 +--- /dev/null ++++ b/memcheck/tests/varlat-no.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat ++vgopts: -q +diff --git a/memcheck/tests/varlat-yes.stderr.exp b/memcheck/tests/varlat-yes.stderr.exp +new file mode 100644 +index 000000000..5393cd2dc +--- /dev/null ++++ b/memcheck/tests/varlat-yes.stderr.exp +@@ -0,0 +1,3 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ ... ++ +diff --git a/memcheck/tests/varlat-yes.vgtest b/memcheck/tests/varlat-yes.vgtest +new file mode 100644 +index 000000000..57a4d9d8e +--- /dev/null ++++ b/memcheck/tests/varlat-yes.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat ++vgopts: -q --variable-latency-errors=yes +diff --git a/memcheck/tests/varlat.c b/memcheck/tests/varlat.c +new file mode 100644 +index 000000000..98ae12dcb +--- /dev/null ++++ b/memcheck/tests/varlat.c +@@ -0,0 +1,16 @@ ++#include ++ ++volatile int storage; ++ ++void storage_init(char c) ++{ ++ storage = 100/(13|c); ++} ++ ++int main() ++{ ++ volatile char *x = malloc(1); ++ storage_init(x[0]); ++ free(x); ++ return 0; ++} +diff --git a/memcheck/tests/varlat.stderr.exp b/memcheck/tests/varlat.stderr.exp +new file mode 100644 +index 000000000..e69de29bb +diff --git a/memcheck/tests/varlat.vgtest b/memcheck/tests/varlat.vgtest +new file mode 100644 +index 000000000..6b55ac8f8 +--- /dev/null ++++ b/memcheck/tests/varlat.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat ++vgopts: -q --variable-latency-errors=no +diff --git a/memcheck/tests/varlat2-env.stderr.exp b/memcheck/tests/varlat2-env.stderr.exp +new file mode 100644 +index 000000000..5393cd2dc +--- /dev/null ++++ b/memcheck/tests/varlat2-env.stderr.exp +@@ -0,0 +1,3 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ ... ++ +diff --git a/memcheck/tests/varlat2-env.vgtest b/memcheck/tests/varlat2-env.vgtest +new file mode 100644 +index 000000000..8ade43b2f +--- /dev/null ++++ b/memcheck/tests/varlat2-env.vgtest +@@ -0,0 +1,3 @@ ++env: VALGRIND_TRY_OPTS='--memcheck:variable-latency-errors=yes' ++prog: varlat2 ++vgopts: -q --command-line-only=no +diff --git a/memcheck/tests/varlat2-no.stderr.exp b/memcheck/tests/varlat2-no.stderr.exp +new file mode 100644 +index 000000000..5393cd2dc +--- /dev/null ++++ b/memcheck/tests/varlat2-no.stderr.exp +@@ -0,0 +1,3 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ ... ++ +diff --git a/memcheck/tests/varlat2-no.vgtest b/memcheck/tests/varlat2-no.vgtest +new file mode 100644 +index 000000000..e608f987c +--- /dev/null ++++ b/memcheck/tests/varlat2-no.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat2 ++vgopts: -q +diff --git a/memcheck/tests/varlat2-yes.stderr.exp b/memcheck/tests/varlat2-yes.stderr.exp +new file mode 100644 +index 000000000..5393cd2dc +--- /dev/null ++++ b/memcheck/tests/varlat2-yes.stderr.exp +@@ -0,0 +1,3 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ ... ++ +diff --git a/memcheck/tests/varlat2-yes.vgtest b/memcheck/tests/varlat2-yes.vgtest +new file mode 100644 +index 000000000..d4c908305 +--- /dev/null ++++ b/memcheck/tests/varlat2-yes.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat2 ++vgopts: -q --variable-latency-errors=yes +diff --git a/memcheck/tests/varlat2.c b/memcheck/tests/varlat2.c +new file mode 100644 +index 000000000..7f4c9c1dd +--- /dev/null ++++ b/memcheck/tests/varlat2.c +@@ -0,0 +1,18 @@ ++#include ++#include "valgrind.h" ++ ++volatile int storage; ++ ++void storage_init(char c) ++{ ++ storage = 100/(13|c); ++} ++ ++int main() ++{ ++ volatile char *x = malloc(1); ++ VALGRIND_CLO_CHANGE("--variable-latency-errors=yes"); ++ storage_init(x[0]); ++ free(x); ++ return 0; ++} +diff --git a/memcheck/tests/varlat2.stderr.exp b/memcheck/tests/varlat2.stderr.exp +new file mode 100644 +index 000000000..1e7a51a5d +--- /dev/null ++++ b/memcheck/tests/varlat2.stderr.exp +@@ -0,0 +1,4 @@ ++Variable-latency instruction operand of size 4 is secret/uninitialised ++ at 0x........: storage_init (varlat2.c:8) ++ by 0x........: main (varlat2.c:15) ++ +diff --git a/memcheck/tests/varlat2.vgtest b/memcheck/tests/varlat2.vgtest +new file mode 100644 +index 000000000..4ef2fad30 +--- /dev/null ++++ b/memcheck/tests/varlat2.vgtest +@@ -0,0 +1,2 @@ ++prog: varlat2 ++vgopts: -q --variable-latency-errors=no diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt new file mode 100644 index 0000000000..465ae90904 --- /dev/null +++ b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt @@ -0,0 +1,23 @@ +diff --git a/memcheck/mc_errors.c b/memcheck/mc_errors.c +index 8d163cd90..0e7eaab8f 100644 +--- a/memcheck/mc_errors.c ++++ b/memcheck/mc_errors.c +@@ -1913,6 +1913,18 @@ SizeT MC_(get_extra_suppression_info) ( const Error* err, + return VG_(snprintf) (buf, nBuf, "%s(%s)", + extra->Err.FishyValue.function_name, + extra->Err.FishyValue.argument_name); ++ ++ } else if (Err_Value == ekind) { ++ /* For variable-latency (VARLAT) errors, add an extra line ++ in the generated suppression template so users can see ++ that this undefined-value is a variable-latency operand. */ ++ MC_Error* extra = VG_(get_error_extra)(err); ++ if (extra && extra->Err.Value.isLatency) { ++ return VG_(snprintf) (buf, nBuf, "variable-latency: yes"); ++ } else { ++ buf[0] = '\0'; ++ return 0; ++ } + } else { + buf[0] = '\0'; + return 0; diff --git a/tests/test_helpers.h b/tests/test_helpers.h index ad73fb4176..6d41f5b010 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -13,6 +13,10 @@ #include #define OQS_TEST_CT_CLASSIFY(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len) +#elif defined(__SANITIZE_MEMORY__) +#include +#define OQS_TEST_CT_CLASSIFY(addr, len) __msan_poison(addr, len) +#define OQS_TEST_CT_DECLASSIFY(addr, len) __msan_unpoison(addr, len) #else #define OQS_TEST_CT_CLASSIFY(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) From e1874c3158726495b69a24f57e4bb2e25ddb22f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Mon, 1 Jun 2026 18:58:34 +0200 Subject: [PATCH 02/15] fixed comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 141 ++---- .gitignore | 3 +- CONFIGURE.md | 7 +- tests/CMakeLists.txt | 12 +- tests/ct_tooling/analyze_results.py | 15 +- tests/ct_tooling/ct_test.sh | 31 +- tests/ct_tooling/tools/memsan/CMakeLists.txt | 246 ---------- tests/ct_tooling/tools/memsan/test_kem.c | 475 ------------------- tests/ct_tooling/tools/memsan/test_sig.c | 396 ---------------- tests/test_helpers.h | 2 +- tests/test_kem.c | 10 + tests/test_sig.c | 18 +- 12 files changed, 93 insertions(+), 1263 deletions(-) delete mode 100644 tests/ct_tooling/tools/memsan/CMakeLists.txt delete mode 100644 tests/ct_tooling/tools/memsan/test_kem.c delete mode 100644 tests/ct_tooling/tools/memsan/test_sig.c diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index def620c8fd..0f1ca3a49c 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -13,100 +13,45 @@ jobs: contents: write actions: write steps: + - name: Checkout code + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 + + - name: Install Python deps + run: python -m pip install pytest requests + + - name: Generate algorithm list + id: set + run: | + python3 - <<'PY' + import os, sys + sys.path.insert(0, 'tests') + import helpers + algs = helpers.available_kems_by_name() + helpers.available_sigs_by_name() + interactive_yaml = ( + "fields:\n" + " - label: runtime-options\n" + " properties:\n" + " description: Choose one or more algorithms to execute constant-time testing on\n" + " display: Select the algorithm(s) to execute constant-time testing on\n" + " type: multiselect\n" + " choices:\n" + ) + for a in algs: + interactive_yaml += f" - {a}\n" + interactive_yaml += " required: true\n" + with open(os.environ['GITHUB_OUTPUT'], 'a') as gout: + gout.write('interactive< /dev/null 2>&1 || true + git clone git://sourceware.org/git/valgrind.git valgrind_varlat_src> /dev/null 2>&1 cd valgrind_varlat_src - git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa > /dev/null 2>&1 || true + git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa > /dev/null 2>&1 echo "Applying Kyberslash patch" - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" > /dev/null 2>&1 || true - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" > /dev/null 2>&1 || true - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" > /dev/null 2>&1 || true + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" > /dev/null 2>&1 + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" > /dev/null 2>&1 + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" > /dev/null 2>&1 echo "Building Valgrind-Varlat" - ./autogen.sh > /dev/null 2>&1 || true - ./configure --prefix="$INSTALL_PREFIX" > /dev/null 2>&1 || true - make -j"$(nproc)" > /dev/null 2>&1 || true - make install > /dev/null 2>&1 || true + ./autogen.sh > /dev/null 2>&1 + ./configure --prefix="$INSTALL_PREFIX" > /dev/null 2>&1 + make -j"$(nproc)" > /dev/null 2>&1 + make install > /dev/null 2>&1 mv "$INSTALL_PREFIX/bin/valgrind" "$INSTALL_PREFIX/bin/valgrind_varlat" chmod -R u+rwX "$INSTALL_PREFIX" diff --git a/.gitignore b/.gitignore index c8faac9f0f..8aab8ad468 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ tags # CMake & testing /build* /tmp* +/CMakeFiles/ # MSVC .vs @@ -38,4 +39,4 @@ __pycache__ compile_commands.json # Generated by Nix flake -result/ +result/ \ No newline at end of file diff --git a/CONFIGURE.md b/CONFIGURE.md index 753392bd87..94db960d92 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -20,6 +20,7 @@ The following options can be passed to CMake before the build file generation pr - [USE_COVERAGE](#USE_COVERAGE) - [USE_SANITIZER](#USE_SANITIZER) - [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME) +- [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - [OQS_STRICT_WARNINGS](#OQS_STRICT_WARNINGS) - [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD) - [OQS_MEMOPT_BUILD](#OQS_MEMOPT_BUILD) @@ -238,9 +239,11 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE **Default**: Unset. -## OQS_ENABLE_TEST_CONSTANT_TIME +## OQS_ENABLE_TEST_CONSTANT_TIME and OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN -This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. +This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. + +Similarly, OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN enables constant-time testing using Clang's MemorySanitizer. See the documentation in [`tests/test_constant_time.py`](https://github.com/open-quantum-safe/liboqs/blob/main/tests/test_constant_time.py) for more usage information. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e777f75321..69821a064b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,7 @@ # SPDX-License-Identifier: MIT option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) +option(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN "Build test suite with support for MemorySanitizer-based detection of non-constant time behaviour." OFF) if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") @@ -79,7 +80,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) endif() endif() -add_executable(test_kem test_kem.c test_helpers.c) +add_executable(test_kem test_kem.c test_helpers.c ${TEST_EXTRA_SOURCES}) target_link_libraries(test_kem PRIVATE ${TEST_DEPS}) if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) @@ -107,6 +108,13 @@ target_link_libraries(speed_kem PRIVATE ${TEST_DEPS}) set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem) +# Add the MSan-specific RNG poison helper from the CT tooling directory +set(TEST_EXTRA_SOURCES "") +if(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) + list(APPEND TEST_EXTRA_SOURCES ct_tooling/tools/memsan/rng_poison_memsan.c) + add_compile_definitions(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +endif() + # SIG API tests add_executable(example_sig example_sig.c) target_link_libraries(example_sig PRIVATE ${TEST_DEPS}) @@ -164,7 +172,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) endif() endif() -add_executable(test_sig test_sig.c test_helpers.c) +add_executable(test_sig test_sig.c test_helpers.c ${TEST_EXTRA_SOURCES}) target_link_libraries(test_sig PRIVATE ${TEST_DEPS}) if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) # workaround for Windows .dll diff --git a/tests/ct_tooling/analyze_results.py b/tests/ct_tooling/analyze_results.py index 1bba74e9ce..8007a1f2e1 100644 --- a/tests/ct_tooling/analyze_results.py +++ b/tests/ct_tooling/analyze_results.py @@ -42,12 +42,6 @@ def parse_summary_file(filepath): content = f.read() header_re = re.compile(r'^Testing (?:KEM|SIG): (\S+) \.\.\. (?:PASS|FAIL)', re.MULTILINE) - count_re = re.compile(r'(?:→|->)?\s*(?:Found|Reached cap).*?(\d+).*?warnings', re.IGNORECASE) - - matches = list(header_re.finditer(content)) - for idx, m in enumerate(matches): - alg_name = m.group(1) - # Skip SPHINCS and SLH_DSA algorithms if 'SPHINCS' in alg_name or 'SLH_DSA' in alg_name: continue @@ -66,14 +60,13 @@ def parse_summary_file(filepath): return results +# Modify analyze_logs to dynamically process all builds and optimization levels def analyze_logs(): - """ - Expected layout: - ///kem/kem_summary_*.txt - ///sig/sig_summary_*.txt + """Walk input log directories and aggregate warning counts dynamically. + +======= """ - kem_data = defaultdict(lambda: defaultdict(dict)) # {build: {opt: {alg: warnings}}} sig_data = defaultdict(lambda: defaultdict(dict)) if not os.path.isdir(LOG_BASE_DIR): diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 647bba2068..e4f952106d 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -51,29 +51,13 @@ build() { SUP_FLAGS+=( "-fsanitize-ignorelist=$f" ) done - # Create backup files of the original tests files - mv "$LIBOQS_DIR/tests/CMakeLists.txt" "$LIBOQS_DIR/tests/CMakeLists.txt.bak" - mv "$LIBOQS_DIR/tests/test_kem.c" "$LIBOQS_DIR/tests/test_kem.c.bak" - mv "$LIBOQS_DIR/tests/test_sig.c" "$LIBOQS_DIR/tests/test_sig.c.bak" - - # Replace original tests/CMakeLists.txt, test_kem.c, and test_sig.txt for their "MemSan poisoned" version - cp "$SCRIPT_DIR/tools/memsan/CMakeLists.txt" "$LIBOQS_DIR/tests/CMakeLists.txt" - cp "$SCRIPT_DIR/tools/memsan/test_kem.c" "$LIBOQS_DIR/tests/test_kem.c" - cp "$SCRIPT_DIR/tools/memsan/test_sig.c" "$LIBOQS_DIR/tests/test_sig.c" - cp "$SCRIPT_DIR/tools/memsan/rng_poison_memsan.c" "$LIBOQS_DIR/tests/rng_poison_memsan.c" - cmake "${CMAKE_ARGS[@]}" \ -DBUILD_SHARED_LIBS=ON \ + -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ - -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 - cmake --build . -j$(nproc) > /dev/null 2>&1 - - # Restore the original test files with the backups - mv "$LIBOQS_DIR/tests/CMakeLists.txt.bak" "$LIBOQS_DIR/tests/CMakeLists.txt" - mv "$LIBOQS_DIR/tests/test_kem.c.bak" "$LIBOQS_DIR/tests/test_kem.c" - mv "$LIBOQS_DIR/tests/test_sig.c.bak" "$LIBOQS_DIR/tests/test_sig.c" - rm "$LIBOQS_DIR/tests/rng_poison_memsan.c" + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" + cmake --build . -j$(nproc) ;; *) echo "Unknown tool: $TOOL"; return 1 @@ -81,15 +65,6 @@ build() { esac } -cleanup() { - mv "$LIBOQS_DIR/tests/CMakeLists.txt.bak" "$LIBOQS_DIR/tests/CMakeLists.txt" 2>/dev/null || true - mv "$LIBOQS_DIR/tests/test_kem.c.bak" "$LIBOQS_DIR/tests/test_kem.c" 2>/dev/null || true - mv "$LIBOQS_DIR/tests/test_sig.c.bak" "$LIBOQS_DIR/tests/test_sig.c" 2>/dev/null || true - rm "$LIBOQS_DIR/tests/rng_poison_memsan.c" 2>/dev/null || true -} - -trap cleanup EXIT INT - test() { local TOOL=$1 local BUILD_DIR=$2 diff --git a/tests/ct_tooling/tools/memsan/CMakeLists.txt b/tests/ct_tooling/tools/memsan/CMakeLists.txt deleted file mode 100644 index e6dbc23f3f..0000000000 --- a/tests/ct_tooling/tools/memsan/CMakeLists.txt +++ /dev/null @@ -1,246 +0,0 @@ -# SPDX-License-Identifier: MIT - -option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) - -if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR - CMAKE_C_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wbad-function-cast) -endif() -if(CMAKE_C_COMPILER_ID MATCHES "Clang") - add_compile_options(-Wcast-qual) - add_compile_options(-Wnarrowing) - add_compile_options(-Wconversion) -endif() -if (MINGW OR MSYS OR CYGWIN) - add_definitions(-D__USE_MINGW_ANSI_STDIO=1) - add_compile_options(-Wno-unknown-pragmas) - add_compile_options(-Wno-unused-parameter) -endif() -if(WIN32 AND NOT (MINGW OR MSYS OR CYGWIN)) - # ignore warning for test apps - add_compile_options(/wd4996 /wd4244) -endif() - -if(OQS_USE_OPENSSL) - include_directories(${OPENSSL_INCLUDE_DIR}) -endif() - -if(NOT WIN32) - set(LIBM m) -endif() - -# List oqs-internal after oqs so that oqs has linking precedence. -if(${OQS_USE_OPENSSL}) - find_package(OpenSSL 1.1.1 REQUIRED) - set(TEST_DEPS oqs oqs-internal ${LIBM} OpenSSL::Crypto) -else() - set(TEST_DEPS oqs oqs-internal ${LIBM}) -endif() - -if(OQS_USE_PTHREADS) - set(TEST_DEPS ${TEST_DEPS} Threads::Threads) -endif() - -if(NOT WIN32) - execute_process(COMMAND ${PROJECT_SOURCE_DIR}/scripts/git_commit.sh OUTPUT_VARIABLE GIT_COMMIT) - add_definitions(-DOQS_COMPILE_GIT_COMMIT="${GIT_COMMIT}") - - add_executable(test_aes test_aes.c) - target_link_libraries(test_aes PRIVATE ${TEST_DEPS}) - - add_executable(test_hash test_hash.c) - target_link_libraries(test_hash PRIVATE ${TEST_DEPS}) - - add_executable(test_sha3 test_sha3.c) - target_link_libraries(test_sha3 PRIVATE ${TEST_DEPS}) - - add_executable(speed_common speed_common.c) - target_link_libraries(speed_common PRIVATE ${TEST_DEPS}) - - set(UNIX_TESTS test_aes test_hash test_sha3 speed_common) - - set(PYTHON3_EXEC python3) -else() - set(PYTHON3_EXEC python) -endif() - -# KEM API tests -add_executable(example_kem example_kem.c) -target_link_libraries(example_kem PRIVATE ${TEST_DEPS}) - -add_executable(kat_kem kat_kem.c test_helpers.c) -target_link_libraries(kat_kem PRIVATE ${TEST_DEPS}) -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) - target_link_options(kat_kem PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(kat_kem PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(test_kem test_kem.c test_helpers.c rng_poison_memsan.c) -target_link_libraries(test_kem PRIVATE ${TEST_DEPS}) - -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) - target_link_options(test_kem PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(test_kem PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(test_kem_mem test_kem_mem.c test_helpers.c) -target_link_libraries(test_kem_mem PRIVATE ${TEST_DEPS}) -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) - target_link_options(test_kem_mem PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(test_kem_mem PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(speed_kem speed_kem.c) -target_link_libraries(speed_kem PRIVATE ${TEST_DEPS}) - -set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem) - -# SIG API tests -add_executable(example_sig example_sig.c) -target_link_libraries(example_sig PRIVATE ${TEST_DEPS}) - -if(OQS_BUILD_FUZZ_TESTS AND '${CMAKE_C_COMPILER_ID}' STREQUAL 'Clang') - add_executable(fuzz_test_sig fuzz_test_sig.c) - target_link_libraries(fuzz_test_sig PRIVATE ${TEST_DEPS}) - set_target_properties(fuzz_test_sig PROPERTIES - COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}" - LINK_FLAGS "${FUZZING_LINK_FLAGS}" - ) - add_executable(fuzz_test_kem fuzz_test_kem.c) - target_link_libraries(fuzz_test_kem PRIVATE ${TEST_DEPS}) - set_target_properties(fuzz_test_kem PROPERTIES - COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}" - LINK_FLAGS "${FUZZING_LINK_FLAGS}" - ) -endif() - -# Stateful SIG API tests -add_executable(example_sig_stfl example_sig_stfl.c) -target_link_libraries(example_sig_stfl PRIVATE ${TEST_DEPS}) - -add_executable(kat_sig kat_sig.c test_helpers.c) -target_link_libraries(kat_sig PRIVATE ${TEST_DEPS}) -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) - target_link_options(kat_sig PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(kat_sig PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(kat_sig_stfl kat_sig_stfl.c test_helpers.c) -target_link_libraries(kat_sig_stfl PRIVATE ${TEST_DEPS}) -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(CMAKE_CROSSCOMPILING) - target_link_options(kat_sig_stfl PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(kat_sig_stfl PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(test_sig test_sig.c test_helpers.c rng_poison_memsan.c) -target_link_libraries(test_sig PRIVATE ${TEST_DEPS}) -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(CMAKE_CROSSCOMPILING) - target_link_options(test_sig PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(test_sig PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(test_sig_mem test_sig_mem.c) -target_link_libraries(test_sig_mem PRIVATE ${TEST_DEPS}) - -add_executable(speed_sig speed_sig.c) -target_link_libraries(speed_sig PRIVATE ${TEST_DEPS}) - - -set(SIG_TESTS example_sig kat_sig test_sig test_sig_mem speed_sig vectors_sig) - -# SIG_STFL API tests -add_executable(test_sig_stfl test_sig_stfl.c test_helpers.c) -if((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_C_COMPILER_ID STREQUAL "GNU")) - target_link_libraries(test_sig_stfl PRIVATE ${TEST_DEPS} Threads::Threads) -else () - target_link_libraries(test_sig_stfl PRIVATE ${TEST_DEPS}) -endif() -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(CMAKE_CROSSCOMPILING) - target_link_options(test_sig_stfl PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(test_sig_stfl PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -add_executable(speed_sig_stfl speed_sig_stfl.c) -target_link_libraries(speed_sig_stfl PRIVATE ${TEST_DEPS}) - -set(SIG_STFL_TESTS kat_sig_stfl test_sig_stfl speed_sig_stfl) - -add_executable(dump_alg_info dump_alg_info.c) -target_link_libraries(dump_alg_info PRIVATE ${TEST_DEPS}) - -# Intermediate values vector tests -add_executable(vectors_sig vectors_sig.c) -target_link_libraries(vectors_sig PRIVATE ${TEST_DEPS}) - -add_executable(vectors_kem vectors_kem.c) -target_link_libraries(vectors_kem PRIVATE ${TEST_DEPS}) - -if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) - # workaround for Windows .dll - if(MINGW OR MSYS OR CYGWIN OR CMAKE_CROSSCOMPILING) - target_link_options(vectors_kem PRIVATE -Wl,--allow-multiple-definition) - else() - target_link_options(vectors_kem PRIVATE "/FORCE:MULTIPLE") - endif() -endif() - -# Enable Valgrind-based timing side-channel analysis for test_kem and test_sig -if(OQS_ENABLE_TEST_CONSTANT_TIME AND NOT OQS_DEBUG_BUILD) - message(WARNING "OQS_ENABLE_TEST_CONSTANT_TIME is incompatible with CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}.") - set(OQS_ENABLE_TEST_CONSTANT_TIME OFF) -endif() - -# Record compile options -- from target speed_kem - don't set any options only for speed_kem! -get_property(OQS_COMPILE_OPTIONS TARGET speed_kem PROPERTY COMPILE_OPTIONS) -add_definitions(-DOQS_COMPILE_OPTIONS="[${OQS_COMPILE_OPTIONS}]") - -if (CMAKE_GENERATOR MATCHES "Visual Studio") - # With Visual studio the output of tests go into a folder with the configuration option. Force it to the same folder as if - # generating with Ninja - set_target_properties( - dump_alg_info ${KEM_TESTS} ${SIG_TESTS} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}/tests" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/tests" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/tests" - RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}/tests") -endif() - -# TODO: Get CMake to find python. -# and set PATH variable in Windows -# for DLL builds. -add_custom_target( - run_tests - # skip long KAT tests - COMMAND ${CMAKE_COMMAND} -E env OQS_BUILD_DIR=${CMAKE_BINARY_DIR} ${PYTHON3_EXEC} -m pytest --verbose --numprocesses=auto --ignore=scripts/copy_from_upstream/repos --ignore=tests/test_kat_all.py - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - DEPENDS oqs dump_alg_info ${KEM_TESTS} ${SIG_TESTS} ${SIG_STFL_TESTS} ${UNIX_TESTS} - USES_TERMINAL) \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/test_kem.c b/tests/ct_tooling/tools/memsan/test_kem.c deleted file mode 100644 index eb4a76e6cf..0000000000 --- a/tests/ct_tooling/tools/memsan/test_kem.c +++ /dev/null @@ -1,475 +0,0 @@ -// SPDX-License-Identifier: MIT - -#include -#include -#include -#if defined(_WIN32) -#include -#define strcasecmp _stricmp -#else -#include -#endif - -#include -#include -#if OQS_USE_PTHREADS -#include -#endif - -#ifdef OQS_ENABLE_KEM_ML_KEM -#define MLKEM_SECRET_LEN 32 -#endif - -#include "system_info.c" -#include "test_helpers.h" - -#include // Include MemSan for CT testing - -#ifdef OQS_ENABLE_KEM_ML_KEM -/* mlkem rejection key testcase */ -static bool mlkem_rej_testcase(OQS_KEM *kem, uint8_t *ciphertext, uint8_t *secret_key) { - // sanity checks - if ((kem == NULL) || (ciphertext == NULL) || (secret_key == NULL)) { - fprintf(stderr, "ERROR: inputs NULL!\n"); - return false; - } - // Only run tests for ML-KEM - if (!(strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_512) == 0 || - strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_768) == 0 || - strcasecmp(kem->method_name, OQS_KEM_alg_ml_kem_1024) == 0)) { - return true; - } - // Buffer to hold z and c. z is always 32 bytes - uint8_t *buff_z_c = NULL; - bool retval = false; - OQS_STATUS rc; - int rv; - size_t length_z_c = 32 + kem->length_ciphertext; - buff_z_c = OQS_MEM_malloc(length_z_c); - if (buff_z_c == NULL) { - fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); - return false; - } - // Scenario 1: Test rejection key by corrupting the secret key - secret_key[0] += 1; - uint8_t shared_secret_r[MLKEM_SECRET_LEN]; // expected output - uint8_t shared_secret_d[MLKEM_SECRET_LEN]; // calculated output - memcpy(buff_z_c, &secret_key[kem->length_secret_key - 32], 32); - memcpy(&buff_z_c[MLKEM_SECRET_LEN], ciphertext, kem->length_ciphertext); - // Calculate expected secret in case of corrupted cipher: shake256(z || c) - OQS_SHA3_shake256(shared_secret_r, MLKEM_SECRET_LEN, buff_z_c, length_z_c); - rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_decaps failed for rejection testcase scenario 1\n"); - goto cleanup; - } - OQS_TEST_CT_DECLASSIFY(shared_secret_d, MLKEM_SECRET_LEN); - OQS_TEST_CT_DECLASSIFY(shared_secret_r, MLKEM_SECRET_LEN); - rv = memcmp(shared_secret_d, shared_secret_r, MLKEM_SECRET_LEN); - if (rv != 0) { - fprintf(stderr, "ERROR: shared secrets are not equal for rejection key in decapsulation scenario 1\n"); - OQS_print_hex_string("shared_secret_d", shared_secret_d, MLKEM_SECRET_LEN); - OQS_print_hex_string("shared_secret_r", shared_secret_r, MLKEM_SECRET_LEN); - goto cleanup; - } - secret_key[0] -= 1; // Restore private key - OQS_MEM_cleanse(buff_z_c, length_z_c); // Reset buffer - - // Scenario 2: Test rejection key by corrupting the ciphertext - ciphertext[0] += 1; - memcpy(buff_z_c, &secret_key[kem->length_secret_key - 32], 32); - memcpy(&buff_z_c[MLKEM_SECRET_LEN], ciphertext, kem->length_ciphertext); - - // Calculate expected secret in case of corrupted cipher: shake256(z || c) - OQS_SHA3_shake256(shared_secret_r, MLKEM_SECRET_LEN, buff_z_c, length_z_c); - rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_decaps failed for rejection testcase scenario 2\n"); - goto cleanup; - } - OQS_TEST_CT_DECLASSIFY(shared_secret_d, MLKEM_SECRET_LEN); - OQS_TEST_CT_DECLASSIFY(shared_secret_r, MLKEM_SECRET_LEN); - rv = memcmp(shared_secret_d, shared_secret_r, MLKEM_SECRET_LEN); - if (rv != 0) { - fprintf(stderr, "ERROR: shared secrets are not equal for rejection key in decapsulation scenario 2\n"); - OQS_print_hex_string("shared_secret_d", shared_secret_d, MLKEM_SECRET_LEN); - OQS_print_hex_string("shared_secret_r", shared_secret_r, MLKEM_SECRET_LEN); - goto cleanup; - } - ciphertext[0] -= 1; // Restore ciphertext - retval = true; -cleanup: - if (buff_z_c) { - OQS_MEM_secure_free(buff_z_c, length_z_c); - } - return retval; -} -#endif //OQS_ENABLE_KEM_ML_KEM - -typedef struct magic_s { - uint8_t val[31]; -} magic_t; - -static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { - - OQS_KEM *kem = NULL; - uint8_t *public_key = NULL; - uint8_t *secret_key = NULL; - uint8_t *ciphertext = NULL; - uint8_t *shared_secret_e = NULL; - uint8_t *shared_secret_d = NULL; - uint8_t *keypair_seed = NULL; - uint8_t *encaps_seed = NULL; - OQS_STATUS rc, ret = OQS_ERROR; - int rv; - - //The magic numbers are random values. - //The length of the magic number was chosen to be 31 to break alignment - magic_t magic; - OQS_randombytes(magic.val, sizeof(magic_t)); - - kem = OQS_KEM_new(method_name); - if (kem == NULL) { - fprintf(stderr, "ERROR: OQS_KEM_new failed\n"); - goto err; - } - - if (!derand) { - printf("================================================================================\n"); - printf("sample computation for KEM %s\n", kem->method_name); - printf("Version source: %s\n", kem->alg_version); - printf("================================================================================\n"); - } - - /* pk, sk, ciphertext, shared secret, and shared secret cmp are needed - * regardless of derand or not - */ - public_key = OQS_MEM_malloc(kem->length_public_key + 2 * sizeof(magic_t)); - secret_key = OQS_MEM_malloc(kem->length_secret_key + 2 * sizeof(magic_t)); - ciphertext = OQS_MEM_malloc(kem->length_ciphertext + 2 * sizeof(magic_t)); - shared_secret_e = OQS_MEM_malloc(kem->length_shared_secret + 2 * sizeof(magic_t)); - shared_secret_d = OQS_MEM_malloc(kem->length_shared_secret + 2 * sizeof(magic_t)); - if ((public_key == NULL) || (secret_key == NULL) || (ciphertext == NULL) || (shared_secret_e == NULL) || (shared_secret_d == NULL)) { - fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); - goto err; - } - memcpy(public_key, magic.val, sizeof(magic_t)); - memcpy(secret_key, magic.val, sizeof(magic_t)); - memcpy(ciphertext, magic.val, sizeof(magic_t)); - memcpy(shared_secret_e, magic.val, sizeof(magic_t)); - memcpy(shared_secret_d, magic.val, sizeof(magic_t)); - public_key += sizeof(magic_t); - secret_key += sizeof(magic_t); - ciphertext += sizeof(magic_t); - shared_secret_e += sizeof(magic_t); - shared_secret_d += sizeof(magic_t); - memcpy(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); - memcpy(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); - memcpy(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); - memcpy(shared_secret_e + kem->length_shared_secret, magic.val, sizeof(magic_t)); - memcpy(shared_secret_d + kem->length_shared_secret, magic.val, sizeof(magic_t)); - - if (derand) { - /* keypair_seed and encaps_seed are only needed if derand */ - keypair_seed = malloc(kem->length_keypair_seed + 2 * sizeof(magic_t)); - if (!keypair_seed) { - fprintf(stderr, "Failed to allocate %zu bytes for keypair seed\n", - kem->length_keypair_seed); - goto err; - } - encaps_seed = malloc(kem->length_encaps_seed + 2 * sizeof(magic_t)); - if (!encaps_seed) { - fprintf(stderr, "Failed to allocate %zu bytes for encaps seed\n", - kem->length_encaps_seed); - goto err; - } - memcpy(keypair_seed, magic.val, sizeof(magic_t)); - memcpy(encaps_seed, magic.val, sizeof(magic_t)); - keypair_seed += sizeof(magic_t); - encaps_seed += sizeof(magic_t); - memcpy(keypair_seed + kem->length_keypair_seed, magic.val, - sizeof(magic_t)); - memcpy(encaps_seed + kem->length_encaps_seed, magic.val, - sizeof(magic_t)); - } - - - - if (derand) { - // On some systems, getentropy fails if given a zero-length array - if (kem->length_keypair_seed > 0) { - OQS_randombytes(keypair_seed, kem->length_keypair_seed); - } - rc = OQS_KEM_keypair_derand(kem, public_key, secret_key, keypair_seed); - __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (kem->length_keypair_seed == 0) { - // If length_keypair_seed is set to 0 for this KEM scheme, a failure is expected - if (rc != OQS_ERROR) { - fprintf(stderr, "ERROR: OQS_KEM_keypair_derand succeeded but expected a failure\n"); - goto err; - } - printf("OQS_KEM_keypair_derand failed, as expected\n"); - ret = OQS_SUCCESS; - goto cleanup; - } else { - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_keypair_derand failed\n"); - goto err; - } - } - } else { - rc = OQS_KEM_keypair(kem, public_key, secret_key); - __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_keypair failed\n"); - goto err; - } - } - - OQS_TEST_CT_DECLASSIFY(public_key, kem->length_public_key); - if (derand) { - // On some systems, getentropy fails if given a zero-length array - if (kem->length_encaps_seed > 0) { - OQS_randombytes(encaps_seed, kem->length_encaps_seed); - } - rc = OQS_KEM_encaps_derand(kem, ciphertext, shared_secret_e, public_key, encaps_seed); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (kem->length_encaps_seed == 0) { - // If length_encaps_seed is set to 0 for this KEM scheme, a failure is expected - if (rc != OQS_ERROR) { - fprintf(stderr, "ERROR: OQS_KEM_encaps_derand succeeded but expected a failure\n"); - goto err; - } - printf("OQS_KEM_encaps_derand failed, as expected\n"); - ret = OQS_SUCCESS; - goto cleanup; - } else { - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_encaps_derand failed\n"); - goto err; - } - } - } else { - rc = OQS_KEM_encaps(kem, ciphertext, shared_secret_e, public_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_encaps failed\n"); - goto err; - } - } - - OQS_TEST_CT_DECLASSIFY(ciphertext, kem->length_ciphertext); - rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_KEM_decaps failed\n"); - goto err; - } - - OQS_TEST_CT_DECLASSIFY(shared_secret_d, kem->length_shared_secret); - OQS_TEST_CT_DECLASSIFY(shared_secret_e, kem->length_shared_secret); - rv = memcmp(shared_secret_e, shared_secret_d, kem->length_shared_secret); - if (rv != 0) { - fprintf(stderr, "ERROR: shared secrets are not equal\n"); - OQS_print_hex_string("shared_secret_e", shared_secret_e, kem->length_shared_secret); - OQS_print_hex_string("shared_secret_d", shared_secret_d, kem->length_shared_secret); - goto err; - } else { - printf("shared secrets are equal\n"); - } - -#ifdef OQS_ENABLE_KEM_ML_KEM - /* check mlkem rejection testcases. returns true for all other kem algos */ - if (false == mlkem_rej_testcase(kem, ciphertext, secret_key)) { - goto err; - } -#endif - - // test invalid encapsulation (call should either fail or result in invalid shared secret) - OQS_randombytes(ciphertext, kem->length_ciphertext); - OQS_TEST_CT_DECLASSIFY(ciphertext, kem->length_ciphertext); - rc = OQS_KEM_decaps(kem, shared_secret_d, ciphertext, secret_key); - OQS_TEST_CT_DECLASSIFY(shared_secret_d, kem->length_shared_secret); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc == OQS_SUCCESS && memcmp(shared_secret_e, shared_secret_d, kem->length_shared_secret) == 0) { - fprintf(stderr, "ERROR: OQS_KEM_decaps succeeded on wrong input\n"); - goto err; - } - -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME - rv = memcmp(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); - rv |= memcmp(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); - rv |= memcmp(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); - rv |= memcmp(shared_secret_e + kem->length_shared_secret, magic.val, sizeof(magic_t)); - rv |= memcmp(shared_secret_d + kem->length_shared_secret, magic.val, sizeof(magic_t)); - rv |= memcmp(public_key - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(secret_key - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(ciphertext - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(shared_secret_e - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(shared_secret_d - sizeof(magic_t), magic.val, sizeof(magic_t)); - if (derand) { - rv |= memcmp(keypair_seed + kem->length_keypair_seed, magic.val, sizeof(magic_t)); - rv |= memcmp(encaps_seed + kem->length_encaps_seed, magic.val, sizeof(magic_t)); - rv |= memcmp(keypair_seed - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(encaps_seed - sizeof(magic_t), magic.val, sizeof(magic_t)); - } - if (rv != 0) { - fprintf(stderr, "ERROR: Magic numbers do not match\n"); - goto err; - } -#endif - - ret = OQS_SUCCESS; - goto cleanup; - -err: - ret = OQS_ERROR; - -cleanup: - if ((secret_key) && (kem != NULL)) { - OQS_MEM_secure_free(secret_key - sizeof(magic_t), kem->length_secret_key + 2 * sizeof(magic_t)); - } - if ((shared_secret_e) && (kem != NULL)) { - OQS_MEM_secure_free(shared_secret_e - sizeof(magic_t), kem->length_shared_secret + 2 * sizeof(magic_t)); - } - if ((shared_secret_d) && (kem != NULL)) { - OQS_MEM_secure_free(shared_secret_d - sizeof(magic_t), kem->length_shared_secret + 2 * sizeof(magic_t)); - } - if (public_key) { - OQS_MEM_insecure_free(public_key - sizeof(magic_t)); - } - if (ciphertext) { - OQS_MEM_insecure_free(ciphertext - sizeof(magic_t)); - } - if ((keypair_seed) && (kem != NULL)) { - OQS_MEM_secure_free(keypair_seed - sizeof(magic_t), kem->length_keypair_seed + 2 * sizeof(magic_t)); - } - if ((encaps_seed) && (kem != NULL)) { - OQS_MEM_secure_free(encaps_seed - sizeof(magic_t), kem->length_encaps_seed + 2 * sizeof(magic_t)); - } - OQS_KEM_free(kem); - - return ret; -} - -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME -static void TEST_KEM_randombytes(uint8_t *random_array, size_t bytes_to_read) { - // We can't make direct calls to the system randombytes on some platforms, - // so we have to swap out the OQS_randombytes provider. - (void)OQS_randombytes_switch_algorithm("system"); - OQS_randombytes(random_array, bytes_to_read); - OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); - - // OQS_TEST_CT_CLASSIFY tells Valgrind's memcheck tool to issue a warning if - // the program branches on any byte that depends on random_array. This helps us - // identify timing side-channels, as these bytes often contain secret data. - OQS_TEST_CT_CLASSIFY(random_array, bytes_to_read); -} -#endif - -#if OQS_USE_PTHREADS -struct thread_data { - char *alg_name; - OQS_STATUS rc; -}; - -void *test_wrapper(void *arg) { - struct thread_data *td = arg; - td->rc = kem_test_correctness(td->alg_name, false); - if (td->rc == OQS_SUCCESS) { - // test derandomized operations - td->rc = kem_test_correctness(td->alg_name, true); - } - OQS_thread_stop(); - return NULL; -} -#endif - -int main(int argc, char **argv) { - OQS_STATUS rc; - OQS_init(); - - printf("Testing KEM algorithms using liboqs version %s\n", OQS_version()); - - if (argc != 2) { - fprintf(stderr, "Usage: test_kem algname\n"); - fprintf(stderr, " algname: "); - for (size_t i = 0; i < OQS_KEM_algs_length; i++) { - if (i > 0) { - fprintf(stderr, ", "); - } - fprintf(stderr, "%s", OQS_KEM_alg_identifier(i)); - } - fprintf(stderr, "\n"); - OQS_destroy(); - return EXIT_FAILURE; - } - - print_system_info(); - - char *alg_name = argv[1]; - if (!OQS_KEM_alg_is_enabled(alg_name)) { - printf("KEM algorithm %s not enabled!\n", alg_name); - OQS_destroy(); - return EXIT_FAILURE; - } - -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME - OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); -#else - rc = OQS_randombytes_switch_algorithm("system"); - if (rc != OQS_SUCCESS) { - printf("Could not generate random data with system RNG\n"); - OQS_destroy(); - return EXIT_FAILURE; - } -#endif - -#if OQS_USE_PTHREADS -#define MAX_LEN_KEM_NAME_ 64 - // don't run Classic McEliece in threads because of large stack usage - char no_thread_kem_patterns[][MAX_LEN_KEM_NAME_] = {"Classic-McEliece", "HQC-256-"}; - int test_in_thread = 1; - for (size_t i = 0 ; i < sizeof(no_thread_kem_patterns) / MAX_LEN_KEM_NAME_; ++i) { - if (strstr(alg_name, no_thread_kem_patterns[i]) != NULL) { - test_in_thread = 0; - break; - } - } - if (test_in_thread) { - pthread_t thread; - struct thread_data td; - td.alg_name = alg_name; - int trc = pthread_create(&thread, NULL, test_wrapper, &td); - if (trc) { - fprintf(stderr, "ERROR: Creating pthread\n"); - OQS_destroy(); - return EXIT_FAILURE; - } - pthread_join(thread, NULL); - rc = td.rc; - } else { - rc = kem_test_correctness(alg_name, false); - if (rc == OQS_SUCCESS) { - // test with derandomized keygen - rc = kem_test_correctness(alg_name, true); - } - } -#else - rc = kem_test_correctness(alg_name, false); - if (rc == OQS_SUCCESS) { - // test with derandomized keygen - rc = kem_test_correctness(alg_name, true); - } -#endif - if (rc != OQS_SUCCESS) { - OQS_destroy(); - return EXIT_FAILURE; - } - OQS_destroy(); - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/tests/ct_tooling/tools/memsan/test_sig.c b/tests/ct_tooling/tools/memsan/test_sig.c deleted file mode 100644 index 253babfcb6..0000000000 --- a/tests/ct_tooling/tools/memsan/test_sig.c +++ /dev/null @@ -1,396 +0,0 @@ -// SPDX-License-Identifier: MIT - -#if defined(_WIN32) -#pragma warning(disable : 4244 4293) -#endif - -#include -#include -#include - -#include - -#if OQS_USE_PTHREADS -#include -#endif - -#include "system_info.c" -#include "test_helpers.h" - -#include // Include MemSan for CT testing - -typedef struct magic_s { - uint8_t val[31]; -} magic_t; - -static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_all[2], size_t bitflips[2], bool extended_tests) { - - OQS_SIG *sig = NULL; - uint8_t *public_key = NULL; - uint8_t *secret_key = NULL; - uint8_t *message = NULL; - size_t message_len = 100; - uint8_t ctx[257] = { 0 }; - uint8_t *signature = NULL; - size_t signature_len; - OQS_STATUS rc, ret = OQS_ERROR; - - //The magic numbers are random values. - //The length of the magic number was chosen to be 31 to break alignment - magic_t magic; - OQS_randombytes(magic.val, sizeof(magic_t)); - - printf(extended_tests ? "Extended tests enabled\n" : "Extended tests disabled\n"); - - - sig = OQS_SIG_new(method_name); - if (sig == NULL) { - fprintf(stderr, "ERROR: OQS_SIG_new failed\n"); - goto err; - } - - char bitflips_as_str[2][50]; - for (int i = 0; i < 2; i++) { - if (bitflips_all[i]) { - snprintf(bitflips_as_str[i], sizeof(bitflips_as_str[i]), "all"); - } else { - snprintf(bitflips_as_str[i], sizeof(bitflips_as_str[i]), "%zu random", bitflips[i]); - } - } - - printf("================================================================================\n"); - printf("Sample computation for signature %s\n", sig->method_name); - if (sig->euf_cma) { - printf("Testing EUF-CMA by flipping %s bits of the message\n", bitflips_as_str[0]); - } - if (sig->suf_cma) { - printf("Testing SUF-CMA by flipping %s bits of the signature\n", bitflips_as_str[1]); - } - printf("Version source: %s\n", sig->alg_version); - printf("================================================================================\n"); - - public_key = OQS_MEM_malloc(sig->length_public_key + 2 * sizeof(magic_t)); - secret_key = OQS_MEM_malloc(sig->length_secret_key + 2 * sizeof(magic_t)); - message = OQS_MEM_malloc(message_len + 2 * sizeof(magic_t)); - signature = OQS_MEM_malloc(sig->length_signature + 2 * sizeof(magic_t)); - - if ((public_key == NULL) || (secret_key == NULL) || (message == NULL) || (signature == NULL)) { - fprintf(stderr, "ERROR: OQS_MEM_malloc failed\n"); - goto err; - } - - //Set the magic numbers before - memcpy(public_key, magic.val, sizeof(magic_t)); - memcpy(secret_key, magic.val, sizeof(magic_t)); - memcpy(message, magic.val, sizeof(magic_t)); - memcpy(signature, magic.val, sizeof(magic_t)); - - public_key += sizeof(magic_t); - secret_key += sizeof(magic_t); - message += sizeof(magic_t); - signature += sizeof(magic_t); - - // and after - memcpy(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); - memcpy(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); - memcpy(message + message_len, magic.val, sizeof(magic_t)); - memcpy(signature + sig->length_signature, magic.val, sizeof(magic_t)); - - OQS_randombytes(message, message_len); - OQS_TEST_CT_DECLASSIFY(message, message_len); - - rc = OQS_SIG_keypair(sig, public_key, secret_key); - __msan_unpoison(public_key, sig->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_keypair failed\n"); - goto err; - } - - rc = OQS_SIG_sign(sig, signature, &signature_len, message, message_len, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_sign failed\n"); - goto err; - } - - /* Commenting so that MemSan constant-time testing does not run on signature verification - - OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); - OQS_TEST_CT_DECLASSIFY(signature, signature_len); - rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); - goto err; - } - - if (extended_tests) { - rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, false, NULL, 0); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - goto err; - } - } */ - - /* testing signing with context, if supported */ - OQS_randombytes(ctx, 257); - if (sig->sig_with_ctx_support) { - size_t ctx_step = 1; - // Only do a small fraction of the context sizes for SLH_DSA for efficiency purposes - if (!strncmp(sig->method_name, "SLH_DSA", 7)) { - ctx_step = 61; // using a prime slightly smaller than a power of 2 to avoid only testing word/block aligned values - } - for (size_t i = 0; i < 256; ++i) { - if (((i % ctx_step == 0) && extended_tests) || i == 255) { - rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, i, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str failed\n"); - goto err; - } - - /* Commenting so that MemSan constant-time testing does not run on signature verification - - OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); - OQS_TEST_CT_DECLASSIFY(signature, signature_len); - rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); - goto err; - } - - if (extended_tests) { - rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - goto err; - } - } */ - } - } - - if (extended_tests) { - rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, 256, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_ERROR) { - fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should only support up to 255 byte contexts\n"); - goto err; - } - } - } else if (extended_tests) { - rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, ctx, 1, secret_key); - if (rc != OQS_ERROR) { - fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should fail without support for context strings\n"); - goto err; - } - } - - if (extended_tests) { - rc = OQS_SIG_sign_with_ctx_str(sig, signature, &signature_len, message, message_len, NULL, 0, secret_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should always succeed when providing a NULL context string\n"); - goto err; - } - - /* Commenting so that MemSan constant-time testing does not run on signature verification - - OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); - OQS_TEST_CT_DECLASSIFY(signature, signature_len); - rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, NULL, 0, public_key); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); - goto err; - } - - rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, NULL, 0); - OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); - if (rc != OQS_SUCCESS) { - goto err; - } */ - } - -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME - /* check magic values */ - int rv = memcmp(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); - rv |= memcmp(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); - rv |= memcmp(message + message_len, magic.val, sizeof(magic_t)); - rv |= memcmp(signature + sig->length_signature, magic.val, sizeof(magic_t)); - rv |= memcmp(public_key - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(secret_key - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(message - sizeof(magic_t), magic.val, sizeof(magic_t)); - rv |= memcmp(signature - sizeof(magic_t), magic.val, sizeof(magic_t)); - if (rv) { - fprintf(stderr, "ERROR: Magic numbers do not match\n"); - goto err; - } -#endif - - printf("verification passes as expected\n"); - ret = OQS_SUCCESS; - goto cleanup; - -err: - ret = OQS_ERROR; - -cleanup: - if ((secret_key) && (sig != NULL)) { - OQS_MEM_secure_free(secret_key - sizeof(magic_t), sig->length_secret_key + 2 * sizeof(magic_t)); - } - if (public_key) { - OQS_MEM_insecure_free(public_key - sizeof(magic_t)); - } - if (message) { - OQS_MEM_insecure_free(message - sizeof(magic_t)); - } - if (signature) { - OQS_MEM_insecure_free(signature - sizeof(magic_t)); - } - OQS_SIG_free(sig); - - return ret; -} - -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME -static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { - // We can't make direct calls to the system randombytes on some platforms, - // so we have to swap out the OQS_randombytes provider. - (void)OQS_randombytes_switch_algorithm("system"); - OQS_randombytes(random_array, bytes_to_read); - OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); - - // OQS_TEST_CT_CLASSIFY tells Valgrind's memcheck tool to issue a warning if - // the program branches on any byte that depends on random_array. This helps us - // identify timing side-channels, as these bytes often contain secret data. - OQS_TEST_CT_CLASSIFY(random_array, bytes_to_read); -} -#endif - -#if OQS_USE_PTHREADS -struct thread_data { - char *alg_name; - bool *bitflips_all; - size_t *bitflips; - bool extended_tests; - OQS_STATUS rc; -}; - -void *test_wrapper(void *arg) { - struct thread_data *td = arg; - td->rc = sig_test_correctness(td->alg_name, td->bitflips_all, td->bitflips, td->extended_tests); - OQS_thread_stop(); - return NULL; -} -#endif - -int main(int argc, char **argv) { - OQS_STATUS rc; - OQS_init(); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(USE_SANITIZER) - long int extended_tests = 0; -#else - long int extended_tests = 1; -#endif - printf("Testing signature algorithms using liboqs version %s\n", OQS_version()); - - if (argc < 2 || argc > 5) { - fprintf(stderr, "Usage: test_sig algname [bitflips_msg] [bitflips_sig] [extended_tests]\n"); - fprintf(stderr, " algname: "); - for (size_t i = 0; i < OQS_SIG_algs_length; i++) { - if (i > 0) { - fprintf(stderr, ", "); - } - fprintf(stderr, "%s", OQS_SIG_alg_identifier(i)); - } - fprintf(stderr, "\n"); - fprintf(stderr, " extended_tests: run extended correctness tests (with bitflips, full context-string tests)\n"); - fprintf(stderr, " bitflips_msg: the number of random bitflips to perform for each EUF-CMA signature (\"all\" to flip every bit)\n"); - fprintf(stderr, " bitflips_sig: the number of random bitflips to perform for each SUF-CMA signature (\"all\" to flip every bit)\n"); - OQS_destroy(); - return EXIT_FAILURE; - } - - print_system_info(); - - char *alg_name = argv[1]; - if (!OQS_SIG_alg_is_enabled(alg_name)) { - printf("Signature algorithm %s not enabled!\n", alg_name); - OQS_destroy(); - return EXIT_FAILURE; - } - - /* by default, flip 50 random bits of the message and signature (to test EUF-CMA and SUF-CMA, respectively) */ - bool bitflips_all[2] = {false, false}; - size_t bitflips[2] = {50, 50}; - if (argc >= 3) { - if (strcmp(argv[2], "all") == 0) { - bitflips_all[0] = true; - } else { - bitflips[0] = (size_t)strtol(argv[2], NULL, 10); - } - } - if (argc >= 4) { - if (strcmp(argv[3], "all") == 0) { - bitflips_all[1] = true; - } else { - bitflips[1] = (size_t)strtol(argv[3], NULL, 10); - } - } - if (argc == 5) { - extended_tests = strtol(argv[4], NULL, 10); - if (extended_tests != 0 && extended_tests != 1) { - fprintf(stderr, "ERROR: invalid value for extended_tests (must be 0 or 1)\n"); - OQS_destroy(); - return EXIT_FAILURE; - } - } - -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME - OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); -#else - rc = OQS_randombytes_switch_algorithm("system"); - if (rc != OQS_SUCCESS) { - printf("Could not generate random data with system RNG\n"); - OQS_destroy(); - return EXIT_FAILURE; - } -#endif - -#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) -#define MAX_LEN_SIG_NAME_ 64 - // don't run algorithms with large stack usage in threads - char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5"}; - int test_in_thread = 1; - for (size_t i = 0 ; i < sizeof(no_thread_sig_patterns) / MAX_LEN_SIG_NAME_; ++i) { - if ( (strncmp(alg_name, "SLH_DSA", 7) == 0) || (strstr(alg_name, no_thread_sig_patterns[i]) != NULL) ) { - test_in_thread = 0; - break; - } - } - if (test_in_thread) { - pthread_t thread; - struct thread_data td = {.alg_name = alg_name, .bitflips_all = bitflips_all, .bitflips = bitflips, .rc = OQS_ERROR, .extended_tests = (bool)extended_tests}; - int trc = pthread_create(&thread, NULL, test_wrapper, &td); - if (trc) { - fprintf(stderr, "ERROR: Creating pthread\n"); - OQS_destroy(); - return EXIT_FAILURE; - } - pthread_join(thread, NULL); - rc = td.rc; - } else { - rc = sig_test_correctness(alg_name, bitflips_all, bitflips, (bool)extended_tests); - } -#else - rc = sig_test_correctness(alg_name, bitflips_all, bitflips, (bool)extended_tests); -#endif - if (rc != OQS_SUCCESS) { - OQS_destroy(); - return EXIT_FAILURE; - } - OQS_destroy(); - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 6d41f5b010..b9220dad9e 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -11,7 +11,7 @@ #ifdef OQS_ENABLE_TEST_CONSTANT_TIME #include -#define OQS_TEST_CT_CLASSIFY(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) +#define OQS_TEST_CT_CLASSIFY(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len) #elif defined(__SANITIZE_MEMORY__) #include diff --git a/tests/test_kem.c b/tests/test_kem.c index 2d6819a75c..63cff057de 100644 --- a/tests/test_kem.c +++ b/tests/test_kem.c @@ -23,6 +23,10 @@ #include "system_info.c" #include "test_helpers.h" +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#include +#endif + #ifdef OQS_ENABLE_KEM_ML_KEM /* mlkem rejection key testcase */ static bool mlkem_rej_testcase(OQS_KEM *kem, uint8_t *ciphertext, uint8_t *secret_key) { @@ -201,6 +205,9 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { OQS_randombytes(keypair_seed, kem->length_keypair_seed); } rc = OQS_KEM_keypair_derand(kem, public_key, secret_key, keypair_seed); + #if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) + __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use + #endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (kem->length_keypair_seed == 0) { // If length_keypair_seed is set to 0 for this KEM scheme, a failure is expected @@ -219,6 +226,9 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { } } else { rc = OQS_KEM_keypair(kem, public_key, secret_key); + #if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) + __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use + #endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_KEM_keypair failed\n"); diff --git a/tests/test_sig.c b/tests/test_sig.c index 91181b48d4..4b88f89d5c 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -17,6 +17,10 @@ #include "system_info.c" #include "test_helpers.h" +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#include +#endif + typedef struct magic_s { uint8_t val[31]; } magic_t; @@ -98,6 +102,9 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al OQS_TEST_CT_DECLASSIFY(message, message_len); rc = OQS_SIG_keypair(sig, public_key, secret_key); +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) + __msan_unpoison(public_key, sig->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use +#endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_SIG_keypair failed\n"); @@ -111,6 +118,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } +#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); @@ -119,6 +127,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); goto err; } +#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, false, NULL, 0); @@ -145,15 +154,16 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } +#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); - rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); + rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); + fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); goto err; } - +#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); @@ -187,6 +197,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should always succeed when providing a NULL context string\n"); goto err; } +#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, NULL, 0, public_key); @@ -201,6 +212,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al if (rc != OQS_SUCCESS) { goto err; } +#endif } #ifndef OQS_ENABLE_TEST_CONSTANT_TIME From 017916a295c8be3b2cc877006102742aee5db570 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Tue, 2 Jun 2026 10:57:32 +0200 Subject: [PATCH 03/15] fixed ci errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .../avx2/KeccakP-1600-times4-SIMD256.c | 50 +++++++++---------- tests/CMakeLists.txt | 14 +++--- tests/test_kem.c | 8 +-- tests/test_sig.c | 2 +- 4 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c b/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c index 9b171814d3..169fe97716 100644 --- a/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c +++ b/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c @@ -720,31 +720,31 @@ void KeccakP1600times4_ExtractAndAddLanesAll(const void *states, const unsigned \ static ALIGN(KeccakP1600times4_statesAlignment_avx2) const uint64_t KeccakF1600RoundConstants[24] = { - 0x0000000000000001ULL, - 0x0000000000008082ULL, - 0x800000000000808aULL, - 0x8000000080008000ULL, - 0x000000000000808bULL, - 0x0000000080000001ULL, - 0x8000000080008081ULL, - 0x8000000000008009ULL, - 0x000000000000008aULL, - 0x0000000000000088ULL, - 0x0000000080008009ULL, - 0x000000008000000aULL, - 0x000000008000808bULL, - 0x800000000000008bULL, - 0x8000000000008089ULL, - 0x8000000000008003ULL, - 0x8000000000008002ULL, - 0x8000000000000080ULL, - 0x000000000000800aULL, - 0x800000008000000aULL, - 0x8000000080008081ULL, - 0x8000000000008080ULL, - 0x0000000080000001ULL, - 0x8000000080008008ULL - }; + 0x0000000000000001ULL, + 0x0000000000008082ULL, + 0x800000000000808aULL, + 0x8000000080008000ULL, + 0x000000000000808bULL, + 0x0000000080000001ULL, + 0x8000000080008081ULL, + 0x8000000000008009ULL, + 0x000000000000008aULL, + 0x0000000000000088ULL, + 0x0000000080008009ULL, + 0x000000008000000aULL, + 0x000000008000808bULL, + 0x800000000000008bULL, + 0x8000000000008089ULL, + 0x8000000000008003ULL, + 0x8000000000008002ULL, + 0x8000000000000080ULL, + 0x000000000000800aULL, + 0x800000008000000aULL, + 0x8000000080008081ULL, + 0x8000000000008080ULL, + 0x0000000080000001ULL, + 0x8000000080008008ULL +}; #define copyFromState(X, state) \ X##ba = LOAD256(state[ 0]); \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 69821a064b..b5d60b84fd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,6 +3,13 @@ option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) option(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN "Build test suite with support for MemorySanitizer-based detection of non-constant time behaviour." OFF) +# Add the MemSan RNG poison file from the CT tooling directory +set(TEST_EXTRA_SOURCES "") +if(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) + list(APPEND TEST_EXTRA_SOURCES ct_tooling/tools/memsan/rng_poison_memsan.c) + add_compile_definitions(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +endif() + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") add_compile_options(-Wbad-function-cast) @@ -108,13 +115,6 @@ target_link_libraries(speed_kem PRIVATE ${TEST_DEPS}) set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem) -# Add the MSan-specific RNG poison helper from the CT tooling directory -set(TEST_EXTRA_SOURCES "") -if(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - list(APPEND TEST_EXTRA_SOURCES ct_tooling/tools/memsan/rng_poison_memsan.c) - add_compile_definitions(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) -endif() - # SIG API tests add_executable(example_sig example_sig.c) target_link_libraries(example_sig PRIVATE ${TEST_DEPS}) diff --git a/tests/test_kem.c b/tests/test_kem.c index 63cff057de..e987192481 100644 --- a/tests/test_kem.c +++ b/tests/test_kem.c @@ -205,9 +205,9 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { OQS_randombytes(keypair_seed, kem->length_keypair_seed); } rc = OQS_KEM_keypair_derand(kem, public_key, secret_key, keypair_seed); - #if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use - #endif +#endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (kem->length_keypair_seed == 0) { // If length_keypair_seed is set to 0 for this KEM scheme, a failure is expected @@ -226,9 +226,9 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { } } else { rc = OQS_KEM_keypair(kem, public_key, secret_key); - #if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use - #endif +#endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_KEM_keypair failed\n"); diff --git a/tests/test_sig.c b/tests/test_sig.c index 4b88f89d5c..39e76d8c94 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -163,7 +163,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); goto err; } -#endif +#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); From 0284430d8530628cad6010d0a2defd26505b59bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Tue, 2 Jun 2026 13:21:25 +0200 Subject: [PATCH 04/15] more CI fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- CONFIGURE.md | 8 +-- .../avx2/KeccakP-1600-times4-SIMD256.c | 50 +++++++++---------- tests/test_sig.c | 6 +-- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/CONFIGURE.md b/CONFIGURE.md index 94db960d92..89b431f727 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -19,8 +19,8 @@ The following options can be passed to CMake before the build file generation pr - [OQS_SPEED_USE_ARM_PMU](#OQS_SPEED_USE_ARM_PMU) - [USE_COVERAGE](#USE_COVERAGE) - [USE_SANITIZER](#USE_SANITIZER) -- [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME) -- [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +- [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +- [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - [OQS_STRICT_WARNINGS](#OQS_STRICT_WARNINGS) - [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD) - [OQS_MEMOPT_BUILD](#OQS_MEMOPT_BUILD) @@ -239,7 +239,7 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE **Default**: Unset. -## OQS_ENABLE_TEST_CONSTANT_TIME and OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +## OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. @@ -251,6 +251,8 @@ When this option is set to `ON`, the additional option `OQS_ENABLE_TEST_CONSTANT **Default**: `OFF`. +### OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN + ## OQS_STRICT_WARNINGS Can be `ON` or `OFF`. When `ON`, all compiler warnings are enabled and treated as errors. This setting is recommended to be enabled prior to submission of a Pull Request as CI runs with this setting active. When `OFF`, significantly fewer compiler warnings are enabled such as to avoid undue build errors triggered by (future) compiler warning features/unknown at the development time of this library. diff --git a/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c b/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c index 169fe97716..9b171814d3 100644 --- a/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c +++ b/src/common/sha3/xkcp_low/KeccakP-1600times4/avx2/KeccakP-1600-times4-SIMD256.c @@ -720,31 +720,31 @@ void KeccakP1600times4_ExtractAndAddLanesAll(const void *states, const unsigned \ static ALIGN(KeccakP1600times4_statesAlignment_avx2) const uint64_t KeccakF1600RoundConstants[24] = { - 0x0000000000000001ULL, - 0x0000000000008082ULL, - 0x800000000000808aULL, - 0x8000000080008000ULL, - 0x000000000000808bULL, - 0x0000000080000001ULL, - 0x8000000080008081ULL, - 0x8000000000008009ULL, - 0x000000000000008aULL, - 0x0000000000000088ULL, - 0x0000000080008009ULL, - 0x000000008000000aULL, - 0x000000008000808bULL, - 0x800000000000008bULL, - 0x8000000000008089ULL, - 0x8000000000008003ULL, - 0x8000000000008002ULL, - 0x8000000000000080ULL, - 0x000000000000800aULL, - 0x800000008000000aULL, - 0x8000000080008081ULL, - 0x8000000000008080ULL, - 0x0000000080000001ULL, - 0x8000000080008008ULL -}; + 0x0000000000000001ULL, + 0x0000000000008082ULL, + 0x800000000000808aULL, + 0x8000000080008000ULL, + 0x000000000000808bULL, + 0x0000000080000001ULL, + 0x8000000080008081ULL, + 0x8000000000008009ULL, + 0x000000000000008aULL, + 0x0000000000000088ULL, + 0x0000000080008009ULL, + 0x000000008000000aULL, + 0x000000008000808bULL, + 0x800000000000008bULL, + 0x8000000000008089ULL, + 0x8000000000008003ULL, + 0x8000000000008002ULL, + 0x8000000000000080ULL, + 0x000000000000800aULL, + 0x800000008000000aULL, + 0x8000000080008081ULL, + 0x8000000000008080ULL, + 0x0000000080000001ULL, + 0x8000000080008008ULL + }; #define copyFromState(X, state) \ X##ba = LOAD256(state[ 0]); \ diff --git a/tests/test_sig.c b/tests/test_sig.c index 39e76d8c94..22c731b991 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -157,13 +157,12 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al #ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); - rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); + rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { - fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); + fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); goto err; } -#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); @@ -171,6 +170,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } } +#endif } } From 3edf0de6b4f4f9a96ca3714e142614d639badd76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Tue, 2 Jun 2026 13:33:31 +0200 Subject: [PATCH 05/15] cleaned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- tests/ct_tooling/ct_test.sh | 4 ++-- tests/test_sig.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index e4f952106d..9041fc54e9 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -56,8 +56,8 @@ build() { -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ - -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" - cmake --build . -j$(nproc) + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 + cmake --build . -j$(nproc) > /dev/null 2>&1 ;; *) echo "Unknown tool: $TOOL"; return 1 diff --git a/tests/test_sig.c b/tests/test_sig.c index 22c731b991..126f6902b7 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -163,6 +163,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); goto err; } + if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); From e6c3a4c85165939487303325795bd532292ee447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Tue, 2 Jun 2026 13:37:44 +0200 Subject: [PATCH 06/15] more cleaning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- tests/test_sig.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sig.c b/tests/test_sig.c index 126f6902b7..e7ee360ace 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -163,7 +163,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); goto err; } - + if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); From ceadb8d1b4c109487b193cf91ba7cbb75dc7bbea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Sat, 6 Jun 2026 21:15:33 +0200 Subject: [PATCH 07/15] Update new algorithm selection logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 58 ++++++++++++++++++++++++-------- CONFIGURE.md | 16 +++++++++ tests/CMakeLists.txt | 1 + tests/ct_tooling/ct_test.sh | 5 +++ tests/test_sig.c | 2 +- 5 files changed, 67 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index 0f1ca3a49c..0f5f7668b5 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -5,10 +5,17 @@ permissions: on: workflow_dispatch: + inputs: + algorithms: + description: 'Algorithms to execute CT testing on (comma-separated)' + required: true + type: string + default: 'ML-KEM-512,ML-KEM-768,ML-KEM-1024' jobs: - interactive-inputs: + resolve-matrix: runs-on: ubuntu-latest +<<<<<<< HEAD permissions: contents: write actions: write @@ -56,16 +63,40 @@ jobs: notifier-discord-enabled: "false" github-token: ${{ github.token }} ngrok-authtoken: ${{ secrets.NGROK_AUTHTOKEN }} +======= +>>>>>>> user-chooses outputs: - runtime-options: ${{ steps.interactive-inputs.outputs.runtime-options }} + matrix: ${{ steps.algs.outputs.matrix }} + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 + - run: python -m pip install pytest requests + - name: Resolve algorithm list + id: algs + run: | + python3 - <<'PY' + import os, sys, json + sys.path.insert(0, 'tests') + import helpers + requested = "${{ github.event.inputs.algorithms }}".strip() + all_algs = helpers.available_kems_by_name() + helpers.available_sigs_by_name() + requested_set = { a.strip() for a in requested.split(',')} + invalid_algorithms = requested_set - set(all_algs) + if invalid_algorithms: + print(f"Invalid algorithms: {invalid_algorithms}", file=sys.stderr) + sys.exit(1) + selected_algorithms = [a for a in all_algs if a in requested_set] + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write(f"matrix={json.dumps(selected_algorithms)}\n") + PY valgrind-varlat: - needs: [interactive-inputs] + needs: [resolve-matrix] runs-on: ubuntu-latest container: image: openquantumsafe/ci-ubuntu-latest:latest strategy: matrix: + algorithm: ${{ fromJson(needs.resolve-matrix.outputs.matrix) }} compiler: [gcc, clang] liboqs_build: [generic, auto] opt_flag: [-O0, -O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-tree-vectorize", "-O3 -fno-tree-vectorize"] @@ -123,15 +154,14 @@ jobs: - name: Run valgrind_varlat tests shell: bash + env: + OPT_FLAG: ${{ matrix.opt_flag }} + ALGORITHM: ${{ matrix.algorithm }} run: | set -eu -o pipefail cd "$GITHUB_WORKSPACE/tests/ct_tooling" chmod +x ct_test.sh - raw_algs="${{ needs.interactive-inputs.outputs.runtime-options }}" - IFS=',' read -r -a algs <<< "$raw_algs" # convert to array - for alg in "${algs[@]}"; do - ./ct_test.sh valgrind-varlat ${{ matrix.compiler }} ${{ matrix.liboqs_build }} ${{ matrix.opt_flag }} "$alg" - done + ./ct_test.sh valgrind-varlat ${{ matrix.compiler }} ${{ matrix.liboqs_build }} "$OPT_FLAG" "$ALGORITHM" - name: Upload valgrind_varlat logs uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4 @@ -141,12 +171,13 @@ jobs: tests/ct_tooling/tools/valgrind_varlat/logs/** memsan: - needs: [interactive-inputs] + needs: [resolve-matrix] runs-on: ubuntu-latest container: image: openquantumsafe/ci-ubuntu-latest:latest strategy: matrix: + algorithm: ${{ fromJson(needs.resolve-matrix.outputs.matrix) }} compiler: [clang] liboqs_build: [generic, auto] opt_flag: [-O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-vectorize", "-O3 -fno-vectorize"] @@ -157,15 +188,14 @@ jobs: - name: Run memsan tests shell: bash + env: + OPT_FLAG: ${{ matrix.opt_flag }} + ALGORITHM: ${{ matrix.algorithm }} run: | set -eu -o pipefail cd "$GITHUB_WORKSPACE/tests/ct_tooling" chmod +x ct_test.sh - raw_algs="${{ needs.interactive-inputs.outputs.runtime-options }}" - IFS=',' read -r -a algs <<< "$raw_algs" # convert to array - for alg in "${algs[@]}"; do - ./ct_test.sh memsan ${{ matrix.compiler }} ${{ matrix.liboqs_build }} ${{ matrix.opt_flag }} "$alg" - done + ./ct_test.sh memsan ${{ matrix.compiler }} ${{ matrix.liboqs_build }} "$OPT_FLAG" "$ALGORITHM" - name: Upload memsan logs uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # pin@v4 diff --git a/CONFIGURE.md b/CONFIGURE.md index 89b431f727..3d1f2649c3 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -19,8 +19,13 @@ The following options can be passed to CMake before the build file generation pr - [OQS_SPEED_USE_ARM_PMU](#OQS_SPEED_USE_ARM_PMU) - [USE_COVERAGE](#USE_COVERAGE) - [USE_SANITIZER](#USE_SANITIZER) +<<<<<<< HEAD - [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +======= +- [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME) +- [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +>>>>>>> user-chooses - [OQS_STRICT_WARNINGS](#OQS_STRICT_WARNINGS) - [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD) - [OQS_MEMOPT_BUILD](#OQS_MEMOPT_BUILD) @@ -242,16 +247,27 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE ## OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. +<<<<<<< HEAD Similarly, OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN enables constant-time testing using Clang's MemorySanitizer. See the documentation in [`tests/test_constant_time.py`](https://github.com/open-quantum-safe/liboqs/blob/main/tests/test_constant_time.py) for more usage information. +======= +>>>>>>> user-chooses When this option is set to `ON`, the additional option `OQS_ENABLE_TEST_CONSTANT_TIME_OPTIMIZED` is made available to control whether liboqs is built using `-O3` optimization, as in a release build, or using the default "Debug" profile. By default, `OQS_ENABLE_TEST_CONSTANT_TIME_OPTIMIZED` is `OFF`. **Default**: `OFF`. +<<<<<<< HEAD ### OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +======= +## OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN + +Similar to [`OQS_ENABLE_TEST_CONSTANT_TIME`](#OQS_ENABLE_TEST_CONSTANT_TIME), this option enables constant-time testing using Clang's MemorySanitizer. + +**Default**: `OFF`. +>>>>>>> user-chooses ## OQS_STRICT_WARNINGS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b5d60b84fd..9ee2c70f82 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -115,6 +115,7 @@ target_link_libraries(speed_kem PRIVATE ${TEST_DEPS}) set(KEM_TESTS example_kem kat_kem test_kem test_kem_mem speed_kem vectors_kem) + # SIG API tests add_executable(example_sig example_sig.c) target_link_libraries(example_sig PRIVATE ${TEST_DEPS}) diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 9041fc54e9..3bce8f8ae9 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -56,8 +56,13 @@ build() { -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ +<<<<<<< HEAD -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 cmake --build . -j$(nproc) > /dev/null 2>&1 +======= + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" + cmake --build . -j$(nproc) +>>>>>>> user-chooses ;; *) echo "Unknown tool: $TOOL"; return 1 diff --git a/tests/test_sig.c b/tests/test_sig.c index e7ee360ace..ae745b40e9 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -163,7 +163,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); goto err; } - +#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); From 08435be6d6d836f2cce9db6e37527c3ea6692dc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Sat, 6 Jun 2026 21:17:16 +0200 Subject: [PATCH 08/15] Cleaned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- CONFIGURE.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/CONFIGURE.md b/CONFIGURE.md index 3d1f2649c3..bf6059c18b 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -247,27 +247,16 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE ## OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. -<<<<<<< HEAD - -Similarly, OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN enables constant-time testing using Clang's MemorySanitizer. - -See the documentation in [`tests/test_constant_time.py`](https://github.com/open-quantum-safe/liboqs/blob/main/tests/test_constant_time.py) for more usage information. -======= ->>>>>>> user-chooses When this option is set to `ON`, the additional option `OQS_ENABLE_TEST_CONSTANT_TIME_OPTIMIZED` is made available to control whether liboqs is built using `-O3` optimization, as in a release build, or using the default "Debug" profile. By default, `OQS_ENABLE_TEST_CONSTANT_TIME_OPTIMIZED` is `OFF`. **Default**: `OFF`. -<<<<<<< HEAD -### OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN -======= ## OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN Similar to [`OQS_ENABLE_TEST_CONSTANT_TIME`](#OQS_ENABLE_TEST_CONSTANT_TIME), this option enables constant-time testing using Clang's MemorySanitizer. **Default**: `OFF`. ->>>>>>> user-chooses ## OQS_STRICT_WARNINGS From 48f1063271ff57e03e97f7c98d3639f6680d3ed4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Sat, 6 Jun 2026 21:18:59 +0200 Subject: [PATCH 09/15] Cleaned x2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 69 +++----------------------------- CONFIGURE.md | 7 +--- tests/ct_tooling/ct_test.sh | 5 --- tests/test_sig.c | 2 +- 4 files changed, 8 insertions(+), 75 deletions(-) diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index 0f5f7668b5..3b34424ac0 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -15,56 +15,6 @@ on: jobs: resolve-matrix: runs-on: ubuntu-latest -<<<<<<< HEAD - permissions: - contents: write - actions: write - steps: - - name: Checkout code - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 - - - name: Install Python deps - run: python -m pip install pytest requests - - - name: Generate algorithm list - id: set - run: | - python3 - <<'PY' - import os, sys - sys.path.insert(0, 'tests') - import helpers - algs = helpers.available_kems_by_name() + helpers.available_sigs_by_name() - interactive_yaml = ( - "fields:\n" - " - label: runtime-options\n" - " properties:\n" - " description: Choose one or more algorithms to execute constant-time testing on\n" - " display: Select the algorithm(s) to execute constant-time testing on\n" - " type: multiselect\n" - " choices:\n" - ) - for a in algs: - interactive_yaml += f" - {a}\n" - interactive_yaml += " required: true\n" - with open(os.environ['GITHUB_OUTPUT'], 'a') as gout: - gout.write('interactive<>>>>>> user-chooses outputs: matrix: ${{ steps.algs.outputs.matrix }} steps: @@ -72,12 +22,14 @@ jobs: - run: python -m pip install pytest requests - name: Resolve algorithm list id: algs + env: + ALGORITHMS_INPUT: ${{ github.event.inputs.algorithms }} run: | python3 - <<'PY' import os, sys, json sys.path.insert(0, 'tests') import helpers - requested = "${{ github.event.inputs.algorithms }}".strip() + requested = os.environ["ALGORITHMS_INPUT"].strip() all_algs = helpers.available_kems_by_name() + helpers.available_sigs_by_name() requested_set = { a.strip() for a in requested.split(',')} invalid_algorithms = requested_set - set(all_algs) @@ -99,24 +51,15 @@ jobs: algorithm: ${{ fromJson(needs.resolve-matrix.outputs.matrix) }} compiler: [gcc, clang] liboqs_build: [generic, auto] - opt_flag: [-O0, -O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-tree-vectorize", "-O3 -fno-tree-vectorize"] + opt_flag: [-O0, -O1, -O2, -O3, -Os, -Ofast, "-O2 -fno-tree-vectorize", "-O3 -fno-tree-vectorize", "-O2 -fno-vectorize", "-O3 -fno-vectorize"] exclude: - compiler: clang opt_flag: "-O2 -fno-tree-vectorize" - compiler: clang opt_flag: "-O3 -fno-tree-vectorize" - include: - - compiler: clang - liboqs_build: generic + - compiler: gcc opt_flag: "-O2 -fno-vectorize" - - compiler: clang - liboqs_build: auto - opt_flag: "-O2 -fno-vectorize" - - compiler: clang - liboqs_build: generic - opt_flag: "-O3 -fno-vectorize" - - compiler: clang - liboqs_build: auto + - compiler: gcc opt_flag: "-O3 -fno-vectorize" max-parallel: 5 steps: diff --git a/CONFIGURE.md b/CONFIGURE.md index bf6059c18b..fb6e5c9ac3 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -19,13 +19,8 @@ The following options can be passed to CMake before the build file generation pr - [OQS_SPEED_USE_ARM_PMU](#OQS_SPEED_USE_ARM_PMU) - [USE_COVERAGE](#USE_COVERAGE) - [USE_SANITIZER](#USE_SANITIZER) -<<<<<<< HEAD -- [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) -- [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) -======= - [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME) - [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) ->>>>>>> user-chooses - [OQS_STRICT_WARNINGS](#OQS_STRICT_WARNINGS) - [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD) - [OQS_MEMOPT_BUILD](#OQS_MEMOPT_BUILD) @@ -244,7 +239,7 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE **Default**: Unset. -## OQS_ENABLE_TEST_CONSTANT_TIME/OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +## OQS_ENABLE_TEST_CONSTANT_TIME This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 3bce8f8ae9..e4f952106d 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -56,13 +56,8 @@ build() { -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ -<<<<<<< HEAD - -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 - cmake --build . -j$(nproc) > /dev/null 2>&1 -======= -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" cmake --build . -j$(nproc) ->>>>>>> user-chooses ;; *) echo "Unknown tool: $TOOL"; return 1 diff --git a/tests/test_sig.c b/tests/test_sig.c index ae745b40e9..e7ee360ace 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -163,7 +163,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_verify_with_ctx_str failed\n"); goto err; } -#endif + if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, true, ctx, i); OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); From 483f44e8b9212f2151d85d37bd3beb2553a97a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Mon, 8 Jun 2026 09:38:52 +0200 Subject: [PATCH 10/15] Enabled memsan macros poisoning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 2 +- tests/CMakeLists.txt | 17 ++++++++--------- tests/ct_tooling/ct_test.sh | 4 ++-- .../ct_tooling/tools/memsan/rng_poison_memsan.c | 11 ----------- tests/test_helpers.h | 2 +- tests/test_kem.c | 6 +++--- tests/test_sig.c | 10 +++++----- 7 files changed, 20 insertions(+), 32 deletions(-) delete mode 100644 tests/ct_tooling/tools/memsan/rng_poison_memsan.c diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index 3b34424ac0..334048ef01 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -19,7 +19,7 @@ jobs: matrix: ${{ steps.algs.outputs.matrix }} steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 - - run: python -m pip install pytest requests + - run: python -m pip install --require-hashes -r .github/workflows/requirements.txt - name: Resolve algorithm list id: algs env: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9ee2c70f82..1524f02fd5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,13 +3,6 @@ option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) option(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN "Build test suite with support for MemorySanitizer-based detection of non-constant time behaviour." OFF) -# Add the MemSan RNG poison file from the CT tooling directory -set(TEST_EXTRA_SOURCES "") -if(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - list(APPEND TEST_EXTRA_SOURCES ct_tooling/tools/memsan/rng_poison_memsan.c) - add_compile_definitions(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) -endif() - if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang") add_compile_options(-Wbad-function-cast) @@ -87,7 +80,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) endif() endif() -add_executable(test_kem test_kem.c test_helpers.c ${TEST_EXTRA_SOURCES}) +add_executable(test_kem test_kem.c test_helpers.c) target_link_libraries(test_kem PRIVATE ${TEST_DEPS}) if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) @@ -173,7 +166,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) endif() endif() -add_executable(test_sig test_sig.c test_helpers.c ${TEST_EXTRA_SOURCES}) +add_executable(test_sig test_sig.c test_helpers.c) target_link_libraries(test_sig PRIVATE ${TEST_DEPS}) if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) # workaround for Windows .dll @@ -239,6 +232,12 @@ if(OQS_ENABLE_TEST_CONSTANT_TIME AND NOT OQS_DEBUG_BUILD) set(OQS_ENABLE_TEST_CONSTANT_TIME OFF) endif() +# Enable MemSan-based timing side-channel analysis for test_kem and test_sig +if(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN AND NOT OQS_DEBUG_BUILD) + message(WARNING "OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN is incompatible with CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}.") + set(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN OFF) +endif() + # Record compile options -- from target speed_kem - don't set any options only for speed_kem! get_property(OQS_COMPILE_OPTIONS TARGET speed_kem PROPERTY COMPILE_OPTIONS) add_definitions(-DOQS_COMPILE_OPTIONS="[${OQS_COMPILE_OPTIONS}]") diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index e4f952106d..9041fc54e9 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -56,8 +56,8 @@ build() { -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ - -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" - cmake --build . -j$(nproc) + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 + cmake --build . -j$(nproc) > /dev/null 2>&1 ;; *) echo "Unknown tool: $TOOL"; return 1 diff --git a/tests/ct_tooling/tools/memsan/rng_poison_memsan.c b/tests/ct_tooling/tools/memsan/rng_poison_memsan.c deleted file mode 100644 index 45a31c4aac..0000000000 --- a/tests/ct_tooling/tools/memsan/rng_poison_memsan.c +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -#include -#include -#include -#include - -void OQS_randombytes(uint8_t *r, size_t len) { - memset(r, 0xA5, len); // Fill with pattern 0xA5 - __msan_poison(r, len); -} \ No newline at end of file diff --git a/tests/test_helpers.h b/tests/test_helpers.h index b9220dad9e..20c4ebf10f 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -13,7 +13,7 @@ #include #define OQS_TEST_CT_CLASSIFY(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len) -#elif defined(__SANITIZE_MEMORY__) +#elif defined(__SANITIZE_MEMORY__) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) #include #define OQS_TEST_CT_CLASSIFY(addr, len) __msan_poison(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) __msan_unpoison(addr, len) diff --git a/tests/test_kem.c b/tests/test_kem.c index e987192481..0fcb5967b8 100644 --- a/tests/test_kem.c +++ b/tests/test_kem.c @@ -306,7 +306,7 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { goto err; } -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) rv = memcmp(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); rv |= memcmp(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); rv |= memcmp(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); @@ -362,7 +362,7 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { return ret; } -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) static void TEST_KEM_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, // so we have to swap out the OQS_randombytes provider. @@ -424,7 +424,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); #else rc = OQS_randombytes_switch_algorithm("system"); diff --git a/tests/test_sig.c b/tests/test_sig.c index e7ee360ace..92b353040f 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -216,7 +216,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al #endif } -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) /* check magic values */ int rv = memcmp(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); rv |= memcmp(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); @@ -257,7 +257,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al return ret; } -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, // so we have to swap out the OQS_randombytes provider. @@ -292,7 +292,7 @@ void *test_wrapper(void *arg) { int main(int argc, char **argv) { OQS_STATUS rc; OQS_init(); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(USE_SANITIZER) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) || defined(USE_SANITIZER) long int extended_tests = 0; #else long int extended_tests = 1; @@ -351,7 +351,7 @@ int main(int argc, char **argv) { } } -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); #else rc = OQS_randombytes_switch_algorithm("system"); @@ -362,7 +362,7 @@ int main(int argc, char **argv) { } #endif -#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) +#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) || !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5", "mqom2_cat1_gf16_fast_r3", "mqom2_cat1_gf16_fast_r5", "mqom2_cat1_gf16_short_r3", "mqom2_cat1_gf16_short_r5", "mqom2_cat3_gf16_fast_r3", "mqom2_cat3_gf16_fast_r5", "mqom2_cat3_gf16_short_r3", "mqom2_cat3_gf16_short_r5", "mqom2_cat5_gf16_fast_r3", "mqom2_cat5_gf16_fast_r5", "mqom2_cat5_gf16_short_r3", "mqom2_cat5_gf16_short_r5"}; From f7d078e2ccc9f47068cd041452754c76046e0977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Mon, 8 Jun 2026 10:22:05 +0200 Subject: [PATCH 11/15] Cleaned and fixed pthreads issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- tests/ct_tooling/README.md | 2 +- tests/ct_tooling/analyze_results.py | 58 +++++++++++++++++++---------- tests/test_sig.c | 2 +- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/tests/ct_tooling/README.md b/tests/ct_tooling/README.md index 635166493e..43ccc01b40 100644 --- a/tests/ct_tooling/README.md +++ b/tests/ct_tooling/README.md @@ -1,6 +1,6 @@ # Constant-Time Tooling -Framework constant-time testing of liboqs across compilers, optimization flags, and `OQS_OPT_TARGET` build modes. +Framework for constant-time testing of liboqs across compilers, optimization flags, and `OQS_OPT_TARGET` build modes. ## Repository Structure diff --git a/tests/ct_tooling/analyze_results.py b/tests/ct_tooling/analyze_results.py index 8007a1f2e1..bdbc491c7b 100644 --- a/tests/ct_tooling/analyze_results.py +++ b/tests/ct_tooling/analyze_results.py @@ -41,7 +41,12 @@ def parse_summary_file(filepath): with open(filepath, 'r') as f: content = f.read() - header_re = re.compile(r'^Testing (?:KEM|SIG): (\S+) \.\.\. (?:PASS|FAIL)', re.MULTILINE) + header_re = re.compile(r'^Testing (?:KEM:|SIG:|)\s*(\S+) \.\.\. (?:PASS|FAIL)', re.MULTILINE) + count_re = re.compile(r'Found (\d+) (?:Valgrind|MemSan|)?\s*warnings') + + matches = list(header_re.finditer(content)) + for idx, m in enumerate(matches): + alg_name = m.group(1) # Skip SPHINCS and SLH_DSA algorithms if 'SPHINCS' in alg_name or 'SLH_DSA' in alg_name: continue @@ -63,10 +68,8 @@ def parse_summary_file(filepath): # Modify analyze_logs to dynamically process all builds and optimization levels def analyze_logs(): """Walk input log directories and aggregate warning counts dynamically. - -======= """ - + kem_data = defaultdict(lambda: defaultdict(dict)) sig_data = defaultdict(lambda: defaultdict(dict)) if not os.path.isdir(LOG_BASE_DIR): @@ -82,7 +85,7 @@ def analyze_logs(): if not os.path.isdir(test_dir): continue - for summary_file in glob.glob(os.path.join(test_dir, f'{test_type}_summary_*.txt')): + for summary_file in glob.glob(os.path.join(test_dir, f'{test_type}_summary*.txt')): results = parse_summary_file(summary_file) if test_type == 'kem': kem_data[build_name][opt_level].update(results) @@ -119,7 +122,7 @@ def generate_csv_file(data, alg_type, opt_levels, output_dir): for alg in all_algs: row = [alg] for opt in opt_levels: - row.append(data[opt].get(alg, 'N/A')) + row.append(data.get(opt, {}).get(alg, 'N/A')) writer.writerow(row) def heatmap_warnings_per_alg_and_opt_level(data, opt_levels, alg_type, tool, output_dir): @@ -131,7 +134,7 @@ def _norm_name(n): all_algs = set() for opt in opt_levels: all_algs.update(data.get(opt, {}).keys()) - alg_total = {alg: sum([data.get(opt, {}).get(alg, 0) for opt in opt_levels]) for alg in all_algs} + alg_total = {alg: sum([data.get(opt, {}).get(alg, 0) for opt in opt_levels]) for alg in all_algs} alg_names = [a for a, _ in sorted(alg_total.items(), key=lambda x: (-x[1], _norm_name(x[0])))] matrix = np.zeros((len(alg_names), len(opt_levels)), dtype=int) @@ -179,8 +182,8 @@ def bar_chart_total_warnings_per_opt_level(kem_data, sig_data, opt_levels, tool, fig, ax = plt.subplots(figsize=(14, 6)) - kem_totals = [sum(kem_data[opt].values()) for opt in opt_levels] - sig_totals = [sum(sig_data[opt].values()) for opt in opt_levels] + kem_totals = [sum(kem_data.get(opt, {}).values()) for opt in opt_levels] + sig_totals = [sum(sig_data.get(opt, {}).values()) for opt in opt_levels] x = np.arange(len(opt_levels)) width = 0.35 @@ -217,20 +220,35 @@ def line_chart_avg_warnings_per_opt_level(kem_data, sig_data, opt_levels, tool, plt.close() def generate_reports(kem_data, sig_data): - for build_name in kem_data.keys(): + all_builds = set(kem_data.keys()).union(sig_data.keys()) + for build_name in all_builds: build_output_dir = os.path.join(OUTPUT_DIR, build_name) os.makedirs(build_output_dir, exist_ok=True) - opt_levels = sort_optimization_levels(kem_data[build_name].keys()) - - generate_csv_file(kem_data[build_name], 'KEM', opt_levels, build_output_dir) - generate_csv_file(sig_data[build_name], 'SIG', opt_levels, build_output_dir) - - heatmap_warnings_per_alg_and_opt_level(kem_data[build_name], opt_levels, 'KEM', args.tool, build_output_dir) - heatmap_warnings_per_alg_and_opt_level(sig_data[build_name], opt_levels, 'SIG', args.tool, build_output_dir) - - bar_chart_total_warnings_per_opt_level(kem_data[build_name], sig_data[build_name], opt_levels, args.tool, build_output_dir) - line_chart_avg_warnings_per_opt_level(kem_data[build_name], sig_data[build_name], opt_levels, args.tool, build_output_dir) + opt_levels = set(kem_data.get(build_name, {}).keys()).union(sig_data.get(build_name, {}).keys()) + opt_levels = sort_optimization_levels(opt_levels) + + if build_name in kem_data and kem_data[build_name]: + generate_csv_file(kem_data[build_name], 'KEM', opt_levels, build_output_dir) + heatmap_warnings_per_alg_and_opt_level(kem_data[build_name], opt_levels, 'KEM', args.tool, build_output_dir) + if build_name in sig_data and sig_data[build_name]: + generate_csv_file(sig_data[build_name], 'SIG', opt_levels, build_output_dir) + heatmap_warnings_per_alg_and_opt_level(sig_data[build_name], opt_levels, 'SIG', args.tool, build_output_dir) + + bar_chart_total_warnings_per_opt_level( + kem_data.get(build_name, {}), + sig_data.get(build_name, {}), + opt_levels, + args.tool, + build_output_dir + ) + line_chart_avg_warnings_per_opt_level( + kem_data.get(build_name, {}), + sig_data.get(build_name, {}), + opt_levels, + args.tool, + build_output_dir + ) def main(): kem_data, sig_data = analyze_logs() diff --git a/tests/test_sig.c b/tests/test_sig.c index 92b353040f..15070cb7f8 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -362,7 +362,7 @@ int main(int argc, char **argv) { } #endif -#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) || !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if OQS_USE_PTHREADS && (!defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN)) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5", "mqom2_cat1_gf16_fast_r3", "mqom2_cat1_gf16_fast_r5", "mqom2_cat1_gf16_short_r3", "mqom2_cat1_gf16_short_r5", "mqom2_cat3_gf16_fast_r3", "mqom2_cat3_gf16_fast_r5", "mqom2_cat3_gf16_short_r3", "mqom2_cat3_gf16_short_r5", "mqom2_cat5_gf16_fast_r3", "mqom2_cat5_gf16_fast_r5", "mqom2_cat5_gf16_short_r3", "mqom2_cat5_gf16_short_r5"}; From 239a289367eedec897e4e655b1a32b885ea41891 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Mon, 8 Jun 2026 11:34:02 +0200 Subject: [PATCH 12/15] Add to weekly tests and minor editing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 26 ++++++++++++++++---------- .github/workflows/weekly.yml | 9 ++++++++- tests/ct_tooling/ct_test.sh | 8 ++++---- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index 334048ef01..48d46fb345 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -11,6 +11,12 @@ on: required: true type: string default: 'ML-KEM-512,ML-KEM-768,ML-KEM-1024' + + workflow_call: + inputs: + algorithms: + required: true + type: string jobs: resolve-matrix: @@ -23,7 +29,7 @@ jobs: - name: Resolve algorithm list id: algs env: - ALGORITHMS_INPUT: ${{ github.event.inputs.algorithms }} + ALGORITHMS_INPUT: ${{ inputs.algorithms }} run: | python3 - <<'PY' import os, sys, json @@ -73,20 +79,20 @@ jobs: INSTALL_PREFIX="$PWD/valgrind_varlat" echo "Cloning Valgrind's source code" - git clone git://sourceware.org/git/valgrind.git valgrind_varlat_src> /dev/null 2>&1 + git clone git://sourceware.org/git/valgrind.git valgrind_varlat_src cd valgrind_varlat_src - git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa > /dev/null 2>&1 + git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa echo "Applying Kyberslash patch" - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" > /dev/null 2>&1 - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" > /dev/null 2>&1 - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" > /dev/null 2>&1 + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" + git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" echo "Building Valgrind-Varlat" - ./autogen.sh > /dev/null 2>&1 - ./configure --prefix="$INSTALL_PREFIX" > /dev/null 2>&1 - make -j"$(nproc)" > /dev/null 2>&1 - make install > /dev/null 2>&1 + ./autogen.sh + ./configure --prefix="$INSTALL_PREFIX" + make -j"$(nproc)" + make install mv "$INSTALL_PREFIX/bin/valgrind" "$INSTALL_PREFIX/bin/valgrind_varlat" chmod -R u+rwX "$INSTALL_PREFIX" diff --git a/.github/workflows/weekly.yml b/.github/workflows/weekly.yml index 1d6a5993b3..a073be59cc 100644 --- a/.github/workflows/weekly.yml +++ b/.github/workflows/weekly.yml @@ -30,4 +30,11 @@ jobs: sig-continuous-benchmarking: uses: ./.github/workflows/sig-bench.yml permissions: - contents: write \ No newline at end of file + contents: write + + ct-tooling: + uses: ./.github/workflows/ct-tooling.yml + permissions: + contents: read + with: + algorithms: 'ML-KEM-512,ML-KEM-768,ML-KEM-1024' \ No newline at end of file diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 9041fc54e9..71b931e36f 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -38,8 +38,8 @@ build() { cmake "${CMAKE_ARGS[@]}" \ -DCMAKE_C_FLAGS="$OPT_FLAG" \ - -DOQS_ENABLE_TEST_CONSTANT_TIME=ON > /dev/null 2>&1 - cmake --build . -j$(nproc) > /dev/null 2>&1 + -DOQS_ENABLE_TEST_CONSTANT_TIME=ON > /dev/null + cmake --build . -j$(nproc) > /dev/null ;; memsan) @@ -56,8 +56,8 @@ build() { -DOQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN=ON \ -DCMAKE_C_FLAGS="-fsanitize=memory -fsanitize-recover=all ${SUP_FLAGS[*]} $OPT_FLAG -g" \ -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=memory" \ - -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null 2>&1 - cmake --build . -j$(nproc) > /dev/null 2>&1 + -DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=memory" > /dev/null + cmake --build . -j$(nproc) > /dev/null ;; *) echo "Unknown tool: $TOOL"; return 1 From fa50488c5e61dd3fa550c17063dece803192071b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Tue, 23 Jun 2026 14:17:24 +0200 Subject: [PATCH 13/15] Move Kyberslash-Valgrind build to ci-containers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- .github/workflows/ct-tooling.yml | 29 - .../valgrind-try-patch-20250805.txt | 1578 ----------------- .../valgrind-varlat-patch-20250805.txt | 909 ---------- .../valgrind-varlat-sup-block.txt | 23 - 4 files changed, 2539 deletions(-) delete mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt delete mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt delete mode 100644 tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt diff --git a/.github/workflows/ct-tooling.yml b/.github/workflows/ct-tooling.yml index 48d46fb345..61e98ed431 100644 --- a/.github/workflows/ct-tooling.yml +++ b/.github/workflows/ct-tooling.yml @@ -72,35 +72,6 @@ jobs: - name: Checkout code uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # pin@v4 - - name: Build valgrind_varlat - shell: bash - run: | - set -eu -o pipefail - INSTALL_PREFIX="$PWD/valgrind_varlat" - - echo "Cloning Valgrind's source code" - git clone git://sourceware.org/git/valgrind.git valgrind_varlat_src - cd valgrind_varlat_src - git checkout 112f1080b7c21e37dfce0a2e589d0dc7aa115afa - - echo "Applying Kyberslash patch" - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt" - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt" - git apply "$GITHUB_WORKSPACE/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt" - - echo "Building Valgrind-Varlat" - ./autogen.sh - ./configure --prefix="$INSTALL_PREFIX" - make -j"$(nproc)" - make install - - mv "$INSTALL_PREFIX/bin/valgrind" "$INSTALL_PREFIX/bin/valgrind_varlat" - chmod -R u+rwX "$INSTALL_PREFIX" - export PATH="$INSTALL_PREFIX/bin:$PATH" - echo "PATH: $PATH" - command -v valgrind_varlat - echo "$INSTALL_PREFIX/bin" >> "$GITHUB_PATH" - - name: Run valgrind_varlat tests shell: bash env: diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt deleted file mode 100644 index 395d41c06b..0000000000 --- a/tests/ct_tooling/tools/valgrind_varlat/valgrind-try-patch-20250805.txt +++ /dev/null @@ -1,1578 +0,0 @@ -diff --git a/coregrind/m_commandline.c b/coregrind/m_commandline.c -index e9fdb9bb4..4e120db0d 100644 ---- a/coregrind/m_commandline.c -+++ b/coregrind/m_commandline.c -@@ -41,7 +41,7 @@ - - /* Add a string to an expandable array of strings. */ - --static void add_string ( XArray* /* of HChar* */xa, HChar* str ) -+static void add_string ( XArray* /* of HChar* */xa, const HChar* str ) - { - (void) VG_(addToXA)( xa, (void*)(&str) ); - } -@@ -93,8 +93,9 @@ static HChar* read_dot_valgrindrc ( const HChar* dir ) - - // Add args from a string into VG_(args_for_valgrind), splitting the - // string at whitespace and adding each component as a separate arg. -+// If try is set: add --try before each arg. - --static void add_args_from_string ( HChar* s ) -+static void add_args_from_string ( HChar* s, Bool try ) - { - HChar* tmp; - HChar* cp = s; -@@ -120,6 +121,7 @@ static void add_args_from_string ( HChar* s ) - } - if (out < cp) *out++ = '\0'; - if ( *cp != 0 ) *cp++ = '\0'; // terminate if not the last -+ if (try) add_string( VG_(args_for_valgrind), "--try" ); - add_string( VG_(args_for_valgrind), tmp ); - } - } -@@ -131,6 +133,7 @@ static void add_args_from_string ( HChar* s ) - The resulting arg list is the concatenation of the following: - - contents of ~/.valgrindrc - - contents of $VALGRIND_OPTS -+ - contents of $VALGRIND_TRY_OPTS with --try included - - contents of ./.valgrindrc - - args from the command line - in the stated order. -@@ -157,7 +160,7 @@ static void add_args_from_string ( HChar* s ) - args-for-v are then copied into tmp_xarray. - - if args-for-v does not include --command-line-only=yes: -- contents of ~/.valgrindrc, $VALGRIND_OPTS and ./.valgrindrc -+ contents of ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS and ./.valgrindrc - are copied into VG_(args_for_valgrind). - else - VG_(args_for_valgrind) is made empty. -@@ -223,7 +226,7 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) - add_string( VG_(args_for_client), argv[i] ); - } - -- /* Get extra args from ~/.valgrindrc, $VALGRIND_OPTS and -+ /* Get extra args from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS and - ./.valgrindrc into VG_(args_for_valgrind). */ - if (augment) { - // read_dot_valgrindrc() allocates the return value with -@@ -233,6 +236,8 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) - HChar* f1_clo = home ? read_dot_valgrindrc( home ) : NULL; - HChar* env_clo = VG_(strdup)( "commandline.sua.4", - VG_(getenv)(VALGRIND_OPTS) ); -+ HChar* env_try_clo = VG_(strdup)( "commandline.sua.5", -+ VG_(getenv)(VALGRIND_TRY_OPTS) ); - HChar* f2_clo = NULL; - - // Don't read ./.valgrindrc if "." is the same as "$HOME", else its -@@ -244,9 +249,10 @@ void VG_(split_up_argv)( Int argc, HChar** argv ) - ? NULL : read_dot_valgrindrc(".") ); - } - -- if (f1_clo) add_args_from_string( f1_clo ); -- if (env_clo) add_args_from_string( env_clo ); -- if (f2_clo) add_args_from_string( f2_clo ); -+ if (f1_clo) add_args_from_string( f1_clo, False ); -+ if (env_clo) add_args_from_string( env_clo, False ); -+ if (env_try_clo) add_args_from_string( env_try_clo, True ); -+ if (f2_clo) add_args_from_string( f2_clo, False ); - } - - /* .. and record how many extras we got. */ -diff --git a/coregrind/m_main.c b/coregrind/m_main.c -index f7fd20dba..7a35b7b6d 100644 ---- a/coregrind/m_main.c -+++ b/coregrind/m_main.c -@@ -289,7 +289,8 @@ static void usage_NORETURN ( int need_help ) - " --sym-offsets=yes|no show syms in form 'name+offset'? [no]\n" - " --progress-interval= report progress every \n" - " CPU seconds [0, meaning disabled]\n" --" --command-line-only=no|yes only use command line options [no]\n\n" -+" --command-line-only=no|yes only use command line options [no]\n" -+" --try skip next option if option is unsupported\n\n" - " Vex options for all Valgrind tools:\n" - " --vex-iropt-verbosity=<0..9> [0]\n" - " --vex-iropt-level=<0..2> [2]\n" -@@ -333,7 +334,7 @@ static void usage_NORETURN ( int need_help ) - - const HChar usage3[] = - "\n" --" Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc\n" -+" Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc\n" - "\n" - " %s is %s\n" - " Valgrind is Copyright (C) 2000-2024, and GNU GPL'd, by Julian Seward et al.\n" -@@ -429,6 +430,10 @@ struct process_option_state { - VgLogTo xml_to; // Where is XML output to be sent? - Int tmp_log_fd; - Int tmp_xml_fd; -+ -+ /* For cloP: True if immediately preceding option was --try. */ -+ /* For other modes: Irrelevant. */ -+ Bool try; - }; - - static void process_option (Clo_Mode mode, -@@ -438,6 +443,7 @@ static void process_option (Clo_Mode mode, - Int toolname_len = VG_(strlen)(VG_(clo_toolname)); - HChar* colon = arg; - UInt ix = 0; -+ Bool prevtry = pos->try; - - /* Constants for parsing PX control flags. */ - const HChar* pxStrings[5] -@@ -448,6 +454,7 @@ static void process_option (Clo_Mode mode, - VexRegUpdAllregsAtMemAccess, VexRegUpdAllregsAtEachInsn, 0/*inval*/ }; - - VG_(set_Clo_Mode) (mode); -+ pos->try = False; - - // Look for a colon in the option name. - while (*colon && *colon != ':' && *colon != '=') -@@ -921,14 +928,17 @@ static void process_option (Clo_Mode mode, - else if VG_XACT_CLO(arg, "--resync-filter=verbose", - VG_(clo_resync_filter), 2) {} - -+ else if VG_XACT_CLO(arg, "--try", pos->try, True) {} - else if ( VG_(Clo_Mode)() != cloE // tool does not have Early options - && !VG_(Clo_Recognised) () - && (! VG_(needs).command_line_options - || ! VG_TDICT_CALL(tool_process_cmd_line_option, arg) )) { - if (VG_(Clo_Mode)() == cloH) - ; -- else if (VG_(Clo_Mode)() == cloP && !VG_(Clo_Recognised) ()) -- VG_(fmsg_unknown_option)(arg); -+ else if (VG_(Clo_Mode)() == cloP && !VG_(Clo_Recognised) ()) { -+ if (!prevtry) -+ VG_(fmsg_unknown_option)(arg); -+ } - else if (VG_(Clo_Mode)() == cloD && !VG_(Clo_Recognised) ()) - VG_(umsg)("Ignoring dynamic change to unrecognised option %s\n", arg); - } -@@ -962,7 +972,7 @@ static void early_process_cmd_line_options ( /*OUT*/Int* need_help ) - UInt i; - HChar* str; - struct process_option_state pos -- = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1}; -+ = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1, False}; - - vg_assert( VG_(args_for_valgrind) ); - -@@ -1003,7 +1013,7 @@ void main_process_cmd_line_options( void ) - { - Int i; - struct process_option_state pos -- = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1}; -+ = {0, 0, False, False, VgLogTo_Fd, VgLogTo_Fd, 2, -1, False}; - - /* Check for sane path in ./configure --prefix=... */ - if (VG_LIBDIR[0] != '/') -diff --git a/coregrind/pub_core_libcproc.h b/coregrind/pub_core_libcproc.h -index 488aaf0ca..5b580a474 100644 ---- a/coregrind/pub_core_libcproc.h -+++ b/coregrind/pub_core_libcproc.h -@@ -53,6 +53,9 @@ - is no quoting mechanism. */ - #define VALGRIND_OPTS "VALGRIND_OPTS" - -+/* Same as VALGRIND_OPTS but puts --try before each option. */ -+#define VALGRIND_TRY_OPTS "VALGRIND_TRY_OPTS" -+ - /* The full name of Valgrind's stage1 (launcher) executable. This is - set by stage1 and read by stage2, and is used for recursive - invocations of Valgrind on child processes. -diff --git a/docs/xml/manual-core.xml b/docs/xml/manual-core.xml -index 9ab09b51b..7d07d93fc 100644 ---- a/docs/xml/manual-core.xml -+++ b/docs/xml/manual-core.xml -@@ -2839,7 +2839,7 @@ Valgrind itself, then you should use the options - - Setting Default Options - --Note that Valgrind also reads options from three places: -+Note that Valgrind also reads options from four places: - - - -@@ -2851,6 +2851,13 @@ Valgrind itself, then you should use the options - $VALGRIND_OPTS - - -+ -+ Starting with Valgrind 3.26: The environment variable -+ $VALGRIND_TRY_OPTS -+ (see below for the difference from -+ $VALGRIND_OPTS) -+ -+ - - The file ./.valgrindrc - -@@ -2873,7 +2880,8 @@ your user account. - - - Any tool-specific options put in --$VALGRIND_OPTS or the -+$VALGRIND_OPTS or -+$VALGRIND_TRY_OPTS or the - .valgrindrc files should be - prefixed with the tool name and a colon. For example, if you - want Memcheck to always do leak checking, you can put the -@@ -2888,6 +2896,51 @@ part, this will cause problems if you select other tools that - don't understand - . - -+Starting with Valgrind 3.26, -+$VALGRIND_TRY_OPTS -+is handled the same way as -+$VALGRIND_OPTS -+except that unsupported options in -+$VALGRIND_TRY_OPTS -+are skipped rather than stopping Valgrind. -+This makes -+$VALGRIND_TRY_OPTS -+suitable for best-effort -+"please use this option if it is available" scripts. -+ -+Starting with Valgrind 3.26, there is also a -+ -+option that, if it immediately precedes an unsupported option, -+will cause the unsupported option -+to be skipped rather than stopping Valgrind. -+The advantage of -+ -+over -+$VALGRIND_TRY_OPTS -+is that, in situations where the order of options is important, -+ -+can be inserted at any point in the option list, -+whereas -+$VALGRIND_TRY_OPTS -+is always handled after -+$VALGRIND_OPTS. -+The disadvantage of -+ -+is that it will stop Valgrind 3.25 and earlier. -+ -+If -+--command-line-only=yes -+appears on the command line -+then all of the above default-option mechanisms are ignored. -+Furthermore, the options -+--command-line-only, -+--profile-heap, -+--core-redzone-size, -+--redzone-size, -+and -+--aspace-minaddr -+are usable only on the command line -+and not via the default-option mechanisms. - - - -diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am -index 91d58b48b..633c036c3 100644 ---- a/memcheck/tests/Makefile.am -+++ b/memcheck/tests/Makefile.am -@@ -472,7 +472,8 @@ EXTRA_DIST = \ - wrapmallocstatic.vgtest wrapmallocstatic.stdout.exp \ - wrapmallocstatic.stderr.exp \ - writev1.stderr.exp writev1.stderr.exp-solaris writev1.vgtest \ -- xml1.stderr.exp xml1.stdout.exp xml1.vgtest xml1.stderr.exp-s390x-mvc -+ xml1.stderr.exp xml1.stdout.exp xml1.vgtest xml1.stderr.exp-s390x-mvc \ -+ xml1_env.stderr.exp xml1_env.stdout.exp xml1_env.vgtest xml1_env.stderr.exp-s390x-mvc - - check_PROGRAMS = \ - accounting \ -diff --git a/memcheck/tests/xml1_env.stderr.exp b/memcheck/tests/xml1_env.stderr.exp -new file mode 100644 -index 000000000..8ff61a531 ---- /dev/null -+++ b/memcheck/tests/xml1_env.stderr.exp -@@ -0,0 +1,440 @@ -+ -+ -+ -+ -+6 -+memcheck -+ -+ -+ ... -+ ... -+ ... -+ ... -+ -+ -+... -+... -+memcheck -+ -+ -+ ... -+ -+ ./xml1 -+ -+ -+ -+ -+ RUNNING -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidRead -+ Invalid read of size N -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is 0 bytes after a block of size 40 alloc'd -+ -+ -+ 0x........ -+ ... -+ malloc -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ UninitCondition -+ Conditional jump or move depends on uninitialised value(s) -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ UninitValue -+ Use of uninitialised value of size N -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidFree -+ Invalid free() / delete / delete[] / realloc() -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is 0 bytes inside a block of size 40 free'd -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidFree -+ Invalid free() / delete / delete[] / realloc() -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is on thread 1's stack -+ in frame #1, created by frame3 (xml1.c:7) -+ -+ -+ -+ 0x........ -+ ... -+ SyscallParam -+ Syscall param exit(status) contains uninitialised byte(s) -+ -+ -+ -+ FINISHED -+ -+ -+ -+... -+ -+ -+ 0x........ -+ ... -+ Leak_DefinitelyLost -+ -+ 396 bytes in 1 blocks are definitely lost in loss record ... of ... -+ 396 -+ 1 -+ -+ -+ -+ 0x........ -+ ... -+ malloc -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+... -+ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ -+... -+ -+... -+ -+ -diff --git a/memcheck/tests/xml1_env.stderr.exp-s390x-mvc b/memcheck/tests/xml1_env.stderr.exp-s390x-mvc -new file mode 100644 -index 000000000..e1abac28a ---- /dev/null -+++ b/memcheck/tests/xml1_env.stderr.exp-s390x-mvc -@@ -0,0 +1,436 @@ -+ -+ -+ -+ -+4 -+memcheck -+ -+ -+ ... -+ ... -+ ... -+ ... -+ -+ -+... -+... -+memcheck -+ -+ -+ ... -+ -+ ./xml1 -+ -+ -+ -+ -+ RUNNING -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidRead -+ Invalid read of size 1 -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is 0 bytes after a block of size 40 alloc'd -+ -+ -+ 0x........ -+ ... -+ malloc -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ UninitCondition -+ Conditional jump or move depends on uninitialised value(s) -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ UninitValue -+ Use of uninitialised value of size N -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidFree -+ Invalid free() / delete / delete[] / realloc() -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is 0 bytes inside a block of size 40 free'd -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ 0x........ -+ ... -+ InvalidFree -+ Invalid free() / delete / delete[] / realloc() -+ -+ -+ 0x........ -+ ... -+ free -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ Address 0x........ is on thread 1's stack -+ in frame #1, created by frame3 (xml1.c:7) -+ -+ -+ -+ 0x........ -+ ... -+ SyscallParam -+ Syscall param exit(status) contains uninitialised byte(s) -+ -+ -+ -+ -+ FINISHED -+ -+ -+ -+ -+ 0x........ -+ ... -+ Leak_DefinitelyLost -+ -+ 396 bytes in 1 blocks are definitely lost in loss record ... of ... -+ 396 -+ 1 -+ -+ -+ -+ 0x........ -+ ... -+ malloc -+ ... -+ vg_replace_malloc.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame3 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame2 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ frame1 -+ ... -+ xml1.c -+ ... -+ -+ -+ 0x........ -+ ... -+ main -+ ... -+ xml1.c -+ ... -+ -+ -+ -+ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ ... -+ 0x........ -+ -+ -+ -+... -+ -+ -+ -diff --git a/memcheck/tests/xml1_env.stdout.exp b/memcheck/tests/xml1_env.stdout.exp -new file mode 100644 -index 000000000..9397eece6 ---- /dev/null -+++ b/memcheck/tests/xml1_env.stdout.exp -@@ -0,0 +1 @@ -+hello from frame3(). The answer is not 42. -diff --git a/memcheck/tests/xml1_env.vgtest b/memcheck/tests/xml1_env.vgtest -new file mode 100644 -index 000000000..6b6cd8aab ---- /dev/null -+++ b/memcheck/tests/xml1_env.vgtest -@@ -0,0 +1,4 @@ -+prog: xml1 -+vgopts: --command-line-only=no -+env: VALGRIND_TRY_OPTS='--xml=yes --xml-fd=2 --log-file=/dev/null --keep-stacktraces=alloc-then-free' -+stderr_filter: filter_xml -diff --git a/none/tests/Makefile.am b/none/tests/Makefile.am -index 716ce000d..9961b80d8 100644 ---- a/none/tests/Makefile.am -+++ b/none/tests/Makefile.am -@@ -88,6 +88,7 @@ dist_noinst_SCRIPTS = \ - filter_fdleak \ - filter_ioctl_moans \ - filter_none_discards \ -+ filter_optlist \ - filter_stderr \ - filter_timestamp \ - filter_xml \ -@@ -134,6 +135,15 @@ EXTRA_DIST = \ - double_close_range_xml.vgtest \ - double_close_range_sup.stderr.exp double_close_range_sup.vgtest \ - empty-exe.vgtest empty-exe.stderr.exp \ -+ env1.stderr.exp env1.vgtest try1.stderr.exp try1.vgtest \ -+ env2.stderr.exp env2.vgtest try2.stderr.exp try2.vgtest \ -+ env3.stderr.exp env3.vgtest try3.stderr.exp try3.vgtest \ -+ env4.stderr.exp env4.vgtest try4.stderr.exp try4.vgtest \ -+ env5.stderr.exp env5.vgtest \ -+ env6.stderr.exp env6.vgtest \ -+ trytry.stderr.exp trytry.vgtest \ -+ trytry2.stderr.exp trytry2.vgtest \ -+ trytry3.stderr.exp trytry3.vgtest \ - exec-sigmask.vgtest exec-sigmask.stdout.exp \ - exec-sigmask.stdout.exp2 exec-sigmask.stdout.exp3 \ - exec-sigmask.stdout.exp-solaris exec-sigmask.stderr.exp \ -@@ -253,6 +263,10 @@ EXTRA_DIST = \ - threadederrno.vgtest \ - timer_delete.vgtest timer_delete.stderr.exp \ - timestamp.stderr.exp timestamp.vgtest \ -+ timestamp_env.stderr.exp timestamp_env.vgtest \ -+ timestamp_env21.stderr.exp timestamp_env21.vgtest \ -+ timestamp_env22.stderr.exp timestamp_env22.vgtest \ -+ timestamp_envoverride.stderr.exp timestamp_envoverride.vgtest \ - tls.vgtest tls.stderr.exp tls.stdout.exp \ - track-fds-exec-children.vgtest track-fds-exec-children.stderr.exp \ - unit_debuglog.stderr.exp unit_debuglog.vgtest \ -diff --git a/none/tests/cmdline1.stdout.exp b/none/tests/cmdline1.stdout.exp -index 5d3ea0569..3bd3d1afc 100644 ---- a/none/tests/cmdline1.stdout.exp -+++ b/none/tests/cmdline1.stdout.exp -@@ -169,7 +169,7 @@ usage: valgrind [options] prog-and-args - user options for Nulgrind: - (none) - -- Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc -+ Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc - - Nulgrind is Copyright (C) 2000, and GNU GPL'd, by Nicholas Nethercote et al. - Valgrind is Copyright (C) 2000, and GNU GPL'd, by Julian Seward et al. -diff --git a/none/tests/cmdline2.stdout.exp b/none/tests/cmdline2.stdout.exp -index 618c43acb..0ca1ddb34 100644 ---- a/none/tests/cmdline2.stdout.exp -+++ b/none/tests/cmdline2.stdout.exp -@@ -201,6 +201,7 @@ usage: valgrind [options] prog-and-args - --progress-interval= report progress every - CPU seconds [0, meaning disabled] - --command-line-only=no|yes only use command line options [no] -+ --try skip next option if option is unsupported - - Vex options for all Valgrind tools: - --vex-iropt-verbosity=<0..9> [0] -@@ -245,7 +246,7 @@ usage: valgrind [options] prog-and-args - debugging options for Nulgrind: - (none) - -- Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, ./.valgrindrc -+ Extra options read from ~/.valgrindrc, $VALGRIND_OPTS, $VALGRIND_TRY_OPTS, ./.valgrindrc - - Nulgrind is Copyright (C) 2000, and GNU GPL'd, by Nicholas Nethercote et al. - Valgrind is Copyright (C) 2000, and GNU GPL'd, by Julian Seward et al. -diff --git a/none/tests/env1.stderr.exp b/none/tests/env1.stderr.exp -new file mode 100644 -index 000000000..e69de29bb -diff --git a/none/tests/env1.vgtest b/none/tests/env1.vgtest -new file mode 100644 -index 000000000..10925d78d ---- /dev/null -+++ b/none/tests/env1.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS=--bad-bad-option=yes -+vgopts: -q --command-line-only=no -+prog: ../../tests/true -diff --git a/none/tests/env2.stderr.exp b/none/tests/env2.stderr.exp -new file mode 100644 -index 000000000..139597f9c ---- /dev/null -+++ b/none/tests/env2.stderr.exp -@@ -0,0 +1,2 @@ -+ -+ -diff --git a/none/tests/env2.vgtest b/none/tests/env2.vgtest -new file mode 100644 -index 000000000..102d99e67 ---- /dev/null -+++ b/none/tests/env2.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS=--trace-children=no -+vgopts: --command-line-only=no -+prog: ../../tests/true -diff --git a/none/tests/env3.stderr.exp b/none/tests/env3.stderr.exp -new file mode 100644 -index 000000000..139597f9c ---- /dev/null -+++ b/none/tests/env3.stderr.exp -@@ -0,0 +1,2 @@ -+ -+ -diff --git a/none/tests/env3.vgtest b/none/tests/env3.vgtest -new file mode 100644 -index 000000000..9a43a3ebd ---- /dev/null -+++ b/none/tests/env3.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS=--trace-children=yes -+vgopts: --command-line-only=no -+prog: ../../tests/true -diff --git a/none/tests/env4.stderr.exp b/none/tests/env4.stderr.exp -new file mode 100644 -index 000000000..fb3624035 ---- /dev/null -+++ b/none/tests/env4.stderr.exp -@@ -0,0 +1,3 @@ -+valgrind: Bad option: --trace-children=1 -+valgrind: Invalid boolean value '1' (should be 'yes' or 'no') -+valgrind: Use --help for more information or consult the user manual. -diff --git a/none/tests/env4.vgtest b/none/tests/env4.vgtest -new file mode 100644 -index 000000000..7794bd74b ---- /dev/null -+++ b/none/tests/env4.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS=--trace-children=1 -+vgopts: --command-line-only=no -+prog: ../../tests/true -diff --git a/none/tests/env5.stderr.exp b/none/tests/env5.stderr.exp -new file mode 100644 -index 000000000..139597f9c ---- /dev/null -+++ b/none/tests/env5.stderr.exp -@@ -0,0 +1,2 @@ -+ -+ -diff --git a/none/tests/env5.vgtest b/none/tests/env5.vgtest -new file mode 100644 -index 000000000..5e2ce8a41 ---- /dev/null -+++ b/none/tests/env5.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS=--trace-children=1 -+vgopts: --command-line-only=yes -+prog: ../../tests/true -diff --git a/none/tests/env6.stderr.exp b/none/tests/env6.stderr.exp -new file mode 100644 -index 000000000..59c75a7f5 ---- /dev/null -+++ b/none/tests/env6.stderr.exp -@@ -0,0 +1,12 @@ -+ --try -+ --gen-suppressions=no -+ --try -+ --trace-children=yes -+ --try -+ -q -+ --command-line-only=yes -+ --memcheck:leak-check=no -+ --tool=none -+ --command-line-only=no -+ --verbose -+ --verbose -diff --git a/none/tests/env6.vgtest b/none/tests/env6.vgtest -new file mode 100644 -index 000000000..d45479d9f ---- /dev/null -+++ b/none/tests/env6.vgtest -@@ -0,0 +1,4 @@ -+env: VALGRIND_TRY_OPTS='--gen-suppressions=no --trace-children=yes -q' -+vgopts: --command-line-only=no --verbose --verbose -+prog: ../../tests/true -+stderr_filter: filter_optlist -diff --git a/none/tests/filter_optlist b/none/tests/filter_optlist -new file mode 100755 -index 000000000..39639a198 ---- /dev/null -+++ b/none/tests/filter_optlist -@@ -0,0 +1,11 @@ -+#! /bin/sh -+ -+dir=`dirname $0` -+ -+$dir/filter_stderr | -+awk ' -+ BEGIN { printing = 0 } -+ /^Valgrind options:/ { printing = 1; next } -+ /^ / { if (printing) print; next } -+ { printing = 0 } -+' -diff --git a/none/tests/timestamp_env.stderr.exp b/none/tests/timestamp_env.stderr.exp -new file mode 100644 -index 000000000..00a5083ae ---- /dev/null -+++ b/none/tests/timestamp_env.stderr.exp -@@ -0,0 +1,2 @@ -+00:00:00:XX:YYY -+00:00:00:XX:YYY -diff --git a/none/tests/timestamp_env.vgtest b/none/tests/timestamp_env.vgtest -new file mode 100644 -index 000000000..fe5b20e3b ---- /dev/null -+++ b/none/tests/timestamp_env.vgtest -@@ -0,0 +1,4 @@ -+env: VALGRIND_TRY_OPTS=--time-stamp=yes -+vgopts: --command-line-only=no -+prog: timestamp -+stderr_filter: filter_timestamp -diff --git a/none/tests/timestamp_env21.stderr.exp b/none/tests/timestamp_env21.stderr.exp -new file mode 100644 -index 000000000..00a5083ae ---- /dev/null -+++ b/none/tests/timestamp_env21.stderr.exp -@@ -0,0 +1,2 @@ -+00:00:00:XX:YYY -+00:00:00:XX:YYY -diff --git a/none/tests/timestamp_env21.vgtest b/none/tests/timestamp_env21.vgtest -new file mode 100644 -index 000000000..aa6e76688 ---- /dev/null -+++ b/none/tests/timestamp_env21.vgtest -@@ -0,0 +1,4 @@ -+env: VALGRIND_TRY_OPTS='--time-stamp=yes --trace-children=yes' -+vgopts: --command-line-only=no -+prog: timestamp -+stderr_filter: filter_timestamp -diff --git a/none/tests/timestamp_env22.stderr.exp b/none/tests/timestamp_env22.stderr.exp -new file mode 100644 -index 000000000..00a5083ae ---- /dev/null -+++ b/none/tests/timestamp_env22.stderr.exp -@@ -0,0 +1,2 @@ -+00:00:00:XX:YYY -+00:00:00:XX:YYY -diff --git a/none/tests/timestamp_env22.vgtest b/none/tests/timestamp_env22.vgtest -new file mode 100644 -index 000000000..cb628ac8d ---- /dev/null -+++ b/none/tests/timestamp_env22.vgtest -@@ -0,0 +1,4 @@ -+env: VALGRIND_TRY_OPTS='--show-below-main=no --time-stamp=yes' -+vgopts: --command-line-only=no -+prog: timestamp -+stderr_filter: filter_timestamp -diff --git a/none/tests/timestamp_envoverride.stderr.exp b/none/tests/timestamp_envoverride.stderr.exp -new file mode 100644 -index 000000000..00a5083ae ---- /dev/null -+++ b/none/tests/timestamp_envoverride.stderr.exp -@@ -0,0 +1,2 @@ -+00:00:00:XX:YYY -+00:00:00:XX:YYY -diff --git a/none/tests/timestamp_envoverride.vgtest b/none/tests/timestamp_envoverride.vgtest -new file mode 100644 -index 000000000..c77b52762 ---- /dev/null -+++ b/none/tests/timestamp_envoverride.vgtest -@@ -0,0 +1,4 @@ -+env: VALGRIND_TRY_OPTS=--time-stamp=no -+vgopts: --time-stamp=yes --command-line-only=no -+prog: timestamp -+stderr_filter: filter_timestamp -diff --git a/none/tests/try1.stderr.exp b/none/tests/try1.stderr.exp -new file mode 100644 -index 000000000..e69de29bb -diff --git a/none/tests/try1.vgtest b/none/tests/try1.vgtest -new file mode 100644 -index 000000000..d00f79dfb ---- /dev/null -+++ b/none/tests/try1.vgtest -@@ -0,0 +1,2 @@ -+vgopts: -q --try --bad-bad-option=yes -+prog: ../../tests/true -diff --git a/none/tests/try2.stderr.exp b/none/tests/try2.stderr.exp -new file mode 100644 -index 000000000..139597f9c ---- /dev/null -+++ b/none/tests/try2.stderr.exp -@@ -0,0 +1,2 @@ -+ -+ -diff --git a/none/tests/try2.vgtest b/none/tests/try2.vgtest -new file mode 100644 -index 000000000..ce763273c ---- /dev/null -+++ b/none/tests/try2.vgtest -@@ -0,0 +1,2 @@ -+vgopts: --try --trace-children=no -+prog: ../../tests/true -diff --git a/none/tests/try3.stderr.exp b/none/tests/try3.stderr.exp -new file mode 100644 -index 000000000..139597f9c ---- /dev/null -+++ b/none/tests/try3.stderr.exp -@@ -0,0 +1,2 @@ -+ -+ -diff --git a/none/tests/try3.vgtest b/none/tests/try3.vgtest -new file mode 100644 -index 000000000..ce763273c ---- /dev/null -+++ b/none/tests/try3.vgtest -@@ -0,0 +1,2 @@ -+vgopts: --try --trace-children=no -+prog: ../../tests/true -diff --git a/none/tests/try4.stderr.exp b/none/tests/try4.stderr.exp -new file mode 100644 -index 000000000..fb3624035 ---- /dev/null -+++ b/none/tests/try4.stderr.exp -@@ -0,0 +1,3 @@ -+valgrind: Bad option: --trace-children=1 -+valgrind: Invalid boolean value '1' (should be 'yes' or 'no') -+valgrind: Use --help for more information or consult the user manual. -diff --git a/none/tests/try4.vgtest b/none/tests/try4.vgtest -new file mode 100644 -index 000000000..21bb392e9 ---- /dev/null -+++ b/none/tests/try4.vgtest -@@ -0,0 +1,2 @@ -+vgopts: --try --trace-children=1 -+prog: ../../tests/true -diff --git a/none/tests/trytry.stderr.exp b/none/tests/trytry.stderr.exp -new file mode 100644 -index 000000000..e69de29bb -diff --git a/none/tests/trytry.vgtest b/none/tests/trytry.vgtest -new file mode 100644 -index 000000000..b638148ed ---- /dev/null -+++ b/none/tests/trytry.vgtest -@@ -0,0 +1,2 @@ -+vgopts: -q --try --try --bad-bad-option -+prog: ../../tests/true -diff --git a/none/tests/trytry2.stderr.exp b/none/tests/trytry2.stderr.exp -new file mode 100644 -index 000000000..879e12a28 ---- /dev/null -+++ b/none/tests/trytry2.stderr.exp -@@ -0,0 +1,2 @@ -+valgrind: Unknown option: --bad-bad-option -+valgrind: Use --help for more information or consult the user manual. -diff --git a/none/tests/trytry2.vgtest b/none/tests/trytry2.vgtest -new file mode 100644 -index 000000000..cb5335456 ---- /dev/null -+++ b/none/tests/trytry2.vgtest -@@ -0,0 +1,2 @@ -+vgopts: -q --try --try --bad-bad-option --bad-bad-option -+prog: ../../tests/true -diff --git a/none/tests/trytry3.stderr.exp b/none/tests/trytry3.stderr.exp -new file mode 100644 -index 000000000..4dac4a198 ---- /dev/null -+++ b/none/tests/trytry3.stderr.exp -@@ -0,0 +1,14 @@ -+ --command-line-only=yes -+ --memcheck:leak-check=no -+ --tool=none -+ --try -+ --gen-suppressions=no -+ --try -+ --trace-children=yes -+ --try -+ -q -+ --try -+ --try -+ --bad-bad-option -+ --verbose -+ --verbose -diff --git a/none/tests/trytry3.vgtest b/none/tests/trytry3.vgtest -new file mode 100644 -index 000000000..b065fd0e9 ---- /dev/null -+++ b/none/tests/trytry3.vgtest -@@ -0,0 +1,3 @@ -+vgopts: --try --gen-suppressions=no --try --trace-children=yes --try -q --try --try --bad-bad-option --verbose --verbose -+prog: ../../tests/true -+stderr_filter: filter_optlist diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt deleted file mode 100644 index de4021f084..0000000000 --- a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-patch-20250805.txt +++ /dev/null @@ -1,909 +0,0 @@ -diff --git a/.gitignore b/.gitignore -index e48a2ab0e..812e8f11f 100644 ---- a/.gitignore -+++ b/.gitignore -@@ -1042,6 +1042,8 @@ - /memcheck/tests/varinfo5so.so - /memcheck/tests/varinfo6 - /memcheck/tests/varinforestrict -+/memcheck/tests/varlat -+/memcheck/tests/varlat2 - /memcheck/tests/vcpu_bz2 - /memcheck/tests/vcpu_fbench - /memcheck/tests/vcpu_fnfns -diff --git a/memcheck/mc_errors.c b/memcheck/mc_errors.c -index a708b3f85..8d163cd90 100644 ---- a/memcheck/mc_errors.c -+++ b/memcheck/mc_errors.c -@@ -94,11 +94,14 @@ struct _MC_Error { - // Use of an undefined value: - // - as a pointer in a load or store - // - as a jump target -+ // - (VARLAT mode) as an operand in a variable-latency instruction - struct { - SizeT szB; // size of value in bytes - // Origin info - UInt otag; // origin tag - ExeContext* origin_ec; // filled in later -+ // VARLAT mode -+ Bool isLatency; // if issue is variable-latency instruction - } Value; - - // Use of an undefined value in a conditional branch or move. -@@ -489,8 +492,13 @@ void MC_(pp_Error) ( const Error* err ) - MC_(any_value_errors) = True; - if (xml) { - emit( " UninitValue\n" ); -- emit( " Use of uninitialised value of size %lu\n", -- extra->Err.Value.szB ); -+ if (extra->Err.Value.isLatency) -+ emit( " Variable-latency instruction operand" -+ " of size %lu is secret/uninitialised\n", -+ extra->Err.Value.szB ); -+ else -+ emit( " Use of uninitialised value of size %lu\n", -+ extra->Err.Value.szB ); - VG_(pp_ExeContext)( VG_(get_error_where)(err) ); - if (extra->Err.Value.origin_ec) - mc_pp_origin( extra->Err.Value.origin_ec, -@@ -498,8 +506,13 @@ void MC_(pp_Error) ( const Error* err ) - } else { - /* Could also show extra->Err.Cond.otag if debugging origin - tracking */ -- emit( "Use of uninitialised value of size %lu\n", -- extra->Err.Value.szB ); -+ if (extra->Err.Value.isLatency) -+ emit( "Variable-latency instruction operand" -+ " of size %lu is secret/uninitialised\n", -+ extra->Err.Value.szB ); -+ else -+ emit( "Use of uninitialised value of size %lu\n", -+ extra->Err.Value.szB ); - VG_(pp_ExeContext)( VG_(get_error_where)(err) ); - if (extra->Err.Value.origin_ec) - mc_pp_origin( extra->Err.Value.origin_ec, -@@ -926,6 +939,20 @@ void MC_(record_value_error) ( ThreadId tid, Int szB, UInt otag ) - extra.Err.Value.szB = szB; - extra.Err.Value.otag = otag; - extra.Err.Value.origin_ec = NULL; /* Filled in later */ -+ extra.Err.Value.isLatency = False; -+ VG_(maybe_record_error)( tid, Err_Value, /*addr*/0, /*s*/NULL, &extra ); -+} -+ -+void MC_(record_varlat_value_error) ( ThreadId tid, Int szB, UInt otag ) -+{ -+ MC_Error extra; -+ tl_assert( MC_(clo_mc_level) >= 2 ); -+ if (otag > 0) -+ tl_assert( MC_(clo_mc_level) == 3 ); -+ extra.Err.Value.szB = szB; -+ extra.Err.Value.otag = otag; -+ extra.Err.Value.origin_ec = NULL; /* Filled in later */ -+ extra.Err.Value.isLatency = True; - VG_(maybe_record_error)( tid, Err_Value, /*addr*/0, /*s*/NULL, &extra ); - } - -diff --git a/memcheck/mc_include.h b/memcheck/mc_include.h -index acc595a74..2578cccb0 100644 ---- a/memcheck/mc_include.h -+++ b/memcheck/mc_include.h -@@ -525,6 +525,10 @@ void MC_(pp_LossRecord)(UInt n_this_record, UInt n_total_records, - rerun with --track-origins=yes might help. */ - extern Bool MC_(any_value_errors); - -+/* Are we running in VARLAT mode, where variable-latency operations -+ on uninitialised (or secret) values are considered as errors ? */ -+extern Bool MC_(clo_variable_latency_errors); -+ - /* Standard functions for error and suppressions as required by the - core/tool iface */ - Bool MC_(eq_Error) ( VgRes res, const Error* e1, const Error* e2 ); -@@ -552,6 +556,7 @@ void MC_(record_address_error) ( ThreadId tid, Addr a, Int szB, - Bool isWrite ); - void MC_(record_cond_error) ( ThreadId tid, UInt otag ); - void MC_(record_value_error) ( ThreadId tid, Int szB, UInt otag ); -+void MC_(record_varlat_value_error) ( ThreadId tid, Int szB, UInt otag ); - void MC_(record_jump_error) ( ThreadId tid, Addr a ); - - void MC_(record_free_error) ( ThreadId tid, Addr a ); -@@ -773,6 +778,10 @@ VG_REGPARM(1) void MC_(helperc_value_check8_fail_w_o) ( UWord ); - VG_REGPARM(1) void MC_(helperc_value_check4_fail_w_o) ( UWord ); - VG_REGPARM(1) void MC_(helperc_value_check1_fail_w_o) ( UWord ); - VG_REGPARM(1) void MC_(helperc_value_check0_fail_w_o) ( UWord ); -+VG_REGPARM(2) void MC_(helperc_value_checkN_varlat_fail_w_o) ( HWord, UWord ); -+VG_REGPARM(1) void MC_(helperc_value_check8_varlat_fail_w_o) ( UWord ); -+VG_REGPARM(1) void MC_(helperc_value_check4_varlat_fail_w_o) ( UWord ); -+VG_REGPARM(1) void MC_(helperc_value_check1_varlat_fail_w_o) ( UWord ); - - /* And call these ones instead to report an uninitialised value error - but with no origin available. */ -@@ -781,6 +790,10 @@ VG_REGPARM(0) void MC_(helperc_value_check8_fail_no_o) ( void ); - VG_REGPARM(0) void MC_(helperc_value_check4_fail_no_o) ( void ); - VG_REGPARM(0) void MC_(helperc_value_check1_fail_no_o) ( void ); - VG_REGPARM(0) void MC_(helperc_value_check0_fail_no_o) ( void ); -+VG_REGPARM(1) void MC_(helperc_value_checkN_varlat_fail_no_o) ( HWord ); -+VG_REGPARM(0) void MC_(helperc_value_check8_varlat_fail_no_o) ( void ); -+VG_REGPARM(0) void MC_(helperc_value_check4_varlat_fail_no_o) ( void ); -+VG_REGPARM(0) void MC_(helperc_value_check1_varlat_fail_no_o) ( void ); - - /* V-bits load/store helpers */ - VG_REGPARM(1) void MC_(helperc_STOREV64be) ( Addr, ULong ); -diff --git a/memcheck/mc_main.c b/memcheck/mc_main.c -index 626d481d2..49f835929 100644 ---- a/memcheck/mc_main.c -+++ b/memcheck/mc_main.c -@@ -5770,6 +5770,27 @@ void MC_(helperc_value_checkN_fail_w_o) ( HWord sz, UWord origin ) { - MC_(record_value_error) ( VG_(get_running_tid)(), (Int)sz, (UInt)origin ); - } - -+VG_REGPARM(1) -+void MC_(helperc_value_check1_varlat_fail_w_o) ( UWord origin ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 1, (UInt)origin ); -+} -+ -+VG_REGPARM(1) -+void MC_(helperc_value_check4_varlat_fail_w_o) ( UWord origin ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 4, (UInt)origin ); -+} -+ -+VG_REGPARM(1) -+void MC_(helperc_value_check8_varlat_fail_w_o) ( UWord origin ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 8, (UInt)origin ); -+} -+ -+VG_REGPARM(2) -+void MC_(helperc_value_checkN_varlat_fail_w_o) ( HWord sz, UWord origin ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), (Int)sz, -+ (UInt)origin ); -+} -+ - /* ... and these when an origin isn't available. */ - - VG_REGPARM(0) -@@ -5797,6 +5818,27 @@ void MC_(helperc_value_checkN_fail_no_o) ( HWord sz ) { - MC_(record_value_error) ( VG_(get_running_tid)(), (Int)sz, 0/*origin*/ ); - } - -+VG_REGPARM(0) -+void MC_(helperc_value_check1_varlat_fail_no_o) ( void ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 1, 0/*origin*/ ); -+} -+ -+VG_REGPARM(0) -+void MC_(helperc_value_check4_varlat_fail_no_o) ( void ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 4, 0/*origin*/ ); -+} -+ -+VG_REGPARM(0) -+void MC_(helperc_value_check8_varlat_fail_no_o) ( void ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), 8, 0/*origin*/ ); -+} -+ -+VG_REGPARM(1) -+void MC_(helperc_value_checkN_varlat_fail_no_o) ( HWord sz ) { -+ MC_(record_varlat_value_error) ( VG_(get_running_tid)(), (Int)sz, -+ 0/*origin*/ ); -+} -+ - - /*------------------------------------------------------------*/ - /*--- Metadata get/set functions, for client requests. ---*/ -@@ -6064,6 +6106,7 @@ UInt MC_(clo_leak_check_heuristics) = H2S(LchStdString) - | H2S( LchMultipleInheritance); - Bool MC_(clo_xtree_leak) = False; - const HChar* MC_(clo_xtree_leak_file) = "xtleak.kcg.%p"; -+Bool MC_(clo_variable_latency_errors) = False; - Bool MC_(clo_workaround_gcc296_bugs) = False; - Int MC_(clo_malloc_fill) = -1; - Int MC_(clo_free_fill) = -1; -@@ -6135,6 +6178,8 @@ static Bool mc_process_cmd_line_options(const HChar* arg) - else if VG_USET_CLOM(cloPD, arg, "--leak-check-heuristics", - MC_(parse_leak_heuristics_tokens), - MC_(clo_leak_check_heuristics)) {} -+ else if VG_BOOL_CLOM(cloPD, arg, "--variable-latency-errors", -+ MC_(clo_variable_latency_errors)) {} - else if (VG_BOOL_CLOM(cloPD, arg, "--show-reachable", tmp_show)) { - if (tmp_show) { - MC_(clo_show_leak_kinds) = MC_(all_Reachedness)(); -@@ -6313,6 +6358,7 @@ static void mc_print_usage(void) - " --xtree-leak=no|yes output leak result in xtree format? [no]\n" - " --xtree-leak-file= xtree leak report file [xtleak.kcg.%%p]\n" - " --undef-value-errors=no|yes check for undefined value errors [yes]\n" -+" --variable-latency-errors=no|yes check for variable latency errors [no]\n" - " --track-origins=no|yes show origins of undefined values? [no]\n" - " --partial-loads-ok=no|yes too hard to explain here; see manual [yes]\n" - " --expensive-definedness-checks=no|auto|yes\n" -diff --git a/memcheck/mc_translate.c b/memcheck/mc_translate.c -index b4e499cc0..2d93f2d18 100644 ---- a/memcheck/mc_translate.c -+++ b/memcheck/mc_translate.c -@@ -1593,8 +1593,13 @@ static void setHelperAnns ( MCEnv* mce, IRDirty* di ) { - - This routine does not generate code to check the definedness of - |guard|. The caller is assumed to have taken care of that already. -+ -+ If varlatLatency is set, then this function emits complaints for -+ VARLAT mode rather than for the usual branches etc. - */ --static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) -+static void complainIfUndefinedOrVariableLatency ( MCEnv* mce, IRAtom* atom, -+ IRExpr *guard, -+ Bool varlatLatency ) - { - IRAtom* vatom; - IRType ty; -@@ -1664,39 +1669,69 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) - break; - case 1: - if (origin) { -- fn = &MC_(helperc_value_check1_fail_w_o); -- nm = "MC_(helperc_value_check1_fail_w_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check1_fail_w_o); -+ nm = "MC_(helperc_value_check1_fail_w_o)"; -+ } else { -+ fn = &MC_(helperc_value_check1_varlat_fail_w_o); -+ nm = "MC_(helperc_value_check1_varlat_fail_w_o)"; -+ } - args = mkIRExprVec_1(origin); - nargs = 1; - } else { -- fn = &MC_(helperc_value_check1_fail_no_o); -- nm = "MC_(helperc_value_check1_fail_no_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check1_fail_no_o); -+ nm = "MC_(helperc_value_check1_fail_no_o)"; -+ } else { -+ fn = &MC_(helperc_value_check1_varlat_fail_no_o); -+ nm = "MC_(helperc_value_check1_varlat_fail_no_o)"; -+ } - args = mkIRExprVec_0(); - nargs = 0; - } - break; - case 4: - if (origin) { -- fn = &MC_(helperc_value_check4_fail_w_o); -- nm = "MC_(helperc_value_check4_fail_w_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check4_fail_w_o); -+ nm = "MC_(helperc_value_check4_fail_w_o)"; -+ } else { -+ fn = &MC_(helperc_value_check4_varlat_fail_w_o); -+ nm = "MC_(helperc_value_check4_varlat_fail_w_o)"; -+ } - args = mkIRExprVec_1(origin); - nargs = 1; - } else { -- fn = &MC_(helperc_value_check4_fail_no_o); -- nm = "MC_(helperc_value_check4_fail_no_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check4_fail_no_o); -+ nm = "MC_(helperc_value_check4_fail_no_o)"; -+ } else { -+ fn = &MC_(helperc_value_check4_varlat_fail_no_o); -+ nm = "MC_(helperc_value_check4_varlat_fail_no_o)"; -+ } - args = mkIRExprVec_0(); - nargs = 0; - } - break; - case 8: - if (origin) { -- fn = &MC_(helperc_value_check8_fail_w_o); -- nm = "MC_(helperc_value_check8_fail_w_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check8_fail_w_o); -+ nm = "MC_(helperc_value_check8_fail_w_o)"; -+ } else { -+ fn = &MC_(helperc_value_check8_varlat_fail_w_o); -+ nm = "MC_(helperc_value_check8_varlat_fail_w_o)"; -+ } - args = mkIRExprVec_1(origin); - nargs = 1; - } else { -- fn = &MC_(helperc_value_check8_fail_no_o); -- nm = "MC_(helperc_value_check8_fail_no_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_check8_fail_no_o); -+ nm = "MC_(helperc_value_check8_fail_no_o)"; -+ } else { -+ fn = &MC_(helperc_value_check8_varlat_fail_no_o); -+ nm = "MC_(helperc_value_check8_varlat_fail_no_o)"; -+ } - args = mkIRExprVec_0(); - nargs = 0; - } -@@ -1704,13 +1739,23 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) - case 2: - case 16: - if (origin) { -- fn = &MC_(helperc_value_checkN_fail_w_o); -- nm = "MC_(helperc_value_checkN_fail_w_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_checkN_fail_w_o); -+ nm = "MC_(helperc_value_checkN_fail_w_o)"; -+ } else { -+ fn = &MC_(helperc_value_checkN_varlat_fail_w_o); -+ nm = "MC_(helperc_value_checkN_varlat_fail_w_o)"; -+ } - args = mkIRExprVec_2( mkIRExpr_HWord( sz ), origin); - nargs = 2; - } else { -- fn = &MC_(helperc_value_checkN_fail_no_o); -- nm = "MC_(helperc_value_checkN_fail_no_o)"; -+ if (!varlatLatency) { -+ fn = &MC_(helperc_value_checkN_fail_no_o); -+ nm = "MC_(helperc_value_checkN_fail_no_o)"; -+ } else { -+ fn = &MC_(helperc_value_checkN_varlat_fail_no_o); -+ nm = "MC_(helperc_value_checkN_varlat_fail_no_o)"; -+ } - args = mkIRExprVec_1( mkIRExpr_HWord( sz ) ); - nargs = 1; - } -@@ -1771,6 +1816,17 @@ static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) - } - } - -+static void complainIfUndefined ( MCEnv* mce, IRAtom* atom, IRExpr *guard ) -+{ -+ complainIfUndefinedOrVariableLatency(mce, atom, guard, False); -+} -+ -+static void complainIfVariableLatency ( MCEnv* mce, IRAtom* atom ) -+{ -+ if (MC_(clo_variable_latency_errors)) -+ complainIfUndefinedOrVariableLatency(mce, atom, NULL, True); -+} -+ - - /*------------------------------------------------------------*/ - /*--- Shadowing PUTs/GETs, and indexed variants thereof ---*/ -@@ -3511,14 +3567,19 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, - case Iop_Yl2xF64: - case Iop_Yl2xp1F64: - case Iop_AtanF64: -- case Iop_PRemF64: -- case Iop_PRem1F64: - case Iop_QuantizeD64: - /* I32(rm) x F64/D64 x F64/D64 -> F64/D64 */ - return mkLazy3(mce, Ity_I64, vatom1, vatom2, vatom3); -+ case Iop_PRemF64: -+ case Iop_PRem1F64: -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); -+ return mkLazy3(mce, Ity_I64, vatom1, vatom2, vatom3); - case Iop_PRemC3210F64: - case Iop_PRem1C3210F64: - /* I32(rm) x F64 x F64 -> I32 */ -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); - return mkLazy3(mce, Ity_I32, vatom1, vatom2, vatom3); - case Iop_AddF32: - case Iop_SubF32: -@@ -3572,21 +3633,33 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, - case Iop_Add64Fx2: - case Iop_Sub64Fx2: - case Iop_Mul64Fx2: -- case Iop_Div64Fx2: - case Iop_Scale2_64Fx2: - return binary64Fx2_w_rm(mce, vatom1, vatom2, vatom3); - -+ case Iop_Div64Fx2: -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); -+ return binary64Fx2_w_rm(mce, vatom1, vatom2, vatom3); -+ - case Iop_Add32Fx4: - case Iop_Sub32Fx4: - case Iop_Mul32Fx4: -- case Iop_Div32Fx4: - case Iop_Scale2_32Fx4: -- return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); -+ return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); -+ -+ case Iop_Div32Fx4: -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); -+ return binary32Fx4_w_rm(mce, vatom1, vatom2, vatom3); - - case Iop_Add64Fx4: - case Iop_Sub64Fx4: - case Iop_Mul64Fx4: -+ return binary64Fx4_w_rm(mce, vatom1, vatom2, vatom3); -+ - case Iop_Div64Fx4: -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); - return binary64Fx4_w_rm(mce, vatom1, vatom2, vatom3); - - /* TODO: remaining versions of 16x4 FP ops when more of the half-precision -@@ -3594,12 +3667,16 @@ IRAtom* expr2vbits_Triop ( MCEnv* mce, - */ - case Iop_Add16Fx8: - case Iop_Sub16Fx8: -- return binary16Fx8_w_rm(mce, vatom1, vatom2, vatom3); -+ return binary16Fx8_w_rm(mce, vatom1, vatom2, vatom3); - - case Iop_Add32Fx8: - case Iop_Sub32Fx8: - case Iop_Mul32Fx8: -+ return binary32Fx8_w_rm(mce, vatom1, vatom2, vatom3); -+ - case Iop_Div32Fx8: -+ complainIfVariableLatency(mce, atom2); -+ complainIfVariableLatency(mce, atom3); - return binary32Fx8_w_rm(mce, vatom1, vatom2, vatom3); - - case Iop_F32x4_2toQ16x8: -@@ -3913,11 +3990,15 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - - case Iop_I32StoF32x4: - case Iop_F32toI32Sx4: -+ return unary16Fx8_w_rm(mce, vatom1, vatom2); - case Iop_Sqrt16Fx8: -+ complainIfVariableLatency(mce, atom2); - return unary16Fx8_w_rm(mce, vatom1, vatom2); - case Iop_Sqrt32Fx4: -+ complainIfVariableLatency(mce, atom2); - return unary32Fx4_w_rm(mce, vatom1, vatom2); - case Iop_Sqrt64Fx2: -+ complainIfVariableLatency(mce, atom2); - return unary64Fx2_w_rm(mce, vatom1, vatom2); - - case Iop_ShrN8x16: -@@ -4153,6 +4234,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_ModU128: - case Iop_ModS128: - /* I128 x I128 -> I128 */ -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_V128, vatom1, vatom2); - - case Iop_QNarrowBin64Sto32Sx4: -@@ -4184,7 +4267,6 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_Mul64F0x2: - case Iop_Min64F0x2: - case Iop_Max64F0x2: -- case Iop_Div64F0x2: - case Iop_CmpLT64F0x2: - case Iop_CmpLE64F0x2: - case Iop_CmpEQ64F0x2: -@@ -4192,6 +4274,11 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_Add64F0x2: - return binary64F0x2(mce, vatom1, vatom2); - -+ case Iop_Div64F0x2: -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); -+ return binary64F0x2(mce, vatom1, vatom2); -+ - case Iop_Min32Fx4: - case Iop_Max32Fx4: - case Iop_CmpLT32Fx4: -@@ -4220,7 +4307,6 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_Mul32F0x4: - case Iop_Min32F0x4: - case Iop_Max32F0x4: -- case Iop_Div32F0x4: - case Iop_CmpLT32F0x4: - case Iop_CmpLE32F0x4: - case Iop_CmpEQ32F0x4: -@@ -4228,6 +4314,11 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_Add32F0x4: - return binary32F0x4(mce, vatom1, vatom2); - -+ case Iop_Div32F0x4: -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); -+ return binary32F0x4(mce, vatom1, vatom2); -+ - case Iop_QShlNsatSU8x16: - case Iop_QShlNsatUU8x16: - case Iop_QShlNsatSS8x16: -@@ -4531,11 +4622,14 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - case Iop_CosF64: - case Iop_TanF64: - case Iop_2xm1F64: -- case Iop_SqrtF64: - case Iop_RecpExpF64: - /* I32(rm) x I64/F64 -> I64/F64 */ - return mkLazy2(mce, Ity_I64, vatom1, vatom2); - -+ case Iop_SqrtF64: -+ complainIfVariableLatency(mce, atom2); -+ return mkLazy2(mce, Ity_I64, vatom1, vatom2); -+ - case Iop_ShlD64: - case Iop_ShrD64: - case Iop_RoundD64toInt: -@@ -4589,16 +4683,21 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - - case Iop_SqrtF16: - /* I32(rm) x F16 -> F16 */ -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I16, vatom1, vatom2); - - case Iop_RoundF32toInt: -- case Iop_SqrtF32: - case Iop_RecpExpF32: - /* I32(rm) x I32/F32 -> I32/F32 */ - return mkLazy2(mce, Ity_I32, vatom1, vatom2); - -+ case Iop_SqrtF32: -+ complainIfVariableLatency(mce, atom2); -+ return mkLazy2(mce, Ity_I32, vatom1, vatom2); -+ - case Iop_SqrtF128: - /* I32(rm) x F128 -> F128 */ -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I128, vatom1, vatom2); - - case Iop_I32StoF32: -@@ -4687,10 +4786,14 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - - case Iop_DivModU64to32: - case Iop_DivModS64to32: -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I64, vatom1, vatom2); - - case Iop_DivModU128to64: - case Iop_DivModS128to64: -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I128, vatom1, vatom2); - - case Iop_8HLto16: -@@ -4702,6 +4805,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - - case Iop_DivModU64to64: - case Iop_DivModS64to64: { -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - IRAtom* vTmp64 = mkLazy2(mce, Ity_I64, vatom1, vatom2); - return assignNew('V', mce, Ity_I128, - binop(Iop_64HLto128, vTmp64, vTmp64)); -@@ -4717,6 +4822,8 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - - case Iop_DivModU32to32: - case Iop_DivModS32to32: { -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - IRAtom* vTmp32 = mkLazy2(mce, Ity_I32, vatom1, vatom2); - return assignNew('V', mce, Ity_I64, - binop(Iop_32HLto64, vTmp32, vTmp32)); -@@ -4746,18 +4853,24 @@ IRAtom* expr2vbits_Binop ( MCEnv* mce, - } - - case Iop_Sad8Ux4: /* maybe we could do better? ftm, do mkLazy2. */ -+ case Iop_QAdd32S: /* could probably do better */ -+ case Iop_QSub32S: /* could probably do better */ -+ return mkLazy2(mce, Ity_I32, vatom1, vatom2); -+ - case Iop_DivS32: - case Iop_DivU32: - case Iop_DivU32E: - case Iop_DivS32E: -- case Iop_QAdd32S: /* could probably do better */ -- case Iop_QSub32S: /* could probably do better */ -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I32, vatom1, vatom2); - - case Iop_DivS64: - case Iop_DivU64: - case Iop_DivS64E: - case Iop_DivU64E: -+ complainIfVariableLatency(mce, atom1); -+ complainIfVariableLatency(mce, atom2); - return mkLazy2(mce, Ity_I64, vatom1, vatom2); - - case Iop_Add32: -@@ -5163,14 +5276,19 @@ IRExpr* expr2vbits_Unop ( MCEnv* mce, IROp op, IRAtom* atom ) - return unary64Fx2(mce, vatom); - - case Iop_Sqrt64F0x2: -+ complainIfVariableLatency(mce, atom); - return unary64F0x2(mce, vatom); - - case Iop_Sqrt32Fx8: -+ complainIfVariableLatency(mce, atom); -+ return unary32Fx8(mce, vatom); -+ - case Iop_RSqrtEst32Fx8: - case Iop_RecipEst32Fx8: - return unary32Fx8(mce, vatom); - - case Iop_Sqrt64Fx4: -+ complainIfVariableLatency(mce, atom); - return unary64Fx4(mce, vatom); - - case Iop_RecipEst32Fx4: -@@ -5200,6 +5318,9 @@ IRExpr* expr2vbits_Unop ( MCEnv* mce, IROp op, IRAtom* atom ) - return unary32Fx2(mce, vatom); - - case Iop_Sqrt32F0x4: -+ complainIfVariableLatency(mce, atom); -+ return unary32F0x4(mce, vatom); -+ - case Iop_RSqrtEst32F0x4: - case Iop_RecipEst32F0x4: - return unary32F0x4(mce, vatom); -@@ -8044,19 +8165,25 @@ Bool check_or_add ( Pairs* tidyingEnv, IRExpr* guard, void* entry ) - static Bool is_helperc_value_checkN_fail ( const HChar* name ) - { - /* This is expensive because it happens a lot. We are checking to -- see whether |name| is one of the following 8 strings: -+ see whether |name| is one of the following 14 strings: - - MC_(helperc_value_check8_fail_no_o) - MC_(helperc_value_check4_fail_no_o) - MC_(helperc_value_check0_fail_no_o) - MC_(helperc_value_check1_fail_no_o) - MC_(helperc_value_check8_fail_w_o) -+ MC_(helperc_value_check4_fail_w_o) - MC_(helperc_value_check0_fail_w_o) - MC_(helperc_value_check1_fail_w_o) -- MC_(helperc_value_check4_fail_w_o) -+ MC_(helperc_value_check8_varlat_fail_no_o) -+ MC_(helperc_value_check4_varlat_fail_no_o) -+ MC_(helperc_value_check1_varlat_fail_no_o) -+ MC_(helperc_value_check8_varlat_fail_w_o) -+ MC_(helperc_value_check4_varlat_fail_w_o) -+ MC_(helperc_value_check1_varlat_fail_w_o) - - To speed it up, check the common prefix just once, rather than -- all 8 times. -+ all 14 times. - */ - const HChar* prefix = "MC_(helperc_value_check"; - -@@ -8081,7 +8208,13 @@ static Bool is_helperc_value_checkN_fail ( const HChar* name ) - || 0==VG_(strcmp)(name, "8_fail_w_o)") - || 0==VG_(strcmp)(name, "4_fail_w_o)") - || 0==VG_(strcmp)(name, "0_fail_w_o)") -- || 0==VG_(strcmp)(name, "1_fail_w_o)"); -+ || 0==VG_(strcmp)(name, "1_fail_w_o)") -+ || 0==VG_(strcmp)(name, "8_varlat_fail_no_o)") -+ || 0==VG_(strcmp)(name, "4_varlat_fail_no_o)") -+ || 0==VG_(strcmp)(name, "1_varlat_fail_no_o)") -+ || 0==VG_(strcmp)(name, "8_varlat_fail_w_o)") -+ || 0==VG_(strcmp)(name, "4_varlat_fail_w_o)") -+ || 0==VG_(strcmp)(name, "1_varlat_fail_w_o)"); - } - - IRSB* MC_(final_tidy) ( IRSB* sb_in ) -@@ -8147,7 +8280,7 @@ void MC_(do_instrumentation_startup_checks)( void ) - # define CHECK(_expected, _string) \ - tl_assert((_expected) == is_helperc_value_checkN_fail(_string)) - -- /* It should identify these 8, and no others, as targets. */ -+ /* It should identify these 14, and no others, as targets. */ - CHECK(True, "MC_(helperc_value_check8_fail_no_o)"); - CHECK(True, "MC_(helperc_value_check4_fail_no_o)"); - CHECK(True, "MC_(helperc_value_check0_fail_no_o)"); -@@ -8156,6 +8289,12 @@ void MC_(do_instrumentation_startup_checks)( void ) - CHECK(True, "MC_(helperc_value_check0_fail_w_o)"); - CHECK(True, "MC_(helperc_value_check1_fail_w_o)"); - CHECK(True, "MC_(helperc_value_check4_fail_w_o)"); -+ CHECK(True, "MC_(helperc_value_check8_varlat_fail_no_o)"); -+ CHECK(True, "MC_(helperc_value_check4_varlat_fail_no_o)"); -+ CHECK(True, "MC_(helperc_value_check1_varlat_fail_no_o)"); -+ CHECK(True, "MC_(helperc_value_check8_varlat_fail_w_o)"); -+ CHECK(True, "MC_(helperc_value_check1_varlat_fail_w_o)"); -+ CHECK(True, "MC_(helperc_value_check4_varlat_fail_w_o)"); - - /* Ad-hoc selection of other strings gathered via a quick test. */ - CHECK(False, "amd64g_dirtyhelper_CPUID_avx2"); -diff --git a/memcheck/tests/Makefile.am b/memcheck/tests/Makefile.am -index 633c036c3..11f440b2a 100644 ---- a/memcheck/tests/Makefile.am -+++ b/memcheck/tests/Makefile.am -@@ -448,6 +448,14 @@ EXTRA_DIST = \ - varinfo6.vgtest varinfo6.stdout.exp varinfo6.stderr.exp \ - varinfo6.stderr.exp-ppc64 \ - varinforestrict.vgtest varinforestrict.stderr.exp \ -+ varlat.vgtest varlat.stderr.exp \ -+ varlat-env.vgtest varlat-env.stderr.exp \ -+ varlat-no.vgtest varlat-no.stderr.exp \ -+ varlat-yes.vgtest varlat-yes.stderr.exp \ -+ varlat2.vgtest varlat2.stderr.exp \ -+ varlat2-env.vgtest varlat2-env.stderr.exp \ -+ varlat2-no.vgtest varlat2-no.stderr.exp \ -+ varlat2-yes.vgtest varlat2-yes.stderr.exp \ - vcpu_bz2.stdout.exp vcpu_bz2.stderr.exp vcpu_bz2.vgtest \ - vcpu_fbench.stdout.exp vcpu_fbench.stderr.exp vcpu_fbench.vgtest \ - vcpu_fnfns.stdout.exp vcpu_fnfns.stdout.exp-glibc28-amd64 \ -@@ -563,6 +571,7 @@ check_PROGRAMS = \ - varinfo1 varinfo2 varinfo3 varinfo4 \ - varinfo5 varinfo5so.so varinfo6 \ - varinforestrict \ -+ varlat varlat2 \ - vcpu_fbench vcpu_fnfns \ - wcs \ - wcscat \ -@@ -881,6 +890,9 @@ else - endif - varinforestrict_CFLAGS = $(AM_CFLAGS) -O0 -g @FLAG_W_NO_MAYBE_UNINITIALIZED@ - -+varlat_CFLAGS = $(AM_CFLAGS) @FLAG_W_NO_UNINITIALIZED@ -+varlat2_CFLAGS = $(AM_CFLAGS) @FLAG_W_NO_UNINITIALIZED@ -+ - # Build shared object for wrap7 - wrap7_SOURCES = wrap7.c - wrap7_DEPENDENCIES = wrap7so.so -diff --git a/memcheck/tests/varlat-env.stderr.exp b/memcheck/tests/varlat-env.stderr.exp -new file mode 100644 -index 000000000..5393cd2dc ---- /dev/null -+++ b/memcheck/tests/varlat-env.stderr.exp -@@ -0,0 +1,3 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ ... -+ -diff --git a/memcheck/tests/varlat-env.vgtest b/memcheck/tests/varlat-env.vgtest -new file mode 100644 -index 000000000..1c0dffbb5 ---- /dev/null -+++ b/memcheck/tests/varlat-env.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS='--memcheck:variable-latency-errors=yes' -+prog: varlat -+vgopts: -q --command-line-only=no -diff --git a/memcheck/tests/varlat-no.stderr.exp b/memcheck/tests/varlat-no.stderr.exp -new file mode 100644 -index 000000000..e69de29bb -diff --git a/memcheck/tests/varlat-no.vgtest b/memcheck/tests/varlat-no.vgtest -new file mode 100644 -index 000000000..ed5fe4495 ---- /dev/null -+++ b/memcheck/tests/varlat-no.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat -+vgopts: -q -diff --git a/memcheck/tests/varlat-yes.stderr.exp b/memcheck/tests/varlat-yes.stderr.exp -new file mode 100644 -index 000000000..5393cd2dc ---- /dev/null -+++ b/memcheck/tests/varlat-yes.stderr.exp -@@ -0,0 +1,3 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ ... -+ -diff --git a/memcheck/tests/varlat-yes.vgtest b/memcheck/tests/varlat-yes.vgtest -new file mode 100644 -index 000000000..57a4d9d8e ---- /dev/null -+++ b/memcheck/tests/varlat-yes.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat -+vgopts: -q --variable-latency-errors=yes -diff --git a/memcheck/tests/varlat.c b/memcheck/tests/varlat.c -new file mode 100644 -index 000000000..98ae12dcb ---- /dev/null -+++ b/memcheck/tests/varlat.c -@@ -0,0 +1,16 @@ -+#include -+ -+volatile int storage; -+ -+void storage_init(char c) -+{ -+ storage = 100/(13|c); -+} -+ -+int main() -+{ -+ volatile char *x = malloc(1); -+ storage_init(x[0]); -+ free(x); -+ return 0; -+} -diff --git a/memcheck/tests/varlat.stderr.exp b/memcheck/tests/varlat.stderr.exp -new file mode 100644 -index 000000000..e69de29bb -diff --git a/memcheck/tests/varlat.vgtest b/memcheck/tests/varlat.vgtest -new file mode 100644 -index 000000000..6b55ac8f8 ---- /dev/null -+++ b/memcheck/tests/varlat.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat -+vgopts: -q --variable-latency-errors=no -diff --git a/memcheck/tests/varlat2-env.stderr.exp b/memcheck/tests/varlat2-env.stderr.exp -new file mode 100644 -index 000000000..5393cd2dc ---- /dev/null -+++ b/memcheck/tests/varlat2-env.stderr.exp -@@ -0,0 +1,3 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ ... -+ -diff --git a/memcheck/tests/varlat2-env.vgtest b/memcheck/tests/varlat2-env.vgtest -new file mode 100644 -index 000000000..8ade43b2f ---- /dev/null -+++ b/memcheck/tests/varlat2-env.vgtest -@@ -0,0 +1,3 @@ -+env: VALGRIND_TRY_OPTS='--memcheck:variable-latency-errors=yes' -+prog: varlat2 -+vgopts: -q --command-line-only=no -diff --git a/memcheck/tests/varlat2-no.stderr.exp b/memcheck/tests/varlat2-no.stderr.exp -new file mode 100644 -index 000000000..5393cd2dc ---- /dev/null -+++ b/memcheck/tests/varlat2-no.stderr.exp -@@ -0,0 +1,3 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ ... -+ -diff --git a/memcheck/tests/varlat2-no.vgtest b/memcheck/tests/varlat2-no.vgtest -new file mode 100644 -index 000000000..e608f987c ---- /dev/null -+++ b/memcheck/tests/varlat2-no.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat2 -+vgopts: -q -diff --git a/memcheck/tests/varlat2-yes.stderr.exp b/memcheck/tests/varlat2-yes.stderr.exp -new file mode 100644 -index 000000000..5393cd2dc ---- /dev/null -+++ b/memcheck/tests/varlat2-yes.stderr.exp -@@ -0,0 +1,3 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ ... -+ -diff --git a/memcheck/tests/varlat2-yes.vgtest b/memcheck/tests/varlat2-yes.vgtest -new file mode 100644 -index 000000000..d4c908305 ---- /dev/null -+++ b/memcheck/tests/varlat2-yes.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat2 -+vgopts: -q --variable-latency-errors=yes -diff --git a/memcheck/tests/varlat2.c b/memcheck/tests/varlat2.c -new file mode 100644 -index 000000000..7f4c9c1dd ---- /dev/null -+++ b/memcheck/tests/varlat2.c -@@ -0,0 +1,18 @@ -+#include -+#include "valgrind.h" -+ -+volatile int storage; -+ -+void storage_init(char c) -+{ -+ storage = 100/(13|c); -+} -+ -+int main() -+{ -+ volatile char *x = malloc(1); -+ VALGRIND_CLO_CHANGE("--variable-latency-errors=yes"); -+ storage_init(x[0]); -+ free(x); -+ return 0; -+} -diff --git a/memcheck/tests/varlat2.stderr.exp b/memcheck/tests/varlat2.stderr.exp -new file mode 100644 -index 000000000..1e7a51a5d ---- /dev/null -+++ b/memcheck/tests/varlat2.stderr.exp -@@ -0,0 +1,4 @@ -+Variable-latency instruction operand of size 4 is secret/uninitialised -+ at 0x........: storage_init (varlat2.c:8) -+ by 0x........: main (varlat2.c:15) -+ -diff --git a/memcheck/tests/varlat2.vgtest b/memcheck/tests/varlat2.vgtest -new file mode 100644 -index 000000000..4ef2fad30 ---- /dev/null -+++ b/memcheck/tests/varlat2.vgtest -@@ -0,0 +1,2 @@ -+prog: varlat2 -+vgopts: -q --variable-latency-errors=no diff --git a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt b/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt deleted file mode 100644 index 465ae90904..0000000000 --- a/tests/ct_tooling/tools/valgrind_varlat/valgrind-varlat-sup-block.txt +++ /dev/null @@ -1,23 +0,0 @@ -diff --git a/memcheck/mc_errors.c b/memcheck/mc_errors.c -index 8d163cd90..0e7eaab8f 100644 ---- a/memcheck/mc_errors.c -+++ b/memcheck/mc_errors.c -@@ -1913,6 +1913,18 @@ SizeT MC_(get_extra_suppression_info) ( const Error* err, - return VG_(snprintf) (buf, nBuf, "%s(%s)", - extra->Err.FishyValue.function_name, - extra->Err.FishyValue.argument_name); -+ -+ } else if (Err_Value == ekind) { -+ /* For variable-latency (VARLAT) errors, add an extra line -+ in the generated suppression template so users can see -+ that this undefined-value is a variable-latency operand. */ -+ MC_Error* extra = VG_(get_error_extra)(err); -+ if (extra && extra->Err.Value.isLatency) { -+ return VG_(snprintf) (buf, nBuf, "variable-latency: yes"); -+ } else { -+ buf[0] = '\0'; -+ return 0; -+ } - } else { - buf[0] = '\0'; - return 0; From 6fc943bb88fe5e8c6359699d2ad941bb497157a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Fri, 3 Jul 2026 13:26:21 +0200 Subject: [PATCH 14/15] update documentation and merged previous tooling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- tests/constant_time/kem/issues.json | 35 --- tests/constant_time/kem/passes.json | 35 --- tests/constant_time/kem/passes/kyber | 35 --- tests/constant_time/kem/passes/sntrup | 5 - tests/constant_time/sig/issues.json | 199 ------------ tests/constant_time/sig/passes.json | 56 ---- tests/constant_time/sig/passes/falcon_sign | 79 ----- tests/ct_tooling/README.md | 31 +- tests/ct_tooling/ct_test.sh | 2 +- tests/ct_tooling/tools/memsan/README.md | 34 ++- .../false_positives/{ml_kem.txt => ml_kem} | 0 .../tools/valgrind_varlat/README.md | 74 ++++- .../valgrind_varlat/false_positives}/bike | 0 .../valgrind_varlat/false_positives}/cross | 0 .../valgrind_varlat/false_positives/falcon} | 80 +++++ .../valgrind_varlat/false_positives}/mayo | 0 .../false_positives/{ml-kem.txt => ml-kem} | 0 .../valgrind_varlat/false_positives}/mqom | 0 .../valgrind_varlat/false_positives}/snova | 0 .../issues/classic-mceliece-348864 | 0 .../issues/classic-mceliece-348864f | 0 .../issues/classic-mceliece-460896 | 0 .../issues/classic-mceliece-460896f | 0 .../issues/classic-mceliece-6688128 | 0 .../issues/classic-mceliece-6688128f | 0 .../issues/classic-mceliece-6960119 | 0 .../issues/classic-mceliece-6960119f | 0 .../issues/classic-mceliece-8192128 | 0 .../issues/classic-mceliece-8192128f | 0 .../tools/valgrind_varlat}/issues/falcon | 0 .../tools/valgrind_varlat}/issues/slh_dsa | 0 tests/test_constant_time.py | 282 ------------------ tests/test_kem.c | 6 - tests/test_sig.c | 15 +- 34 files changed, 209 insertions(+), 759 deletions(-) delete mode 100644 tests/constant_time/kem/issues.json delete mode 100644 tests/constant_time/kem/passes.json delete mode 100644 tests/constant_time/kem/passes/kyber delete mode 100644 tests/constant_time/kem/passes/sntrup delete mode 100644 tests/constant_time/sig/issues.json delete mode 100644 tests/constant_time/sig/passes.json delete mode 100644 tests/constant_time/sig/passes/falcon_sign rename tests/ct_tooling/tools/memsan/false_positives/{ml_kem.txt => ml_kem} (100%) rename tests/{constant_time/kem/passes => ct_tooling/tools/valgrind_varlat/false_positives}/bike (100%) rename tests/{constant_time/sig/passes => ct_tooling/tools/valgrind_varlat/false_positives}/cross (100%) rename tests/{constant_time/sig/passes/falcon_keygen => ct_tooling/tools/valgrind_varlat/false_positives/falcon} (67%) rename tests/{constant_time/sig/passes => ct_tooling/tools/valgrind_varlat/false_positives}/mayo (100%) rename tests/ct_tooling/tools/valgrind_varlat/false_positives/{ml-kem.txt => ml-kem} (100%) rename tests/{constant_time/sig/passes => ct_tooling/tools/valgrind_varlat/false_positives}/mqom (100%) rename tests/{constant_time/sig/passes => ct_tooling/tools/valgrind_varlat/false_positives}/snova (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-348864 (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-348864f (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-460896 (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-460896f (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-6688128 (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-6688128f (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-6960119 (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-6960119f (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-8192128 (100%) rename tests/{constant_time/kem => ct_tooling/tools/valgrind_varlat}/issues/classic-mceliece-8192128f (100%) rename tests/{constant_time/sig => ct_tooling/tools/valgrind_varlat}/issues/falcon (100%) rename tests/{constant_time/sig => ct_tooling/tools/valgrind_varlat}/issues/slh_dsa (100%) delete mode 100644 tests/test_constant_time.py diff --git a/tests/constant_time/kem/issues.json b/tests/constant_time/kem/issues.json deleted file mode 100644 index 038b87877e..0000000000 --- a/tests/constant_time/kem/issues.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "BIKE-L1": [], - "BIKE-L3": [], - "BIKE-L5": [], - "Classic-McEliece-348864": ["classic-mceliece-348864"], - "Classic-McEliece-348864f": ["classic-mceliece-348864f"], - "Classic-McEliece-460896": ["classic-mceliece-460896"], - "Classic-McEliece-460896f": ["classic-mceliece-460896f"], - "Classic-McEliece-6688128": ["classic-mceliece-6688128"], - "Classic-McEliece-6688128f": ["classic-mceliece-6688128f"], - "Classic-McEliece-6960119": ["classic-mceliece-6960119"], - "Classic-McEliece-6960119f": ["classic-mceliece-6960119f"], - "Classic-McEliece-8192128": ["classic-mceliece-8192128"], - "Classic-McEliece-8192128f": ["classic-mceliece-8192128f"], - "FrodoKEM-1344-AES": [], - "FrodoKEM-1344-SHAKE": [], - "FrodoKEM-640-AES": [], - "FrodoKEM-640-SHAKE": [], - "FrodoKEM-976-AES": [], - "FrodoKEM-976-SHAKE": [], - "HQC-128": [], - "HQC-192": [], - "HQC-256": [], - "Kyber1024": [], - "Kyber512": [], - "Kyber768": [], - "ML-KEM-512": [], - "ML-KEM-768": [], - "ML-KEM-1024": [], - "NTRU-HPS-2048-509": [], - "NTRU-HPS-2048-677": [], - "NTRU-HPS-4096-821": [], - "NTRU-HRSS-701": [], - "sntrup761": [] -} diff --git a/tests/constant_time/kem/passes.json b/tests/constant_time/kem/passes.json deleted file mode 100644 index 3b1dcd18ef..0000000000 --- a/tests/constant_time/kem/passes.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "BIKE-L1": ["bike"], - "BIKE-L3": ["bike"], - "BIKE-L5": ["bike"], - "Classic-McEliece-348864": [], - "Classic-McEliece-348864f": [], - "Classic-McEliece-460896": [], - "Classic-McEliece-460896f": [], - "Classic-McEliece-6688128": [], - "Classic-McEliece-6688128f": [], - "Classic-McEliece-6960119": [], - "Classic-McEliece-6960119f": [], - "Classic-McEliece-8192128": [], - "Classic-McEliece-8192128f": [], - "FrodoKEM-1344-AES": [], - "FrodoKEM-1344-SHAKE": [], - "FrodoKEM-640-AES": [], - "FrodoKEM-640-SHAKE": [], - "FrodoKEM-976-AES": [], - "FrodoKEM-976-SHAKE": [], - "HQC-128": [], - "HQC-192": [], - "HQC-256": [], - "Kyber1024": ["kyber"], - "Kyber512": ["kyber"], - "Kyber768": ["kyber"], - "ML-KEM-512": [], - "ML-KEM-768": [], - "ML-KEM-1024": [], - "NTRU-HPS-2048-509": [], - "NTRU-HPS-2048-677": [], - "NTRU-HPS-4096-821": [], - "NTRU-HRSS-701": [], - "sntrup761": ["sntrup"] -} diff --git a/tests/constant_time/kem/passes/kyber b/tests/constant_time/kem/passes/kyber deleted file mode 100644 index 63a11707cb..0000000000 --- a/tests/constant_time/kem/passes/kyber +++ /dev/null @@ -1,35 +0,0 @@ -{ - Rejection sampling to produce public "A" matrix - Memcheck:Cond - fun:rej_uniform - fun:pqcrystals_kyber*_ref_gen_matrix - fun:pqcrystals_kyber*_ref_indcpa_* -} -{ - Rejection sampling to produce public "A" matrix - Memcheck:Cond - ... - fun:pqcrystals_kyber*_avx2_gen_matrix - fun:pqcrystals_kyber*_avx2_indcpa_* -} -{ - Rejection sampling to produce public "A" matrix - Memcheck:Value8 - ... - fun:pqcrystals_kyber*_avx2_gen_matrix - fun:pqcrystals_kyber*_avx2_indcpa_* -} -{ - Rejection sampling to produce public "A" matrix - Memcheck:Value8 - ... - fun:PQCLEAN_KYBER*_AARCH64_gen_matrix - fun:PQCLEAN_KYBER*_AARCH64_indcpa_* -} -{ - Rejection sampling to produce public "A" matrix - Memcheck:Cond - ... - fun:PQCLEAN_KYBER*_AARCH64_gen_matrix - fun:PQCLEAN_KYBER*_AARCH64_indcpa_* -} diff --git a/tests/constant_time/kem/passes/sntrup b/tests/constant_time/kem/passes/sntrup deleted file mode 100644 index f3ee77aed2..0000000000 --- a/tests/constant_time/kem/passes/sntrup +++ /dev/null @@ -1,5 +0,0 @@ -{ - Rejection sampling on non-invertible g - Memcheck:Cond - src:sntrup761.c:1985 # fun:KeyGen -} diff --git a/tests/constant_time/sig/issues.json b/tests/constant_time/sig/issues.json deleted file mode 100644 index 1298c2143d..0000000000 --- a/tests/constant_time/sig/issues.json +++ /dev/null @@ -1,199 +0,0 @@ - -{ - "cross-rsdp-128-balanced": [], - "cross-rsdp-128-fast": [], - "cross-rsdp-128-small": [], - "cross-rsdp-192-balanced": [], - "cross-rsdp-192-fast": [], - "cross-rsdp-192-small": [], - "cross-rsdp-256-balanced": [], - "cross-rsdp-256-fast": [], - "cross-rsdp-256-small": [], - "cross-rsdpg-128-balanced": [], - "cross-rsdpg-128-fast": [], - "cross-rsdpg-128-small": [], - "cross-rsdpg-192-balanced": [], - "cross-rsdpg-192-fast": [], - "cross-rsdpg-192-small": [], - "cross-rsdpg-256-balanced": [], - "cross-rsdpg-256-fast": [], - "cross-rsdpg-256-small": [], - "Falcon-1024": ["falcon"], - "Falcon-512": ["falcon"], - "Falcon-padded-1024": ["falcon"], - "Falcon-padded-512": ["falcon"], - "MAYO_1": [], - "MAYO_2": [], - "MAYO_3": [], - "ML-DSA-44": [], - "ML-DSA-65": [], - "ML-DSA-87": [], - "SNOVA_24_5_4": [], - "SNOVA_24_5_4_SHAKE": [], - "SNOVA_24_5_4_SHAKE_esk": [], - "SNOVA_24_5_4_esk": [], - "SNOVA_24_5_5": [], - "SNOVA_25_8_3": [], - "SNOVA_29_6_5": [], - "SNOVA_37_17_2": [], - "SNOVA_37_8_4": [], - "SNOVA_49_11_3": [], - "SNOVA_56_25_2": [], - "SNOVA_60_10_4": [], - "SLH_DSA_PURE_SHA2_128S": ["slh_dsa"], - "SLH_DSA_PURE_SHA2_128F": ["slh_dsa"], - "SLH_DSA_PURE_SHA2_192S": ["slh_dsa"], - "SLH_DSA_PURE_SHA2_192F": ["slh_dsa"], - "SLH_DSA_PURE_SHA2_256S": ["slh_dsa"], - "SLH_DSA_PURE_SHA2_256F": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_PURE_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_224_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_256_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_384_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_224_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA2_512_256_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA3_224_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA3_256_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA3_384_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHA3_512_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHAKE_128_PREHASH_SHAKE_256F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_128S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_128F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_192S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_192F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_256S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHA2_256F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_128S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_128F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_192S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_192F": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_256S": ["slh_dsa"], - "SLH_DSA_SHAKE_256_PREHASH_SHAKE_256F": ["slh_dsa"] -} diff --git a/tests/constant_time/sig/passes.json b/tests/constant_time/sig/passes.json deleted file mode 100644 index 9a9099115b..0000000000 --- a/tests/constant_time/sig/passes.json +++ /dev/null @@ -1,56 +0,0 @@ - -{ - "cross-rsdp-128-balanced": ["cross"], - "cross-rsdp-128-fast": ["cross"], - "cross-rsdp-128-small": ["cross"], - "cross-rsdp-192-balanced": ["cross"], - "cross-rsdp-192-fast": ["cross"], - "cross-rsdp-192-small": ["cross"], - "cross-rsdp-256-balanced": ["cross"], - "cross-rsdp-256-fast": ["cross"], - "cross-rsdp-256-small": ["cross"], - "cross-rsdpg-128-balanced": ["cross"], - "cross-rsdpg-128-fast": ["cross"], - "cross-rsdpg-128-small": ["cross"], - "cross-rsdpg-192-balanced": ["cross"], - "cross-rsdpg-192-fast": ["cross"], - "cross-rsdpg-192-small": ["cross"], - "cross-rsdpg-256-balanced": ["cross"], - "cross-rsdpg-256-fast": ["cross"], - "cross-rsdpg-256-small": ["cross"], - "Falcon-1024": ["falcon_keygen", "falcon_sign"], - "Falcon-512": ["falcon_keygen", "falcon_sign"], - "Falcon-padded-1024": ["falcon_keygen", "falcon_sign"], - "Falcon-padded-512": ["falcon_keygen", "falcon_sign"], - "MAYO-1": ["mayo"], - "MAYO-2": ["mayo"], - "MAYO-3": ["mayo"], - "MAYO-5": ["mayo"], - "ML-DSA-44": [], - "ML-DSA-65": [], - "ML-DSA-87": [], - "SNOVA_24_5_4": ["snova"], - "SNOVA_24_5_4_SHAKE": ["snova"], - "SNOVA_24_5_4_SHAKE_esk": ["snova"], - "SNOVA_24_5_4_esk": ["snova"], - "SNOVA_24_5_5": ["snova"], - "SNOVA_25_8_3": ["snova"], - "SNOVA_29_6_5": ["snova"], - "SNOVA_37_17_2": ["snova"], - "SNOVA_37_8_4": ["snova"], - "SNOVA_49_11_3": ["snova"], - "SNOVA_56_25_2": ["snova"], - "SNOVA_60_10_4": ["snova"], - "mqom2_cat1_gf16_fast_r3": ["mqom"], - "mqom2_cat1_gf16_fast_r5": ["mqom"], - "mqom2_cat1_gf16_short_r3": ["mqom"], - "mqom2_cat1_gf16_short_r5": ["mqom"], - "mqom2_cat3_gf16_fast_r3": ["mqom"], - "mqom2_cat3_gf16_fast_r5": ["mqom"], - "mqom2_cat3_gf16_short_r3": ["mqom"], - "mqom2_cat3_gf16_short_r5": ["mqom"], - "mqom2_cat5_gf16_fast_r3": ["mqom"], - "mqom2_cat5_gf16_fast_r5": ["mqom"], - "mqom2_cat5_gf16_short_r3": ["mqom"], - "mqom2_cat5_gf16_short_r5": ["mqom"] -} diff --git a/tests/constant_time/sig/passes/falcon_sign b/tests/constant_time/sig/passes/falcon_sign deleted file mode 100644 index 210c37c071..0000000000 --- a/tests/constant_time/sig/passes/falcon_sign +++ /dev/null @@ -1,79 +0,0 @@ -# Suppressions that work for both CLEAN and AVX2 implementations - -{ - Exception while reading secret key. No signature is produced. - Memcheck:Cond - fun:PQCLEAN_FALCON*_*_trim_i8_decode - fun:do_sign - fun:PQCLEAN_FALCON*_*_crypto_sign_signature -} -{ - Exception while re-computing private basis (non-invertible f). No signature is produced. - Memcheck:Cond - src:vrfy.c:726 # fun:PQCLEAN_FALCON*_*_complete_private - fun:do_sign - fun:PQCLEAN_FALCON*_*_crypto_sign_signature -} -{ - Exception while re-computing private basis (large G coefficients). No signature is produced. - Memcheck:Cond - src:vrfy.c:739 # fun:PQCLEAN_FALCON*_*_complete_private - fun:do_sign - fun:PQCLEAN_FALCON*_*_crypto_sign_signature -} -{ - Rejection sampling on encoded signature size - Memcheck:Cond - fun:PQCLEAN_FALCON*_*_comp_encode - fun:do_sign - fun:PQCLEAN_FALCON*_*_crypto_sign_signature -} - - -# CLEAN implementation of sign.c - -{ - Rejection sampling to re-center discrete Gaussian distribution - Memcheck:Cond - # Wildcard to catch the while loop in fun:BerExp as well as line the if on src:sign.c:1155 - ... - src:sign.c:1155 # fun:PQCLEAN_FALCON*_CLEAN_sampler - ... - fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature -} -{ - Rejection sampling on signature length - Memcheck:Cond - src:sign.c:946 # fun:do_sign_dyn - fun:PQCLEAN_FALCON*_CLEAN_sign_dyn - fun:do_sign - fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature -} -{ - Rejection sampling on encoded signature size - Memcheck:Cond - fun:PQCLEAN_FALCON*_CLEAN_comp_encode - fun:do_sign - fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature -} - - -# AVX2 implementation of sign.c - -{ - Rejection sampling to re-center discrete Gaussian distribution - Memcheck:Cond - # Wildcard to catch the while loop in fun:BerExp as well as line the if on src:sign.c:1153 - ... - src:sign.c:1213 # fun:PQCLEAN_FALCON*_AVX2_sampler - ... - fun:PQCLEAN_FALCON*_AVX2_crypto_sign_signature -} -{ - Rejection sampling on signature length - Memcheck:Cond - src:sign.c:939 # fun:do_sign_dyn - fun:PQCLEAN_FALCON*_AVX2_sign_dyn - fun:do_sign - fun:PQCLEAN_FALCON*_AVX2_crypto_sign_signature -} diff --git a/tests/ct_tooling/README.md b/tests/ct_tooling/README.md index 43ccc01b40..d5fa4e3adc 100644 --- a/tests/ct_tooling/README.md +++ b/tests/ct_tooling/README.md @@ -1,9 +1,7 @@ # Constant-Time Tooling - Framework for constant-time testing of liboqs across compilers, optimization flags, and `OQS_OPT_TARGET` build modes. ## Repository Structure - ``` tests/ct_tooling/ ├── README.md @@ -12,22 +10,16 @@ tests/ct_tooling/ ├── analyze_results.py # Parse log output and generate summary CSVs and graphs └── tools/ ├── memsan/ - │ ├── CMakeLists.txt # MemSan-specific CMake configuration for liboqs/tests - │ ├── rng_poison_memsan.c # RNG poisoning for MemSan testing - │ ├── test_kem.c # MemSan-specific KEM test - │ ├── test_sig.c # MemSan-specific SIG test + │ ├── false_positives/ # Directory containing false-positives suppression files + │ ├── issues/ # Directory containing possible constant-time isues detected with Valgrind-Varlat │ └── README.md └── valgrind_varlat/ ├── false_positives/ # Directory containing false-positives suppression files - │ └── *.supp - ├── valgrind-try-patch-20250805.txt # Valgrind patch file - ├── valgrind-varlat-patch-20250805.txt # Valgrind variable-latency patch - ├── valgrind-varlat-sup-block.txt # Valgrind suppression block + ├── issues/ # Directory containing possible constant-time isues detected with MemSan └── README.md ``` ## Tools - ### 1. Valgrind-Varlat (`valgrind_varlat/`) - **Purpose**: Uninitialized memory error detection for constant-time analysis using Kyberslash patch for Valgrind - **Output**: Directory containing all unique suppression blocks for each warning output @@ -150,11 +142,12 @@ tests/ct_tooling/tools//logs/ The summary file for each run includes the compiler path, compiler version, target architecture, and compilation flags used, followed by a pass/fail line for each algorithm tested. -### Simultaneous testing -Since MemSan requires to replace several files within liboqs/tests, it is not recommended to run both tests at the same time. This would cause Valgrind_Varlat tests to fail because of using MemSan-oriented files. +### Cathegorizing warnings and false positive handling +This framework does not directly differentiate between false positives and issues directly. It must be manually maintained by contributors, who carefully analyze the framework's output and label them. The label is for auditors. We assign "false positive" to a warning that is known not to be a security threat, and we store its suppression file in the corresponding file and subdirectory. We "raise an issue" about any other error, and we store the corresponding suppression file in the "issues" subdirectory. These can be found in `liboqs/tests/ct_tooling/tools/{valgrind_varlat, memsan}/{false_positives, issues}`. If you are unsure where your suppression file belongs, then save it to the "issues" subdirectory. -### Analyzing Results +The handling of false positives slightly differs between tools, with both using the output returned by the tool during execution to generate the final suppression files. This process is outlined in the READMEs within each tool's subdirectory. +### Analyzing Results Use `analyze_results.py` to parse the warnings data from the log files and generate graphs describing the results. ```bash @@ -167,4 +160,12 @@ python3 analyze_results.py \ **Generates**: - `KEM_warnings_per_opt_level.csv` - Warning counts per algorithm per optimization - `SIG_warnings_per_opt_level.csv` - Same for signature schemes -- `*.png` - 4 visualization graphs regarding the total and average number of warnings per KEM and signature \ No newline at end of file +- `*.png` - 4 visualization graphs regarding the total and average number of warnings per KEM and signature + +### Credits +The observation that Valgrind can be used to identify non-constant time behaviour is due to Adam Langley [1, 2]. Mortiz Neikes' TIMECOP project applies Langley's idea to the SUPERCOP benchmarking suite [3]. Versions of SUPERCOP starting with 20200816 include TIMECOP and apply Langley's idea to randombytes calls in particular [4]. We have borrowed the idea of instrumenting randombytes calls from SUPERCOP. + +[1] https://github.com/agl/ctgrind +[2] https://boringssl.googlesource.com/boringssl/+/a6a049a6fb51a052347611d41583a0622bc89d60 +[2] https://post-apocalyptic-crypto.org/timecop/index.html +[3] http://bench.cr.yp.to/tips.html#timecop \ No newline at end of file diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 71b931e36f..79a74341b0 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -46,7 +46,7 @@ build() { # Generate suppression flags for all suppression files containing false positives SUP_DIR="$SCRIPT_DIR/tools/memsan/false_positives" SUP_FLAGS=() - for f in "$SUP_DIR"/*.txt; do + for f in "$SUP_DIR"/*; do [ -f "$f" ] || continue SUP_FLAGS+=( "-fsanitize-ignorelist=$f" ) done diff --git a/tests/ct_tooling/tools/memsan/README.md b/tests/ct_tooling/tools/memsan/README.md index 047f5d966d..c69edcee51 100644 --- a/tests/ct_tooling/tools/memsan/README.md +++ b/tests/ct_tooling/tools/memsan/README.md @@ -1,5 +1,4 @@ # MemSan - This directory contains the files required to execute MemSan's tooling for liboqs constant-time testing. MemSan handles false-positive warnings by storing specific functions into `.txt` files within the `false_positives/` directory. These files are passed as parameters of the `-fsanitize-ignorelist` flag during compilation, successfully disregarding those warnings that are cathegorized as not constant-time issues after review. @@ -20,6 +19,39 @@ Because of how many warnings are output, it is not feasible to store all the war The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. +## False positive handling +MemSan follows a similar suppression mechanism to that of Valgrind-Varlat. Users can specify entities to ignore during testing by listing them in a suppression file, using a prefix that defines the entity's type. For this framework, the `fun:` prefix is used (although there are others too), since the observed false-positives originate from specific functions. The suppression file is then passed to clang at compile-time using the `-fsanitize-ignorelist` flag. + +MemSan's output also includes a full stack trace leading to the root cause. To successfully suppress a warning, the suppression file must target the exact function listed in the report's SUMMARY line. For example, given an output of the form: + +```text +==9793==WARNING: MemorySanitizer: use-of-uninitialized-value + #0 0x7eb79d2bf3f2 in sampling.c:62:9 + #1 0x7eb79d2bf3f2 in sampling.c:138:10 + #2 0x7eb79d2bf3f2 in sampling.c:174:12 + #3 0x7eb79d2bd57d in indcpa.c:278:5 + #4 0x7eb79d2bd57d in indcpa.c:508:3 + #5 0x7eb79d2be04a in kem.c:416:9 + #6 0x5d25bce8bce2 in test_kem.c:63:7 + #7 0x5d25bce8bce2 in test_kem.c:293:15 + #8 0x5d25bce8b4a5 in test_kem.c:391:12 + #9 0x7eb79ce9caa3 in pthread_create.c:447:8 + #10 0x7eb79cf29c6b in clone3.S:78 + +SUMMARY: MemorySanitizer: use-of-uninitialized-value /home/pablogf/liboqs/src/kem/ml_kem/mlkem-native_ml-kem-512_ref/mlkem/src/sampling.c:62:9 in mlk_rej_uniform_c +==9793==WARNING: MemorySanitizer: use-of-uninitialized-value +``` + +The framework will disregard this warning on future executions by including the following line in the suppression file: +```text +fun:mlk_rej_uniform_c +``` +MemSan also enables the use of the wildcard (*) within the suppression files. + +Each family of algorithms will have a specific suppression block listing the functions that output false-positives. The framework automatically includes all suppression files within the respective subdirectories so that known false positives are not returned during testing. + +For further information see https://clang.llvm.org/docs/MemorySanitizer.html and https://clang.llvm.org/docs/SanitizerSpecialCaseList.html + ## Dependencies Remember to install the required dependencies before testing: diff --git a/tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt b/tests/ct_tooling/tools/memsan/false_positives/ml_kem similarity index 100% rename from tests/ct_tooling/tools/memsan/false_positives/ml_kem.txt rename to tests/ct_tooling/tools/memsan/false_positives/ml_kem diff --git a/tests/ct_tooling/tools/valgrind_varlat/README.md b/tests/ct_tooling/tools/valgrind_varlat/README.md index 3a0d88d873..e2274e9c38 100644 --- a/tests/ct_tooling/tools/valgrind_varlat/README.md +++ b/tests/ct_tooling/tools/valgrind_varlat/README.md @@ -1,5 +1,4 @@ # Valgrind-Varlat - This directory contains the necessary files to execute Valgrind's memcheck tool on liboqs with [Daniel Bernstein's Kyberslash patches](https://kyberslash.cr.yp.to/papers.html) (valgrind-try-patch-20250805.txt and valgrind-varlat-patch-20250805.txt) and another patch including variable latency warnings in the suppression block (valgrind_varlat_sup_block.txt). Valgrind-Varlat handles false-positive warnings by storing their suppression block into `.txt` files within the `false_positives/` directory. These files are passed during the tools execution, successfully disregarding those warnings that are cathegorized as not constant-time issues after review. @@ -85,13 +84,84 @@ To check whether the installation was successful, you can use the varlat tests p ``` ## Algorithm Testing - Valgrind-Varlat produces numerous warnings, making complete storage impractical in terms of memory and runtime. The test script therefore processes output as follows: - It captures each unique suppression block (the {...} content with full warning details) and logs it to a file. - Only these unique blocks are counted as warnings, avoiding duplication from repeated occurrences during execution. The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. +## False positive handling +Here is an example of a suppression file of a known false-positive for the Kyber algorithm: + +```text + { + Rejection sampling to produce public "A" matrix + Memcheck:Cond + fun:rej_uniform + fun:PQCLEAN_KYBER*_CLEAN_gen_matrix + } +``` + +The brackets wrap a single error that is to be suppressed. Within the brackets, the first line is a comment. The remaining lines tell Valgrind to ignore any "Memcheck:Cond" errors that occur when a function named rej_uniform is called from a function whose name matches the glob pattern PQCLEAN_KYBER*_CLEAN_gen_matrix. + +Before this suppression file was written, a run of this script produced the following output. +```text + ==594== Conditional jump or move depends on uninitialised value(s) + ==594== at 0x22550D: rej_uniform (indcpa.c:133) + ==594== by 0x225654: PQCLEAN_KYBER512_CLEAN_gen_matrix (indcpa.c:177) + ==594== by 0x2257D1: PQCLEAN_KYBER512_CLEAN_indcpa_keypair (indcpa.c:216) + ==594== by 0x1B6C1E: PQCLEAN_KYBER512_CLEAN_crypto_kem_keypair (kem.c:26) + ==594== by 0x1B6B9F: OQS_KEM_kyber_512_keypair (kem_kyber_512.c:56) + ==594== by 0x10D123: OQS_KEM_keypair (kem.c:818) + ==594== by 0x10AD07: kem_test_correctness (test_kem.c:103) + ==594== by 0x10B4E7: test_wrapper (test_kem.c:186) + ==594== by 0x4CDAFA2: start_thread (pthread_create.c:486) + ==594== by 0x4DED4CE: clone (clone.S:95) + ==594== + { + + Memcheck:Cond + fun:rej_uniform + fun:PQCLEAN_KYBER512_CLEAN_gen_matrix + fun:PQCLEAN_KYBER512_CLEAN_indcpa_keypair + fun:PQCLEAN_KYBER512_CLEAN_crypto_kem_keypair + fun:OQS_KEM_kyber_512_keypair + fun:OQS_KEM_keypair + fun:kem_test_correctness + fun:test_wrapper + fun:start_thread + fun:clone + } +``` +The lines beginning with "==" are a Valgrind error message. The bracketed text is a suppression file template. To produce the final suppression file we added a comment, replaced "512" with a wildcard (since an identical error occurs in other Kyber parameter sets), and truncated the backtrace (since the extra lines provide no interesting information to auditors). + +The "fun:rej_uniform" line says to ignore _all_ Memcheck:Cond errors in rej_uniform, but Valgrind told us that line 133 was the problem. Any "fun:name" line in the backtrace can be replaced by an equivalent "src:file:line", so we could have narrowed the scope of our suppression: +```text + { + Rejection sampling to produce public "A" matrix + Memcheck:Cond + src:indcpa.c:133 # fun:rej_uniform + fun:PQCLEAN_KYBER*_CLEAN_gen_matrix + } +``` +Here "# fun:rej_uniform" is a comment. An update to the Kyber source code might break our suppression file by changing the line number, and leaving the function name as a comment might help a future reviewer. + +An ellipsis (...) can serve as a wildcard for a portion of the backtrace. We could have written: +```text + { + Rejection sampling to produce public "A" matrix + Memcheck:Cond + ... + fun:PQCLEAN_KYBER*_CLEAN_gen_matrix + } +``` +But this is perhaps too concise. Remember that the goal here is to help auditors. + +Further information can be found in Valgrind's manual. See + https://www.valgrind.org/docs/manual/manual-core.html#manual-core.suppress +and + https://www.valgrind.org/docs/manual/mc-manual.html#mc-manual.suppfiles + ## Dependencies Remember to install the prerequisites for building liboqs: diff --git a/tests/constant_time/kem/passes/bike b/tests/ct_tooling/tools/valgrind_varlat/false_positives/bike similarity index 100% rename from tests/constant_time/kem/passes/bike rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/bike diff --git a/tests/constant_time/sig/passes/cross b/tests/ct_tooling/tools/valgrind_varlat/false_positives/cross similarity index 100% rename from tests/constant_time/sig/passes/cross rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/cross diff --git a/tests/constant_time/sig/passes/falcon_keygen b/tests/ct_tooling/tools/valgrind_varlat/false_positives/falcon similarity index 67% rename from tests/constant_time/sig/passes/falcon_keygen rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/falcon index deca2f9e3b..0364a95e03 100644 --- a/tests/constant_time/sig/passes/falcon_keygen +++ b/tests/ct_tooling/tools/valgrind_varlat/false_positives/falcon @@ -168,3 +168,83 @@ fun:PQCLEAN_FALCON*_*_modq_encode fun:PQCLEAN_FALCON*_*_crypto_sign_keypair } + +# Suppressions that work for both CLEAN and AVX2 implementations + +{ + Exception while reading secret key. No signature is produced. + Memcheck:Cond + fun:PQCLEAN_FALCON*_*_trim_i8_decode + fun:do_sign + fun:PQCLEAN_FALCON*_*_crypto_sign_signature +} +{ + Exception while re-computing private basis (non-invertible f). No signature is produced. + Memcheck:Cond + src:vrfy.c:726 # fun:PQCLEAN_FALCON*_*_complete_private + fun:do_sign + fun:PQCLEAN_FALCON*_*_crypto_sign_signature +} +{ + Exception while re-computing private basis (large G coefficients). No signature is produced. + Memcheck:Cond + src:vrfy.c:739 # fun:PQCLEAN_FALCON*_*_complete_private + fun:do_sign + fun:PQCLEAN_FALCON*_*_crypto_sign_signature +} +{ + Rejection sampling on encoded signature size + Memcheck:Cond + fun:PQCLEAN_FALCON*_*_comp_encode + fun:do_sign + fun:PQCLEAN_FALCON*_*_crypto_sign_signature +} + + +# CLEAN implementation of sign.c + +{ + Rejection sampling to re-center discrete Gaussian distribution + Memcheck:Cond + # Wildcard to catch the while loop in fun:BerExp as well as line the if on src:sign.c:1155 + ... + src:sign.c:1155 # fun:PQCLEAN_FALCON*_CLEAN_sampler + ... + fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature +} +{ + Rejection sampling on signature length + Memcheck:Cond + src:sign.c:946 # fun:do_sign_dyn + fun:PQCLEAN_FALCON*_CLEAN_sign_dyn + fun:do_sign + fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature +} +{ + Rejection sampling on encoded signature size + Memcheck:Cond + fun:PQCLEAN_FALCON*_CLEAN_comp_encode + fun:do_sign + fun:PQCLEAN_FALCON*_CLEAN_crypto_sign_signature +} + + +# AVX2 implementation of sign.c + +{ + Rejection sampling to re-center discrete Gaussian distribution + Memcheck:Cond + # Wildcard to catch the while loop in fun:BerExp as well as line the if on src:sign.c:1153 + ... + src:sign.c:1213 # fun:PQCLEAN_FALCON*_AVX2_sampler + ... + fun:PQCLEAN_FALCON*_AVX2_crypto_sign_signature +} +{ + Rejection sampling on signature length + Memcheck:Cond + src:sign.c:939 # fun:do_sign_dyn + fun:PQCLEAN_FALCON*_AVX2_sign_dyn + fun:do_sign + fun:PQCLEAN_FALCON*_AVX2_crypto_sign_signature +} diff --git a/tests/constant_time/sig/passes/mayo b/tests/ct_tooling/tools/valgrind_varlat/false_positives/mayo similarity index 100% rename from tests/constant_time/sig/passes/mayo rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/mayo diff --git a/tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt b/tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem similarity index 100% rename from tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem.txt rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/ml-kem diff --git a/tests/constant_time/sig/passes/mqom b/tests/ct_tooling/tools/valgrind_varlat/false_positives/mqom similarity index 100% rename from tests/constant_time/sig/passes/mqom rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/mqom diff --git a/tests/constant_time/sig/passes/snova b/tests/ct_tooling/tools/valgrind_varlat/false_positives/snova similarity index 100% rename from tests/constant_time/sig/passes/snova rename to tests/ct_tooling/tools/valgrind_varlat/false_positives/snova diff --git a/tests/constant_time/kem/issues/classic-mceliece-348864 b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-348864 similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-348864 rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-348864 diff --git a/tests/constant_time/kem/issues/classic-mceliece-348864f b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-348864f similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-348864f rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-348864f diff --git a/tests/constant_time/kem/issues/classic-mceliece-460896 b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-460896 similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-460896 rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-460896 diff --git a/tests/constant_time/kem/issues/classic-mceliece-460896f b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-460896f similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-460896f rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-460896f diff --git a/tests/constant_time/kem/issues/classic-mceliece-6688128 b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6688128 similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-6688128 rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6688128 diff --git a/tests/constant_time/kem/issues/classic-mceliece-6688128f b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6688128f similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-6688128f rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6688128f diff --git a/tests/constant_time/kem/issues/classic-mceliece-6960119 b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6960119 similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-6960119 rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6960119 diff --git a/tests/constant_time/kem/issues/classic-mceliece-6960119f b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6960119f similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-6960119f rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-6960119f diff --git a/tests/constant_time/kem/issues/classic-mceliece-8192128 b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-8192128 similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-8192128 rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-8192128 diff --git a/tests/constant_time/kem/issues/classic-mceliece-8192128f b/tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-8192128f similarity index 100% rename from tests/constant_time/kem/issues/classic-mceliece-8192128f rename to tests/ct_tooling/tools/valgrind_varlat/issues/classic-mceliece-8192128f diff --git a/tests/constant_time/sig/issues/falcon b/tests/ct_tooling/tools/valgrind_varlat/issues/falcon similarity index 100% rename from tests/constant_time/sig/issues/falcon rename to tests/ct_tooling/tools/valgrind_varlat/issues/falcon diff --git a/tests/constant_time/sig/issues/slh_dsa b/tests/ct_tooling/tools/valgrind_varlat/issues/slh_dsa similarity index 100% rename from tests/constant_time/sig/issues/slh_dsa rename to tests/ct_tooling/tools/valgrind_varlat/issues/slh_dsa diff --git a/tests/test_constant_time.py b/tests/test_constant_time.py deleted file mode 100644 index c31436480d..0000000000 --- a/tests/test_constant_time.py +++ /dev/null @@ -1,282 +0,0 @@ -# SPDX-License-Identifier: MIT - -""" test_constant_time.py - -The goal of this script is to ensure that every instance of secret-dependant -control flow in liboqs is documented. This script does not ensure that all of -the software in liboqs is constant time. Rather, it is intended to aid auditors -in their search for non-constant time behaviour. - -WARNING: This script currently runs test_kem and test_sig on random seeds. -It is not coverage guided. It will miss instances of non-constant time -behaviour in code paths that are rarely executed. - -This script requires Valgrind version >= 3.14.0, and it only gives meaningful -results if test_kem and test_sig have been compiled with CMAKE_BUILD_TYPE=Debug -and OQS_ENABLE_TEST_CONSTANT_TIME. - - -How this script works ---------------------- -This script runs test_kem (and/or test_sig) through Valgrind's Memcheck tool. -Valgrind executes the test program and issues an error message if/whenever the -program's control flow depends on uninitialized data. As observed by Adam -Langley [1], if we tell Valgrind that secrets are uninitialized, then Valgrind -will tell us about secret-dependant control flow. - -Assuming that each scheme in liboqs passes test_kat, our test_kem and test_sig -programs are structured such that all secret data can be traced back to a call -to OQS_randombytes. The tests intercept calls to OQS_randombytes and tell -Valgrind that every random byte is uninitialized. Hence, Valgrind will issue an -error if (but not only if!) our tests branch on secret data. - -Since there may be false positives, we say that Valgrind identifies "suspected -non-constant time behaviour". - -Again, the purpose of this script is to ensure that suspected non-constant time -behaviour is documented. This script ships with a collection of Valgrind -"suppression files". Each suppression file documents one or more instances of -suspected non-constant time behaviour in liboqs. - -The suppression files are also used to silence errors from Valgrind. If this -script runs without error, then all of the suspected non-constant time behaviour -in liboqs has been documented. If this script fails, then a new suppression -file should be written. - - -How to write suppression files ------------------------------- -Valgrind will output a suppression file template along with its error message. -It's your job to copy this template to the correct location, edit it, and tell -this script about the new file. - -Suppression files for KEMs are stored in - liboqs/tests/constant_time/kem/{passes,issues}/. -Suppression files for signature schemes are stored in - liboqs/tests/constant_time/sig/{passes,issues}/. - -This script does not differentiate between the passes and issues -subdirectories. The label is for auditors. We "give a pass" to an error that -is known not to be a security threat, and we store the corresponding -suppression file in the "passes" subdirectory. We "raise an issue" about any -other error, and we store the corresponding suppression file in the "issues" -subdirectory. - -If you are unsure where your suppression file belongs, then save it to the -"issues" subdirectory. - -Once you've written a suppression file, give it a descriptive file name and -tell this script about it. There are json files called passes.json and -issues.json in - liboqs/tests/constant_time/{kem,sig}/ -These json files contain dictionaries of the form - { "Scheme name" : ["list", "of", "suppression", "files"], ... } -Add the name of your suppression file to the appropriate list to suppress -the errors that you have documented. - - -How to write a good suppression file ------------------------------------- -Here is an example of a suppression file: - { - Rejection sampling to produce public "A" matrix - Memcheck:Cond - fun:rej_uniform - fun:PQCLEAN_KYBER*_CLEAN_gen_matrix - } - -The brackets wrap a single error that is to be suppressed. Within the brackets, -the first line is a comment. The remaining lines tell Valgrind to ignore -any "Memcheck:Cond" errors that occur when a function named rej_uniform is -called from a function whose name matches the glob pattern -PQCLEAN_KYBER*_CLEAN_gen_matrix. - -Before this suppression file was written, a run of this script produced the -following output. - - ==594== Conditional jump or move depends on uninitialised value(s) - ==594== at 0x22550D: rej_uniform (indcpa.c:133) - ==594== by 0x225654: PQCLEAN_KYBER512_CLEAN_gen_matrix (indcpa.c:177) - ==594== by 0x2257D1: PQCLEAN_KYBER512_CLEAN_indcpa_keypair (indcpa.c:216) - ==594== by 0x1B6C1E: PQCLEAN_KYBER512_CLEAN_crypto_kem_keypair (kem.c:26) - ==594== by 0x1B6B9F: OQS_KEM_kyber_512_keypair (kem_kyber_512.c:56) - ==594== by 0x10D123: OQS_KEM_keypair (kem.c:818) - ==594== by 0x10AD07: kem_test_correctness (test_kem.c:103) - ==594== by 0x10B4E7: test_wrapper (test_kem.c:186) - ==594== by 0x4CDAFA2: start_thread (pthread_create.c:486) - ==594== by 0x4DED4CE: clone (clone.S:95) - ==594== - { - - Memcheck:Cond - fun:rej_uniform - fun:PQCLEAN_KYBER512_CLEAN_gen_matrix - fun:PQCLEAN_KYBER512_CLEAN_indcpa_keypair - fun:PQCLEAN_KYBER512_CLEAN_crypto_kem_keypair - fun:OQS_KEM_kyber_512_keypair - fun:OQS_KEM_keypair - fun:kem_test_correctness - fun:test_wrapper - fun:start_thread - fun:clone - } - -The lines beginning with "==" are a Valgrind error message. The bracketed text -is a suppression file template. To produce the final suppression file we -added a comment, replaced "512" with a wildcard (since an identical error occurs -in other Kyber parameter sets), and truncated the backtrace (since the extra lines -provide no interesting information to auditors). - -The "fun:rej_uniform" line says to ignore _all_ Memcheck:Cond errors in -rej_uniform, but Valgrind told us that line 133 was the problem. Any -"fun:name" line in the backtrace can be replaced by an equivalent -"src:file:line", so we could have narrowed the scope of our suppression: - { - Rejection sampling to produce public "A" matrix - Memcheck:Cond - src:indcpa.c:133 # fun:rej_uniform - fun:PQCLEAN_KYBER*_CLEAN_gen_matrix - } -Here "# fun:rej_uniform" is a comment. An update to the Kyber source code might -break our suppression file by changing the line number, and leaving the function -name as a comment might help a future reviewer. - -An ellipsis (...) can serve as a wildcard for a portion of the backtrace. -We could have written: - { - Rejection sampling to produce public "A" matrix - Memcheck:Cond - ... - fun:PQCLEAN_KYBER*_CLEAN_gen_matrix - } -But this is perhaps too concise. Remember that the goal here is to help auditors. - -Further information can be found in Valgrind's manual. See - https://www.valgrind.org/docs/manual/manual-core.html#manual-core.suppress -and - https://www.valgrind.org/docs/manual/mc-manual.html#mc-manual.suppfiles - -Credits -------- -The observation that Valgrind can be used to identify non-constant time -behaviour is due to Adam Langley [1, 2]. Mortiz Neikes' TIMECOP project applies -Langley's idea to the SUPERCOP benchmarking suite [3]. Versions of SUPERCOP -starting with 20200816 include TIMECOP and apply Langley's idea to randombytes -calls in particular [4]. We have borrowed the idea of instrumenting randombytes -calls from SUPERCOP. - -[1] https://github.com/agl/ctgrind -[2] https://boringssl.googlesource.com/boringssl/+/a6a049a6fb51a052347611d41583a0622bc89d60 -[2] https://post-apocalyptic-crypto.org/timecop/index.html -[3] http://bench.cr.yp.to/tips.html#timecop -""" - - -import helpers -import json -import os -import pytest -import sys -import re - -REQ_LIBOQS_BUILD_OPTS = ['OQS_ENABLE_TEST_CONSTANT_TIME', - 'OQS_DEBUG_BUILD'] - -# Error suppression based on file and line number was introduced in -# Valgrind 3.14.0 (9 October 2018). -# https://www.valgrind.org/docs/manual/dist.news.html -MIN_VALGRIND_VERSION = [3, 14, 0] - -VALGRIND = ['valgrind', - # '-v', # Turn on -v to see which suppression files are used - '--tool=memcheck', - '--gen-suppressions=all', - '--error-exitcode=1', - '--max-stackframe=20480000', - '--num-callers=20', - ] - -# The following two functions read the json files -# liboqs/tests/constant_time/{kem,sig}/{passes,issues}.json -# into python dictionaries `ct_passes' and `ct_issues', which -# are of the form -# { 'kem' : { 'Kem Name' : ['list', 'of', 'filenames'], ... }, -# 'sig' : { 'Sig Name' : ['list', 'of', 'filenames'], ... } -# } - -ct_passes = {'kem': None, 'sig': None} -ct_issues = {'kem': None, 'sig': None} - -def get_ct_passes(t, name): - ct_t = os.path.join('tests', 'constant_time', t) - if ct_passes[t] is None: - with open(os.path.join(ct_t, 'passes.json'), 'r') as fp: - ct_passes[t] = json.load(fp) - passes = ct_passes[t].get(name,[]) - return [os.path.join(ct_t, 'passes', f) for f in passes] - -def get_ct_issues(t, name): - ct_t = os.path.join('tests', 'constant_time', t) - if ct_issues[t] is None: - with open(os.path.join(ct_t, 'issues.json'), 'r') as fp: - ct_issues[t] = json.load(fp) - issues = ct_issues[t].get(name,[]) - return [os.path.join(ct_t, 'issues', f) for f in issues] - - -@helpers.filtered_test -@helpers.test_requires_build_options(*REQ_LIBOQS_BUILD_OPTS) -@helpers.test_requires_valgrind_version_at_least(*MIN_VALGRIND_VERSION) -@pytest.mark.parametrize('kem_name', helpers.available_kems_by_name()) -def test_constant_time_kem(kem_name): - if not(helpers.is_kem_enabled_by_name(kem_name)): pytest.skip('Not enabled') - if ('SKIP_ALGS' in os.environ) and len(os.environ['SKIP_ALGS'])>0: - for algexp in os.environ['SKIP_ALGS'].split(','): - if len(re.findall(algexp, kem_name))>0: - pytest.skip("Test disabled by alg filter") - passes = get_ct_passes('kem', kem_name) - issues = get_ct_issues('kem', kem_name) - output = helpers.run_subprocess( - VALGRIND + [ - *(['--suppressions='+f for f in passes]), - *(['--suppressions='+f for f in issues]), - helpers.path_to_executable('test_kem'), - kem_name - ] - ) - -@helpers.filtered_test -@helpers.test_requires_build_options(*REQ_LIBOQS_BUILD_OPTS) -@helpers.test_requires_valgrind_version_at_least(*MIN_VALGRIND_VERSION) -@pytest.mark.parametrize('sig_name', helpers.available_sigs_by_name()) -def test_constant_time_sig(sig_name): - if not(helpers.is_sig_enabled_by_name(sig_name)): pytest.skip('Not enabled') - if ('SKIP_ALGS' in os.environ) and len(os.environ['SKIP_ALGS'])>0: - for algexp in os.environ['SKIP_ALGS'].split(','): - if len(re.findall(algexp, sig_name))>0: - pytest.skip("Test disabled by alg filter") - passes = get_ct_passes('sig', sig_name) - issues = get_ct_issues('sig', sig_name) - output = helpers.run_subprocess( - VALGRIND + [ - *(['--suppressions='+f for f in passes]), - *(['--suppressions='+f for f in issues]), - helpers.path_to_executable('test_sig'), - sig_name - ] - ) - -if __name__ == '__main__': - pytest.main(sys.argv) - -# Unused/obsolete suppressions are a burden on reviewers. You can find out which suppressions -# are used by passing the -v flag to valgrind. To find unused suppressions we have to extract -# a list of available suppressions first. You can use awk to find lines that contain only a '{'. -# Increment these line numbers by 1 to match the output of valgrind -v, then compare against -# the used suppressions. -# -# awk '$0 ~ /^{$/{print FILENAME ":" NR+1}' suppression files > /tmp/available_suppressions -# valgrind -v --suppressions=[...] ./build/tests/test_kem KEM_NAME 2>&1 \ -# | grep used_suppression \ -# | awk '{ print $NF }' > /tmp/used_suppressions -# cat /tmp/used_suppressions /tmp/available_suppressions | sort | uniq -u diff --git a/tests/test_kem.c b/tests/test_kem.c index 0fcb5967b8..31dc4afed5 100644 --- a/tests/test_kem.c +++ b/tests/test_kem.c @@ -205,9 +205,6 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { OQS_randombytes(keypair_seed, kem->length_keypair_seed); } rc = OQS_KEM_keypair_derand(kem, public_key, secret_key, keypair_seed); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use -#endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (kem->length_keypair_seed == 0) { // If length_keypair_seed is set to 0 for this KEM scheme, a failure is expected @@ -226,9 +223,6 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { } } else { rc = OQS_KEM_keypair(kem, public_key, secret_key); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - __msan_unpoison(public_key, kem->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use -#endif OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_KEM_keypair failed\n"); diff --git a/tests/test_sig.c b/tests/test_sig.c index 15070cb7f8..376a9a8973 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -102,9 +102,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al OQS_TEST_CT_DECLASSIFY(message, message_len); rc = OQS_SIG_keypair(sig, public_key, secret_key); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - __msan_unpoison(public_key, sig->length_public_key); // Unpoison the public key so MemSan doesn't warn on its use -#endif + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_SIG_keypair failed\n"); @@ -118,16 +116,16 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); + OQS_TEST_CT_DECLASSIFY(&rc, sizeof rc); if (rc != OQS_SUCCESS) { fprintf(stderr, "ERROR: OQS_SIG_verify failed\n"); goto err; } -#endif if (extended_tests) { rc = test_sig_bitflip(sig, message, message_len, signature, signature_len, public_key, bitflips_all, bitflips, false, NULL, 0); @@ -136,6 +134,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } } +#endif /* testing signing with context, if supported */ OQS_randombytes(ctx, 257); @@ -154,7 +153,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); @@ -198,7 +197,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should always succeed when providing a NULL context string\n"); goto err; } -#ifndef OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, NULL, 0, public_key); @@ -362,7 +361,7 @@ int main(int argc, char **argv) { } #endif -#if OQS_USE_PTHREADS && (!defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN)) +#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5", "mqom2_cat1_gf16_fast_r3", "mqom2_cat1_gf16_fast_r5", "mqom2_cat1_gf16_short_r3", "mqom2_cat1_gf16_short_r5", "mqom2_cat3_gf16_fast_r3", "mqom2_cat3_gf16_fast_r5", "mqom2_cat3_gf16_short_r3", "mqom2_cat3_gf16_short_r5", "mqom2_cat5_gf16_fast_r3", "mqom2_cat5_gf16_fast_r5", "mqom2_cat5_gf16_short_r3", "mqom2_cat5_gf16_short_r5"}; From 925bc299b99699eb879d753a731a0946379aac04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Guti=C3=A9rrez?= Date: Mon, 6 Jul 2026 14:42:19 +0200 Subject: [PATCH 15/15] fixed comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Pablo Gutiérrez --- CONFIGURE.md | 6 +++--- tests/CMakeLists.txt | 8 ++++---- tests/ct_tooling/README.md | 6 ++---- tests/ct_tooling/analyze_results.py | 4 ++-- tests/ct_tooling/ct_test.sh | 4 ++-- tests/ct_tooling/tools/memsan/README.md | 2 -- tests/ct_tooling/tools/valgrind_varlat/README.md | 4 +--- tests/test_helpers.h | 2 +- tests/test_kem.c | 6 +++--- tests/test_sig.c | 16 ++++++++-------- 10 files changed, 26 insertions(+), 32 deletions(-) diff --git a/CONFIGURE.md b/CONFIGURE.md index fb6e5c9ac3..4d8a89cd7d 100644 --- a/CONFIGURE.md +++ b/CONFIGURE.md @@ -19,7 +19,7 @@ The following options can be passed to CMake before the build file generation pr - [OQS_SPEED_USE_ARM_PMU](#OQS_SPEED_USE_ARM_PMU) - [USE_COVERAGE](#USE_COVERAGE) - [USE_SANITIZER](#USE_SANITIZER) -- [OQS_ENABLE_TEST_CONSTANT_TIME](#OQS_ENABLE_TEST_CONSTANT_TIME) +- [OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND](#OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) - [OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN](#OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) - [OQS_STRICT_WARNINGS](#OQS_STRICT_WARNINGS) - [OQS_EMBEDDED_BUILD](#OQS_EMBEDDED_BUILD) @@ -239,7 +239,7 @@ This has an effect when the compiler is Clang and when [CMAKE_BUILD_TYPE](#CMAKE **Default**: Unset. -## OQS_ENABLE_TEST_CONSTANT_TIME +## OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND This is used in conjunction with `tests/test_constant_time.py` to use Valgrind to look for instances of secret-dependent control flow. liboqs must also be compiled with [CMAKE_BUILD_TYPE](#CMAKE_BUILD_TYPE) set to `Debug`. @@ -249,7 +249,7 @@ When this option is set to `ON`, the additional option `OQS_ENABLE_TEST_CONSTANT ## OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN -Similar to [`OQS_ENABLE_TEST_CONSTANT_TIME`](#OQS_ENABLE_TEST_CONSTANT_TIME), this option enables constant-time testing using Clang's MemorySanitizer. +Similar to [`OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND`](#OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND), this option enables constant-time testing using Clang's MemorySanitizer. **Default**: `OFF`. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1524f02fd5..cea7b8fdcc 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MIT -option(OQS_ENABLE_TEST_CONSTANT_TIME "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) +option(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND "Build test suite with support for Valgrind-based detection of non-constant time behaviour." OFF) option(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN "Build test suite with support for MemorySanitizer-based detection of non-constant time behaviour." OFF) if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR @@ -227,9 +227,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" AND BUILD_SHARED_LIBS) endif() # Enable Valgrind-based timing side-channel analysis for test_kem and test_sig -if(OQS_ENABLE_TEST_CONSTANT_TIME AND NOT OQS_DEBUG_BUILD) - message(WARNING "OQS_ENABLE_TEST_CONSTANT_TIME is incompatible with CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}.") - set(OQS_ENABLE_TEST_CONSTANT_TIME OFF) +if(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND AND NOT OQS_DEBUG_BUILD) + message(WARNING "OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND is incompatible with CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}.") + set(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND OFF) endif() # Enable MemSan-based timing side-channel analysis for test_kem and test_sig diff --git a/tests/ct_tooling/README.md b/tests/ct_tooling/README.md index d5fa4e3adc..1c67956364 100644 --- a/tests/ct_tooling/README.md +++ b/tests/ct_tooling/README.md @@ -28,7 +28,7 @@ tests/ct_tooling/ - **Purpose**: LLVM-based uninitialized memory error detection for constant-time analysis using MemorySanitizer - **Output**: Unique `SUMMARY: MemorySanitizer` lines for each warning output -Both tools are driven by the single unified `ct_test.sh` script located at the root of `tests/ct_tooling/`. The tool to execute is selected via the first argument: +Both tools are driven by the single unified `ct_test.sh` script located at the root of `tests/ct_tooling/` and enforce a warning cap of 100,000 unique warnings per algorithm run. Once the cap is reached, further warnings are suppressed. All SLH-DSA signature variants are currently skipped during SIG tests due to the excessive time they require to execute. The tool used for testing is selected via the first argument: ```bash ./ct_test.sh @@ -37,7 +37,7 @@ Both tools are driven by the single unified `ct_test.sh` script located at the r - `tool`: `valgrind-varlat` or `memsan` - `compiler_version`: clang, clang-20, gcc, gcc-14, ... - `liboqs_build`: `generic` or `auto` -- `input`: `all`, `kems`, `sigs`, or a specific enabled algorithm variant +- `input`: `all`, `kems`, `sigs`, or a specific enabled algorithm variant (`all` excludes SLH-DSA variants for the aforementioned reason). - `opt_flags`: All arguments from position 4 up to position N-1 are treated as compiler optimization flags (including multi-flag combinations such as `-O3 -fno-tree-vectorize`). Examples: @@ -119,8 +119,6 @@ Once the script builds each configuration into a build folder, it calls the test 2. **Test execution** Then, `test()` is tasked with executing the tool's test on selected liboqs algorithms. Each tool has a different process through which it parses the tool's output to keep unique instances of the warnings, which are further detailed in their respective README files: [Valgrind-Varlat's README](tools/valgrind_varlat/README.md) and [MemSan's README](tools/memsan/README.md). -Both tools enforce a warning cap of 100,000 unique warnings per algorithm run. Once the cap is reached, further warnings are suppressed. All SPHINCS and SLH-DSA signature variants are currently skipped during SIG tests due to the excessive time they require to execute. - ### Output Structure The workflow organizes test outputs into log files that capture unique warnings for each algorithm. These logs are written inside `tests/ct_tooling/tools//logs/`, categorized into concrete subdirectories based on the compiler and build configuration (`gcc_14_auto`, `clang_generic`, ...), which then contain further subdivisions by optimization levels (`O0`, `O1`, ...) and algorithm types (`kem` or `sig`). The structure is as follows: diff --git a/tests/ct_tooling/analyze_results.py b/tests/ct_tooling/analyze_results.py index bdbc491c7b..bac27328f4 100644 --- a/tests/ct_tooling/analyze_results.py +++ b/tests/ct_tooling/analyze_results.py @@ -47,8 +47,8 @@ def parse_summary_file(filepath): matches = list(header_re.finditer(content)) for idx, m in enumerate(matches): alg_name = m.group(1) - # Skip SPHINCS and SLH_DSA algorithms - if 'SPHINCS' in alg_name or 'SLH_DSA' in alg_name: + # Skip SLH_DSA variants + if 'SLH_DSA' in alg_name: continue start = m.end() diff --git a/tests/ct_tooling/ct_test.sh b/tests/ct_tooling/ct_test.sh index 79a74341b0..8355085a98 100755 --- a/tests/ct_tooling/ct_test.sh +++ b/tests/ct_tooling/ct_test.sh @@ -82,8 +82,8 @@ test() { UPPER_TYPE="SIG" fi - # Skip SPHINCS and SLH-DSA for SIG tests - if [[ "$ALGORITHM" == *SPHINCS* || "$ALGORITHM" == *SLH_DSA* ]]; then + # Skip SLH-DSA for SIG tests + if [[ "$ALGORITHM" == *SLH_DSA* ]]; then echo "Skipping ${UPPER_TYPE} ${ALGORITHM}" return 0 fi diff --git a/tests/ct_tooling/tools/memsan/README.md b/tests/ct_tooling/tools/memsan/README.md index c69edcee51..cb8d53ee5f 100644 --- a/tests/ct_tooling/tools/memsan/README.md +++ b/tests/ct_tooling/tools/memsan/README.md @@ -17,8 +17,6 @@ Because of how many warnings are output, it is not feasible to store all the war - It captures the first SUMMARY line of each warning, which contains key details (file, line, issue type), and stores these in log files. - Only unique SUMMARY lines are retained, avoiding duplication from repeated warnings during execution. -The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. - ## False positive handling MemSan follows a similar suppression mechanism to that of Valgrind-Varlat. Users can specify entities to ignore during testing by listing them in a suppression file, using a prefix that defines the entity's type. For this framework, the `fun:` prefix is used (although there are others too), since the observed false-positives originate from specific functions. The suppression file is then passed to clang at compile-time using the `-fsanitize-ignorelist` flag. diff --git a/tests/ct_tooling/tools/valgrind_varlat/README.md b/tests/ct_tooling/tools/valgrind_varlat/README.md index e2274e9c38..7e4a805902 100644 --- a/tests/ct_tooling/tools/valgrind_varlat/README.md +++ b/tests/ct_tooling/tools/valgrind_varlat/README.md @@ -1,5 +1,5 @@ # Valgrind-Varlat -This directory contains the necessary files to execute Valgrind's memcheck tool on liboqs with [Daniel Bernstein's Kyberslash patches](https://kyberslash.cr.yp.to/papers.html) (valgrind-try-patch-20250805.txt and valgrind-varlat-patch-20250805.txt) and another patch including variable latency warnings in the suppression block (valgrind_varlat_sup_block.txt). +This directory uses [Daniel Bernstein's Kyberslash patches](https://kyberslash.cr.yp.to/papers.html) (valgrind-try-patch-20250805.txt and valgrind-varlat-patch-20250805.txt) and another patch including variable latency warnings in the suppression block (valgrind_varlat_sup_block.txt). These patches can also be found under the [ubuntu-latest directory of the ci-containers](https://github.com/open-quantum-safe/ci-containers/tree/main/ubuntu-latest) repository. Valgrind-Varlat handles false-positive warnings by storing their suppression block into `.txt` files within the `false_positives/` directory. These files are passed during the tools execution, successfully disregarding those warnings that are cathegorized as not constant-time issues after review. @@ -88,8 +88,6 @@ Valgrind-Varlat produces numerous warnings, making complete storage impractical - It captures each unique suppression block (the {...} content with full warning details) and logs it to a file. - Only these unique blocks are counted as warnings, avoiding duplication from repeated occurrences during execution. -The testing framework currently skips all SPHINCS and SLH-DSA tests due to the execessive length of time they require to execute. - ## False positive handling Here is an example of a suppression file of a known false-positive for the Kyber algorithm: diff --git a/tests/test_helpers.h b/tests/test_helpers.h index 20c4ebf10f..85e64f04ea 100644 --- a/tests/test_helpers.h +++ b/tests/test_helpers.h @@ -9,7 +9,7 @@ #include #include -#ifdef OQS_ENABLE_TEST_CONSTANT_TIME +#ifdef OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND #include #define OQS_TEST_CT_CLASSIFY(addr, len) VALGRIND_MAKE_MEM_UNDEFINED(addr, len) #define OQS_TEST_CT_DECLASSIFY(addr, len) VALGRIND_MAKE_MEM_DEFINED(addr, len) diff --git a/tests/test_kem.c b/tests/test_kem.c index 31dc4afed5..1eacae627e 100644 --- a/tests/test_kem.c +++ b/tests/test_kem.c @@ -300,7 +300,7 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { goto err; } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) rv = memcmp(public_key + kem->length_public_key, magic.val, sizeof(magic_t)); rv |= memcmp(secret_key + kem->length_secret_key, magic.val, sizeof(magic_t)); rv |= memcmp(ciphertext + kem->length_ciphertext, magic.val, sizeof(magic_t)); @@ -356,7 +356,7 @@ static OQS_STATUS kem_test_correctness(const char *method_name, bool derand) { return ret; } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) static void TEST_KEM_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, // so we have to swap out the OQS_randombytes provider. @@ -418,7 +418,7 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_randombytes_custom_algorithm(&TEST_KEM_randombytes); #else rc = OQS_randombytes_switch_algorithm("system"); diff --git a/tests/test_sig.c b/tests/test_sig.c index 376a9a8973..1ba570644a 100644 --- a/tests/test_sig.c +++ b/tests/test_sig.c @@ -116,7 +116,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } -#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify(sig, message, message_len, signature, signature_len, public_key); @@ -153,7 +153,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al goto err; } -#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, ctx, i, public_key); @@ -197,7 +197,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al fprintf(stderr, "ERROR: OQS_SIG_sign_with_ctx_str should always succeed when providing a NULL context string\n"); goto err; } -#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if !defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_TEST_CT_DECLASSIFY(public_key, sig->length_public_key); OQS_TEST_CT_DECLASSIFY(signature, signature_len); rc = OQS_SIG_verify_with_ctx_str(sig, message, message_len, signature, signature_len, NULL, 0, public_key); @@ -215,7 +215,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al #endif } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) /* check magic values */ int rv = memcmp(public_key + sig->length_public_key, magic.val, sizeof(magic_t)); rv |= memcmp(secret_key + sig->length_secret_key, magic.val, sizeof(magic_t)); @@ -256,7 +256,7 @@ static OQS_STATUS sig_test_correctness(const char *method_name, bool bitflips_al return ret; } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) static void TEST_SIG_randombytes(uint8_t *random_array, size_t bytes_to_read) { // We can't make direct calls to the system randombytes on some platforms, // so we have to swap out the OQS_randombytes provider. @@ -291,7 +291,7 @@ void *test_wrapper(void *arg) { int main(int argc, char **argv) { OQS_STATUS rc; OQS_init(); -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) || defined(USE_SANITIZER) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) || defined(USE_SANITIZER) long int extended_tests = 0; #else long int extended_tests = 1; @@ -350,7 +350,7 @@ int main(int argc, char **argv) { } } -#if defined(OQS_ENABLE_TEST_CONSTANT_TIME) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) +#if defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) || defined(OQS_ENABLE_TEST_CONSTANT_TIME_MEMSAN) OQS_randombytes_custom_algorithm(&TEST_SIG_randombytes); #else rc = OQS_randombytes_switch_algorithm("system"); @@ -361,7 +361,7 @@ int main(int argc, char **argv) { } #endif -#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME) +#if OQS_USE_PTHREADS && !defined(OQS_ENABLE_TEST_CONSTANT_TIME_VALGRIND) #define MAX_LEN_SIG_NAME_ 64 // don't run algorithms with large stack usage in threads char no_thread_sig_patterns[][MAX_LEN_SIG_NAME_] = {"MAYO-5", "cross-rsdp-128-small", "cross-rsdp-192-small", "cross-rsdp-256-balanced", "cross-rsdp-256-small", "cross-rsdpg-192-small", "cross-rsdpg-256-small", "SNOVA_37_17_2", "SNOVA_56_25_2", "SNOVA_49_11_3", "SNOVA_37_8_4", "SNOVA_24_5_5", "SNOVA_60_10_4", "SNOVA_29_6_5", "mqom2_cat1_gf16_fast_r3", "mqom2_cat1_gf16_fast_r5", "mqom2_cat1_gf16_short_r3", "mqom2_cat1_gf16_short_r5", "mqom2_cat3_gf16_fast_r3", "mqom2_cat3_gf16_fast_r5", "mqom2_cat3_gf16_short_r3", "mqom2_cat3_gf16_short_r5", "mqom2_cat5_gf16_fast_r3", "mqom2_cat5_gf16_fast_r5", "mqom2_cat5_gf16_short_r3", "mqom2_cat5_gf16_short_r5"};