Skip to content

Commit b0e7a8b

Browse files
committed
THRIFT-5855: Add netstd fuzzers
1 parent 4afb7d9 commit b0e7a8b

20 files changed

+790
-4
lines changed

FUZZING.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ We currently maintain fuzzers for the following languages:
2323
- Python
2424
- Rust
2525
- Swift
26-
27-
We are working on adding fuzzers for the following languages:
28-
29-
- netstd
26+
- netstd (only supported locally, and not on oss-fuzz)
3027

3128
## Fuzzer Types
3229

lib/netstd/Makefile.am

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ check-local:
2828
$(DOTNETCORE) test Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/Thrift.Compile.netstd2.csproj
2929
$(DOTNETCORE) test Tests/Thrift.Tests/Thrift.Tests.csproj
3030
$(DOTNETCORE) test Tests/Thrift.IntegrationTests/Thrift.IntegrationTests.csproj
31+
$(MAKE) build-fuzzers
32+
33+
build-fuzzers:
34+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Parse -p:Engine=AFL
35+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Parse -p:Engine=Libfuzzer
36+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Roundtrip -p:Engine=AFL
37+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Binary -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer
38+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Parse -p:Engine=AFL
39+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Parse -p:Engine=Libfuzzer
40+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Roundtrip -p:Engine=AFL
41+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Compact -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer
42+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Parse -p:Engine=AFL
43+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Parse -p:Engine=Libfuzzer
44+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Roundtrip -p:Engine=AFL
45+
$(DOTNETCORE) build Tests/Thrift.FuzzTests/Thrift.FuzzTests.csproj -p:Protocol=Json -p:FuzzerType=Roundtrip -p:Engine=Libfuzzer
3146

