From a6562fd5005a01081bf0efa4c01edfd1899f87bf Mon Sep 17 00:00:00 2001 From: Miha Zupan Date: Mon, 20 Jul 2026 14:05:24 +0200 Subject: [PATCH] Revert WinZipAesStreamFuzzer, ZipCryptoStreamFuzzer --- .../libraries/fuzzing/deploy-to-onefuzz.yml | 16 -- .../Fuzzers/WinZipAesStreamFuzzer.cs | 171 ------------------ .../Fuzzers/ZipCryptoStreamFuzzer.cs | 127 ------------- 3 files changed, 314 deletions(-) delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs delete mode 100644 src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs diff --git a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml index ff5db8cf02512c..85c92f292323f9 100644 --- a/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml +++ b/eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml @@ -200,14 +200,6 @@ extends: SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Send Utf8JsonWriterFuzzer to OneFuzz - - task: onefuzz-task@0 - inputs: - onefuzzOSes: 'Windows' - env: - onefuzzDropDirectory: $(fuzzerProject)/deployment/WinZipAesStreamFuzzer - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Send WinZipAesStreamFuzzer to OneFuzz - - task: onefuzz-task@0 inputs: onefuzzOSes: 'Windows' @@ -215,12 +207,4 @@ extends: onefuzzDropDirectory: $(fuzzerProject)/deployment/ZipArchiveFuzzer SYSTEM_ACCESSTOKEN: $(System.AccessToken) displayName: Send ZipArchiveFuzzer to OneFuzz - - - task: onefuzz-task@0 - inputs: - onefuzzOSes: 'Windows' - env: - onefuzzDropDirectory: $(fuzzerProject)/deployment/ZipCryptoStreamFuzzer - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - displayName: Send ZipCryptoStreamFuzzer to OneFuzz # ONEFUZZ_TASK_WORKAROUND_END diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs deleted file mode 100644 index 7632e9b6b33197..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/WinZipAesStreamFuzzer.cs +++ /dev/null @@ -1,171 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.IO.Compression; -using System.Reflection; -using System.Runtime.Versioning; -using System.Security.Cryptography; -using System.Threading.Tasks; - -namespace DotnetFuzzing.Fuzzers; - -[UnsupportedOSPlatform("browser")] -internal sealed class WinZipAesStreamFuzzer : IFuzzer -{ - public string[] TargetAssemblies { get; } = ["System.IO.Compression"]; - public string[] TargetCoreLibPrefixes => []; - public string Corpus => "winzipaesstream"; - - // AES-256 key size in bits; salt size = keySizeBits / 16 = 16 bytes. - private const int KeySizeBits = 256; - - // ReadOnlySpan is a ref struct and cannot be boxed for MethodInfo.Invoke, - // and CreateDelegate cannot handle struct-to-object return covariance. - // Use DynamicMethod to emit a wrapper that boxes the struct return value. - private delegate object CreateKeyDelegate(ReadOnlySpan password, byte[]? salt, int keySizeBits); - - private static readonly CreateKeyDelegate _createKey; - private static readonly MethodInfo _createMethod; - - // The salt and password verifier properties are needed to prepend a valid header - // so the stream's ReadAndValidateHeaderCore succeeds and decryption logic is reached. - private static readonly PropertyInfo _saltProp; - private static readonly PropertyInfo _verifierProp; - - // Pre-derive key material once with a fixed password and no salt so the fuzzer focuses - // on the stream's decryption/HMAC logic rather than key derivation. - private static readonly object s_keyMaterial; - - // Cache the salt and password verifier bytes for prepending to the fuzz input. - private static readonly byte[] s_salt; - private static readonly byte[] s_verifier; - - static WinZipAesStreamFuzzer() - { - Type winZipAesStreamType = Type.GetType("System.IO.Compression.WinZipAesStream, System.IO.Compression")!; - Type winZipAesKeyMaterialType = Type.GetType("System.IO.Compression.WinZipAesKeyMaterial, System.IO.Compression")!; - -#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not AOT-compatible; fuzzers run under CoreCLR only. - _createKey = CreateBoxingDelegate(winZipAesKeyMaterialType); -#pragma warning restore IL3050 - - _createMethod = winZipAesStreamType.GetMethod( - "Create", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, - binder: null, - types: [typeof(Stream), winZipAesKeyMaterialType, typeof(long), typeof(bool), typeof(bool)], - modifiers: null)!; - - _saltProp = winZipAesKeyMaterialType.GetProperty( - "Salt", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!; - - _verifierProp = winZipAesKeyMaterialType.GetProperty( - "PasswordVerifier", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)!; - - s_keyMaterial = _createKey("fuzz", null, KeySizeBits); - s_salt = (byte[])_saltProp.GetValue(s_keyMaterial)!; - s_verifier = (byte[])_verifierProp.GetValue(s_keyMaterial)!; - } - - private static CreateKeyDelegate CreateBoxingDelegate(Type winZipAesKeyMaterialType) - { - MethodInfo createKeyMethod = winZipAesKeyMaterialType.GetMethod( - "Create", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, - binder: null, - types: [typeof(ReadOnlySpan), typeof(byte[]), typeof(int)], - modifiers: null)!; - - var dm = new System.Reflection.Emit.DynamicMethod( - "CreateKeyWrapper", - typeof(object), - [typeof(ReadOnlySpan), typeof(byte[]), typeof(int)], - typeof(WinZipAesStreamFuzzer).Module, - skipVisibility: true); - var il = dm.GetILGenerator(); - il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0); - il.Emit(System.Reflection.Emit.OpCodes.Ldarg_1); - il.Emit(System.Reflection.Emit.OpCodes.Ldarg_2); - il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod); - il.Emit(System.Reflection.Emit.OpCodes.Box, winZipAesKeyMaterialType); - il.Emit(System.Reflection.Emit.OpCodes.Ret); - return dm.CreateDelegate(); - } - - // Minimum fuzz input: at least 1 byte of encrypted data beyond the header. - // The header (salt + verifier) is prepended by CreateStream, so the fuzz input - // only needs to supply encrypted data + the 10-byte auth code. - private const int MinInputLength = 11; // 1 byte data + 10 bytes HMAC - - public void FuzzTarget(ReadOnlySpan bytes) - { - if (bytes.Length < MinInputLength) - { - return; - } - - TestStream(CopyToRentedArray(bytes), bytes.Length, async: false).GetAwaiter().GetResult(); - TestStream(CopyToRentedArray(bytes), bytes.Length, async: true).GetAwaiter().GetResult(); - } - - private static Stream CreateStream(byte[] bytes, int length) - { - // Prepend the valid salt + password verifier so ReadAndValidateHeaderCore passes, - // allowing the fuzzer to exercise the CTR decryption and HMAC validation paths. - int headerSize = s_salt.Length + s_verifier.Length; - int totalSize = headerSize + length; - byte[] combined = new byte[totalSize]; - s_salt.CopyTo(combined, 0); - s_verifier.CopyTo(combined, s_salt.Length); - Buffer.BlockCopy(bytes, 0, combined, headerSize, length); - -#pragma warning disable IL2072 // dynamic invocation - return (Stream)_createMethod.Invoke( - obj: null, - parameters: [new MemoryStream(combined), s_keyMaterial, (long)totalSize, /*encrypting*/ false, /*leaveOpen*/ false])!; -#pragma warning restore IL2072 - } - - private static byte[] CopyToRentedArray(ReadOnlySpan bytes) - { - byte[] buffer = ArrayPool.Shared.Rent(bytes.Length); - bytes.CopyTo(buffer); - return buffer; - } - - private async Task TestStream(byte[] buffer, int length, bool async) - { - try - { - using var stream = CreateStream(buffer, length); - if (async) - { - await stream.CopyToAsync(Stream.Null); - } - else - { - stream.CopyTo(Stream.Null); - } - } - catch (InvalidDataException) - { - // ignore, this exception is expected for invalid/corrupted data. - } - catch (CryptographicException) - { - // ignore, crypto failures are expected for random fuzz input. - } - catch (TargetInvocationException ex) when (ex.InnerException is InvalidDataException or CryptographicException) - { - // The reflected WinZipAesStream.Create call wraps exceptions - // in TargetInvocationException when header validation fails. - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } -} diff --git a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs b/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs deleted file mode 100644 index 71a6851d37659e..00000000000000 --- a/src/libraries/Fuzzing/DotnetFuzzing/Fuzzers/ZipCryptoStreamFuzzer.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Buffers; -using System.IO.Compression; -using System.Reflection; -using System.Threading.Tasks; - -namespace DotnetFuzzing.Fuzzers; - -internal sealed class ZipCryptoStreamFuzzer : IFuzzer -{ - public string[] TargetAssemblies { get; } = ["System.IO.Compression"]; - public string[] TargetCoreLibPrefixes => []; - public string Corpus => "zipcryptostream"; - - public void FuzzTarget(ReadOnlySpan bytes) - { - // ZipCryptoStream.Create reads a 12-byte header from the stream and validates the - // last decrypted byte against the expected check byte. Require at least 13 bytes - // (1 check byte + 12 header bytes) so the fuzzer can reach past the header. - if (bytes.Length < 13) - { - return; - } - - TestStream(CopyToRentedArray(bytes), bytes.Length, async: false).GetAwaiter().GetResult(); - TestStream(CopyToRentedArray(bytes), bytes.Length, async: true).GetAwaiter().GetResult(); - } - - // ReadOnlySpan is a ref struct and cannot be boxed for MethodInfo.Invoke, - // and CreateDelegate cannot handle struct-to-object return covariance. - // Use DynamicMethod to emit a wrapper that boxes the struct return value. - private delegate object CreateKeyDelegate(ReadOnlySpan password); - - private static readonly CreateKeyDelegate _createKey; - private static readonly MethodInfo _createMethod; - private static readonly object s_keys; - - static ZipCryptoStreamFuzzer() - { - Type zipCryptoStreamType = Type.GetType("System.IO.Compression.ZipCryptoStream, System.IO.Compression")!; - Type zipCryptoKeysType = Type.GetType("System.IO.Compression.ZipCryptoKeys, System.IO.Compression")!; - -#pragma warning disable IL3050 // RequiresDynamicCode: DynamicMethod is not AOT-compatible; fuzzers run under CoreCLR only. - _createKey = CreateBoxingDelegate(zipCryptoStreamType, zipCryptoKeysType); -#pragma warning restore IL3050 - - _createMethod = zipCryptoStreamType.GetMethod( - "Create", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, - binder: null, - types: [typeof(Stream), zipCryptoKeysType, typeof(byte), typeof(bool), typeof(bool)], - modifiers: null)!; - - s_keys = _createKey("fuzz"); - } - - private static CreateKeyDelegate CreateBoxingDelegate(Type zipCryptoStreamType, Type zipCryptoKeysType) - { - MethodInfo createKeyMethod = zipCryptoStreamType.GetMethod( - "CreateKey", - BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)!; - - var dm = new System.Reflection.Emit.DynamicMethod( - "CreateKeyWrapper", - typeof(object), - [typeof(ReadOnlySpan)], - typeof(ZipCryptoStreamFuzzer).Module, - skipVisibility: true); - var il = dm.GetILGenerator(); - il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0); - il.Emit(System.Reflection.Emit.OpCodes.Call, createKeyMethod); - il.Emit(System.Reflection.Emit.OpCodes.Box, zipCryptoKeysType); - il.Emit(System.Reflection.Emit.OpCodes.Ret); - return dm.CreateDelegate(); - } - - private static Stream CreateStream(byte[] bytes, int length) - { - // Use the first byte of the input as the "expected check byte" so that the - // header validation path is exercised with varying values. - byte expectedCheckByte = bytes[0]; - var baseStream = new MemoryStream(bytes, 1, length - 1); -#pragma warning disable IL2072 // dynamic invocation - return (Stream)_createMethod.Invoke( - obj: null, - parameters: [baseStream, s_keys, expectedCheckByte, /*encrypting*/ false, /*leaveOpen*/ false])!; -#pragma warning restore IL2072 - } - - private static byte[] CopyToRentedArray(ReadOnlySpan bytes) - { - byte[] buffer = ArrayPool.Shared.Rent(bytes.Length); - bytes.CopyTo(buffer); - return buffer; - } - - private async Task TestStream(byte[] buffer, int length, bool async) - { - try - { - using var stream = CreateStream(buffer, length); - if (async) - { - await stream.CopyToAsync(Stream.Null); - } - else - { - stream.CopyTo(Stream.Null); - } - } - catch (InvalidDataException) - { - // ignore, this exception is expected for invalid/corrupted data. - } - catch (TargetInvocationException ex) when (ex.InnerException is InvalidDataException) - { - // The reflected ZipCryptoStream.Create call wraps InvalidDataException - // (e.g. password mismatch, truncated header) in TargetInvocationException. - } - finally - { - ArrayPool.Shared.Return(buffer); - } - } -}