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] 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)