Skip to content

Commit bd02eaa

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

File tree

3 files changed

+68
-7
lines changed

3 files changed

+68
-7
lines changed

Sources/FoundationEssentials/FileManager/FileManager+Files.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,19 +522,23 @@ 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)
528+
#elseif os(Linux)
529+
var size = followSymlinks ? listxattr(path, nil, 0) : llistxattr(path, nil, 0)
528530
#else
529531
var size = listxattr(path, nil, 0)
530532
#endif
531533
guard size > 0 else { return nil }
532534
let keyList = UnsafeMutableBufferPointer<CChar>.allocate(capacity: size)
533535
defer { keyList.deallocate() }
534536
#if canImport(Darwin)
535-
size = listxattr(path, keyList.baseAddress!, size, 0)
537+
size = listxattr(path, keyList.baseAddress!, size, followSymlinks ? 0 : XATTR_NOFOLLOW)
536538
#elseif os(FreeBSD)
537539
size = (followSymlinks ? extattr_list_file : extattr_list_link)(path, EXTATTR_NAMESPACE_USER, nil, 0)
540+
#elseif os(Linux)
541+
size = followSymlinks ? listxattr(path, keyList.baseAddress!, size) : llistxattr(path, keyList.baseAddress!, size)
538542
#else
539543
size = listxattr(path, keyList.baseAddress!, size)
540544
#endif
@@ -553,7 +557,7 @@ extension _FileManagerImpl {
553557
}
554558
#endif
555559

556-
if let value = try _extendedAttribute(current, at: path, followSymlinks: false) {
560+
if let value = try _extendedAttribute(current, at: path, followSymlinks: followSymlinks) {
557561
extendedAttrs[currentKey] = value
558562
}
559563
}

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: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,63 @@ 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(linkAttrs[xattrKey] as? [String: Data])
1086+
let targetXattrs = try #require(targetAttrs[xattrKey] as? [String: Data])
1087+
1088+
// Prefer the symlink to carry the xattr; if the FS follows symlinks, accept target instead.
1089+
if let linkXattrs {
1090+
#expect(
1091+
linkXattrs[attrName] == attrValue,
1092+
"xattr should be applied to the symlink itself")
1093+
#expect(
1094+
targetXattrs?[attrName] == nil,
1095+
"setAttributes must not follow symlinks when setting extended attributes")
1096+
} else {
1097+
#expect(
1098+
targetXattrs?[attrName] == attrValue,
1099+
"xattr should be applied somewhere (target has it if symlinks are followed)"
1100+
)
1101+
}
1102+
}
1103+
}
1104+
#endif
1105+
10491106
#if !canImport(Darwin) || os(macOS)
10501107
@Test func currentUserHomeDirectory() async throws {
10511108
let userName = ProcessInfo.processInfo.userName

0 commit comments

Comments
 (0)