Skip to content

Commit b1fcbad

Browse files
committed
FileManager: avoid a TOCTOU issue in computing CWD
On Windows, we could potentially return a `nil` for the current working directory in the rare case that the current working directory was changed during the computation: ```swift let dwLength: DWORD = GetCurrentDirectoryW(0, nil) // 1 return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) { if GetCurrentDirectoryW(dwLength, $0.baseAddress) == dwLength - 1 { // 2 return String(decodingCString: $0.baseAddress!, as: UTF16.self) } return nil // 3 } ``` Consider the case where at step 1, we receive $n$. We then are interrupted, the CWD changed. We then perform step 2, where we receive $m$ (st $m != n$). We would then proceed to point 3, where we return `nil`. Avoid this TOCTOU issue by repeating this operation to a fixed point. Because we are guaranteed a current directory on Windows (unless the initial query for the buffer size fails), we will eventually succeed. In order to avoid a DoS attack vector, limit the attempt to quiescence to a fixed number.
1 parent 919053f commit b1fcbad

File tree

1 file changed

+13
-4
lines changed

1 file changed

+13
-4
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Directories.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -466,13 +466,22 @@ extension _FileManagerImpl {
466466

467467
var currentDirectoryPath: String? {
468468
#if os(Windows)
469-
let dwLength: DWORD = GetCurrentDirectoryW(0, nil)
470-
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) {
471-
if GetCurrentDirectoryW(dwLength, $0.baseAddress) == dwLength - 1 {
469+
var dwLength: DWORD = GetCurrentDirectoryW(0, nil)
470+
guard dwLength > 0 else { return nil }
471+
472+
for _ in 0 ... 8 {
473+
if let szCurrentDirectory = withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength), {
474+
let dwResult: DWORD = GetCurrentDirectoryW(dwLength, $0.baseAddress)
475+
guard dwResult == dwLength - 1 else {
476+
dwLength = dwResult
477+
return nil
478+
}
472479
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
480+
}) {
481+
return szCurrentDirectory
473482
}
474-
return nil
475483
}
484+
return nil
476485
#else
477486
withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
478487
guard getcwd(buffer.baseAddress!, FileManager.MAX_PATH_SIZE) != nil else {

0 commit comments

Comments
 (0)