diff --git a/FUZZING.md b/FUZZING.md index 146b015edfb..df30c8a8f55 100644 --- a/FUZZING.md +++ b/FUZZING.md @@ -23,10 +23,7 @@ We currently maintain fuzzers for the following languages: - Python - Rust - Swift - -We are working on adding fuzzers for the following languages: - -- netstd +- netstd (only supported locally, and not on oss-fuzz) ## Fuzzer Types diff --git a/lib/netstd/Makefile.am b/lib/netstd/Makefile.am index ed855e45f3f..34036d73806 100644 --- a/lib/netstd/Makefile.am +++ b/lib/netstd/Makefile.am @@ -28,6 +28,21 @@ check-local: $(DOTNETCORE) test Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/Thrift.Compile.netstd2.csproj $(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj $(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj + $(MAKE) build-fuzzers + +build-fuzzers: + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Parse -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Parse -p:Engine=Libfuzzer + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Roundtrip -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Parse -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Parse -p:Engine=Libfuzzer + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Roundtrip -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Parse -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Parse -p:Engine=Libfuzzer + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Roundtrip -p:Engine=AFL + $(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer clean-local: $(RM) -r Thrift/bin @@ -44,6 +59,8 @@ clean-local: $(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.net9/obj $(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/bin $(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/obj + $(RM) -r Tests/Thrift.FuzzTests/bin + $(RM) -r Tests/Thrift.FuzzTests/obj distdir: $(MAKE) $(AM_MAKEFLAGS) distdir-am @@ -60,6 +77,7 @@ EXTRA_DIST = \ Tests/Thrift.Compile.Tests/Thrift.Compile.net8/Thrift.Compile.net8.csproj \ Tests/Thrift.Compile.Tests/Thrift.Compile.net9/Thrift.Compile.net9.csproj \ Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/Thrift.Compile.netstd2.csproj \ + Tests/Thrift.FuzzTests \ Tests/Thrift.Tests/Collections \ Tests/Thrift.Tests/DataModel \ Tests/Thrift.Tests/Protocols \ diff --git a/lib/netstd/README.md b/lib/netstd/README.md index 912ea011c1d..0fb72887245 100644 --- a/lib/netstd/README.md +++ b/lib/netstd/README.md @@ -53,3 +53,14 @@ Because of the different environment requirements, migration from C# takes sligh - In case you are using Thrift server event handlers: the `SetEventHandler` method now starts with an uppercase letter - and you will also have to revise the method names of all `TServerEventHandler` descendants you have in your code +# Fuzzing + +We use SharpFuzz (and its libfuzzer variant) for fuzzing. This is **not** supported on oss-fuzz, so all fuzzing must be run locally (currently only tested on a linux machine) + +To get started: + +* Install https://github.com/Metalnem/sharpfuzz +* Install https://github.com/Metalnem/libfuzzer-dotnet +* Create a directory which contains the executable `libfuzzer-dotnet` and set $SHARPFUZZ_DIR to point to it. + +Then you can run `buildfuzzers.sh` to build the fuzzers and `runfuzzer.sh` to run a given fuzzer. \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/ProtocolFuzzerBase.cs b/lib/netstd/Tests/Thrift.FuzzTests/ProtocolFuzzerBase.cs new file mode 100644 index 00000000000..aaf258d493b --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/ProtocolFuzzerBase.cs @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + /// + /// Base class for protocol fuzzers that handles the common fuzzing logic. + /// + /// The type of protocol to use for deserialization. + public abstract class ProtocolFuzzerBase where FuzzProtocol : TProtocol + { + /// + /// Environment variable that controls whether to use in-process fuzzing for AFL. + /// When set to "1", uses Fuzzer.Run instead of Fuzzer.OutOfProcess.Run. + /// + protected const string UseInProcessFuzzingEnvVar = "THRIFT_AFL_IN_PROCESS"; + + /// + /// 10MB message size limit to prevent over-allocation during fuzzing + /// + protected const int FUZZ_MAX_MESSAGE_SIZE = 10 * 1024 * 1024; + + /// + /// Creates a new instance of the protocol for the given transport. + /// + protected abstract FuzzProtocol CreateProtocol(TTransport transport); + + /// + /// Helper method that contains the core fuzzing logic. + /// + private void ProcessFuzzStream(Stream stream) + { + try + { + var config = new TConfiguration(); + config.MaxMessageSize = FUZZ_MAX_MESSAGE_SIZE; + var transport = new TStreamTransport(stream, null, config); + var protocol = CreateProtocol(transport); + + var obj = new FuzzTest(); + obj.ReadAsync(protocol, default).GetAwaiter().GetResult(); + } + catch (TException) { /* Expected for malformed input */ } + catch (Exception) { /* Expected for malformed input */ } + } + + /// + /// The core fuzzing logic that processes a single input. + /// + protected void ProcessFuzzInput(ReadOnlySpan span) + { + using var stream = new MemoryStream(span.ToArray()); + ProcessFuzzStream(stream); + } + + /// + /// Runs the fuzzer with LibFuzzer. + /// + protected void RunLibFuzzer() + { + Fuzzer.LibFuzzer.Run(ProcessFuzzInput); + } + + /// + /// Runs the fuzzer with AFL. + /// + protected void RunAFL() + { + var useInProcess = Environment.GetEnvironmentVariable(UseInProcessFuzzingEnvVar) == "1"; + if (useInProcess) + { + Fuzzer.Run(ProcessFuzzStream); + } + else + { + Fuzzer.OutOfProcess.Run(ProcessFuzzStream); + } + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/ProtocolRoundtripFuzzerBase.cs b/lib/netstd/Tests/Thrift.FuzzTests/ProtocolRoundtripFuzzerBase.cs new file mode 100644 index 00000000000..70b85fa9fa8 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/ProtocolRoundtripFuzzerBase.cs @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + /// + /// Base class for protocol round-trip fuzzers that handles the common fuzzing logic. + /// + /// The type of protocol to use for serialization/deserialization. + public abstract class ProtocolRoundtripFuzzerBase where FuzzProtocol : TProtocol + { + /// + /// Environment variable that controls whether to use in-process fuzzing for AFL. + /// When set to "1", uses Fuzzer.Run instead of Fuzzer.OutOfProcess.Run. + /// + protected const string UseInProcessFuzzingEnvVar = "THRIFT_AFL_IN_PROCESS"; + + /// + /// 10MB message size limit to prevent over-allocation during fuzzing + /// + protected const int FUZZ_MAX_MESSAGE_SIZE = 10 * 1024 * 1024; + + /// + /// Creates a new instance of the protocol for the given transport. + /// + protected abstract FuzzProtocol CreateProtocol(TTransport transport); + + /// + /// Helper method that contains the core fuzzing logic. + /// + private void ProcessFuzzStream(Stream stream) + { + try + { + // First deserialize the input + var config = new TConfiguration(); + config.MaxMessageSize = FUZZ_MAX_MESSAGE_SIZE; + var inputTransport = new TStreamTransport(stream, null, config); + var inputProtocol = CreateProtocol(inputTransport); + + var inputObj = new FuzzTest(); + inputObj.ReadAsync(inputProtocol, default).GetAwaiter().GetResult(); + + // Now serialize it back + using var outputStream = new MemoryStream(); + var outputTransport = new TStreamTransport(null, outputStream, config); + var outputProtocol = CreateProtocol(outputTransport); + inputObj.WriteAsync(outputProtocol, default).GetAwaiter().GetResult(); + outputTransport.FlushAsync(default).GetAwaiter().GetResult(); + + // Get the serialized bytes and deserialize again + var serialized = outputStream.ToArray(); + using var reStream = new MemoryStream(serialized); + var reTransport = new TStreamTransport(reStream, null, config); + var reProtocol = CreateProtocol(reTransport); + + var outputObj = new FuzzTest(); + outputObj.ReadAsync(reProtocol, default).GetAwaiter().GetResult(); + + // Compare the objects + if (!inputObj.Equals(outputObj)) + { + throw new Exception("Round-trip objects are not equal"); + } + } + catch (TException) { /* Expected for malformed input */ } + catch (Exception) { /* Expected for malformed input */ } + } + + /// + /// The core fuzzing logic that processes a single input. + /// + protected void ProcessFuzzInput(ReadOnlySpan span) + { + using var stream = new MemoryStream(span.ToArray()); + ProcessFuzzStream(stream); + } + + /// + /// Runs the fuzzer with LibFuzzer. + /// + protected void RunLibFuzzer() + { + Fuzzer.LibFuzzer.Run(ProcessFuzzInput); + } + + /// + /// Runs the fuzzer with AFL. + /// + protected void RunAFL() + { + var useInProcess = Environment.GetEnvironmentVariable(UseInProcessFuzzingEnvVar) == "1"; + if (useInProcess) + { + Fuzzer.Run(ProcessFuzzStream); + } + else + { + Fuzzer.OutOfProcess.Run(ProcessFuzzStream); + } + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolAFL.cs new file mode 100644 index 00000000000..6827a4cdd47 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TBinaryProtocolFuzzer : ProtocolFuzzerBase + { + protected override TBinaryProtocol CreateProtocol(TTransport transport) + { + return new TBinaryProtocol(transport); + } + + public static void Main(string[] args) + { + new TBinaryProtocolFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolLibfuzzer.cs new file mode 100644 index 00000000000..5d2cb52a848 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolLibfuzzer.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TBinaryProtocolFuzzer : ProtocolFuzzerBase + { + protected override TBinaryProtocol CreateProtocol(TTransport transport) + { + return new TBinaryProtocol(transport); + } + + public static void Main(string[] args) + { + new TBinaryProtocolFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripAFL.cs new file mode 100644 index 00000000000..6f7b071cd95 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TBinaryProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TBinaryProtocol CreateProtocol(TTransport transport) + { + return new TBinaryProtocol(transport); + } + + public static void Main(string[] args) + { + new TBinaryProtocolRoundtripFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripLibfuzzer.cs new file mode 100644 index 00000000000..298698b6a44 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TBinaryProtocolRoundtripLibfuzzer.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TBinaryProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TBinaryProtocol CreateProtocol(TTransport transport) + { + return new TBinaryProtocol(transport); + } + + public static void Main(string[] args) + { + new TBinaryProtocolRoundtripFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolAFL.cs new file mode 100644 index 00000000000..3391b3cd05d --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TCompactProtocolFuzzer : ProtocolFuzzerBase + { + protected override TCompactProtocol CreateProtocol(TTransport transport) + { + return new TCompactProtocol(transport); + } + + public static void Main(string[] args) + { + new TCompactProtocolFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolLibfuzzer.cs new file mode 100644 index 00000000000..e2fa0f7a879 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolLibfuzzer.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TCompactProtocolFuzzer : ProtocolFuzzerBase + { + protected override TCompactProtocol CreateProtocol(TTransport transport) + { + return new TCompactProtocol(transport); + } + + public static void Main(string[] args) + { + new TCompactProtocolFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripAFL.cs new file mode 100644 index 00000000000..4b4afc39d4b --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TCompactProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TCompactProtocol CreateProtocol(TTransport transport) + { + return new TCompactProtocol(transport); + } + + public static void Main(string[] args) + { + new TCompactProtocolRoundtripFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripLibfuzzer.cs new file mode 100644 index 00000000000..6bb97423bd2 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TCompactProtocolRoundtripLibfuzzer.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TCompactProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TCompactProtocol CreateProtocol(TTransport transport) + { + return new TCompactProtocol(transport); + } + + public static void Main(string[] args) + { + new TCompactProtocolRoundtripFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolAFL.cs new file mode 100644 index 00000000000..a68659bfd85 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TJsonProtocolFuzzer : ProtocolFuzzerBase + { + protected override TJsonProtocol CreateProtocol(TTransport transport) + { + return new TJsonProtocol(transport); + } + + public static void Main(string[] args) + { + new TJsonProtocolFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolLibfuzzer.cs new file mode 100644 index 00000000000..e6d46746043 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolLibfuzzer.cs @@ -0,0 +1,44 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Boilerplate fuzzer for TJSONProtocol using SharpFuzz +// Attempts to deserialize a FuzzTest struct from the input stream using TJSONProtocol +// Expected exceptions (e.g., TProtocolException) are caught and ignored +// Unexpected exceptions are reported as crashes + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TJsonProtocolFuzzer : ProtocolFuzzerBase + { + protected override TJsonProtocol CreateProtocol(TTransport transport) + { + return new TJsonProtocol(transport); + } + + public static void Main(string[] args) + { + new TJsonProtocolFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripAFL.cs b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripAFL.cs new file mode 100644 index 00000000000..6b956c0d825 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripAFL.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TJsonProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TJsonProtocol CreateProtocol(TTransport transport) + { + return new TJsonProtocol(transport); + } + + public static void Main(string[] args) + { + new TJsonProtocolRoundtripFuzzer().RunAFL(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripLibfuzzer.cs b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripLibfuzzer.cs new file mode 100644 index 00000000000..5f16813c2c0 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/TJsonProtocolRoundtripLibfuzzer.cs @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.IO; +using SharpFuzz; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Transport.Client; + +namespace Thrift.Tests.Protocols.Fuzzers +{ + public class TJsonProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase + { + protected override TJsonProtocol CreateProtocol(TTransport transport) + { + return new TJsonProtocol(transport); + } + + public static void Main(string[] args) + { + new TJsonProtocolRoundtripFuzzer().RunLibFuzzer(); + } + } +} \ No newline at end of file diff --git a/lib/netstd/Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj b/lib/netstd/Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj new file mode 100644 index 00000000000..724d8c270a4 --- /dev/null +++ b/lib/netstd/Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj @@ -0,0 +1,70 @@ + + + + + net9.0 + latestMajor + enable + Exe + false + + + Binary + Parse + AFL + + + Thrift.FuzzTests.$(Protocol)$(FuzzerType)$(Engine) + + + obj\$(Configuration)\$(TargetFramework)\$(Protocol)$(FuzzerType)$(Engine)\ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/netstd/buildfuzzers.sh b/lib/netstd/buildfuzzers.sh new file mode 100755 index 00000000000..53d9e5660cb --- /dev/null +++ b/lib/netstd/buildfuzzers.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +# Check for SHARPFUZZ_DIR environment variable +if [ -z "$SHARPFUZZ_DIR" ]; then + echo "Error: SHARPFUZZ_DIR environment variable is not set." + echo "Please set SHARPFUZZ_DIR to the location of your SharpFuzz installation." + echo "See README for installation instructions." + exit 1 +fi + +# Verify libfuzzer-dotnet exists +LIBFUZZER="$SHARPFUZZ_DIR/libfuzzer-dotnet" +if [ ! -f "$LIBFUZZER" ]; then + echo "Error: libfuzzer-dotnet not found at $LIBFUZZER" + echo "Please ensure SharpFuzz is properly installed in $SHARPFUZZ_DIR" + echo "See README for installation instructions." + exit 1 +fi + +# Find the local Thrift compiler +THRIFT="$(dirname "$0")/../../compiler/cpp/thrift" +THRIFT=$(realpath "$THRIFT") + +# Paths +THRIFT_FILE="$(dirname "$0")/../../test/FuzzTest.thrift" +GEN_DIR="$(dirname "$0")/Tests/Thrift.FuzzTests/gen-netstd" +FUZZERS_DIR="$(dirname "$0")/Tests/Thrift.FuzzTests" +OUTPUT_DIR="$FUZZERS_DIR/bin/Debug/net9.0" + +# Step 1: Generate C# code from FuzzTest.thrift +if [ ! -x "$THRIFT" ]; then + echo "Error: Thrift compiler not found at $THRIFT" + exit 1 +fi + +# Clean and create the generated directory +rm -rf "$GEN_DIR" +mkdir -p "$GEN_DIR" + +echo "[1/13] Generating C# code from $THRIFT_FILE ..." +"$THRIFT" --gen netstd:net9 -out "$GEN_DIR" "$THRIFT_FILE" +echo "C# code generated in $GEN_DIR." + +# Step 2: Build all fuzzer projects +cd "$(dirname "$0")" + +# Build all fuzzer combinations using the consolidated project +BUILD_COUNT=2 +for protocol in Binary Compact Json; do + for fuzzer_type_raw in Parse Roundtrip; do + for engine in AFL Libfuzzer; do + # Construct display name + if [ "$fuzzer_type_raw" = "Parse" ]; then + display_name="$protocol Protocol $engine" + fuzzer_type="" + else + display_name="$protocol Protocol round-trip $engine" + fuzzer_type="Roundtrip" + fi + + echo "[$BUILD_COUNT/13] Building $display_name ..." + dotnet build "$FUZZERS_DIR/Thrift.FuzzTests.csproj" \ + -p:Protocol=$protocol \ + -p:FuzzerType=$fuzzer_type_raw \ + -p:Engine=$engine + + BUILD_COUNT=$((BUILD_COUNT + 1)) + done + done +done + +# Step 3: Instrument the assemblies +echo "Instrumenting assemblies for fuzzing ..." + +# Exclusions for instrumentation +EXCLUSIONS=("dnlib.dll" "SharpFuzz.dll" "SharpFuzz.Common.dll") + +# Find and instrument fuzzing targets +while IFS= read -r -d '' dll; do + dll_name=$(basename "$dll") + skip=false + for excl in "${EXCLUSIONS[@]}"; do + if [[ "$dll_name" == "$excl" ]]; then + skip=true + break + fi + done + if [[ "$dll_name" == System.*.dll ]]; then + skip=true + fi + if [[ "$dll_name" == Thrift.FuzzTests.*.dll ]]; then + skip=true # Skip the fuzzer assemblies themselves + fi + if [ "$skip" = true ]; then + echo "Skipping $dll_name" + continue + fi + if [ "$skip" = false ]; then + echo "Instrumenting $dll_name" + sharpfuzz "$dll" + if [ $? -ne 0 ]; then + echo "An error occurred while instrumenting $dll" + exit 1 + fi + fi +done < <(find "$OUTPUT_DIR" -maxdepth 1 -type f -name "*.dll" -print0) + +echo "Build and instrumentation complete." \ No newline at end of file diff --git a/lib/netstd/runfuzzer.sh b/lib/netstd/runfuzzer.sh new file mode 100755 index 00000000000..7bc649e2611 --- /dev/null +++ b/lib/netstd/runfuzzer.sh @@ -0,0 +1,160 @@ +#!/bin/bash + +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +# Check if a fuzzer name and type were provided +if [ $# -lt 2 ]; then + echo "Usage: $0 [additional fuzzer arguments...]" + echo "Available fuzzer names: json, compact, binary, json-roundtrip, compact-roundtrip, binary-roundtrip" + echo "Available fuzzer types: libfuzzer, afl" + exit 1 +fi + +FUZZER_NAME="$1" +FUZZER_TYPE="$2" +shift 2 # Remove the first two arguments, leaving only additional arguments + +VALID_FUZZERS=("json" "compact" "binary" "json-roundtrip" "compact-roundtrip" "binary-roundtrip") +VALID_TYPES=("libfuzzer" "afl") + +# Validate fuzzer name +VALID=0 +for f in "${VALID_FUZZERS[@]}"; do + if [ "$f" = "$FUZZER_NAME" ]; then + VALID=1 + break + fi +done + +if [ $VALID -eq 0 ]; then + echo "Invalid fuzzer name: $FUZZER_NAME" + echo "Available fuzzers: json, compact, binary, json-roundtrip, compact-roundtrip, binary-roundtrip" + exit 1 +fi + +# Validate fuzzer type +VALID=0 +for t in "${VALID_TYPES[@]}"; do + if [ "$t" = "$FUZZER_TYPE" ]; then + VALID=1 + break + fi +done + +if [ $VALID -eq 0 ]; then + echo "Invalid fuzzer type: $FUZZER_TYPE" + echo "Available types: libfuzzer, afl" + exit 1 +fi + +# Map fuzzer name and type to assembly name +# With the consolidated project, names follow the pattern: +# Thrift.FuzzTests.{Protocol}{FuzzerType}{Engine} +# where FuzzerType is "Parse" or "Roundtrip" +case "$FUZZER_NAME" in + "json") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.JsonParseLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.JsonParseAFL" + fi + ;; + "compact") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.CompactParseLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.CompactParseAFL" + fi + ;; + "binary") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.BinaryParseLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.BinaryParseAFL" + fi + ;; + "json-roundtrip") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.JsonRoundtripLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.JsonRoundtripAFL" + fi + ;; + "compact-roundtrip") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.CompactRoundtripLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.CompactRoundtripAFL" + fi + ;; + "binary-roundtrip") + if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + ASSEMBLY_NAME="Thrift.FuzzTests.BinaryRoundtripLibfuzzer" + else + ASSEMBLY_NAME="Thrift.FuzzTests.BinaryRoundtripAFL" + fi + ;; +esac + +# Check for SHARPFUZZ_DIR environment variable +if [ -z "$SHARPFUZZ_DIR" ]; then + echo "Error: SHARPFUZZ_DIR environment variable is not set." + echo "Please set SHARPFUZZ_DIR to the location of your SharpFuzz installation." + echo "See README for installation instructions." + exit 1 +fi + +# Verify libfuzzer-dotnet exists +LIBFUZZER="$SHARPFUZZ_DIR/libfuzzer-dotnet" +if [ ! -f "$LIBFUZZER" ]; then + echo "Error: libfuzzer-dotnet not found at $LIBFUZZER" + echo "Please ensure SharpFuzz is properly installed in $SHARPFUZZ_DIR" + echo "See README for installation instructions." + exit 1 +fi +OUTPUT_DIR="$(dirname "$0")/Tests/Thrift.FuzzTests/bin/Debug/net9.0" +CORPUS_DIR="$(dirname "$0")/corpus/$FUZZER_NAME" + +# Create corpus directory if it doesn't exist +mkdir -p "$CORPUS_DIR" + +# Get project path +PROJECT_PATH="$OUTPUT_DIR/$ASSEMBLY_NAME.dll" + +# Run the appropriate fuzzer +echo "Running $ASSEMBLY_NAME fuzzer..." +if [ "$FUZZER_TYPE" = "libfuzzer" ]; then + "$LIBFUZZER" --target_path=dotnet --target_arg="$PROJECT_PATH" "$CORPUS_DIR" "$@" +else + # For AFL, we need separate input and findings directories + AFL_INPUT_DIR="$CORPUS_DIR/input" + AFL_FINDINGS_DIR="$CORPUS_DIR/findings" + mkdir -p "$AFL_INPUT_DIR" + mkdir -p "$AFL_FINDINGS_DIR" + + # If input directory is empty, create a minimal test case + if [ ! "$(ls -A $AFL_INPUT_DIR)" ]; then + echo -n "test" > "$AFL_INPUT_DIR/test.txt" + fi + export AFL_SKIP_BIN_CHECK=1 + afl-fuzz -i "$AFL_INPUT_DIR" -o "$AFL_FINDINGS_DIR" -m none dotnet "$PROJECT_PATH" "$@" +fi \ No newline at end of file