Skip to content

Commit 6f62c0e

Browse files
🛡️ Sentinel: [CRITICAL] Fix unsafe path handling (#417)
Fixed insecure path handling in `StatusSocket.swift` where POSIX file APIs (`open()`) were called with raw path variables, leading to unsafe filesystem representations. Replaced raw POSIX calls with safe C-string pointer wrappers (`URL(fileURLWithPath:).withUnsafeFileSystemRepresentation`) to securely bridge file paths. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com>
1 parent ddc12dd commit 6f62c0e

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

.jules/sentinel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,8 @@ grep -nE "O_NOFOLLOW|O_CLOEXEC|fchmod|withUnsafeFileSystemRepresentation" <candi
138138
**Vulnerability:** Insecure file creation due to missing O_NOFOLLOW flag.
139139
**Learning:** Using `open()` with `O_CREAT` but without `O_NOFOLLOW` and `O_EXCL` allows an attacker to conduct a TOCTOU symlink attack to truncate or overwrite unintended target files.
140140
**Prevention:** Always combine `O_CREAT` with `O_NOFOLLOW` when creating files, and prefer explicit octal permissions like `0o600` over bitmasks.
141+
142+
## 2024-06-20 - Unsafe Path Bridging in StatusSocket
143+
**Vulnerability:** Calling `open(2)` with raw Swift `String` paths in `StatusSocket.swift` creates an unsafe filesystem representation.
144+
**Learning:** Passing Swift `String` paths implicitly to C functions can result in memory issues or incorrect path resolution if the string is not null-terminated or is moved in memory.
145+
**Prevention:** Always use `URL(fileURLWithPath:).withUnsafeFileSystemRepresentation` to obtain the correct C-string pointer when bridging file paths to POSIX APIs.

Sources/Cacheout/Headless/StatusSocket.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ public final class StatusSocket: @unchecked Sendable {
111111
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: [
112112
.posixPermissions: 0o700
113113
])
114-
let dirFd = open(dir, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)
114+
let dirFd = URL(fileURLWithPath: dir).withUnsafeFileSystemRepresentation { pathPtr -> Int32 in
115+
guard let pathPtr = pathPtr else { return -1 }
116+
return open(pathPtr, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)
117+
}
115118
guard dirFd >= 0 else {
116119
throw StatusSocketError.directoryHardeningFailed(errno)
117120
}
@@ -497,7 +500,10 @@ public final class StatusSocket: @unchecked Sendable {
497500
// component is rejected up front and we hold a stable fd for the rest
498501
// of the checks — no path resolution happens between type/size check
499502
// and read, closing the lstat → open TOCTOU window.
500-
let fileFd = open(expandedPath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
503+
let fileFd = URL(fileURLWithPath: expandedPath).withUnsafeFileSystemRepresentation { pathPtr -> Int32 in
504+
guard let pathPtr = pathPtr else { return -1 }
505+
return open(pathPtr, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
506+
}
501507
guard fileFd >= 0 else {
502508
let err = errno
503509
let reason = err == ELOOP ? "Refusing to follow symlink: \(expandedPath)"

0 commit comments

Comments
 (0)