Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,17 @@ public final class ProtoDecoder {
count: buffer.count
)

while fullBuffer.isDataRemaining, let size = try? fullBuffer.readVarint() {
while fullBuffer.isDataRemaining {
let size = try fullBuffer.readVarint()
if size == 0 { break }
guard let size = Int(exactly: size) else {
throw ProtoDecoder.Error.unexpectedEndOfData
}
try fullBuffer.verifyAdditional(count: size)

let messageBuffer = ReadBuffer(
storage: fullBuffer.pointer,
count: Int(size)
count: size
)

let reader = ProtoReader(
Expand All @@ -320,7 +325,7 @@ public final class ProtoDecoder {
values.append(try reader.decode(type))

// Advance the buffer before reading the next item in the stream.
_ = try fullBuffer.readBuffer(count: Int(size))
_ = try fullBuffer.readBuffer(count: size)
}
}

Expand Down
11 changes: 8 additions & 3 deletions wire-runtime-swift/src/main/swift/ProtoCodable/ProtoReader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ public final class ProtoReader {

let frame = MessageFrame(
isProto3: isProto3Message,
messageEnd: buffer.pointer.advanced(by: length)
messageEnd: try buffer.endPointer(count: length)
)
messageStack.advanced(by: messageStackIndex).initialize(to: frame)

Expand Down Expand Up @@ -875,6 +875,8 @@ public final class ProtoReader {
return
}

let messageEnd = try buffer.endPointer(count: length)

// Preallocate space for the unpacked data.
// It's allowable to have a packed field spread across multiple places
// in the buffer, so add to the existing capacity.
Expand All @@ -885,13 +887,16 @@ public final class ProtoReader {
array.reserveCapacity(array.count + (length / MemoryLayout<T>.size))

// This is a packed field, so keep decoding until we're out of bytes.
let messageEnd = buffer.pointer.advanced(by: length)
while buffer.pointer < messageEnd {
// Reading a scalar will set the state to `.tag` because we assume
// we're reading a single value most of the time and are then done.
// Since we're in a repeated field we'll keep reading values though.
state = .packedValue
if let decodedVal = try decode() {
let decodedVal = try decode()
guard buffer.pointer <= messageEnd else {
throw ProtoDecoder.Error.unexpectedEndOfData
}
if let decodedVal = decodedVal {
array.append(decodedVal)
}
}
Expand Down
28 changes: 17 additions & 11 deletions wire-runtime-swift/src/main/swift/ProtoCodable/ReadBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,22 @@ final class ReadBuffer {
}

init(storage: UnsafePointer<UInt8>, count: Int) {
precondition(count >= 0)
self.start = storage
self.end = storage.advanced(by: count)
self.pointer = storage
}

func verifyAdditional(count: Int) throws {
guard count >= 0, pointer.advanced(by: count) <= end else {
_ = try endPointer(count: count)
}

func endPointer(count: Int) throws -> UnsafePointer<UInt8> {
let remainingCount = end - pointer
guard count >= 0, remainingCount >= 0, count <= remainingCount else {
throw ProtoDecoder.Error.unexpectedEndOfData
}
return pointer.advanced(by: count)
}

}
Expand All @@ -79,39 +86,39 @@ extension ReadBuffer {
// MARK: - Reading

func readBuffer(count: Int) throws -> UnsafeRawBufferPointer {
try verifyAdditional(count: count)
let newPointer = try endPointer(count: count)
let buffer = UnsafeRawBufferPointer(start: pointer, count: count)
pointer = pointer.advanced(by: count)
pointer = newPointer

return buffer
}

func readData(count: Int) throws -> Data {
try verifyAdditional(count: count)
let newPointer = try endPointer(count: count)
let data = Data(bytes: pointer, count: count)
pointer = pointer.advanced(by: count)
pointer = newPointer

return data
}

func readFixed32() throws -> UInt32 {
try verifyAdditional(count: 4)
let newPointer = try endPointer(count: 4)
var value: UInt32 = 0
withUnsafeMutableBytes(of: &value) { dest -> Void in
dest.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: 4))
}
pointer = pointer.advanced(by: 4)
pointer = newPointer

return UInt32(littleEndian: value)
}

func readFixed64() throws -> UInt64 {
try verifyAdditional(count: 8)
let newPointer = try endPointer(count: 8)
var value: UInt64 = 0
withUnsafeMutableBytes(of: &value) { dest -> Void in
dest.copyMemory(from: UnsafeRawBufferPointer(start: pointer, count: 8))
}
pointer = pointer.advanced(by: 8)
pointer = newPointer

return UInt64(littleEndian: value)
}
Expand All @@ -122,6 +129,7 @@ extension ReadBuffer {
var result: UInt64 = 0

while shift < 64 {
try verifyAdditional(count: 1)
let byte = pointer.pointee
pointer = pointer.advanced(by: 1)

Expand All @@ -130,8 +138,6 @@ extension ReadBuffer {
return result
}
shift += 7

try verifyAdditional(count: 1)
}
throw ProtoDecoder.Error.malformedVarint
}
Expand Down
32 changes: 32 additions & 0 deletions wire-runtime-swift/src/test/swift/ProtoDecoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,38 @@ final class ProtoDecoderTests: XCTestCase {
XCTAssertEqual(object, [])
}

func testDecodeSizeDelimitedRejectsUnrepresentableSize() throws {
let decoder = ProtoDecoder()
let data = Foundation.Data(hexEncoded: """
FFFFFFFFFFFFFFFFFF01 // UInt64.max
""")!

XCTAssertThrowsError(
try decoder.decodeSizeDelimited(SimpleOptional2.self, from: data)
) { error in
guard case ProtoDecoder.Error.unexpectedEndOfData = error else {
XCTFail("Unexpected error: \(error)")
return
}
}
}

func testDecodeSizeDelimitedRejectsTruncatedSize() throws {
let decoder = ProtoDecoder()
let data = Foundation.Data(hexEncoded: """
80 // Truncated size varint
""")!

XCTAssertThrowsError(
try decoder.decodeSizeDelimited(SimpleOptional2.self, from: data)
) { error in
guard case ProtoDecoder.Error.unexpectedEndOfData = error else {
XCTFail("Unexpected error: \(error)")
return
}
}
}

func testDecodeEmptyDataTwice() throws {
let decoder = ProtoDecoder()
// The empty message case is optimized to reuse objects, so make sure
Expand Down
82 changes: 82 additions & 0 deletions wire-runtime-swift/src/test/swift/ProtoReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,23 @@ final class ProtoReaderTests: XCTestCase {
}
}

func testDecodePackedRepeatedFixedUInt32RejectsPartialValue() throws {
let data = Foundation.Data(hexEncoded: """
0A // (Tag 1 | Length Delimited)
02 // Length 2
01000000 // Enough bytes in the message, but not in the packed value
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
var values: [UInt32] = []
try reader.decode(tag: 1) { try reader.decode(into: &values, encoding: .fixed) }
}
) { error in
assertUnexpectedEndError(error)
}
}

func testDecodePackedRepeatedFixedUInt32Empty() throws {
let data = Foundation.Data(hexEncoded: """
0A // (Tag 1 | Length Delimited)
Expand Down Expand Up @@ -1147,6 +1164,64 @@ final class ProtoReaderTests: XCTestCase {
}
}

func testReadVarintRejectsMissingValue() throws {
let data = Foundation.Data(hexEncoded: """
08 // (Tag 1 | Varint)
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { tag in
XCTAssertEqual(tag, 1)
_ = try reader.readVarint()
}
}
) { error in
assertUnexpectedEndError(error)
}
}

func testNestedMessageRejectsOversizedLength() throws {
let data = Foundation.Data(hexEncoded: """
12 // (Tag 2 | Length Delimited)
FFFFFFFF07 // Length Int32.max
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
_ = try reader.forEachTag { tag in
switch tag {
case 2: _ = try reader.decode(NestedMessage.self)
default: XCTFail("Unexpected field")
}
}
}
) { error in
assertUnexpectedEndError(error)
}
}

func testPackedRepeatedRejectsOversizedLengthBeforePreallocation() throws {
let data = Foundation.Data(hexEncoded: """
0A // (Tag 1 | Length Delimited)
FFFFFFFF07 // Length Int32.max
""")!

XCTAssertThrowsError(
try test(data: data) { reader in
var values: [Bool] = []
_ = try reader.forEachTag { tag in
switch tag {
case 1: try reader.decode(into: &values)
default: XCTFail("Unexpected field")
}
}
}
) { error in
assertUnexpectedEndError(error)
}
}

// MARK: - Tests - Unknown Fields

func testUnknownFields() throws {
Expand Down Expand Up @@ -1374,6 +1449,13 @@ final class ProtoReaderTests: XCTestCase {
}
XCTAssertEqual(message, "Negative length: -128. Reader position: \(readerPosition). Last read tag: 1.")
}

private func assertUnexpectedEndError(_ error: Error) {
guard case ProtoDecoder.Error.unexpectedEndOfData = error else {
XCTFail("Unexpected error: \(error)")
return
}
}
}

// MARK: -
Expand Down
Loading
Loading