|
| 1 | +#![no_main] |
| 2 | + |
| 3 | +use ed25519_dalek::{Signer, SigningKey}; |
| 4 | +use libfuzzer_sys::fuzz_target; |
| 5 | +use soroban_sdk::{testutils::Address as _, token, vec, Address, BytesN, Env, String}; |
| 6 | +use stellar_strkey::{ed25519, Strkey}; |
| 7 | + |
| 8 | +use substream_contracts::{ |
| 9 | + BulkImportItem, Plan, SubStreamContract, SubStreamContractClient, SEP12_KYC_ISSUER, |
| 10 | +}; |
| 11 | + |
| 12 | +const MONTH: u64 = 30 * 24 * 60 * 60; |
| 13 | + |
| 14 | +fn user_from_sk(env: &Env, sk: &SigningKey) -> (Address, BytesN<32>) { |
| 15 | + let raw: [u8; 32] = sk.verifying_key().to_bytes(); |
| 16 | + let pk = ed25519::PublicKey(raw); |
| 17 | + let s = Strkey::PublicKeyEd25519(pk).to_string(); |
| 18 | + let addr = Address::from_str(env, s.as_str()); |
| 19 | + (addr, BytesN::from_array(env, &raw)) |
| 20 | +} |
| 21 | + |
| 22 | +/// Exercises `batch_import_subscriptions` with **adversarial signature bytes** while the |
| 23 | +/// rest of the payload is consistent (valid merchant, plan, binding, nonce). The host should |
| 24 | +/// trap on `ed25519_verify` for almost all random inputs; `catch_unwind` keeps the fuzzer alive. |
| 25 | +fuzz_target!(|data: &[u8]| { |
| 26 | + if data.len() < 64 { |
| 27 | + return; |
| 28 | + } |
| 29 | + |
| 30 | + let env = Env::default(); |
| 31 | + env.mock_all_auths(); |
| 32 | + |
| 33 | + let admin = Address::generate(&env); |
| 34 | + let merchant = Address::generate(&env); |
| 35 | + let issuer = Address::from_str(&env, SEP12_KYC_ISSUER); |
| 36 | + let sac = env.register_stellar_asset_contract_v2(admin.clone()); |
| 37 | + let t = token::Client::new(&env, &sac.address()); |
| 38 | + |
| 39 | + let contract_id = env.register(SubStreamContract, ()); |
| 40 | + let client = SubStreamContractClient::new(&env, &contract_id); |
| 41 | + env.ledger().set_timestamp(12_000); |
| 42 | + |
| 43 | + client.register_merchant_with_kyc( |
| 44 | + &merchant, |
| 45 | + soroban_sdk::vec![&env, 0u8], |
| 46 | + &issuer, |
| 47 | + ); |
| 48 | + |
| 49 | + let plan = Plan { |
| 50 | + plan_id: 1, |
| 51 | + name: String::from_str(&env, "Fuzz"), |
| 52 | + billing_amount: 100, |
| 53 | + billing_cycle: MONTH, |
| 54 | + has_trial: false, |
| 55 | + trial_duration: 0, |
| 56 | + is_active: true, |
| 57 | + }; |
| 58 | + client.register_plan(&merchant, plan); |
| 59 | + client.set_accepted_token(&merchant, &t.address()); |
| 60 | + |
| 61 | + let mut seed = [11u8; 32]; |
| 62 | + for (i, b) in data.iter().enumerate() { |
| 63 | + seed[i % 32] ^= *b; |
| 64 | + } |
| 65 | + let sk = SigningKey::from_bytes(&seed); |
| 66 | + let (user, pk_bn) = user_from_sk(&env, &sk); |
| 67 | + |
| 68 | + let mut sig = [0u8; 64]; |
| 69 | + sig.copy_from_slice(&data[..64]); |
| 70 | + |
| 71 | + let item = BulkImportItem { |
| 72 | + user, |
| 73 | + user_public_key: pk_bn, |
| 74 | + plan_id: 1, |
| 75 | + nonce: 1, |
| 76 | + signature: BytesN::from_array(&env, &sig), |
| 77 | + }; |
| 78 | + |
| 79 | + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { |
| 80 | + client.batch_import_subscriptions(&merchant, &vec![&env, item]); |
| 81 | + })); |
| 82 | +}); |
0 commit comments