Skip to content

Commit 4afb7d9

Browse files
authored
THRIFT-5855: Add swift fuzzers
Add fuzzers for Swift support, to improve the reliability/robustness of the implementation
1 parent 7ac79ab commit 4afb7d9

File tree

11 files changed

+429
-1
lines changed

11 files changed

+429
-1
lines changed

FUZZING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ We currently maintain fuzzers for the following languages:
2222
- JavaScript
2323
- Python
2424
- Rust
25+
- Swift
2526

2627
We are working on adding fuzzers for the following languages:
2728

28-
- Swift
2929
- netstd
3030

3131
## Fuzzer Types
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// swift-tools-version:5.5
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
19+
import PackageDescription
20+
21+
let package = Package(
22+
name: "ThriftFuzzTesting",
23+
dependencies: [
24+
.package(name: "Thrift", path: "../")
25+
],
26+
targets: [
27+
// Generated code from Thrift definitions
28+
.target(
29+
name: "Fuzz",
30+
dependencies: ["Thrift"],
31+
path: "Sources/Fuzz"
32+
),
33+
// Common utilities for fuzzing
34+
.target(
35+
name: "FuzzCommon",
36+
dependencies: ["Thrift", "Fuzz"],
37+
path: "Sources/FuzzCommon"
38+
),
39+
.executableTarget(
40+
name: "FuzzParseBinary",
41+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
42+
path: "Sources/FuzzParseBinary",
43+
linkerSettings: [
44+
.unsafeFlags(["-sanitize=fuzzer"])
45+
]
46+
),
47+
.executableTarget(
48+
name: "FuzzRoundtripBinary",
49+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
50+
path: "Sources/FuzzRoundtripBinary",
51+
linkerSettings: [
52+
.unsafeFlags(["-sanitize=fuzzer"])
53+
]
54+
),
55+
.executableTarget(
56+
name: "FuzzParseCompact",
57+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
58+
path: "Sources/FuzzParseCompact",
59+
linkerSettings: [
60+
.unsafeFlags(["-sanitize=fuzzer"])
61+
]
62+
),
63+
.executableTarget(
64+
name: "FuzzRoundtripCompact",
65+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
66+
path: "Sources/FuzzRoundtripCompact",
67+
linkerSettings: [
68+
.unsafeFlags(["-sanitize=fuzzer"])
69+
]
70+
),
71+
.executableTarget(
72+
name: "FuzzParseJSON",
73+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
74+
path: "Sources/FuzzParseJSON",
75+
linkerSettings: [
76+
.unsafeFlags(["-sanitize=fuzzer"])
77+
]
78+
),
79+
.executableTarget(
80+
name: "FuzzRoundtripJSON",
81+
dependencies: ["FuzzCommon", "Thrift", "Fuzz"],
82+
path: "Sources/FuzzRoundtripJSON",
83+
linkerSettings: [
84+
.unsafeFlags(["-sanitize=fuzzer"])
85+
]
86+
)
87+
]
88+
)