3247
clean-local:
3348
$(RM) -r Thrift/bin
@@ -44,6 +59,8 @@ clean-local:
4459
$(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.net9/obj
4560
$(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/bin
4661
$(RM) -r Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/obj
62+
$(RM) -r Tests/Thrift.FuzzTests/bin
63+
$(RM) -r Tests/Thrift.FuzzTests/obj
4764

4865
distdir:
4966
$(MAKE) $(AM_MAKEFLAGS) distdir-am
@@ -60,6 +77,7 @@ EXTRA_DIST = \
6077
Tests/Thrift.Compile.Tests/Thrift.Compile.net8/Thrift.Compile.net8.csproj \
6178
Tests/Thrift.Compile.Tests/Thrift.Compile.net9/Thrift.Compile.net9.csproj \
6279
Tests/Thrift.Compile.Tests/Thrift.Compile.netstd2/Thrift.Compile.netstd2.csproj \
80+
Tests/Thrift.FuzzTests \
6381
Tests/Thrift.Tests/Collections \
6482
Tests/Thrift.Tests/DataModel \
6583
Tests/Thrift.Tests/Protocols \

lib/netstd/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,14 @@ Because of the different environment requirements, migration from C# takes sligh
5353
- In case you are using Thrift server event handlers: the `SetEventHandler` method now starts with an uppercase letter
5454
- and you will also have to revise the method names of all `TServerEventHandler` descendants you have in your code
5555

56+
# Fuzzing
57+
58+
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)
59+
60+
To get started:
61+
62+
* Install https://github.com/Metalnem/sharpfuzz
63+
* Install https://github.com/Metalnem/libfuzzer-dotnet
64+
* Create a directory which contains the executable `libfuzzer-dotnet` and set $SHARPFUZZ_DIR to point to it.
65+
66+
Then you can run `buildfuzzers.sh` to build the fuzzers and `runfuzzer.sh` to run a given fuzzer.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
/// <summary>
11+
/// Base class for protocol fuzzers that handles the common fuzzing logic.
12+
/// </summary>
13+
/// <typeparam name="FuzzProtocol">The type of protocol to use for deserialization.</typeparam>
14+
public abstract class ProtocolFuzzerBase<FuzzProtocol> where FuzzProtocol : TProtocol
15+
{
16+
/// <summary>
17+
/// Environment variable that controls whether to use in-process fuzzing for AFL.
18+
/// When set to "1", uses Fuzzer.Run instead of Fuzzer.OutOfProcess.Run.
19+
/// </summary>
20+
protected const string UseInProcessFuzzingEnvVar = "THRIFT_AFL_IN_PROCESS";
21+
22+
/// <summary>
23+
/// 10MB message size limit to prevent over-allocation during fuzzing
24+
/// </summary>
25+
protected const int FUZZ_MAX_MESSAGE_SIZE = 10 * 1024 * 1024;
26+
27+
/// <summary>
28+
/// Creates a new instance of the protocol for the given transport.
29+
/// </summary>
30+
protected abstract FuzzProtocol CreateProtocol(TTransport transport);
31+
32+
/// <summary>
33+
/// Helper method that contains the core fuzzing logic.
34+
/// </summary>
35+
private void ProcessFuzzStream(Stream stream)
36+
{
37+
try
38+
{
39+
var config = new TConfiguration();
40+
config.MaxMessageSize = FUZZ_MAX_MESSAGE_SIZE;
41+
var transport = new TStreamTransport(stream, null, config);
42+
var protocol = CreateProtocol(transport);
43+
44+
var obj = new FuzzTest();
45+
obj.ReadAsync(protocol, default).GetAwaiter().GetResult();
46+
}
47+
catch (TException) { /* Expected for malformed input */ }
48+
catch (Exception) { /* Expected for malformed input */ }
49+
}
50+
51+
/// <summary>
52+
/// The core fuzzing logic that processes a single input.
53+
/// </summary>
54+
protected void ProcessFuzzInput(ReadOnlySpan<byte> span)
55+
{
56+
using var stream = new MemoryStream(span.ToArray());
57+
ProcessFuzzStream(stream);
58+
}
59+
60+
/// <summary>
61+
/// Runs the fuzzer with LibFuzzer.
62+
/// </summary>
63+
protected void RunLibFuzzer()
64+
{
65+
Fuzzer.LibFuzzer.Run(ProcessFuzzInput);
66+
}
67+
68+
/// <summary>
69+
/// Runs the fuzzer with AFL.
70+
/// </summary>
71+
protected void RunAFL()
72+
{
73+
var useInProcess = Environment.GetEnvironmentVariable(UseInProcessFuzzingEnvVar) == "1";
74+
if (useInProcess)
75+
{
76+
Fuzzer.Run(ProcessFuzzStream);
77+
}
78+
else
79+
{
80+
Fuzzer.OutOfProcess.Run(ProcessFuzzStream);
81+
}
82+
}
83+
}
84+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
/// <summary>
11+
/// Base class for protocol round-trip fuzzers that handles the common fuzzing logic.
12+
/// </summary>
13+
/// <typeparam name="FuzzProtocol">The type of protocol to use for serialization/deserialization.</typeparam>
14+
public abstract class ProtocolRoundtripFuzzerBase<FuzzProtocol> where FuzzProtocol : TProtocol
15+
{
16+
/// <summary>
17+
/// Environment variable that controls whether to use in-process fuzzing for AFL.
18+
/// When set to "1", uses Fuzzer.Run instead of Fuzzer.OutOfProcess.Run.
19+
/// </summary>
20+
protected const string UseInProcessFuzzingEnvVar = "THRIFT_AFL_IN_PROCESS";
21+
22+
/// <summary>
23+
/// 10MB message size limit to prevent over-allocation during fuzzing
24+
/// </summary>
25+
protected const int FUZZ_MAX_MESSAGE_SIZE = 10 * 1024 * 1024;
26+
27+
/// <summary>
28+
/// Creates a new instance of the protocol for the given transport.
29+
/// </summary>
30+
protected abstract FuzzProtocol CreateProtocol(TTransport transport);
31+
32+
/// <summary>
33+
/// Helper method that contains the core fuzzing logic.
34+
/// </summary>
35+
private void ProcessFuzzStream(Stream stream)
36+
{
37+
try
38+
{
39+
// First deserialize the input
40+
var config = new TConfiguration();
41+
config.MaxMessageSize = FUZZ_MAX_MESSAGE_SIZE;
42+
var inputTransport = new TStreamTransport(stream, null, config);
43+
var inputProtocol = CreateProtocol(inputTransport);
44+
45+
var inputObj = new FuzzTest();
46+
inputObj.ReadAsync(inputProtocol, default).GetAwaiter().GetResult();
47+
48+
// Now serialize it back
49+
using var outputStream = new MemoryStream();
50+
var outputTransport = new TStreamTransport(null, outputStream, config);
51+
var outputProtocol = CreateProtocol(outputTransport);
52+
inputObj.WriteAsync(outputProtocol, default).GetAwaiter().GetResult();
53+
outputTransport.FlushAsync(default).GetAwaiter().GetResult();
54+
55+
// Get the serialized bytes and deserialize again
56+
var serialized = outputStream.ToArray();
57+
using var reStream = new MemoryStream(serialized);
58+
var reTransport = new TStreamTransport(reStream, null, config);
59+
var reProtocol = CreateProtocol(reTransport);
60+
61+
var outputObj = new FuzzTest();
62+
outputObj.ReadAsync(reProtocol, default).GetAwaiter().GetResult();
63+
64+
// Compare the objects
65+
if (!inputObj.Equals(outputObj))
66+
{
67+
throw new Exception("Round-trip objects are not equal");
68+
}
69+
}
70+
catch (TException) { /* Expected for malformed input */ }
71+
catch (Exception) { /* Expected for malformed input */ }
72+
}
73+
74+
/// <summary>
75+
/// The core fuzzing logic that processes a single input.
76+
/// </summary>
77+
protected void ProcessFuzzInput(ReadOnlySpan<byte> span)
78+
{
79+
using var stream = new MemoryStream(span.ToArray());
80+
ProcessFuzzStream(stream);
81+
}
82+
83+
/// <summary>
84+
/// Runs the fuzzer with LibFuzzer.
85+
/// </summary>
86+
protected void RunLibFuzzer()
87+
{
88+
Fuzzer.LibFuzzer.Run(ProcessFuzzInput);
89+
}
90+
91+
/// <summary>
92+
/// Runs the fuzzer with AFL.
93+
/// </summary>
94+
protected void RunAFL()
95+
{
96+
var useInProcess = Environment.GetEnvironmentVariable(UseInProcessFuzzingEnvVar) == "1";
97+
if (useInProcess)
98+
{
99+
Fuzzer.Run(ProcessFuzzStream);
100+
}
101+
else
102+
{
103+
Fuzzer.OutOfProcess.Run(ProcessFuzzStream);
104+
}
105+
}
106+
}
107+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
public class TBinaryProtocolFuzzer : ProtocolFuzzerBase<TBinaryProtocol>
11+
{
12+
protected override TBinaryProtocol CreateProtocol(TTransport transport)
13+
{
14+
return new TBinaryProtocol(transport);
15+
}
16+
17+
public static void Main(string[] args)
18+
{
19+
new TBinaryProtocolFuzzer().RunAFL();
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
public class TBinaryProtocolFuzzer : ProtocolFuzzerBase<TBinaryProtocol>
11+
{
12+
protected override TBinaryProtocol CreateProtocol(TTransport transport)
13+
{
14+
return new TBinaryProtocol(transport);
15+
}
16+
17+
public static void Main(string[] args)
18+
{
19+
new TBinaryProtocolFuzzer().RunLibFuzzer();
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
public class TBinaryProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase<TBinaryProtocol>
11+
{
12+
protected override TBinaryProtocol CreateProtocol(TTransport transport)
13+
{
14+
return new TBinaryProtocol(transport);
15+
}
16+
17+
public static void Main(string[] args)
18+
{
19+
new TBinaryProtocolRoundtripFuzzer().RunAFL();
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
public class TBinaryProtocolRoundtripFuzzer : ProtocolRoundtripFuzzerBase<TBinaryProtocol>
11+
{
12+
protected override TBinaryProtocol CreateProtocol(TTransport transport)
13+
{
14+
return new TBinaryProtocol(transport);
15+
}
16+
17+
public static void Main(string[] args)
18+
{
19+
new TBinaryProtocolRoundtripFuzzer().RunLibFuzzer();
20+
}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
using System;
2+
using System.IO;
3+
using SharpFuzz;
4+
using Thrift.Protocol;
5+
using Thrift.Transport;
6+
using Thrift.Transport.Client;
7+
8+
namespace Thrift.Tests.Protocols.Fuzzers
9+
{
10+
public class TCompactProtocolFuzzer : ProtocolFuzzerBase<TCompactProtocol>
11+
{
12+
protected override TCompactProtocol CreateProtocol(TTransport transport)
13+
{
14+
return new TCompactProtocol(transport);
15+
}
16+
17+
public static void Main(string[] args)
18+
{
19+
new TCompactProtocolFuzzer().RunAFL();
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)