Skip to content

Commit a9dc42c

Browse files
authored
FileManager: avoid a TOCTOU issue in computing CWD (#1035)
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 79bd7e5 commit a9dc42c

File tree

1 file changed

+14
-5
lines changed

1 file changed

+14
-5
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Directories.swift

+14-5
Original file line numberDiff line numberDiff line change
@@ -513,13 +513,22 @@ extension _FileManagerImpl {
513513

514514
var currentDirectoryPath: String? {
515515
#if os(Windows)
516-
let dwLength: DWORD = GetCurrentDirectoryW(0, nil)
517-
return withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength)) {
518-
if GetCurrentDirectoryW(dwLength, $0.baseAddress) == dwLength - 1 {
519-
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
516+
var dwLength: DWORD = GetCurrentDirectoryW(0, nil)
517+
guard dwLength > 0 else { return nil }
518+
519+
for _ in 0 ... 8 {
520+
if let szCurrentDirectory = withUnsafeTemporaryAllocation(of: WCHAR.self, capacity: Int(dwLength), {
521+
let dwResult: DWORD = GetCurrentDirectoryW(dwLength, $0.baseAddress)
522+
if dwResult == dwLength - 1 {
523+
return String(decodingCString: $0.baseAddress!, as: UTF16.self)
524+
}
525+
dwLength = dwResult
526+
return nil
527+
}) {
528+
return szCurrentDirectory
520529
}
521-
return nil
522530
}
531+
return nil
523532
#else
524533
withUnsafeTemporaryAllocation(of: CChar.self, capacity: FileManager.MAX_PATH_SIZE) { buffer in
525534
guard getcwd(buffer.baseAddress!, FileManager.MAX_PATH_SIZE) != nil else {

0 commit comments

Comments
 (0)