lib/swift/FuzzTesting/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Swift Fuzzing README
2+
3+
The Swift Thrift implementation uses LLVM's libFuzzer for fuzzing.
4+
5+
## Fuzzer Structure
6+
7+
We currently have several fuzz targets that test different aspects of the Thrift implementation:
8+
9+
* FuzzParseBinary -- Tries to deserialize the code-generated FuzzTest struct from arbitrary input data using the binary protocol
10+
* FuzzRoundtripBinary -- Tries to deserialize a FuzzTest struct and then tests roundtrip serialization/deserialization with the binary protocol
11+
* FuzzParseCompact
12+
* FuzzRoundtripCompact
13+
* FuzzParseJSON
14+
* FuzzRoundtripJSON
15+
16+
The fuzzers need a dummy main() to ensure that compilation in non-fuzzer modes doesn't regress.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import Foundation
19+
import Thrift
20+
import Fuzz
21+
22+
/// Generic parser that returns a parsed object from binary data - for use as a converter
23+
public func parseObjectWithProtocol<P: TProtocol>(
24+
start: UnsafeRawPointer,
25+
count: Int,
26+
protocolType: P.Type) -> Fuzz.FuzzTest? {
27+
let data = Data(bytes: start, count: count)
28+
let transport = TMemoryBufferTransport(readBuffer: data)
29+
let proto = P(on: transport)
30+
31+
do {
32+
return try Fuzz.FuzzTest.read(from: proto)
33+
} catch {
34+
return nil
35+
}
36+
}
37+
38+
/// Test roundtrip serialization/deserialization with the specified protocol and conversion function
39+
public func roundtripWithProtocol<P: TProtocol>(
40+
start: UnsafeRawPointer,
41+
count: Int,
42+
protocolType: P.Type
43+
) -> Int32 {
44+
// Try to convert data to a test object
45+
guard let testObj = parseObjectWithProtocol(start: start, count: count, protocolType: protocolType) else {
46+
return 0
47+
}
48+
49+
// Now do a roundtrip test with the converted object
50+
do {
51+
// Serialize
52+
let writeTransport = TMemoryBufferTransport()
53+
let writeProto = P(on: writeTransport)
54+
55+
try testObj.write(to: writeProto)
56+
try writeTransport.flush()
57+
58+
// Deserialize
59+
let readTransport = TMemoryBufferTransport(readBuffer: writeTransport.writeBuffer)
60+
let readProto = P(on: readTransport)
61+
62+
let deserialized = try Fuzz.FuzzTest.read(from: readProto)
63+
64+
// This should always be true, but we check just to be sure
65+
guard deserialized == testObj else {
66+
fatalError("Roundtrip test failed: objects not equal after serialization/deserialization")
67+
}
68+
69+
} catch {
70+
// Catch expected exceptions
71+
}
72+
73+
return 0
74+
}
75+
76+
/// Typedef for the fuzzer function signature required by libFuzzer
77+
public typealias FuzzTarget = @convention(c) (UnsafeRawPointer, Int) -> Int32
78+
79+
// Import the libFuzzer driver function
80+
@_silgen_name("LLVMFuzzerRunDriver")
81+
public func LLVMFuzzerRunDriver(
82+
_ argc: UnsafeMutablePointer<Int32>,
83+
_ argv: UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>>>,
84+
_ userCb: @escaping @convention(c) (UnsafeRawPointer, Int) -> Int32) -> Int32
85+
86+
// Run the libFuzzer driver with the given test function
87+
// We use this to get around swift compilation issues, which create main functions that otherwise
88+
// conflict with the libfuzzer main.
89+
// See more documentation here: https://llvm.org/docs/LibFuzzer.html#using-libfuzzer-as-a-library
90+
public func runLibFuzzerDriver(testOneInput: @escaping FuzzTarget) -> Never {
91+
// Create C-style arguments to pass to LLVMFuzzerRunDriver
92+
var args = CommandLine.arguments.map { strdup($0) }
93+
var argc = Int32(args.count)
94+
var argv = args.map { UnsafeMutablePointer<CChar>($0!) }
95+
let argvPtr = UnsafeMutablePointer<UnsafeMutablePointer<CChar>>.allocate(capacity: args.count)
96+
argvPtr.initialize(from: &argv, count: args.count)
97+
let argvPtrPtr = UnsafeMutablePointer<UnsafeMutablePointer<UnsafeMutablePointer<CChar>>>.allocate(capacity: 1)
98+
argvPtrPtr.pointee = argvPtr
99+
100+
// Start the fuzzer engine with our test function
101+
let result = LLVMFuzzerRunDriver(&argc, argvPtrPtr, testOneInput)
102+
103+
// Clean up
104+
argvPtrPtr.deallocate()
105+
argvPtr.deallocate()
106+
107+
exit(Int32(result))
108+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import FuzzCommon
19+
import Thrift
20+
21+
@_cdecl("LLVMFuzzerTestOneInput")
22+
public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
23+
_ = parseObjectWithProtocol(start: start, count: count, protocolType: TBinaryProtocol.self)
24+
return 0
25+
}
26+
27+
@main
28+
public struct FuzzParseBinary {
29+
public static func main() {
30+
runLibFuzzerDriver(testOneInput: testOneInput)
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import FuzzCommon
19+
import Thrift
20+
21+
@_cdecl("LLVMFuzzerTestOneInput")
22+
public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
23+
_ = parseObjectWithProtocol(start: start, count: count, protocolType: TCompactProtocol.self)
24+
return 0
25+
}
26+
27+
@main
28+
public struct FuzzParseCompact {
29+
public static func main() {
30+
runLibFuzzerDriver(testOneInput: testOneInput)
31+
}
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import FuzzCommon
19+
import Thrift
20+
21+
@_cdecl("LLVMFuzzerTestOneInput")
22+
public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
23+
_ = parseObjectWithProtocol(start: start, count: count, protocolType: TJSONProtocol.self)
24+
return 0
25+
}
26+
27+
@main
28+
public struct FuzzParseJSON {
29+
public static func main() {
30+
runLibFuzzerDriver(testOneInput: testOneInput)
31+
}
32+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
import FuzzCommon
19+
import Thrift
20+
21+
@_cdecl("LLVMFuzzerTestOneInput")
22+
public func testOneInput(_ start: UnsafeRawPointer, _ count: Int) -> Int32 {
23+
return roundtripWithProtocol(
24+
start: start,
25+
count: count,
26+
protocolType: TBinaryProtocol.self
27+
)
28+
}
29+
30+
@main
31+
public struct FuzzRoundtripBinary {
32+
public static func main() {
33+
runLibFuzzerDriver(testOneInput: testOneInput)
34+
}
35+
}

0 commit comments

Comments
 (0)