From 1e3f4fa3dbf4324e9bdcd013fc68b8b27625c4c4 Mon Sep 17 00:00:00 2001 From: AZero13 Date: Fri, 5 Dec 2025 13:27:32 -0500 Subject: [PATCH] FileManager: lsetxattr and setxattr are swapped The if statement appears inverted: setxattr follows simlinks: lsetxattr does not. --- .../FileManager/FileManager+Utilities.swift | 8 ++-- .../FileManager/FileManagerTests.swift | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift b/Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift index d882a67bb..24cb3ec0f 100644 --- a/Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift +++ b/Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift @@ -193,15 +193,15 @@ extension _FileManagerImpl { #else var result: Int32 if followSymLinks { - result = lsetxattr(path, key, buffer.baseAddress!, buffer.count, 0) - } else { result = setxattr(path, key, buffer.baseAddress!, buffer.count, 0) + } else { + result = lsetxattr(path, key, buffer.baseAddress!, buffer.count, 0) } #endif #if os(macOS) && FOUNDATION_FRAMEWORK - // if setxaddr failed and its a permission error for a sandbox app trying to set quaratine attribute, ignore it since its not - // permitted, the attribute will be put on the file by the quaratine MAC hook + // if setxattr failed and its a permission error for a sandbox app trying to set quarantine attribute, ignore it since its not + // permitted, the attribute will be put on the file by the quarantine MAC hook if result == -1 && errno == EPERM && _xpc_runtime_is_app_sandboxed() && strcmp(key, "com.apple.quarantine") == 0 { return } diff --git a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift index 44893c17e..46d6bfb3e 100644 --- a/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift +++ b/Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift @@ -1046,6 +1046,43 @@ private struct FileManagerTests { } } + @Test func extendedAttributesDoNotFollowSymlinksWhenSetting() async throws { +#if os(Windows) || os(WASI) || os(OpenBSD) || canImport(Android) + throw Skip("Extended attributes not supported on this platform") +#else + let xattrKey = FileAttributeKey("NSFileExtendedAttributes") + #if canImport(Darwin) + let attrName = "com.swiftfoundation.symlinktest" + #elseif os(Linux) + // Linux requires the user.* namespace prefix for regular files + let attrName = "user.swiftfoundation.symlinktest" + #else + let attrName = "swiftfoundation.symlinktest" + #endif + let attrValue = Data([0xAA, 0xBB, 0xCC]) + + try await FilePlayground { + File("target", contents: Data("payload".utf8)) + SymbolicLink("link", destination: "target") + }.test { fileManager in + do { + try fileManager.setAttributes([xattrKey: [attrName: attrValue]], ofItemAtPath: "link") + } catch let error as CocoaError where error.code == .featureUnsupported { + throw Skip("Extended attributes not supported on this filesystem: \(error)") + } + + let linkAttrs = try fileManager.attributesOfItem(atPath: "link") + let targetAttrs = try fileManager.attributesOfItem(atPath: "target") + + let linkXattrs = linkAttrs[xattrKey] as? [String : Data] + let targetXattrs = targetAttrs[xattrKey] as? [String : Data] + + #expect(linkXattrs?[attrName] == attrValue, "xattr should be applied to the symlink itself") + #expect(targetXattrs?[attrName] == nil, "setAttributes must not follow symlinks when setting extended attributes") + } +#endif + } + #if !canImport(Darwin) || os(macOS) @Test func currentUserHomeDirectory() async throws { let userName = ProcessInfo.processInfo.userName