mirror of
https://github.com/apple/swift-protobuf.git
synced 2026-05-17 10:20:36 +00:00
Use the fuzz options support to control encoding options. (#2049)
In looking at the code coverage on oss-fuzz, I realized the encoding support never goes down paths related to options being not the default, so go ahead and allow the existing options extraction to set those also, so we're running everything possible to help look for bugs. All but one of the options defaulted to "false", so since the fuzz options parsing was ensuring the extra bits were zero, it is safe to just add the option. The one that wasn't zero, we add a new "inverted" bool so all saved cases remain valid. This also gave us the change to control "partial" on binary decoding, so wire that up also. Lastly, while at it, increase the options logging so it is a little more complete.
This commit is contained in:
committed by
GitHub
parent
19ff62e5ff
commit
3c8b0109b1
@@ -11,7 +11,7 @@ import SwiftProtobuf
|
||||
|
||||
@_cdecl("LLVMFuzzerTestOneInput")
|
||||
public func FuzzBinary(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
guard let (options, bytes) = BinaryDecodingOptions.extractOptions(start, count) else {
|
||||
guard let (options, bytes) = BinaryFuzzingOptions.extractOptions(start, count) else {
|
||||
return 1
|
||||
}
|
||||
var msg: SwiftProtoTesting_Fuzz_Message?
|
||||
@@ -19,14 +19,15 @@ public func FuzzBinary(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
msg = try SwiftProtoTesting_Fuzz_Message(
|
||||
serializedBytes: Array(bytes),
|
||||
extensions: SwiftProtoTesting_Fuzz_FuzzTesting_Extensions,
|
||||
options: options
|
||||
partial: options.partialDecoding,
|
||||
options: options.decoding
|
||||
)
|
||||
} catch {
|
||||
// Error parsing are to be expected since not all input will be well formed.
|
||||
}
|
||||
// Test serialization for completeness.
|
||||
// If a message was parsed, it should not fail to serialize, so assert as such.
|
||||
let _: [UInt8]? = try! msg?.serializedBytes()
|
||||
let _: [UInt8]? = try! msg?.serializedBytes(options: options.encoding)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ import SwiftProtobuf
|
||||
public enum FuzzOption<T: SupportsFuzzOptions> {
|
||||
case boolean(WritableKeyPath<T, Bool>)
|
||||
case byte(WritableKeyPath<T, Int>, mod: UInt8 = .max)
|
||||
// This next one was only needed to add TextFormatEncodingOptions.printUnknownFields because it
|
||||
// defaults to `true` and the use of the option was added after we had saved existing regression
|
||||
// inputs. Since this options support ensures the unused values were zero, by adding it later we
|
||||
// end up needing to invert the value to ensure we will read saved values and get the same
|
||||
// options back.
|
||||
case invertedBoolean(WritableKeyPath<T, Bool>)
|
||||
}
|
||||
|
||||
public protocol SupportsFuzzOptions {
|
||||
@@ -60,6 +66,9 @@ extension SupportsFuzzOptions {
|
||||
break
|
||||
}
|
||||
guard count >= 1 else {
|
||||
if reportInfo {
|
||||
print("Continuation options, but no more data, count as invalid.")
|
||||
}
|
||||
return nil // No data left to read bits
|
||||
}
|
||||
optionsBits = start.loadUnaligned(as: UInt8.self)
|
||||
@@ -76,6 +85,9 @@ extension SupportsFuzzOptions {
|
||||
assert(mod >= 1 && mod <= UInt8.max)
|
||||
if isSet {
|
||||
guard count >= 1 else {
|
||||
if reportInfo {
|
||||
print(".byte, but no more data to read, count as invalid.")
|
||||
}
|
||||
return nil // No more bytes to get a value, fail
|
||||
}
|
||||
let value = start.loadUnaligned(as: UInt8.self)
|
||||
@@ -83,12 +95,17 @@ extension SupportsFuzzOptions {
|
||||
count -= 1
|
||||
options[keyPath: keypath] = Int(value % mod)
|
||||
}
|
||||
case .invertedBoolean(let keypath):
|
||||
options[keyPath: keypath] = !isSet
|
||||
}
|
||||
bit += 1
|
||||
}
|
||||
// Ensure the any remaining bits are zero so they can be used in the future
|
||||
while bit < 8 {
|
||||
if optionsBits & (1 << bit) != 0 {
|
||||
if reportInfo {
|
||||
print("Reserved options bit non zero, count as invalid.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
bit += 1
|
||||
@@ -102,7 +119,15 @@ extension SupportsFuzzOptions {
|
||||
|
||||
}
|
||||
|
||||
extension BinaryDecodingOptions: SupportsFuzzOptions {
|
||||
public struct BinaryFuzzingOptions: SupportsFuzzOptions {
|
||||
public var decoding = BinaryDecodingOptions()
|
||||
public var encoding = BinaryEncodingOptions()
|
||||
|
||||
// Historically this never was in BinaryDecodingOptions, so control it via a standalone boolean.
|
||||
public var partialDecoding: Bool = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public static var fuzzOptionsList: [FuzzOption<Self>] {
|
||||
[
|
||||
// NOTE: Do not reorder these in the future as it invalidates all
|
||||
@@ -110,13 +135,20 @@ extension BinaryDecodingOptions: SupportsFuzzOptions {
|
||||
|
||||
// The default depth is 100, so limit outselves to modding by 8 to
|
||||
// avoid allowing larger depths that could timeout.
|
||||
.byte(\.messageDepthLimit, mod: 8),
|
||||
.boolean(\.discardUnknownFields),
|
||||
.byte(\.decoding.messageDepthLimit, mod: 8),
|
||||
.boolean(\.decoding.discardUnknownFields),
|
||||
.boolean(\.partialDecoding),
|
||||
.boolean(\.encoding.useDeterministicOrdering),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONDecodingOptions: SupportsFuzzOptions {
|
||||
public struct JSONFuzzingOptions: SupportsFuzzOptions {
|
||||
public var decoding = JSONDecodingOptions()
|
||||
public var encoding = JSONEncodingOptions()
|
||||
|
||||
public init() {}
|
||||
|
||||
public static var fuzzOptionsList: [FuzzOption<Self>] {
|
||||
[
|
||||
// NOTE: Do not reorder these in the future as it invalidates all
|
||||
@@ -124,13 +156,22 @@ extension JSONDecodingOptions: SupportsFuzzOptions {
|
||||
|
||||
// The default depth is 100, so limit outselves to modding by 8 to
|
||||
// avoid allowing larger depths that could timeout.
|
||||
.byte(\.messageDepthLimit, mod: 8),
|
||||
.boolean(\.ignoreUnknownFields),
|
||||
.byte(\.decoding.messageDepthLimit, mod: 8),
|
||||
.boolean(\.decoding.ignoreUnknownFields),
|
||||
.boolean(\.encoding.alwaysPrintInt64sAsNumbers),
|
||||
.boolean(\.encoding.alwaysPrintEnumsAsInts),
|
||||
.boolean(\.encoding.preserveProtoFieldNames),
|
||||
.boolean(\.encoding.useDeterministicOrdering),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
extension TextFormatDecodingOptions: SupportsFuzzOptions {
|
||||
public struct TextFormatFuzzingOptions: SupportsFuzzOptions {
|
||||
public var decoding = TextFormatDecodingOptions()
|
||||
public var encoding = TextFormatEncodingOptions()
|
||||
|
||||
public init() {}
|
||||
|
||||
public static var fuzzOptionsList: [FuzzOption<Self>] {
|
||||
[
|
||||
// NOTE: Do not reorder these in the future as it invalidates all
|
||||
@@ -138,9 +179,10 @@ extension TextFormatDecodingOptions: SupportsFuzzOptions {
|
||||
|
||||
// The default depth is 100, so limit outselves to modding by 8 to
|
||||
// avoid allowing larger depths that could timeout.
|
||||
.byte(\.messageDepthLimit, mod: 8),
|
||||
.boolean(\.ignoreUnknownFields),
|
||||
.boolean(\.ignoreUnknownExtensionFields),
|
||||
.byte(\.decoding.messageDepthLimit, mod: 8),
|
||||
.boolean(\.decoding.ignoreUnknownFields),
|
||||
.boolean(\.decoding.ignoreUnknownExtensionFields),
|
||||
.invertedBoolean(\.encoding.printUnknownFields), // see note above for why inverted
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import SwiftProtobuf
|
||||
|
||||
@_cdecl("LLVMFuzzerTestOneInput")
|
||||
public func FuzzJSON(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
guard let (options, bytes) = JSONDecodingOptions.extractOptions(start, count) else {
|
||||
guard let (options, bytes) = JSONFuzzingOptions.extractOptions(start, count) else {
|
||||
return 1
|
||||
}
|
||||
var msg: SwiftProtoTesting_Fuzz_Message?
|
||||
@@ -20,13 +20,13 @@ public func FuzzJSON(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
msg = try SwiftProtoTesting_Fuzz_Message(
|
||||
jsonUTF8Data: Data(bytes),
|
||||
extensions: SwiftProtoTesting_Fuzz_FuzzTesting_Extensions,
|
||||
options: options
|
||||
options: options.decoding
|
||||
)
|
||||
} catch {
|
||||
// Error parsing are to be expected since not all input will be well formed.
|
||||
}
|
||||
// Test serialization for completeness.
|
||||
// If a message was parsed, it should not fail to serialize, so assert as such.
|
||||
let _ = try! msg?.jsonString()
|
||||
let _ = try! msg?.jsonString(options: options.encoding)
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import SwiftProtobuf
|
||||
|
||||
@_cdecl("LLVMFuzzerTestOneInput")
|
||||
public func FuzzTextFormat(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
guard let (options, bytes) = TextFormatDecodingOptions.extractOptions(start, count) else {
|
||||
guard let (options, bytes) = TextFormatFuzzingOptions.extractOptions(start, count) else {
|
||||
return 1
|
||||
}
|
||||
guard let str = String(data: Data(bytes), encoding: .utf8) else { return 0 }
|
||||
@@ -20,14 +20,14 @@ public func FuzzTextFormat(_ start: UnsafeRawPointer, _ count: Int) -> CInt {
|
||||
do {
|
||||
msg = try SwiftProtoTesting_Fuzz_Message(
|
||||
textFormatString: str,
|
||||
options: options,
|
||||
options: options.decoding,
|
||||
extensions: SwiftProtoTesting_Fuzz_FuzzTesting_Extensions
|
||||
)
|
||||
} catch {
|
||||
// Error parsing are to be expected since not all input will be well formed.
|
||||
}
|
||||
// Test serialization for completeness.
|
||||
let _ = msg?.textFormatString()
|
||||
let _ = msg?.textFormatString(options: options.encoding)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user