From be2c80f73140fa538069d472c0e133298f41ff5b Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Thu, 6 Nov 2025 08:46:04 -0500 Subject: [PATCH 1/2] feat(fuzz): Add missing fuzzing targets. --- .github/workflows/ci.yml | 10 +- .github/workflows/fuzz-extended.yml | 65 +++++++ .../workflows/tests/csharp/android/action.yml | 25 ++- .github/workflows/tests/csharp/ios/action.yml | 6 +- .github/workflows/tests/fuzz/quick/action.yml | 69 ++++++++ fuzz/Cargo.toml | 76 ++++++++ .../ciphertext/decrypt_asymmetric_with_aad.rs | 17 ++ .../ciphertext/decrypt_with_aad.rs | 16 ++ .../ciphertext/encrypt_asymmetric_with_aad.rs | 18 ++ .../ciphertext/encrypt_with_aad.rs | 17 ++ fuzz/fuzz_targets/key/generate_keypair.rs | 14 ++ fuzz/fuzz_targets/key/mix_key_exchange.rs | 15 ++ .../online_ciphertext/decrypt_asymmetric.rs | 40 +++++ .../online_ciphertext/decrypt_symmetric.rs | 39 ++++ .../online_ciphertext/encrypt_asymmetric.rs | 40 +++++ .../online_ciphertext/encrypt_symmetric.rs | 36 ++++ .../header_deserialization.rs | 10 ++ fuzz/fuzz_targets/signature/sign.rs | 17 ++ .../signature/signature_deserialization.rs | 10 ++ fuzz/fuzz_targets/signature/verify.rs | 17 ++ .../signing_keypair_deserialization.rs | 10 ++ .../signing_public_key_deserialization.rs | 10 ++ .../utils/constant_time_equals.rs | 15 ++ fuzz/fuzz_targets/utils/derive_key_argon2.rs | 43 +++++ fuzz/fuzz_targets/utils/scrypt_simple.rs | 38 ++++ fuzz/run_all_fuzz_tests.sh | 167 ++++++++++++++++++ fuzz/run_quick_test.sh | 37 ++++ src/enums.rs | 104 +++-------- src/online_ciphertext/mod.rs | 8 +- .../tests/unit-tests/nugets/iOS/iOS.csproj | 2 +- 30 files changed, 900 insertions(+), 91 deletions(-) create mode 100644 .github/workflows/fuzz-extended.yml create mode 100644 .github/workflows/tests/fuzz/quick/action.yml create mode 100644 fuzz/fuzz_targets/ciphertext/decrypt_asymmetric_with_aad.rs create mode 100644 fuzz/fuzz_targets/ciphertext/decrypt_with_aad.rs create mode 100644 fuzz/fuzz_targets/ciphertext/encrypt_asymmetric_with_aad.rs create mode 100644 fuzz/fuzz_targets/ciphertext/encrypt_with_aad.rs create mode 100644 fuzz/fuzz_targets/key/generate_keypair.rs create mode 100644 fuzz/fuzz_targets/key/mix_key_exchange.rs create mode 100644 fuzz/fuzz_targets/online_ciphertext/decrypt_asymmetric.rs create mode 100644 fuzz/fuzz_targets/online_ciphertext/decrypt_symmetric.rs create mode 100644 fuzz/fuzz_targets/online_ciphertext/encrypt_asymmetric.rs create mode 100644 fuzz/fuzz_targets/online_ciphertext/encrypt_symmetric.rs create mode 100644 fuzz/fuzz_targets/online_ciphertext/header_deserialization.rs create mode 100644 fuzz/fuzz_targets/signature/sign.rs create mode 100644 fuzz/fuzz_targets/signature/signature_deserialization.rs create mode 100644 fuzz/fuzz_targets/signature/verify.rs create mode 100644 fuzz/fuzz_targets/signing_key/signing_keypair_deserialization.rs create mode 100644 fuzz/fuzz_targets/signing_key/signing_public_key_deserialization.rs create mode 100644 fuzz/fuzz_targets/utils/constant_time_equals.rs create mode 100644 fuzz/fuzz_targets/utils/derive_key_argon2.rs create mode 100644 fuzz/fuzz_targets/utils/scrypt_simple.rs create mode 100644 fuzz/run_all_fuzz_tests.sh create mode 100644 fuzz/run_quick_test.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea432beff..cccd2a9a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,8 +145,7 @@ jobs: tests_nuget_ios: needs: [nugets, setup_config] - runs-on: "macos-15" - + runs-on: "macos-26" steps: - uses: actions/checkout@v4 with: @@ -230,6 +229,13 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/workflows/formatting/rust + #### FUZZ TESTING #### + quick_fuzz: + runs-on: "ubuntu-22.04" + steps: + - uses: actions/checkout@v4 + - uses: ./.github/workflows/tests/fuzz/quick + csharp_code_format: needs: setup_config runs-on: "windows-2022" diff --git a/.github/workflows/fuzz-extended.yml b/.github/workflows/fuzz-extended.yml new file mode 100644 index 000000000..77ebf712c --- /dev/null +++ b/.github/workflows/fuzz-extended.yml @@ -0,0 +1,65 @@ +name: Extended Fuzzing + +on: + schedule: + # Run nightly at 2 AM UTC + - cron: '0 2 * * *' + workflow_dispatch: + inputs: + duration: + description: 'Duration per target in seconds' + required: false + default: '300' + type: string + +jobs: + extended_fuzz: + runs-on: ubuntu-22.04 + timeout-minutes: 300 # 5 hours max + steps: + - uses: actions/checkout@v4 + + - name: Install Rust nightly + run: | + rustup toolchain install nightly + rustup default nightly + + - name: Install cargo-fuzz + run: cargo install cargo-fuzz + + - name: Restore corpus cache + uses: actions/cache@v4 + with: + path: fuzz/corpus + key: fuzz-corpus-${{ github.sha }} + restore-keys: | + fuzz-corpus- + + - name: Run extended fuzz tests + working-directory: ./fuzz + run: | + DURATION=${{ inputs.duration || '300' }} + ./run_all_fuzz_tests.sh $DURATION + + - name: Save corpus cache + if: always() + uses: actions/cache/save@v4 + with: + path: fuzz/corpus + key: fuzz-corpus-${{ github.sha }} + + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@v4.3.6 + with: + name: fuzz-crash-artifacts-extended-${{ github.run_id }} + path: fuzz/artifacts/ + if-no-files-found: ignore + + - name: Upload corpus on failure + if: failure() + uses: actions/upload-artifact@v4.3.6 + with: + name: fuzz-corpus-${{ github.run_id }} + path: fuzz/corpus/ + if-no-files-found: ignore diff --git a/.github/workflows/tests/csharp/android/action.yml b/.github/workflows/tests/csharp/android/action.yml index c86e66f8f..675d65920 100644 --- a/.github/workflows/tests/csharp/android/action.yml +++ b/.github/workflows/tests/csharp/android/action.yml @@ -48,9 +48,32 @@ runs: shell: bash run: echo "y" | sdkmanager --install "system-images;android-33;google_apis;x86_64" + - name: Clean up AVD cache + shell: bash + run: | + rm -rf ~/.android/avd/ + rm -rf ~/.android/cache + + - name: Free up disk space + shell: bash + run: | + echo "Disk space before cleanup:" + df -h + + # Remove large unused packages + sudo rm -rf /opt/ghc + sudo rm -rf /usr/local/share/boost + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + # Clean docker + sudo docker image prune --all --force + + echo "Disk space after cleanup:" + df -h + - name: Creating Android device shell: bash - run: echo "no" | avdmanager create avd -n test_emulator -k "system-images;android-33;google_apis;x86_64" + run: echo "no" | avdmanager create avd -n test_emulator -k "system-images;android-33;google_apis;x86_64" -c 2048M - name: Starting emulator shell: bash diff --git a/.github/workflows/tests/csharp/ios/action.yml b/.github/workflows/tests/csharp/ios/action.yml index bc964403b..3987fa6a0 100644 --- a/.github/workflows/tests/csharp/ios/action.yml +++ b/.github/workflows/tests/csharp/ios/action.yml @@ -27,8 +27,8 @@ runs: - name: Extract UDID shell: bash run: | - # Find the UDID of the iPhone 16 simulator running iOS 18.0 - SIMULATOR_UDID=$(xcrun simctl list devices available 'iOS 18.0' | grep 'iPhone 16' | awk -F '[()]' '{print $2}' | head -n 1) + # Find the UDID of the iPhone 16 simulator running iOS 18 + SIMULATOR_UDID=$(xcrun simctl list devices available 'iOS 18' | grep 'iPhone 16' | awk -F '[()]' '{print $2}' | head -n 1) # Check if a UDID was found if [ -n "$SIMULATOR_UDID" ]; then @@ -37,7 +37,7 @@ runs: echo "IPHONE_16_SIM_UDID=$SIMULATOR_UDID" >> $GITHUB_ENV echo "iPhone 16 UDID stored in environment variable IPHONE_16_SIM_UDID: $IPHONE_16_SIM_UDID" else - echo "iPhone 16 simulator with iOS 18.0 not found." + echo "iPhone 16 simulator with iOS 18 not found." exit 1 fi diff --git a/.github/workflows/tests/fuzz/quick/action.yml b/.github/workflows/tests/fuzz/quick/action.yml new file mode 100644 index 000000000..d54f42530 --- /dev/null +++ b/.github/workflows/tests/fuzz/quick/action.yml @@ -0,0 +1,69 @@ +name: Quick Fuzz Test +description: Run quick fuzz tests on all targets (10 seconds each) +runs: + using: composite + steps: + - name: Install Rust nightly + shell: bash + run: | + rustup toolchain install nightly + rustup default nightly + + - name: Install cargo-fuzz + shell: bash + run: cargo install cargo-fuzz + + - name: Run quick fuzz tests + working-directory: ./fuzz + shell: bash + run: | + set -e + + echo "=== Running Quick Fuzz Tests (10s per target) ===" + + # Get all fuzz targets + TARGETS=$(cargo fuzz list) + TOTAL=$(echo "$TARGETS" | wc -l) + CURRENT=0 + FAILED_TARGETS=() + + for target in $TARGETS; do + CURRENT=$((CURRENT + 1)) + echo "" + echo "[$CURRENT/$TOTAL] Testing: $target" + + if timeout 15s cargo fuzz run "$target" -- -max_total_time=10 -rss_limit_mb=2048 2>&1; then + echo "✓ $target passed" + else + EXIT_CODE=$? + if [ $EXIT_CODE -eq 124 ]; then + echo "⏱ $target timed out (expected)" + else + echo "✗ $target failed with exit code $EXIT_CODE" + FAILED_TARGETS+=("$target") + fi + fi + done + + echo "" + echo "=== Summary ===" + echo "Total targets: $TOTAL" + echo "Failed targets: ${#FAILED_TARGETS[@]}" + + if [ ${#FAILED_TARGETS[@]} -gt 0 ]; then + echo "" + echo "Failed targets:" + printf '%s\n' "${FAILED_TARGETS[@]}" + exit 1 + fi + + echo "" + echo "All fuzz tests passed!" + + - name: Upload crash artifacts + if: failure() + uses: actions/upload-artifact@v4.3.6 + with: + name: fuzz-crash-artifacts + path: fuzz/artifacts/ + if-no-files-found: ignore diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index d69db6fd5..0b0c2d972 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -61,6 +61,10 @@ path = "fuzz_targets/key/private_key_deserialization.rs" name = "argon2parameters_deserialization" path = "fuzz_targets/key/argon2parameters_deserialization.rs" +[[bin]] +name = "mix_key_exchange" +path = "fuzz_targets/key/mix_key_exchange.rs" + [[bin]] name = "share_deserialization" path = "fuzz_targets/secret_sharing/share_deserialization.rs" @@ -100,3 +104,75 @@ path = "fuzz_targets/utils/base64_decode.rs" [[bin]] name = "base64_decode_url" path = "fuzz_targets/utils/base64_decode_url.rs" + +[[bin]] +name = "derive_key_argon2" +path = "fuzz_targets/utils/derive_key_argon2.rs" + +[[bin]] +name = "scrypt_simple" +path = "fuzz_targets/utils/scrypt_simple.rs" + +[[bin]] +name = "signature_deserialization" +path = "fuzz_targets/signature/signature_deserialization.rs" + +[[bin]] +name = "sign" +path = "fuzz_targets/signature/sign.rs" + +[[bin]] +name = "verify" +path = "fuzz_targets/signature/verify.rs" + +[[bin]] +name = "signing_keypair_deserialization" +path = "fuzz_targets/signing_key/signing_keypair_deserialization.rs" + +[[bin]] +name = "signing_public_key_deserialization" +path = "fuzz_targets/signing_key/signing_public_key_deserialization.rs" + +[[bin]] +name = "online_ciphertext_header_deserialization" +path = "fuzz_targets/online_ciphertext/header_deserialization.rs" + +[[bin]] +name = "online_encrypt_symmetric" +path = "fuzz_targets/online_ciphertext/encrypt_symmetric.rs" + +[[bin]] +name = "online_encrypt_asymmetric" +path = "fuzz_targets/online_ciphertext/encrypt_asymmetric.rs" + +[[bin]] +name = "online_decrypt_symmetric" +path = "fuzz_targets/online_ciphertext/decrypt_symmetric.rs" + +[[bin]] +name = "online_decrypt_asymmetric" +path = "fuzz_targets/online_ciphertext/decrypt_asymmetric.rs" + +[[bin]] +name = "encrypt_with_aad" +path = "fuzz_targets/ciphertext/encrypt_with_aad.rs" + +[[bin]] +name = "decrypt_with_aad" +path = "fuzz_targets/ciphertext/decrypt_with_aad.rs" + +[[bin]] +name = "encrypt_asymmetric_with_aad" +path = "fuzz_targets/ciphertext/encrypt_asymmetric_with_aad.rs" + +[[bin]] +name = "decrypt_asymmetric_with_aad" +path = "fuzz_targets/ciphertext/decrypt_asymmetric_with_aad.rs" + +[[bin]] +name = "generate_keypair" +path = "fuzz_targets/key/generate_keypair.rs" + +[[bin]] +name = "constant_time_equals" +path = "fuzz_targets/utils/constant_time_equals.rs" diff --git a/fuzz/fuzz_targets/ciphertext/decrypt_asymmetric_with_aad.rs b/fuzz/fuzz_targets/ciphertext/decrypt_asymmetric_with_aad.rs new file mode 100644 index 000000000..c26c1b4a3 --- /dev/null +++ b/fuzz/fuzz_targets/ciphertext/decrypt_asymmetric_with_aad.rs @@ -0,0 +1,17 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::ciphertext::Ciphertext; +use devolutions_crypto::key::PrivateKey; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Ciphertext, + key: PrivateKey, + aad: Vec, +} + +fuzz_target!(|data: Input| { + let _ = data.data.decrypt_asymmetric_with_aad(&data.key, &data.aad); +}); diff --git a/fuzz/fuzz_targets/ciphertext/decrypt_with_aad.rs b/fuzz/fuzz_targets/ciphertext/decrypt_with_aad.rs new file mode 100644 index 000000000..ad8f103a4 --- /dev/null +++ b/fuzz/fuzz_targets/ciphertext/decrypt_with_aad.rs @@ -0,0 +1,16 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::ciphertext::Ciphertext; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Ciphertext, + key: Vec, + aad: Vec, +} + +fuzz_target!(|data: Input| { + let _ = data.data.decrypt_with_aad(&data.key, &data.aad); +}); diff --git a/fuzz/fuzz_targets/ciphertext/encrypt_asymmetric_with_aad.rs b/fuzz/fuzz_targets/ciphertext/encrypt_asymmetric_with_aad.rs new file mode 100644 index 000000000..ab7c05e9a --- /dev/null +++ b/fuzz/fuzz_targets/ciphertext/encrypt_asymmetric_with_aad.rs @@ -0,0 +1,18 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::ciphertext::{encrypt_asymmetric_with_aad, CiphertextVersion}; +use devolutions_crypto::key::PublicKey; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Vec, + key: PublicKey, + aad: Vec, + version: CiphertextVersion, +} + +fuzz_target!(|data: Input| { + let _ = encrypt_asymmetric_with_aad(&data.data, &data.key, &data.aad, data.version); +}); diff --git a/fuzz/fuzz_targets/ciphertext/encrypt_with_aad.rs b/fuzz/fuzz_targets/ciphertext/encrypt_with_aad.rs new file mode 100644 index 000000000..e1c4e1481 --- /dev/null +++ b/fuzz/fuzz_targets/ciphertext/encrypt_with_aad.rs @@ -0,0 +1,17 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::ciphertext::{encrypt_with_aad, CiphertextVersion}; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Vec, + key: Vec, + aad: Vec, + version: CiphertextVersion, +} + +fuzz_target!(|data: Input| { + let _ = encrypt_with_aad(&data.data, &data.key, &data.aad, data.version); +}); diff --git a/fuzz/fuzz_targets/key/generate_keypair.rs b/fuzz/fuzz_targets/key/generate_keypair.rs new file mode 100644 index 000000000..01b3f6b29 --- /dev/null +++ b/fuzz/fuzz_targets/key/generate_keypair.rs @@ -0,0 +1,14 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::key::{generate_keypair, KeyVersion}; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + version: KeyVersion, +} + +fuzz_target!(|data: Input| { + let _ = generate_keypair(data.version); +}); diff --git a/fuzz/fuzz_targets/key/mix_key_exchange.rs b/fuzz/fuzz_targets/key/mix_key_exchange.rs new file mode 100644 index 000000000..c5a7429c2 --- /dev/null +++ b/fuzz/fuzz_targets/key/mix_key_exchange.rs @@ -0,0 +1,15 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::key::{mix_key_exchange, PrivateKey, PublicKey}; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + private_key: PrivateKey, + public_key: PublicKey, +} + +fuzz_target!(|input: Input| { + let _ = mix_key_exchange(&input.private_key, &input.public_key); +}); diff --git a/fuzz/fuzz_targets/online_ciphertext/decrypt_asymmetric.rs b/fuzz/fuzz_targets/online_ciphertext/decrypt_asymmetric.rs new file mode 100644 index 000000000..62b5c19e8 --- /dev/null +++ b/fuzz/fuzz_targets/online_ciphertext/decrypt_asymmetric.rs @@ -0,0 +1,40 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::key::PrivateKey; +use devolutions_crypto::online_ciphertext::OnlineCiphertextHeader; + +use std::convert::TryFrom; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + header_data: Vec, + private_key: PrivateKey, + aad: Vec, + encrypted_chunks: Vec>, +} + +fuzz_target!(|input: Input| { + let Ok(header) = OnlineCiphertextHeader::try_from(input.header_data.as_slice()) else { + return; + }; + + let Ok(mut decryptor) = header.into_decryptor_asymmetric(&input.private_key, &input.aad) else { + return; + }; + + // Decrypt chunks + for chunk in input + .encrypted_chunks + .iter() + .take(input.encrypted_chunks.len().saturating_sub(1)) + { + let _ = decryptor.decrypt_next_chunk(chunk, &[]); + } + + // Decrypt last chunk if available + if let Some(last_chunk) = input.encrypted_chunks.last() { + let _ = decryptor.decrypt_last_chunk(last_chunk, &[]); + } +}); diff --git a/fuzz/fuzz_targets/online_ciphertext/decrypt_symmetric.rs b/fuzz/fuzz_targets/online_ciphertext/decrypt_symmetric.rs new file mode 100644 index 000000000..eea8ae44c --- /dev/null +++ b/fuzz/fuzz_targets/online_ciphertext/decrypt_symmetric.rs @@ -0,0 +1,39 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::online_ciphertext::OnlineCiphertextHeader; + +use std::convert::TryFrom; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + header_data: Vec, + key: Vec, + aad: Vec, + encrypted_chunks: Vec>, +} + +fuzz_target!(|input: Input| { + let Ok(header) = OnlineCiphertextHeader::try_from(input.header_data.as_slice()) else { + return; + }; + + let Ok(mut decryptor) = header.into_decryptor(&input.key, &input.aad) else { + return; + }; + + // Decrypt chunks + for chunk in input + .encrypted_chunks + .iter() + .take(input.encrypted_chunks.len().saturating_sub(1)) + { + let _ = decryptor.decrypt_next_chunk(chunk, &[]); + } + + // Decrypt last chunk if available + if let Some(last_chunk) = input.encrypted_chunks.last() { + let _ = decryptor.decrypt_last_chunk(last_chunk, &[]); + } +}); diff --git a/fuzz/fuzz_targets/online_ciphertext/encrypt_asymmetric.rs b/fuzz/fuzz_targets/online_ciphertext/encrypt_asymmetric.rs new file mode 100644 index 000000000..6651dcb74 --- /dev/null +++ b/fuzz/fuzz_targets/online_ciphertext/encrypt_asymmetric.rs @@ -0,0 +1,40 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::key::PublicKey; +use devolutions_crypto::online_ciphertext::{OnlineCiphertextEncryptor, OnlineCiphertextVersion}; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + public_key: PublicKey, + aad: Vec, + chunk_size: u32, + chunks: Vec>, + version: OnlineCiphertextVersion, +} + +fuzz_target!(|input: Input| { + let Ok(mut encryptor) = OnlineCiphertextEncryptor::new_asymmetric( + &input.public_key, + &input.aad, + input.chunk_size, + input.version, + ) else { + return; + }; + + // Encrypt chunks + for chunk in input + .chunks + .iter() + .take(input.chunks.len().saturating_sub(1)) + { + let _ = encryptor.encrypt_next_chunk(chunk, &[]); + } + + // Encrypt last chunk if available + if let Some(last_chunk) = input.chunks.last() { + let _ = encryptor.encrypt_last_chunk(last_chunk, &[]); + } +}); diff --git a/fuzz/fuzz_targets/online_ciphertext/encrypt_symmetric.rs b/fuzz/fuzz_targets/online_ciphertext/encrypt_symmetric.rs new file mode 100644 index 000000000..305ddfec2 --- /dev/null +++ b/fuzz/fuzz_targets/online_ciphertext/encrypt_symmetric.rs @@ -0,0 +1,36 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::online_ciphertext::{OnlineCiphertextEncryptor, OnlineCiphertextVersion}; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + key: Vec, + aad: Vec, + chunk_size: u32, + chunks: Vec>, + version: OnlineCiphertextVersion, +} + +fuzz_target!(|input: Input| { + let Ok(mut encryptor) = + OnlineCiphertextEncryptor::new(&input.key, &input.aad, input.chunk_size, input.version) + else { + return; + }; + + // Encrypt chunks + for chunk in input + .chunks + .iter() + .take(input.chunks.len().saturating_sub(1)) + { + let _ = encryptor.encrypt_next_chunk(chunk, &[]); + } + + // Encrypt last chunk if available + if let Some(last_chunk) = input.chunks.last() { + let _ = encryptor.encrypt_last_chunk(last_chunk, &[]); + } +}); diff --git a/fuzz/fuzz_targets/online_ciphertext/header_deserialization.rs b/fuzz/fuzz_targets/online_ciphertext/header_deserialization.rs new file mode 100644 index 000000000..8157aeabd --- /dev/null +++ b/fuzz/fuzz_targets/online_ciphertext/header_deserialization.rs @@ -0,0 +1,10 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::online_ciphertext::OnlineCiphertextHeader; + +use std::convert::TryFrom; + +fuzz_target!(|data: &[u8]| { + let _ = OnlineCiphertextHeader::try_from(data); +}); diff --git a/fuzz/fuzz_targets/signature/sign.rs b/fuzz/fuzz_targets/signature/sign.rs new file mode 100644 index 000000000..cd94019b0 --- /dev/null +++ b/fuzz/fuzz_targets/signature/sign.rs @@ -0,0 +1,17 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::signature::{sign, SignatureVersion}; +use devolutions_crypto::signing_key::SigningKeyPair; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Vec, + keypair: SigningKeyPair, + version: SignatureVersion, +} + +fuzz_target!(|input: Input| { + let _ = sign(&input.data, &input.keypair, input.version); +}); diff --git a/fuzz/fuzz_targets/signature/signature_deserialization.rs b/fuzz/fuzz_targets/signature/signature_deserialization.rs new file mode 100644 index 000000000..ef1fe36fe --- /dev/null +++ b/fuzz/fuzz_targets/signature/signature_deserialization.rs @@ -0,0 +1,10 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::signature::Signature; + +use std::convert::TryFrom; + +fuzz_target!(|data: &[u8]| { + let _ = Signature::try_from(data); +}); diff --git a/fuzz/fuzz_targets/signature/verify.rs b/fuzz/fuzz_targets/signature/verify.rs new file mode 100644 index 000000000..42279bcf0 --- /dev/null +++ b/fuzz/fuzz_targets/signature/verify.rs @@ -0,0 +1,17 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::signature::Signature; +use devolutions_crypto::signing_key::SigningPublicKey; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + data: Vec, + signature: Signature, + public_key: SigningPublicKey, +} + +fuzz_target!(|input: Input| { + let _ = input.signature.verify(&input.data, &input.public_key); +}); diff --git a/fuzz/fuzz_targets/signing_key/signing_keypair_deserialization.rs b/fuzz/fuzz_targets/signing_key/signing_keypair_deserialization.rs new file mode 100644 index 000000000..73a50e1e1 --- /dev/null +++ b/fuzz/fuzz_targets/signing_key/signing_keypair_deserialization.rs @@ -0,0 +1,10 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::signing_key::SigningKeyPair; + +use std::convert::TryFrom; + +fuzz_target!(|data: &[u8]| { + let _ = SigningKeyPair::try_from(data); +}); diff --git a/fuzz/fuzz_targets/signing_key/signing_public_key_deserialization.rs b/fuzz/fuzz_targets/signing_key/signing_public_key_deserialization.rs new file mode 100644 index 000000000..21f770b4f --- /dev/null +++ b/fuzz/fuzz_targets/signing_key/signing_public_key_deserialization.rs @@ -0,0 +1,10 @@ +#![no_main] +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::signing_key::SigningPublicKey; + +use std::convert::TryFrom; + +fuzz_target!(|data: &[u8]| { + let _ = SigningPublicKey::try_from(data); +}); diff --git a/fuzz/fuzz_targets/utils/constant_time_equals.rs b/fuzz/fuzz_targets/utils/constant_time_equals.rs new file mode 100644 index 000000000..0fbd06912 --- /dev/null +++ b/fuzz/fuzz_targets/utils/constant_time_equals.rs @@ -0,0 +1,15 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::utils::constant_time_equals; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + x: Vec, + y: Vec, +} + +fuzz_target!(|data: Input| { + let _ = constant_time_equals(&data.x, &data.y); +}); diff --git a/fuzz/fuzz_targets/utils/derive_key_argon2.rs b/fuzz/fuzz_targets/utils/derive_key_argon2.rs new file mode 100644 index 000000000..df6fdc54a --- /dev/null +++ b/fuzz/fuzz_targets/utils/derive_key_argon2.rs @@ -0,0 +1,43 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::utils::derive_key_argon2; +use devolutions_crypto::Argon2Parameters; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + key: Vec, + length: u32, + lanes: u32, + memory: u32, + iterations: u32, + salt: Vec, +} + +fuzz_target!(|input: Input| { + // Clamp values to reasonable ranges to avoid excessive computation + let length = input.length.clamp(1, 128); + let lanes = input.lanes.clamp(1, 16); + let memory = input.memory.clamp(8, 65536); + let iterations = input.iterations.clamp(1, 10); + + // Use only small salts for fuzzing performance + let salt = if input.salt.len() > 64 { + &input.salt[..64] + } else if input.salt.is_empty() { + &[0u8; 8][..] + } else { + &input.salt[..] + }; + + let parameters = Argon2Parameters::builder() + .length(length) + .lanes(lanes) + .memory(memory) + .iterations(iterations) + .salt(salt.to_vec()) + .build(); + + let _ = derive_key_argon2(&input.key, ¶meters); +}); diff --git a/fuzz/fuzz_targets/utils/scrypt_simple.rs b/fuzz/fuzz_targets/utils/scrypt_simple.rs new file mode 100644 index 000000000..269b62d6f --- /dev/null +++ b/fuzz/fuzz_targets/utils/scrypt_simple.rs @@ -0,0 +1,38 @@ +#![no_main] +use arbitrary::Arbitrary; +use libfuzzer_sys::fuzz_target; + +use devolutions_crypto::utils::scrypt_simple; + +#[derive(Arbitrary, Clone, Debug)] +struct Input { + password: Vec, + salt: Vec, + log_n: u8, + r: u32, + p: u32, +} + +fuzz_target!(|input: Input| { + // Clamp values to prevent excessive computation during fuzzing + let log_n = input.log_n.clamp(1, 15); // 2^15 = 32768 max iterations + let r = input.r.clamp(1, 8); + let p = input.p.clamp(1, 4); + + // Limit input sizes for performance + let password = if input.password.len() > 128 { + &input.password[..128] + } else { + &input.password[..] + }; + + let salt = if input.salt.len() > 64 { + &input.salt[..64] + } else if input.salt.is_empty() { + &[0u8; 8][..] + } else { + &input.salt[..] + }; + + let _ = scrypt_simple(password, salt, log_n, r, p); +}); diff --git a/fuzz/run_all_fuzz_tests.sh b/fuzz/run_all_fuzz_tests.sh new file mode 100644 index 000000000..a24947fbd --- /dev/null +++ b/fuzz/run_all_fuzz_tests.sh @@ -0,0 +1,167 @@ +#!/bin/bash +# Run all fuzz tests for 60 seconds each +# Usage: ./run_all_fuzz_tests.sh [duration_in_seconds] +# Example: ./run_all_fuzz_tests.sh 120 (runs each test for 2 minutes) + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +NC='\033[0m' # No Color + +# Default duration per test (60 seconds) +DURATION=${1:-60} +CORPUS_DIR="corpus" +ARTIFACTS_DIR="artifacts" + +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Devolutions Crypto Fuzz Test Suite${NC}" +echo -e "${CYAN}========================================${NC}" +echo -e "Duration per test: ${YELLOW}${DURATION}s${NC}" +echo -e "Start time: ${BLUE}$(date)${NC}\n" + +# Check if we're in the fuzz directory +if [[ ! -f "Cargo.toml" ]] || [[ ! -d "fuzz_targets" ]]; then + echo -e "${RED}Error: Must be run from the fuzz directory${NC}" + echo "Please run: cd fuzz && ./run_all_fuzz_tests.sh" + exit 1 +fi + +# Check if cargo-fuzz is installed +if ! command -v cargo-fuzz &> /dev/null; then + echo -e "${RED}Error: cargo-fuzz is not installed${NC}" + echo -e "Install with: ${YELLOW}cargo install cargo-fuzz${NC}" + exit 1 +fi + +# Check if nightly toolchain is available +if ! rustup toolchain list | grep -q nightly; then + echo -e "${YELLOW}Warning: Nightly toolchain not found. Installing...${NC}" + rustup install nightly + rustup default nightly +fi + +# Get list of all fuzz targets +echo -e "${BLUE}Discovering fuzz targets...${NC}" +TARGETS=($(cargo fuzz list 2>/dev/null | grep -v "^warning:")) +TOTAL_TARGETS=${#TARGETS[@]} + +echo -e "Found ${GREEN}${TOTAL_TARGETS}${NC} fuzz targets\n" + +# Create directories for results +mkdir -p "${CORPUS_DIR}" +mkdir -p "${ARTIFACTS_DIR}" + +# Track statistics +PASSED=0 +FAILED=0 +TOTAL_TIME=0 +START_TIMESTAMP=$(date +%s) + +# Array to store failed tests +declare -a FAILED_TESTS + +# Function to run a single fuzz test +run_fuzz_test() { + local target=$1 + local num=$2 + local total=$3 + + echo -e "${CYAN}[${num}/${total}]${NC} Running ${YELLOW}${target}${NC}..." + + local start_time=$(date +%s) + local log_file="fuzz_${target}_$(date +%Y%m%d_%H%M%S).log" + + # Run the fuzzer + if timeout ${DURATION}s cargo fuzz run "${target}" -- \ + -max_total_time=${DURATION} \ + -print_final_stats=1 \ + -rss_limit_mb=2048 \ + > "${log_file}" 2>&1; then + + local end_time=$(date +%s) + local elapsed=$((end_time - start_time)) + + # Check if any crashes were found + if [[ -d "artifacts/${target}" ]] && [[ -n "$(ls -A artifacts/${target} 2>/dev/null)" ]]; then + echo -e " ${RED}✗ FAILED${NC} - Crashes found! (${elapsed}s)" + echo -e " Artifacts saved in: ${YELLOW}artifacts/${target}/${NC}" + FAILED=$((FAILED + 1)) + FAILED_TESTS+=("${target}") + else + echo -e " ${GREEN}✓ PASSED${NC} (${elapsed}s)" + PASSED=$((PASSED + 1)) + fi + + # Extract and display stats if available + if grep -q "stat::" "${log_file}"; then + local execs=$(grep "stat::number_of_executed_units:" "${log_file}" | tail -1 | awk '{print $2}') + local coverage=$(grep "stat::average_exec_per_sec:" "${log_file}" | tail -1 | awk '{print $2}') + [[ -n "${execs}" ]] && echo -e " Executions: ${execs}" + [[ -n "${coverage}" ]] && echo -e " Exec/sec: ${coverage}" + fi + else + local exit_code=$? + local end_time=$(date +%s) + local elapsed=$((end_time - start_time)) + + if [[ ${exit_code} -eq 124 ]]; then + # Timeout is expected + echo -e " ${GREEN}✓ PASSED${NC} (${elapsed}s, timeout reached)" + PASSED=$((PASSED + 1)) + else + echo -e " ${RED}✗ FAILED${NC} - Error code: ${exit_code} (${elapsed}s)" + FAILED=$((FAILED + 1)) + FAILED_TESTS+=("${target}") + fi + fi + + # Clean up log file if no issues + if [[ ${FAILED} -eq 0 ]] || [[ ! " ${FAILED_TESTS[@]} " =~ " ${target} " ]]; then + rm -f "${log_file}" + else + echo -e " Log saved: ${YELLOW}${log_file}${NC}" + fi + + TOTAL_TIME=$((TOTAL_TIME + elapsed)) + echo "" +} + +# Run all fuzz tests +for i in "${!TARGETS[@]}"; do + run_fuzz_test "${TARGETS[$i]}" $((i + 1)) ${TOTAL_TARGETS} +done + +# Calculate total elapsed time +END_TIMESTAMP=$(date +%s) +TOTAL_ELAPSED=$((END_TIMESTAMP - START_TIMESTAMP)) +HOURS=$((TOTAL_ELAPSED / 3600)) +MINUTES=$(((TOTAL_ELAPSED % 3600) / 60)) +SECONDS=$((TOTAL_ELAPSED % 60)) + +# Print summary +echo -e "${CYAN}========================================${NC}" +echo -e "${CYAN} Fuzzing Summary${NC}" +echo -e "${CYAN}========================================${NC}" +echo -e "Total targets: ${BLUE}${TOTAL_TARGETS}${NC}" +echo -e "Passed: ${GREEN}${PASSED}${NC}" +echo -e "Failed: ${RED}${FAILED}${NC}" +echo -e "Total time: ${YELLOW}${HOURS}h ${MINUTES}m ${SECONDS}s${NC}" +echo -e "End time: ${BLUE}$(date)${NC}" + +# Show failed tests if any +if [[ ${FAILED} -gt 0 ]]; then + echo -e "\n${RED}Failed tests:${NC}" + for test in "${FAILED_TESTS[@]}"; do + echo -e " - ${test}" + done + echo -e "\nCheck ${YELLOW}artifacts/${NC} directory for crash details" + exit 1 +else + echo -e "\n${GREEN}All fuzz tests passed! ✓${NC}" + exit 0 +fi diff --git a/fuzz/run_quick_test.sh b/fuzz/run_quick_test.sh new file mode 100644 index 000000000..db96e66d3 --- /dev/null +++ b/fuzz/run_quick_test.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Quick fuzz test - runs new targets for 10 seconds each +# Useful for quick validation before longer fuzzing runs + +set -e + +CYAN='\033[0;36m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo -e "${CYAN}Running quick fuzz test on new targets (10s each)${NC}\n" + +# Test the new signature targets +NEW_TARGETS=( + "signature_deserialization" + "sign" + "verify" + "signing_keypair_deserialization" + "signing_public_key_deserialization" + "online_ciphertext_header_deserialization" + "online_encrypt_symmetric" + "online_encrypt_asymmetric" + "online_decrypt_symmetric" + "online_decrypt_asymmetric" + "mix_key_exchange" + "derive_key_argon2" + "scrypt_simple" +) + +for target in "${NEW_TARGETS[@]}"; do + echo -e "${YELLOW}Testing: ${target}${NC}" + timeout 10s cargo fuzz run "${target}" -- -max_total_time=10 || true + echo "" +done + +echo -e "${CYAN}Quick test complete!${NC}" +echo "For full fuzzing, run: ./run_all_fuzz_tests.sh" diff --git a/src/enums.rs b/src/enums.rs index 11efcee0a..fd4ceb08d 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -12,8 +12,10 @@ use wasm_bindgen::prelude::*; #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum DataType { /// No data type. Only used as a default value. + #[default] None = 0, /// A wrapped key. Key = 1, @@ -31,19 +33,15 @@ pub enum DataType { OnlineCiphertext = 7, } -impl Default for DataType { - fn default() -> Self { - Self::None - } -} - /// The versions of the encryption scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum CiphertextVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: AES256-CBC-HMAC-SHA2-256. V1 = 1, @@ -51,185 +49,135 @@ pub enum CiphertextVersion { V2 = 2, } -impl Default for CiphertextVersion { - fn default() -> Self { - Self::Latest - } -} - /// The versions of the online encryption scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum OnlineCiphertextVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: XChaCha20-Poly1305 wrapped in a STREAM construction. V1 = 1, } -impl Default for OnlineCiphertextVersion { - fn default() -> Self { - Self::Latest - } -} - /// The versions of the password hashing scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum PasswordHashVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: PBKDF2-HMAC-SHA2-256. V1 = 1, } -impl Default for PasswordHashVersion { - fn default() -> Self { - Self::Latest - } -} - /// The versions of the key scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum KeyVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: Curve25519 keys and x25519 key exchange. V1 = 1, } -impl Default for KeyVersion { - fn default() -> Self { - Self::Latest - } -} - #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum SigningKeyVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: Ed25519. V1 = 1, } -impl Default for SigningKeyVersion { - fn default() -> Self { - Self::Latest - } -} - /// The versions of the secret sharing scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum SecretSharingVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: Shamir Secret Sharing over GF256. V1 = 1, } -impl Default for SecretSharingVersion { - fn default() -> Self { - Self::Latest - } -} - /// The versions of the secret sharing scheme to use. #[cfg_attr(feature = "wbindgen", wasm_bindgen())] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[repr(u16)] +#[derive(Default)] pub enum SignatureVersion { /// Uses the latest version. + #[default] Latest = 0, /// Uses version 1: ed25519 V1 = 1, } -impl Default for SignatureVersion { - fn default() -> Self { - Self::Latest - } -} - #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[repr(u16)] +#[derive(Default)] pub enum CiphertextSubtype { + #[default] None = 0, Symmetric = 1, Asymmetric = 2, } -impl Default for CiphertextSubtype { - fn default() -> Self { - Self::None - } -} - #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[repr(u16)] +#[derive(Default)] pub enum KeySubtype { + #[default] None = 0, Private = 1, Public = 2, Pair = 3, } -impl Default for KeySubtype { - fn default() -> Self { - Self::None - } -} - #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[repr(u16)] +#[derive(Default)] pub enum PasswordHashSubtype { + #[default] None = 0, } -impl Default for PasswordHashSubtype { - fn default() -> Self { - Self::None - } -} - #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[repr(u16)] +#[derive(Default)] pub enum ShareSubtype { + #[default] None = 0, } -impl Default for ShareSubtype { - fn default() -> Self { - Self::None - } -} - #[derive(Clone, Copy, PartialEq, Eq, Zeroize, IntoPrimitive, TryFromPrimitive, Debug)] #[cfg_attr(feature = "fuzz", derive(Arbitrary))] #[repr(u16)] +#[derive(Default)] pub enum SignatureSubtype { + #[default] None = 0, } - -impl Default for SignatureSubtype { - fn default() -> Self { - Self::None - } -} diff --git a/src/online_ciphertext/mod.rs b/src/online_ciphertext/mod.rs index 1752d231d..e798fea4e 100644 --- a/src/online_ciphertext/mod.rs +++ b/src/online_ciphertext/mod.rs @@ -114,7 +114,7 @@ impl OnlineCiphertextEncryptor { chunk_size: u32, version: OnlineCiphertextVersion, ) -> Result { - let mut header = Header:: { + let header = Header:: { data_subtype: CiphertextSubtype::Symmetric, ..Default::default() }; @@ -124,8 +124,6 @@ impl OnlineCiphertextEncryptor { match version { OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { - header.version = OnlineCiphertextVersion::V1; - let cipher = OnlineCiphertextV1Encryptor::new(key, full_aad, chunk_size)?; Ok(OnlineCiphertextEncryptor::V1(cipher)) @@ -139,7 +137,7 @@ impl OnlineCiphertextEncryptor { chunk_size: u32, version: OnlineCiphertextVersion, ) -> Result { - let mut header = Header:: { + let header = Header:: { data_subtype: CiphertextSubtype::Asymmetric, ..Default::default() }; @@ -149,8 +147,6 @@ impl OnlineCiphertextEncryptor { match version { OnlineCiphertextVersion::V1 | OnlineCiphertextVersion::Latest => { - header.version = OnlineCiphertextVersion::V1; - let cipher = OnlineCiphertextV1Encryptor::new_asymmetric(public_key, full_aad, chunk_size)?; diff --git a/wrappers/csharp/tests/unit-tests/nugets/iOS/iOS.csproj b/wrappers/csharp/tests/unit-tests/nugets/iOS/iOS.csproj index ea5f1ea66..abb20d4f8 100644 --- a/wrappers/csharp/tests/unit-tests/nugets/iOS/iOS.csproj +++ b/wrappers/csharp/tests/unit-tests/nugets/iOS/iOS.csproj @@ -1,6 +1,6 @@ - net8.0-ios + net9.0-ios Exe enable true From 0aa625c2538632d3c500eba20550a78a8dc173f3 Mon Sep 17 00:00:00 2001 From: Mathieu Morrissette Date: Mon, 10 Nov 2025 15:02:45 -0500 Subject: [PATCH 2/2] review fix --- .github/workflows/fuzz-extended.yml | 2 +- fuzz/run_all_fuzz_tests.sh | 17 +++++++++++++---- fuzz/run_quick_test.sh | 11 ++++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.github/workflows/fuzz-extended.yml b/.github/workflows/fuzz-extended.yml index 77ebf712c..dce182acf 100644 --- a/.github/workflows/fuzz-extended.yml +++ b/.github/workflows/fuzz-extended.yml @@ -15,7 +15,7 @@ on: jobs: extended_fuzz: runs-on: ubuntu-22.04 - timeout-minutes: 300 # 5 hours max + timeout-minutes: 60 # 1 hour max steps: - uses: actions/checkout@v4 diff --git a/fuzz/run_all_fuzz_tests.sh b/fuzz/run_all_fuzz_tests.sh index a24947fbd..e581dcc92 100644 --- a/fuzz/run_all_fuzz_tests.sh +++ b/fuzz/run_all_fuzz_tests.sh @@ -5,6 +5,16 @@ set -e +# Handle Ctrl+C gracefully +cleanup() { + echo -e "\n\n${YELLOW}Interrupted by user. Cleaning up...${NC}" + # Kill any running cargo fuzz processes + pkill -P $$ cargo 2>/dev/null || true + exit 130 +} + +trap cleanup SIGINT SIGTERM + # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' @@ -41,13 +51,12 @@ fi # Check if nightly toolchain is available if ! rustup toolchain list | grep -q nightly; then echo -e "${YELLOW}Warning: Nightly toolchain not found. Installing...${NC}" - rustup install nightly - rustup default nightly + rustup toolchain install nightly fi # Get list of all fuzz targets echo -e "${BLUE}Discovering fuzz targets...${NC}" -TARGETS=($(cargo fuzz list 2>/dev/null | grep -v "^warning:")) +TARGETS=($(cargo +nightly fuzz list 2>/dev/null | grep -v "^warning:")) TOTAL_TARGETS=${#TARGETS[@]} echo -e "Found ${GREEN}${TOTAL_TARGETS}${NC} fuzz targets\n" @@ -77,7 +86,7 @@ run_fuzz_test() { local log_file="fuzz_${target}_$(date +%Y%m%d_%H%M%S).log" # Run the fuzzer - if timeout ${DURATION}s cargo fuzz run "${target}" -- \ + if timeout ${DURATION}s cargo +nightly fuzz run "${target}" -- \ -max_total_time=${DURATION} \ -print_final_stats=1 \ -rss_limit_mb=2048 \ diff --git a/fuzz/run_quick_test.sh b/fuzz/run_quick_test.sh index db96e66d3..52d2a5dbe 100644 --- a/fuzz/run_quick_test.sh +++ b/fuzz/run_quick_test.sh @@ -4,6 +4,15 @@ set -e +# Handle Ctrl+C gracefully +cleanup() { + echo -e "\n\n${YELLOW}Interrupted by user. Cleaning up...${NC}" + pkill -P $$ cargo 2>/dev/null || true + exit 130 +} + +trap cleanup SIGINT SIGTERM + CYAN='\033[0;36m' YELLOW='\033[1;33m' NC='\033[0m' @@ -29,7 +38,7 @@ NEW_TARGETS=( for target in "${NEW_TARGETS[@]}"; do echo -e "${YELLOW}Testing: ${target}${NC}" - timeout 10s cargo fuzz run "${target}" -- -max_total_time=10 || true + timeout 10s cargo +nightly fuzz run "${target}" -- -max_total_time=10 || true echo "" done