Skip to content

Commit

Permalink
Added the ability to specify a maximum depth for CBOR decoding
Browse files Browse the repository at this point in the history
Closes #92
  • Loading branch information
dimitribouniol authored and hamchapman committed Mar 26, 2024
1 parent edc0176 commit 2b771dd
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 4 deletions.
7 changes: 7 additions & 0 deletions Sources/CBORDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public enum CBORError : Error {
case wrongTypeInsideSequence
case tooLongSequence
case incorrectUTF8String
case maximumDepthExceeded
}

extension CBOR {
Expand All @@ -18,6 +19,7 @@ extension CBOR {
public class CBORDecoder {
private var istream : CBORInputStream
public var options: CBOROptions
private var currentDepth = 0

public init(stream: CBORInputStream, options: CBOROptions = CBOROptions()) {
self.istream = stream
Expand Down Expand Up @@ -108,6 +110,11 @@ public class CBORDecoder {
}

public func decodeItem() throws -> CBOR? {
guard currentDepth <= options.maximumDepth
else { throw CBORError.maximumDepthExceeded }

currentDepth += 1
defer { currentDepth -= 1 }
let b = try istream.popByte()

switch b {
Expand Down
9 changes: 7 additions & 2 deletions Sources/CBOROptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ public struct CBOROptions {
let useStringKeys: Bool
let dateStrategy: DateStrategy
let forbidNonStringMapKeys: Bool
/// The maximum number of nested items, inclusive, to decode. A maximum set to 0 dissallows anything other than top-level primitives.
let maximumDepth: Int

public init(
useStringKeys: Bool = false,
dateStrategy: DateStrategy = .taggedAsEpochTimestamp,
forbidNonStringMapKeys: Bool = false
forbidNonStringMapKeys: Bool = false,
maximumDepth: Int = .max
) {
self.useStringKeys = useStringKeys
self.dateStrategy = dateStrategy
self.forbidNonStringMapKeys = forbidNonStringMapKeys
self.maximumDepth = maximumDepth
}

func toCodableEncoderOptions() -> CodableCBOREncoder._Options {
Expand All @@ -24,7 +28,8 @@ public struct CBOROptions {
func toCodableDecoderOptions() -> CodableCBORDecoder._Options {
return CodableCBORDecoder._Options(
useStringKeys: self.useStringKeys,
dateStrategy: self.dateStrategy
dateStrategy: self.dateStrategy,
maximumDepth: self.maximumDepth
)
}
}
Expand Down
14 changes: 12 additions & 2 deletions Sources/Decoder/CodableCBORDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,24 @@ final public class CodableCBORDecoder {
struct _Options {
let useStringKeys: Bool
let dateStrategy: DateStrategy
let maximumDepth: Int

init(useStringKeys: Bool = false, dateStrategy: DateStrategy = .taggedAsEpochTimestamp) {
init(
useStringKeys: Bool = false,
dateStrategy: DateStrategy = .taggedAsEpochTimestamp,
maximumDepth: Int = .max
) {
self.useStringKeys = useStringKeys
self.dateStrategy = dateStrategy
self.maximumDepth = maximumDepth
}

func toCBOROptions() -> CBOROptions {
return CBOROptions(useStringKeys: self.useStringKeys, dateStrategy: self.dateStrategy)
return CBOROptions(
useStringKeys: self.useStringKeys,
dateStrategy: self.dateStrategy,
maximumDepth: self.maximumDepth
)
}
}

Expand Down
33 changes: 33 additions & 0 deletions Tests/CBORDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,37 @@ class CBORDecoderTests: XCTestCase {

XCTAssertEqual(decoded, expected)
}

func testDecodeFailsForExtremelyDeepStructures() {
let justOverTags: [UInt8] = Array(repeating: 202, count: 1025) + [0]
XCTAssertThrowsError(try CBOR.decode(justOverTags, options: CBOROptions(maximumDepth: 1024))) { error in
XCTAssertEqual(error as? CBORError, CBORError.maximumDepthExceeded)
}
let endlessTags: [UInt8] = Array(repeating: 202, count: 10000) + [0]
XCTAssertThrowsError(try CBOR.decode(endlessTags, options: CBOROptions(maximumDepth: 1024))) { error in
XCTAssertEqual(error as? CBORError, CBORError.maximumDepthExceeded)
}
}

func testDecodeFailsForSillyMaximumDepths() {
let singleItem: [UInt8] = [0]
XCTAssertThrowsError(try CBOR.decode(singleItem, options: CBOROptions(maximumDepth: -1))) { error in
XCTAssertEqual(error as? CBORError, CBORError.maximumDepthExceeded)
}
}

func testDecodeSucceedsForAllowedDeepStructures() {
let singleItem: [UInt8] = [0]
XCTAssertNoThrow(try CBOR.decode(singleItem, options: CBOROptions(maximumDepth: 0)))
let endlessTags: [UInt8] = Array(repeating: 202, count: 1024) + [0]
XCTAssertNoThrow(try CBOR.decode(endlessTags, options: CBOROptions(maximumDepth: 1024)))
}

func testRandomInputDoesNotHitStackLimits() {
for _ in 1...50 {
let length = Int.random(in: 1...1_000_000)
let randomData: [UInt8] = Array(repeating: UInt8.random(in: 0...255), count: length)
_ = try? CBOR.decode(randomData, options: CBOROptions(maximumDepth: 1024))
}
}
}

0 comments on commit 2b771dd

Please sign in to comment.