Skip to content

Commit f337a2c

Browse files
authored
Prevent potential buffer over-reads of file system representations (#1124)
* (133687793) Prevent buffer over-reads with string file system representations that fail Signed-off-by: Jeremy Schonfeld <[email protected]> * Account for empty buffers Signed-off-by: Jeremy Schonfeld <[email protected]> * Move empty buffer check earlier Signed-off-by: Jeremy Schonfeld <[email protected]> --------- Signed-off-by: Jeremy Schonfeld <[email protected]>
1 parent e9d59b6 commit f337a2c

File tree

1 file changed

+28
-0
lines changed

1 file changed

+28
-0
lines changed

Sources/FoundationEssentials/String/String+Internals.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,23 @@ extension UnsafeBufferPointer {
220220
var decoder = T()
221221
var iterator = self.makeIterator()
222222

223+
guard !buffer.isEmpty else {
224+
if !nullTerminated && iterator.next() == nil {
225+
// No bytes to write, so an empty buffer is OK
226+
return 0
227+
} else {
228+
throw DecompositionError.insufficientSpace
229+
}
230+
}
231+
232+
defer {
233+
if nullTerminated {
234+
// Ensure buffer is always null-terminated even on failure to prevent buffer over-reads
235+
// At this point, the buffer is known to be non-empty, so it must have space for at least a null terminating byte (even if it overwrites the final output byte in the buffer)
236+
buffer[buffer.count - 1] = 0
237+
}
238+
}
239+
223240
func appendOutput(_ values: some Collection<UInt8>) throws {
224241
let bufferPortion = UnsafeMutableBufferPointer(start: buffer.baseAddress!.advanced(by: bufferIdx), count: bufferLength - bufferIdx)
225242
guard bufferPortion.count >= values.count else {
@@ -316,6 +333,12 @@ extension NSString {
316333
func __swiftFillFileSystemRepresentation(pointer: UnsafeMutablePointer<CChar>, maxLength: Int) -> Bool {
317334
autoreleasepool {
318335
let buffer = UnsafeMutableBufferPointer(start: pointer, count: maxLength)
336+
337+
guard !buffer.isEmpty else {
338+
// No space for a null terminating byte, so it's not worth even trying to read the string contents
339+
return false
340+
}
341+
319342
// See if we have a quick-access buffer we can just convert directly
320343
if let fastCharacters = self._fastCharacterContents() {
321344
// If we have quick access to UTF-16 contents, decompose from UTF-16
@@ -324,6 +347,11 @@ extension NSString {
324347
} else if self.fastestEncoding == NSASCIIStringEncoding, let fastUTF8 = self._fastCStringContents(false) {
325348
// If we have quick access to ASCII contents, no need to decompose
326349
let utf8Buffer = UnsafeBufferPointer(start: fastUTF8, count: self.length)
350+
defer {
351+
// Ensure buffer is always null-terminated even on failure to prevent buffer over-reads
352+
// At this point, the buffer is known to be non-empty, so it must have space for at least a null terminating byte (even if it overwrites the final output byte in the buffer)
353+
buffer[buffer.count - 1] = 0
354+
}
327355

328356
// We only allow embedded nulls if there are no non-null characters following the first null character
329357
if let embeddedNullIdx = utf8Buffer.firstIndex(of: 0) {

0 commit comments

Comments
 (0)