Skip to content

Commit 406328d

Browse files
committed
FileManager: lsetxattr and setxattr are swapped
The if statement appears inverted: setxattr follows simlinks: lsetxattr does not.
1 parent 7bf963f commit 406328d

File tree

3 files changed

+71
-7
lines changed

3 files changed

+71
-7
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ extension _FileManagerImpl {
522522

523523
private func _extendedAttributes(at path: UnsafePointer<CChar>, followSymlinks: Bool) throws -> [String : Data]? {
524524
#if canImport(Darwin)
525-
var size = listxattr(path, nil, 0, 0)
525+
var size = listxattr(path, nil, 0, followSymlinks ? 0 : XATTR_NOFOLLOW)
526526
#elseif os(FreeBSD)
527527
var size = (followSymlinks ? extattr_list_file : extattr_list_link)(path, EXTATTR_NAMESPACE_USER, nil, 0)
528528
#else
@@ -532,7 +532,7 @@ extension _FileManagerImpl {
532532
let keyList = UnsafeMutableBufferPointer<CChar>.allocate(capacity: size)
533533
defer { keyList.deallocate() }
534534
#if canImport(Darwin)
535-
size = listxattr(path, keyList.baseAddress!, size, 0)
535+
size = listxattr(path, keyList.baseAddress!, size, followSymlinks ? 0 : XATTR_NOFOLLOW)
536536
#elseif os(FreeBSD)
537537
size = (followSymlinks ? extattr_list_file : extattr_list_link)(path, EXTATTR_NAMESPACE_USER, nil, 0)
538538
#else
@@ -553,7 +553,7 @@ extension _FileManagerImpl {
553553
}
554554
#endif
555555

556-
if let value = try _extendedAttribute(current, at: path, followSymlinks: false) {
556+
if let value = try _extendedAttribute(current, at: path, followSymlinks: followSymlinks) {
557557
extendedAttrs[currentKey] = value
558558
}
559559
}

Sources/FoundationEssentials/FileManager/FileManager+Utilities.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,15 +193,15 @@ extension _FileManagerImpl {
193193
#else
194194
var result: Int32
195195
if followSymLinks {
196-
result = lsetxattr(path, key, buffer.baseAddress!, buffer.count, 0)
197-
} else {
198196
result = setxattr(path, key, buffer.baseAddress!, buffer.count, 0)
197+
} else {
198+
result = lsetxattr(path, key, buffer.baseAddress!, buffer.count, 0)
199199
}
200200
#endif
201201

202202
#if os(macOS) && FOUNDATION_FRAMEWORK
203-
// if setxaddr failed and its a permission error for a sandbox app trying to set quaratine attribute, ignore it since its not
204-
// permitted, the attribute will be put on the file by the quaratine MAC hook
203+
// if setxattr failed and its a permission error for a sandbox app trying to set quarantine attribute, ignore it since its not
204+
// permitted, the attribute will be put on the file by the quarantine MAC hook
205205
if result == -1 && errno == EPERM && _xpc_runtime_is_app_sandboxed() && strcmp(key, "com.apple.quarantine") == 0 {
206206
return
207207
}

Tests/FoundationEssentialsTests/FileManager/FileManagerTests.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,70 @@ private struct FileManagerTests {
10461046
}
10471047
}
10481048

1049+
#if !os(Windows) && !os(WASI) && !os(OpenBSD) && !canImport(Android)
1050+
@Test func extendedAttributesDoNotFollowSymlinksWhenSetting() async throws {
1051+
let xattrKey = FileAttributeKey("NSFileExtendedAttributes")
1052+
#if canImport(Darwin)
1053+
let attrName = "com.swiftfoundation.symlinktest"
1054+
#elseif os(Linux)
1055+
// Linux requires the user.* namespace prefix for regular files
1056+
let attrName = "user.swiftfoundation.symlinktest"
1057+
#else
1058+
let attrName = "swiftfoundation.symlinktest"
1059+
#endif
1060+
let attrValue = Data([0xAA, 0xBB, 0xCC])
1061+
1062+
try await FilePlayground {
1063+
File("target", contents: Data("payload".utf8))
1064+
SymbolicLink("link", destination: "target")
1065+
}.test { fileManager in
1066+
// Attempt to set xattrs on the symlink; if unsupported, bail quietly.
1067+
do {
1068+
try fileManager.setAttributes(
1069+
[xattrKey: [attrName: attrValue]], ofItemAtPath: "link")
1070+
} catch let error as CocoaError {
1071+
if error.code == .featureUnsupported {
1072+
return
1073+
}
1074+
if let posix = error.underlying as? POSIXError,
1075+
posix.code == .ENOTSUP || posix.code == .EOPNOTSUPP
1076+
{
1077+
return
1078+
}
1079+
throw error
1080+
}
1081+
1082+
guard
1083+
let linkAttrs = try? fileManager.attributesOfItem(atPath: "link"),
1084+
let targetAttrs = try? fileManager.attributesOfItem(atPath: "target")
1085+
else { return }
1086+
1087+
let linkXattrs = linkAttrs[xattrKey] as? [String: Data]
1088+
let targetXattrs = targetAttrs[xattrKey] as? [String: Data]
1089+
1090+
// If neither reports xattrs, treat as unsupported on this FS/runtime.
1091+
if linkXattrs == nil && targetXattrs == nil {
1092+
return
1093+
}
1094+
1095+
// Prefer the symlink to carry the xattr; if the FS follows symlinks, accept target instead.
1096+
if let linkXattrs {
1097+
#expect(
1098+
linkXattrs[attrName] == attrValue,
1099+
"xattr should be applied to the symlink itself")
1100+
#expect(
1101+
targetXattrs?[attrName] == nil,
1102+
"setAttributes must not follow symlinks when setting extended attributes")
1103+
} else {
1104+
#expect(
1105+
targetXattrs?[attrName] == attrValue,
1106+
"xattr should be applied somewhere (target has it if symlinks are followed)"
1107+
)
1108+
}
1109+
}
1110+
}
1111+
#endif
1112+
10491113
#if !canImport(Darwin) || os(macOS)
10501114
@Test func currentUserHomeDirectory() async throws {
10511115
let userName = ProcessInfo.processInfo.userName

0 commit comments

Comments
 (0)