Skip to content

Commit 3e8d153

Browse files
authored
Merge pull request #42304 from glessard/rdar90336023-5.7
[5.7][stdlib] Prevent preventable buffer overflows when constructing Strings from C strings.
2 parents aa54188 + ece2965 commit 3e8d153

File tree

6 files changed

+525
-23
lines changed

6 files changed

+525
-23
lines changed

stdlib/private/StdlibUnittest/StdlibUnittest.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,9 +2182,10 @@ func _getSystemVersionPlistProperty(_ propertyName: String) -> String? {
21822182
func _getSystemVersionPlistProperty(_ propertyName: String) -> String? {
21832183
var count = 0
21842184
sysctlbyname("kern.osproductversion", nil, &count, nil, 0)
2185-
var s = [CChar](repeating: 0, count: count)
2186-
sysctlbyname("kern.osproductversion", &s, &count, nil, 0)
2187-
return String(cString: &s)
2185+
return withUnsafeTemporaryAllocation(of: CChar.self, capacity: count) {
2186+
sysctlbyname("kern.osproductversion", $0.baseAddress, &count, nil, 0)
2187+
return String(cString: $0.baseAddress!)
2188+
}
21882189
}
21892190
#endif
21902191
#endif

stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -373,25 +373,25 @@ internal func reflect(instanceAddress: UInt,
373373
shouldUnwrapClassExistential: Bool = false) {
374374
while let command = readLine(strippingNewline: true) {
375375
switch command {
376-
case String(validatingUTF8: RequestInstanceKind)!:
376+
case RequestInstanceKind:
377377
sendValue(kind.rawValue)
378-
case String(validatingUTF8: RequestShouldUnwrapClassExistential)!:
378+
case RequestShouldUnwrapClassExistential:
379379
sendValue(shouldUnwrapClassExistential)
380-
case String(validatingUTF8: RequestInstanceAddress)!:
380+
case RequestInstanceAddress:
381381
sendValue(instanceAddress)
382-
case String(validatingUTF8: RequestReflectionInfos)!:
382+
case RequestReflectionInfos:
383383
sendReflectionInfos()
384-
case String(validatingUTF8: RequestImages)!:
384+
case RequestImages:
385385
sendImages()
386-
case String(validatingUTF8: RequestReadBytes)!:
386+
case RequestReadBytes:
387387
sendBytes()
388-
case String(validatingUTF8: RequestSymbolAddress)!:
388+
case RequestSymbolAddress:
389389
sendSymbolAddress()
390-
case String(validatingUTF8: RequestStringLength)!:
390+
case RequestStringLength:
391391
sendStringLength()
392-
case String(validatingUTF8: RequestPointerSize)!:
392+
case RequestPointerSize:
393393
sendPointerSize()
394-
case String(validatingUTF8: RequestDone)!:
394+
case RequestDone:
395395
return
396396
default:
397397
fatalError("Unknown request received: '\(Array(command.utf8))'!")

stdlib/public/core/CString.swift

Lines changed: 218 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,84 @@ extension String {
4242
/// }
4343
/// // Prints "Caf�"
4444
///
45-
/// - Parameter cString: A pointer to a null-terminated UTF-8 code sequence.
46-
public init(cString: UnsafePointer<CChar>) {
47-
let len = UTF8._nullCodeUnitOffset(in: cString)
45+
/// - Parameter nullTerminatedUTF8: A pointer to a null-terminated UTF-8 code sequence.
46+
public init(cString nullTerminatedUTF8: UnsafePointer<CChar>) {
47+
let len = UTF8._nullCodeUnitOffset(in: nullTerminatedUTF8)
4848
self = String._fromUTF8Repairing(
49-
UnsafeBufferPointer(start: cString._asUInt8, count: len)).0
49+
UnsafeBufferPointer(start: nullTerminatedUTF8._asUInt8, count: len)).0
50+
}
51+
52+
@inlinable
53+
@_alwaysEmitIntoClient
54+
public init(cString nullTerminatedUTF8: [CChar]) {
55+
self = nullTerminatedUTF8.withUnsafeBytes {
56+
String(_checkingCString: $0.assumingMemoryBound(to: UInt8.self))
57+
}
58+
}
59+
60+
@_alwaysEmitIntoClient
61+
private init(_checkingCString bytes: UnsafeBufferPointer<UInt8>) {
62+
guard let length = bytes.firstIndex(of: 0) else {
63+
_preconditionFailure(
64+
"input of String.init(cString:) must be null-terminated"
65+
)
66+
}
67+
self = String._fromUTF8Repairing(
68+
UnsafeBufferPointer(
69+
start: bytes.baseAddress._unsafelyUnwrappedUnchecked,
70+
count: length
71+
)
72+
).0
73+
}
74+
75+
@inlinable
76+
@_alwaysEmitIntoClient
77+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
78+
public init(cString nullTerminatedUTF8: inout CChar) {
79+
guard nullTerminatedUTF8 == 0 else {
80+
_preconditionFailure(
81+
"input of String.init(cString:) must be null-terminated"
82+
)
83+
}
84+
self = ""
5085
}
5186

5287
/// Creates a new string by copying the null-terminated UTF-8 data referenced
5388
/// by the given pointer.
5489
///
5590
/// This is identical to `init(cString: UnsafePointer<CChar>)` but operates on
5691
/// an unsigned sequence of bytes.
57-
public init(cString: UnsafePointer<UInt8>) {
58-
let len = UTF8._nullCodeUnitOffset(in: cString)
92+
public init(cString nullTerminatedUTF8: UnsafePointer<UInt8>) {
93+
let len = UTF8._nullCodeUnitOffset(in: nullTerminatedUTF8)
5994
self = String._fromUTF8Repairing(
60-
UnsafeBufferPointer(start: cString, count: len)).0
95+
UnsafeBufferPointer(start: nullTerminatedUTF8, count: len)).0
96+
}
97+
98+
@inlinable
99+
@_alwaysEmitIntoClient
100+
public init(cString nullTerminatedUTF8: [UInt8]) {
101+
self = nullTerminatedUTF8.withUnsafeBufferPointer {
102+
String(_checkingCString: $0)
103+
}
104+
}
105+
106+
@inlinable
107+
@_alwaysEmitIntoClient
108+
@available(*, deprecated, message: "Use a copy of the String argument")
109+
public init(cString nullTerminatedUTF8: String) {
110+
self = nullTerminatedUTF8.withCString(String.init(cString:))
111+
}
112+
113+
@inlinable
114+
@_alwaysEmitIntoClient
115+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
116+
public init(cString nullTerminatedUTF8: inout UInt8) {
117+
guard nullTerminatedUTF8 == 0 else {
118+
_preconditionFailure(
119+
"input of String.init(cString:) must be null-terminated"
120+
)
121+
}
122+
self = ""
61123
}
62124

63125
/// Creates a new string by copying and validating the null-terminated UTF-8
@@ -95,6 +157,40 @@ extension String {
95157
self = str
96158
}
97159

160+
@inlinable
161+
@_alwaysEmitIntoClient
162+
public init?(validatingUTF8 cString: [CChar]) {
163+
guard let length = cString.firstIndex(of: 0) else {
164+
_preconditionFailure(
165+
"input of String.init(validatingUTF8:) must be null-terminated"
166+
)
167+
}
168+
guard let string = cString.prefix(length).withUnsafeBytes({
169+
String._tryFromUTF8($0.assumingMemoryBound(to: UInt8.self))
170+
}) else { return nil }
171+
172+
self = string
173+
}
174+
175+
@inlinable
176+
@_alwaysEmitIntoClient
177+
@available(*, deprecated, message: "Use a copy of the String argument")
178+
public init?(validatingUTF8 cString: String) {
179+
self = cString.withCString(String.init(cString:))
180+
}
181+
182+
@inlinable
183+
@_alwaysEmitIntoClient
184+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
185+
public init?(validatingUTF8 cString: inout CChar) {
186+
guard cString == 0 else {
187+
_preconditionFailure(
188+
"input of String.init(validatingUTF8:) must be null-terminated"
189+
)
190+
}
191+
self = ""
192+
}
193+
98194
/// Creates a new string by copying the null-terminated data referenced by
99195
/// the given pointer using the specified encoding.
100196
///
@@ -166,6 +262,77 @@ extension String {
166262
return String._fromCodeUnits(
167263
codeUnits, encoding: encoding, repair: isRepairing)
168264
}
265+
266+
@_specialize(where Encoding == Unicode.UTF8)
267+
@_specialize(where Encoding == Unicode.UTF16)
268+
@inlinable // Fold away specializations
269+
@_alwaysEmitIntoClient
270+
public static func decodeCString<Encoding: _UnicodeEncoding>(
271+
_ cString: [Encoding.CodeUnit],
272+
as encoding: Encoding.Type,
273+
repairingInvalidCodeUnits isRepairing: Bool = true
274+
) -> (result: String, repairsMade: Bool)? {
275+
guard let length = cString.firstIndex(of: 0) else {
276+
_preconditionFailure(
277+
"input of decodeCString(_:as:repairingInvalidCodeUnits:) must be null-terminated"
278+
)
279+
}
280+
281+
if _fastPath(encoding == Unicode.UTF8.self) {
282+
return cString.prefix(length).withUnsafeBytes {
283+
buf -> (result: String, repairsMade: Bool)? in
284+
let codeUnits = buf.assumingMemoryBound(to: UInt8.self)
285+
if isRepairing {
286+
return String._fromUTF8Repairing(codeUnits)
287+
}
288+
else if let str = String._tryFromUTF8(codeUnits) {
289+
return (str, false)
290+
}
291+
return nil
292+
}
293+
}
294+
295+
return cString.prefix(length).withUnsafeBufferPointer {
296+
buf -> (result: String, repairsMade: Bool)? in
297+
String._fromCodeUnits(buf, encoding: encoding, repair: isRepairing)
298+
}
299+
}
300+
301+
@_specialize(where Encoding == Unicode.UTF8)
302+
@_specialize(where Encoding == Unicode.UTF16)
303+
@inlinable
304+
@_alwaysEmitIntoClient
305+
@available(*, deprecated, message: "Use a copy of the String argument")
306+
public static func decodeCString<Encoding: _UnicodeEncoding>(
307+
_ cString: String,
308+
as encoding: Encoding.Type,
309+
repairingInvalidCodeUnits isRepairing: Bool = true
310+
) -> (result: String, repairsMade: Bool)? {
311+
return cString.withCString(encodedAs: encoding) {
312+
String.decodeCString(
313+
$0, as: encoding, repairingInvalidCodeUnits: isRepairing
314+
)
315+
}
316+
}
317+
318+
@_specialize(where Encoding == Unicode.UTF8)
319+
@_specialize(where Encoding == Unicode.UTF16)
320+
@inlinable
321+
@_alwaysEmitIntoClient
322+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
323+
public static func decodeCString<Encoding: _UnicodeEncoding>(
324+
_ cString: inout Encoding.CodeUnit,
325+
as encoding: Encoding.Type,
326+
repairingInvalidCodeUnits isRepairing: Bool = true
327+
) -> (result: String, repairsMade: Bool)? {
328+
guard cString == 0 else {
329+
_preconditionFailure(
330+
"input of decodeCString(_:as:repairingInvalidCodeUnits:) must be null-terminated"
331+
)
332+
}
333+
return ("", false)
334+
}
335+
169336
/// Creates a string from the null-terminated sequence of bytes at the given
170337
/// pointer.
171338
///
@@ -179,10 +346,52 @@ extension String {
179346
@_specialize(where Encoding == Unicode.UTF16)
180347
@inlinable // Fold away specializations
181348
public init<Encoding: Unicode.Encoding>(
182-
decodingCString ptr: UnsafePointer<Encoding.CodeUnit>,
349+
decodingCString nullTerminatedCodeUnits: UnsafePointer<Encoding.CodeUnit>,
183350
as sourceEncoding: Encoding.Type
184351
) {
185-
self = String.decodeCString(ptr, as: sourceEncoding)!.0
352+
self = String.decodeCString(nullTerminatedCodeUnits, as: sourceEncoding)!.0
353+
}
354+
355+
@_specialize(where Encoding == Unicode.UTF8)
356+
@_specialize(where Encoding == Unicode.UTF16)
357+
@inlinable // Fold away specializations
358+
@_alwaysEmitIntoClient
359+
public init<Encoding: Unicode.Encoding>(
360+
decodingCString nullTerminatedCodeUnits: [Encoding.CodeUnit],
361+
as sourceEncoding: Encoding.Type
362+
) {
363+
self = String.decodeCString(nullTerminatedCodeUnits, as: sourceEncoding)!.0
364+
}
365+
366+
@_specialize(where Encoding == Unicode.UTF8)
367+
@_specialize(where Encoding == Unicode.UTF16)
368+
@inlinable
369+
@_alwaysEmitIntoClient
370+
@available(*, deprecated, message: "Use a copy of the String argument")
371+
public init<Encoding: _UnicodeEncoding>(
372+
decodingCString nullTerminatedCodeUnits: String,
373+
as sourceEncoding: Encoding.Type
374+
) {
375+
self = nullTerminatedCodeUnits.withCString(encodedAs: sourceEncoding) {
376+
String(decodingCString: $0, as: sourceEncoding.self)
377+
}
378+
}
379+
380+
@_specialize(where Encoding == Unicode.UTF8)
381+
@_specialize(where Encoding == Unicode.UTF16)
382+
@inlinable // Fold away specializations
383+
@_alwaysEmitIntoClient
384+
@available(*, deprecated, message: "Use String(_ scalar: Unicode.Scalar)")
385+
public init<Encoding: Unicode.Encoding>(
386+
decodingCString nullTerminatedCodeUnits: inout Encoding.CodeUnit,
387+
as sourceEncoding: Encoding.Type
388+
) {
389+
guard nullTerminatedCodeUnits == 0 else {
390+
_preconditionFailure(
391+
"input of String.init(decodingCString:as:) must be null-terminated"
392+
)
393+
}
394+
self = ""
186395
}
187396
}
188397

test/Prototypes/PatternMatching.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,9 @@ extension Collection where Iterator.Element == UTF8.CodeUnit {
302302
a.reserveCapacity(count + 1)
303303
a.append(contentsOf: self)
304304
a.append(0)
305-
return String(reflecting: String(cString: a))
305+
return a.withUnsafeBufferPointer {
306+
String(reflecting: String(cString: $0.baseAddress!))
307+
}
306308
}
307309
}
308310

0 commit comments

Comments
 (0)