Skip to content

Commit 17a7fd5

Browse files
committed
Switch to a safer technique for obtaining the working directory on Windows
Instead of looping 8 times to work around the TOCTOU issue with sizing the current directory buffer, instead keep doubling the buffer up until the 32767 character limit until the result fits. This ensures we always get a working directory if GetWorkingDirectoryW didn't return some other error, rather than returning nil in the case of a race condition.
1 parent 7ae9160 commit 17a7fd5

File tree

2 files changed

+28
-15
lines changed

2 files changed

+28
-15
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Directories.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -489,22 +489,10 @@ extension _FileManagerImpl {
489489

490490
var currentDirectoryPath: String? {
491491
#if os(Windows)
492-
var dwLength: DWORD = GetCurrentDirectoryW(0, nil)
493-
guard dwLength > 0 else { return nil }
494-
495-
for _ in 0 ... 8 {
496-
if let szCurrentDirectory = withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength), {
497-
let dwResult: DWORD = GetCurrentDirectoryW(dwLength, $0.baseAddress)
498-
if dwResult == dwLength - 1 {
499-
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
500-
}
501-
dwLength = dwResult
502-
return nil
503-
}) {
504-
return szCurrentDirectory
505-
}
492+
let initialSize = GetCurrentDirectoryW(0, nil)
493+
return try? Win32FillNullTerminatedStringBuffer(initialSize: initialSize != 0 ? initialSize : DWORD(MAX_PATH), maxSize: DWORD(Int16.max)) {
494+
GetCurrentDirectoryW(DWORD($0.count), $0.baseAddress)
506495
}
507-
return nil
508496
#else
509497
withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
510498
guard getcwd(buffer.baseAddress!, FileManager.MAX_PATH_SIZE) != nil else {

Sources/FoundationEssentials/WinSDK+Extensions.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,29 @@ internal func WIN32_FROM_HRESULT(_ hr: HRESULT) -> DWORD {
288288
return DWORD(hr)
289289
}
290290

291+
internal func Win32FillNullTerminatedStringBuffer(initialSize: DWORD, maxSize: DWORD, _ attempt: (UnsafeMutableBufferPointer<WCHAR>) throws -> DWORD) throws -> String {
292+
var bufferCount = max(1, min(initialSize, maxSize))
293+
while bufferCount < maxSize {
294+
if let result = try withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(bufferCount), { buffer in
295+
let count = try attempt(buffer)
296+
switch count {
297+
case 0:
298+
throw Win32Error(GetLastError())
299+
case 1..<DWORD(buffer.count):
300+
guard let result = String.decodeCString(buffer.baseAddress!, as: UTF16.self)?.result else {
301+
throw Win32Error(DWORD(ERROR_ILLEGAL_CHARACTER))
302+
}
303+
assert(result.utf16.count == count, "Parsed UTF-16 count \(result.utf16.count) != reported UTF-16 count \(count)")
304+
return result
305+
default:
306+
bufferCount *= 2
307+
return nil
308+
}
309+
}) {
310+
return result
311+
}
312+
}
313+
throw Win32Error(DWORD(ERROR_INSUFFICIENT_BUFFER))
314+
}
315+
291316
#endif

0 commit comments

Comments
 (0)