Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,8 @@ grep -nE "O_NOFOLLOW|O_CLOEXEC|fchmod|withUnsafeFileSystemRepresentation" <candi
**Vulnerability:** Insecure file creation due to missing O_NOFOLLOW flag.
**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.
**Prevention:** Always combine `O_CREAT` with `O_NOFOLLOW` when creating files, and prefer explicit octal permissions like `0o600` over bitmasks.

## 2024-06-20 - Unsafe Path Bridging in StatusSocket
**Vulnerability:** Calling `open(2)` with raw Swift `String` paths in `StatusSocket.swift` creates an unsafe filesystem representation.
**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.
**Prevention:** Always use `URL(fileURLWithPath:).withUnsafeFileSystemRepresentation` to obtain the correct C-string pointer when bridging file paths to POSIX APIs.
10 changes: 8 additions & 2 deletions Sources/Cacheout/Headless/StatusSocket.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ public final class StatusSocket: @unchecked Sendable {
try FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true, attributes: [
.posixPermissions: 0o700
])
let dirFd = open(dir, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)
let dirFd = URL(fileURLWithPath: dir).withUnsafeFileSystemRepresentation { pathPtr -> Int32 in
guard let pathPtr = pathPtr else { return -1 }
return open(pathPtr, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)
}
guard dirFd >= 0 else {
throw StatusSocketError.directoryHardeningFailed(errno)
}
Expand Down Expand Up @@ -497,7 +500,10 @@ public final class StatusSocket: @unchecked Sendable {
// component is rejected up front and we hold a stable fd for the rest
// of the checks β€” no path resolution happens between type/size check
// and read, closing the lstat β†’ open TOCTOU window.
let fileFd = open(expandedPath, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
let fileFd = URL(fileURLWithPath: expandedPath).withUnsafeFileSystemRepresentation { pathPtr -> Int32 in
guard let pathPtr = pathPtr else { return -1 }
return open(pathPtr, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)
}
guard fileFd >= 0 else {
let err = errno
let reason = err == ELOOP ? "Refusing to follow symlink: \(expandedPath)"
Expand Down
Loading