From bb98152fb6d727f5418953abfa5e2255aa5c1d4d Mon Sep 17 00:00:00 2001 From: Ian Gordon Date: Mon, 20 Jul 2026 22:43:29 -0400 Subject: [PATCH] Darwin: fix sockaddr_un overflow in makeAddressUnix for long paths Truncate paths to 103 bytes and always NUL-terminate sun_path, which Darwin declares as char sun_path[104]. Previously strncpy was bounded by sun_len (up to 106), writing past the end of the struct. Also fix the same off-by-one in maximumPathLengthForUnixDomainSocket, which copied 105 bytes into the 104-byte field (caught by ASan), and add round-trip tests for max-length and overlong paths. Co-Authored-By: Claude Fable 5 --- FlyingSocks/Sources/Socket+Darwin.swift | 9 +++++--- FlyingSocks/Tests/SocketAddressTests.swift | 26 +++++++++++++++++++++- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/FlyingSocks/Sources/Socket+Darwin.swift b/FlyingSocks/Sources/Socket+Darwin.swift index c36f674e..25244117 100644 --- a/FlyingSocks/Sources/Socket+Darwin.swift +++ b/FlyingSocks/Sources/Socket+Darwin.swift @@ -89,12 +89,15 @@ extension Socket { static func makeAddressUnix(path: String) -> Darwin.sockaddr_un { var addr = Darwin.sockaddr_un() addr.sun_family = sa_family_t(AF_UNIX) - let pathCount = min(path.utf8.count, 104) + // Darwin declares `char sun_path[104]`; reserve the last byte + // for the NUL terminator that String(cString:) and unlink() read back. + let pathCount = min(path.utf8.count, 103) let len = UInt8(MemoryLayout.size + MemoryLayout.size + pathCount + 1) - _ = withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in + withUnsafeMutablePointer(to: &addr.sun_path.0) { ptr in path.withCString { - strncpy(ptr, $0, Int(len)) + _ = strncpy(ptr, $0, pathCount) } + ptr[pathCount] = 0 } addr.sun_len = len return addr diff --git a/FlyingSocks/Tests/SocketAddressTests.swift b/FlyingSocks/Tests/SocketAddressTests.swift index 995ad74c..91081997 100644 --- a/FlyingSocks/Tests/SocketAddressTests.swift +++ b/FlyingSocks/Tests/SocketAddressTests.swift @@ -148,6 +148,30 @@ struct SocketAddressTests { ) } + #if canImport(Darwin) + @Test + func unixMaxLengthPath_IsCorrectlyDecodedFromStorage() throws { + let path = "/tmp/" + String(repeating: "x", count: 98) + let addr = sockaddr_un.unix(path: path) + + #expect(Int(addr.sun_len) <= MemoryLayout.size) + #expect( + try Socket.makeAddress(from: addr.makeStorage()) == .unix(path) + ) + } + + @Test + func unixOverlongPath_TruncatesWithoutOverflow() throws { + let path = "/tmp/" + String(repeating: "x", count: 99) + let addr = sockaddr_un.unix(path: path) + + #expect(Int(addr.sun_len) <= MemoryLayout.size) + #expect( + try Socket.makeAddress(from: addr.makeStorage()) == .unix(String(path.prefix(103))) + ) + } + #endif + #if canImport(Glibc) || canImport(Musl) || canImport(Android) @Test func unixAbstractNamespace_IsCorrectlyDecodedFromStorage() throws { @@ -268,7 +292,7 @@ struct SocketAddressTests { func maximumPathLengthForUnixDomainSocket() { var addrUn = sockaddr_un() addrUn.sun_family = sa_family_t(AF_UNIX) - let maxPathLength = MemoryLayout.size - MemoryLayout.size - 1 + let maxPathLength = MemoryLayout.size(ofValue: addrUn.sun_path) - 1 let maxPath = String(repeating: "a", count: maxPathLength) _ = maxPath.withCString { pathPtr in memcpy(&addrUn.sun_path, pathPtr, maxPath.count + 1)