Skip to content

Commit 8c2ec28

Browse files
committed
FileManager: lsetxattr and setxattr are swapped
The if statement appears inverted: setxattr follows simlinks: lsetxattr does not.
1 parent b0d9eb4 commit 8c2ec28

File tree

3 files changed

+61
-9
lines changed

3 files changed

+61
-9
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -522,21 +522,21 @@ 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
529-
var size = listxattr(path, nil, 0)
529+
var size = followSymlinks ? listxattr(path, nil, 0) : llistxattr(path, nil, 0)
530530
#endif
531531
guard size > 0 else { return nil }
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
539-
size = listxattr(path, keyList.baseAddress!, size)
539+
size = followSymlinks ? listxattr(path, keyList.baseAddress!, size) : llistxattr(path, keyList.baseAddress!, size)
540540
#endif
541541
guard size > 0 else { return nil }
542542

@@ -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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,58 @@ 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+
let linkAttrs = try fileManager.attributesOfItem(atPath: "link")
1083+
let targetAttrs = try fileManager.attributesOfItem(atPath: "target")
1084+
1085+
let linkXattrs = try #require(
1086+
linkAttrs[xattrKey] as? [String: Data],
1087+
"Expected extended attributes on symlink after setAttributes call")
1088+
let targetXattrs = targetAttrs[xattrKey] as? [String: Data]
1089+
1090+
// Require xattr on the symlink and ensure it wasn't applied to the target.
1091+
#expect(
1092+
linkXattrs[attrName] == attrValue,
1093+
"xattr should be applied to the symlink itself")
1094+
#expect(
1095+
targetXattrs?[attrName] == nil,
1096+
"setAttributes must not follow symlinks when setting extended attributes")
1097+
}
1098+
}
1099+
#endif
1100+
10491101
#if !canImport(Darwin) || os(macOS)
10501102
@Test func currentUserHomeDirectory() async throws {
10511103
let userName = ProcessInfo.processInfo.userName

0 commit comments

Comments
 (0)