Skip to content

Commit 5093127

Browse files
πŸ›‘οΈ Sentinel: [CRITICAL] Fix insecure file creation permissions (#410)
🚨 Severity: CRITICAL πŸ’‘ Vulnerability: Fixed insecure file creation permissions during state directory setup. 🎯 Impact: Prevented potential unintended local file modifications. πŸ”§ Fix: Implemented secure file descriptor-based permission handling. 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 ece7991 commit 5093127

2 files changed

Lines changed: 18 additions & 4 deletions

File tree

β€Ž.jules/sentinel.mdβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@
4848
**Learning:** High-level Swift file writing APIs do not natively protect against malicious symlinks in untrusted directories, potentially allowing unintended files to be overwritten or appended to.
4949
**Prevention:** Always use POSIX `open(2)` with `O_CREAT | O_WRONLY | O_APPEND | O_NOFOLLOW | O_CLOEXEC` to securely refuse symlink traversal, then wrap the resulting file descriptor in a `FileHandle`. Ensure directories are also securely created using `.posixPermissions`.
5050

51+
## 2026-05-03 - TOCTOU Vulnerability via FileManager.setAttributes
52+
**Vulnerability:** Used `FileManager.default.setAttributes` to apply permissions (`0o700`) to the state directory after creation.
53+
**Learning:** High-level Swift APIs like `FileManager.default.setAttributes` operate on string paths and follow symlinks by default. This makes them susceptible to Time-of-Check Time-of-Use (TOCTOU) symlink attacks, similarly to C `chmod()`.
54+
**Prevention:** Avoid `FileManager.default.setAttributes` for securing permissions on directories or sensitive files. Always use `withUnsafeFileSystemRepresentation`, `open()` with `O_NOFOLLOW | O_CLOEXEC`, and `fchmod()`.

β€ŽSources/Cacheout/Headless/DaemonMode.swiftβ€Ž

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,26 @@ public actor DaemonMode: StatusSocket.DataSource {
289289
// Ensure state directory exists with 0700 permissions.
290290
// createDirectory only sets attributes on newly created dirs, so we
291291
// explicitly chmod afterward to harden pre-existing directories.
292+
// Use O_NOFOLLOW | O_DIRECTORY + fchmod on the resulting fd so a
293+
// concurrent symlink swap at `stateDir` can't redirect the chmod target.
292294
do {
293295
try FileManager.default.createDirectory(
294296
at: config.stateDir,
295297
withIntermediateDirectories: true,
296298
attributes: [.posixPermissions: 0o700]
297299
)
298-
try FileManager.default.setAttributes(
299-
[.posixPermissions: 0o700],
300-
ofItemAtPath: config.stateDir.path
301-
)
300+
301+
let dirFd = config.stateDir.withUnsafeFileSystemRepresentation { pathPtr -> Int32 in
302+
guard let pathPtr = pathPtr else { return -1 }
303+
return open(pathPtr, O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC)
304+
}
305+
guard dirFd >= 0 else {
306+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: [NSLocalizedDescriptionKey: "open failed"])
307+
}
308+
defer { close(dirFd) }
309+
guard fchmod(dirFd, 0o700) == 0 else {
310+
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno), userInfo: [NSLocalizedDescriptionKey: "fchmod failed"])
311+
}
302312
} catch {
303313
logger.error("Failed to create/secure state directory: \(error.localizedDescription, privacy: .public)")
304314
Foundation.exit(1)

0 commit comments

Comments
Β (0)