From 05935a51b8f43db83efc84f60266b2374240bba1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 15 May 2026 06:19:48 +0000 Subject: [PATCH] Fix fd leak and unsafe path bridging on PID lock Calling `open(2)` without `O_CLOEXEC` causes the file descriptor to leak to child processes, which could prevent the PID lock from being released on daemon shutdown. Implicit path string bridging may not produce a valid file system representation. This adds `O_CLOEXEC`, uses `S_IRUSR | S_IWUSR` for atomic 0600 mode application, and `withUnsafeFileSystemRepresentation` for safe string passing. Co-authored-by: acebytes <2820910+acebytes@users.noreply.github.com> --- .jules/sentinel.md | 5 +++++ Sources/Cacheout/Headless/DaemonMode.swift | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.jules/sentinel.md b/.jules/sentinel.md index a16dfc9..ff1502e 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -21,3 +21,8 @@ ## 2024-05-18 - Process Deadlock in CacheoutViewModel.runCleanCommand **Vulnerability:** `process.waitUntilExit()` was called before reading `pipe.fileHandleForReading` when executing `docker system prune` in `CacheoutViewModel.swift`. A specific instance of the 2024-04-22 pattern. **Prevention:** Same as 2024-04-22 — read the pipe before calling `waitUntilExit()`, or prefer `try fileHandle.readToEnd()`. + +## 2024-05-08 - File Descriptor Leak and Unsafe Path Bridging +**Vulnerability:** Calling `open(2)` without `O_CLOEXEC` causes the file descriptor to leak to child processes, and passing Swift `String` paths implicitly to C functions can result in unsafe filesystem representations. +**Learning:** Child processes inheriting the PID lock file descriptor could prevent the lock from being released. Using `withUnsafeFileSystemRepresentation` is the only safe way to bridge file paths to POSIX APIs. +**Prevention:** Always include `O_CLOEXEC` when opening files with POSIX APIs, and use `URL(fileURLWithPath:).withUnsafeFileSystemRepresentation` to obtain the correct C-string pointer. diff --git a/Sources/Cacheout/Headless/DaemonMode.swift b/Sources/Cacheout/Headless/DaemonMode.swift index 3733f9d..f42c687 100644 --- a/Sources/Cacheout/Headless/DaemonMode.swift +++ b/Sources/Cacheout/Headless/DaemonMode.swift @@ -353,8 +353,11 @@ public actor DaemonMode: StatusSocket.DataSource { private func acquirePIDLock() -> Bool { let pidPath = pidFilePath - // Open (or create) the PID file with 0600 permissions - let fd = open(pidPath, O_WRONLY | O_CREAT, 0o600) + // Open (or create) the PID file with 0600 permissions safely + let fd = URL(fileURLWithPath: pidPath).withUnsafeFileSystemRepresentation { pathPtr in + guard let ptr = pathPtr else { return Int32(-1) } + return open(ptr, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR) + } guard fd >= 0 else { logger.error("Failed to open PID file: errno \(errno)") return false