Sanboxed Apps Reading Extended Security Information (ACL)

My custom filesystem kernel extension stores ACLs as an extended attribute, com.apple.system.Security.

Sanboxed apps such as TextEdit, Pages, etc., running as a non-privileged process, fail to save modified contents when permissive ACLs are in use.

Running them as a privileged process, does allow for file changes to be saved though.

Non-sandboxed apps, such as VSCode, and command line programs are not susceptible to this behaviour.

APFS, on the other hand, seems to handle ACLs as an ATTR_CMN_EXTENDED_SECURITY filesystem attribute, rather than as an EA. In this case, sandboxed apps have no trouble accessing the ACL data.

I implemented a minimal PoC within my custom kext to verify this. I construct an ACL in memory allowing a given user to write,append,delete file contents, and return it that via vnop_getattr. This allows the file contents to be modified and saved by sandboxed apps.

Can you please confirm if my findings are accurate and sandboxed apps fail to read the com.apple.system.Security EA by design?

Also, Is it an accurate assumption, that ACLs should be handled either as an EA, or an ATTR_CMN_EXTENDED_SECURITY, but not both?

Thanks.

Can you please confirm if my findings are accurate and sandboxed apps fail to read the com.apple.system.Security EA by design?

No, I don't think that's correct. You can see the code here but I believe that xattr was set up as the "fallback" storage mechanism which the VFS system uses if the can't retrieve the data through ATTR_CMN_EXTENDED_SECURITY. As far as directly reading the xattr, the vfs layer is what blocks that, not the sandbox, through this check.

However, my bigger question here is what you mean by "read". ACL enforcement and validation happens in the kernel, not user space, so a process doesn't really "read" its ACL, whether or not it's stored in an xattr. I don't know what's going on here, but the general theory you're describing doesn't really make sense to me. Have you tried starting with a minimal test app that's sandboxed and directly access the file? I'd start with our basic app template with the sandbox enabled, then use the File Access Entitlement to hard code its access to a specific file.

Related to that point:

I construct an ACL in memory allowing a given user to write,append,delete file contents, and return it that via vnop_getattr.

Be aware that the way most document based apps interact with their documents isn't through the standard "open-> read-> write-> close" Unix "cycle". Most document based apps use some form of safe save semantics where they copy the original, modify the copy, then exchange (ideally, atomically), the original with their modified copy.

Also, Is it an accurate assumption, that ACLs should be handled either as an EA, or an ATTR_CMN_EXTENDED_SECURITY, but not both?

Well, strictly speaking, it looks you could actually "mix" them but the bigger issue is basically "why bother". If the file system is going to handle them within it's metadata, then setting com.apple.system.Security is just extra work without any value.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for your response.

When evaluating KAUTH_FILESEC_XATTR via getxattr(), xattr_entitlement_check() succeeds if the calling process is that of the superuser, or if it carries the FILESEC_ACCESS_ENTITLEMENT entitlement.

TextEdit does not cary the FILESEC_ACCESS_ENTITLEMENT:

% codesign -d --entitlements - --xml /System/Applications/TextEdit.app | plutil -p -
Executable=/System/Applications/TextEdit.app/Contents/MacOS/TextEdit
{
  "com.apple.application-identifier" => "com.apple.TextEdit"
  "com.apple.developer.ubiquity-container-identifiers" => [
    0 => "com.apple.TextEdit"
  ]
  "com.apple.private.hid.client.event-dispatch.internal" => true
  "com.apple.security.app-sandbox" => true
  "com.apple.security.files.user-selected.executable" => true
  "com.apple.security.files.user-selected.read-write" => true
  "com.apple.security.print" => true
}

This explains the behaviour I'm observing, i.e. running TextEdit as a regular user fails to write file changes and returning EPERM, and running TextEdit as a superuser succeeds in writing the file contents.

xattr(1) behaves the same way:

% ls -le /Volumes/myfs/f.txt 
-r--r--r--@ 1 user  staff  0 Aug 27 18:39 /Volumes/myfs/f.txt
 0: user:user allow write,delete,append

% xattr /Volumes/myfs/f.txt
com.apple.TextEncoding
xattr: [Errno 1] Operation not permitted: '/Volumes/myfs/f.txt'

% sudo xattr /Volumes/myfs/f.txt
com.apple.TextEncoding
com.apple.system.Security

What I need clarified is this, if my custom filesystem kext handles ACL data as the KAUTH_FILESEC_XATTR, which it does in the current implementation, then sandboxed apps won't be able to save modified file contents, because they fail the xattr_entitlement_check(). Is my only option is this case to handle ACL data through ATTR_CMN_EXTENDED_SECURITY? Which the PoC I wrote confirmed as working.

When evaluating KAUTH_FILESEC_XATTR via getxattr(), xattr_entitlement_check() succeeds if the calling process is that of the superuser, or if it carries the FILESEC_ACCESS_ENTITLEMENT entitlement.

Sure, but that's actually what makes what you're describing confusing. I don't know why TextEdit would be trying to access that xattr. It's certainly not intentional and I don't think it's part of the process of actually validating the security state. That should be going through a completely different path.

The core problem here is that it's still not clear to me what's actually failing here. The problem here is that the comparison here shouldn't actually be "the same":

(1)

xattr(1) behaves the same way:

xattr is directly accessing the xattr which is then failing, exactly as documented. This is what an app that was explicitly trying to view or modify the ACL would do, which is what it’s set up to fail.

(2)

This explains the behaviour I'm observing, i.e. running TextEdit as a regular user fails to write file changes and returning EPERM, and running TextEdit as a superuser succeeds in writing the file contents.

TextEdit isn't (or shouldn't be) trying to view the ACL, it’s just trying to do “its work". Now, it's POSSIBLE that's failing because it's safe save semantics mean that it ends up trying to do a copy, which then fails due to the ACL. However, I'd then also expect "cp" and other similar tools to fail for the same reason. However, that would also mean that this ISN'T about sandbox'ing, it's about straight Unix permission enforcement.

That leads me back to what I suggested here:

Have you tried starting with a minimal test app that's sandboxed and directly access the file?

At this point, you still don't actually know what's failing inside TextEdit (or any other app), which makes it hard to know what your options are.

Is my only option in this case to handle ACL data through ATTR_CMN_EXTENDED_SECURITY?

I'm not sure. It's possible the xattr-based approach just doesn't support our safe-save semantics. That seems like a serious issue; however, it's possible as ACLs aren't all that widely used, and when they are used, they tend to be attached to directories, not individual files. However, it's also possible that something more complex is going on that’s specifically tied to your implementation. One question related to that point— where's this ACL data originally coming from? Is it possible that YOU ended up calling "getxattr" yourself as part of the permission check?

I don't think that would happen in a traditional block or network file system, but it might be an issue if you're doing some kind of filter file system.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Is it possible that YOU ended up calling "getxattr" yourself as part of the permission check?

No, I do not call getxattr explicitly. Both setxattr and getxattr are called by the system and my filesystem just stores and retrieves the ACL EA from the server.

I've built a test app with the following entitlements enabled:

% codesign -d --entitlements - --xml SandboxedFileEditor.app | plutil -p -
Executable=SandboxedFileEditor.app/Contents/MacOS/SandboxedFileEditor
{
  "com.apple.developer.applesignin" => {
    "com.apple.security.app-sandbox" => true
    "com.apple.security.files.bookmarks.app-scope" => true
    "com.apple.security.files.user-selected.read-write" => true
  }
  "com.apple.security.app-sandbox" => true
  "com.apple.security.files.user-selected.read-write" => true
  "com.apple.security.get-task-allow" => true
}

I was able to successfully modify and save the contents of the test file residing on my custom filesystem:

% cat /Volumes/myfs/f.txt         
line1
line2

With the following POSIX and ACL permissions set:

% ls -le /Volumes/myfs/f.txt 
-r--r--r--@ 1 user  staff  6 Sep  3 17:55 /Volumes/myfs/f.txt
 0: user:user allow write,delete,append

And the ACL being stored as a com.apple.system.Security EA:

% sudo xattr /Volumes/myfs/f.txt
com.apple.TextEncoding
com.apple.quarantine
com.apple.system.Security

I still don't understand how to debug TextEdit and Pages not being permitted to save the modified file contents.

In the system log, I've found the following error message:

2026-08-14 16:50:41.313445+0300 0x2718a    Error       0x62db7              442    0    Finder: (Foundation) [com.apple.foundation.filecoordination:claims] 73600188-34F7-4F6C-A697-34A74474169C grantAccessClaim reply is an error: Error Domain=NSCocoaErrorDomain Code=513 "The document “f1.txt” could not be autosaved. You don’t have permission." UserInfo={NSUnderlyingError=0x83891b900 {Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “f1.txt” in the folder “." UserInfo={NSURL=file:///Volumes/myfs/f1.txt, NSUnderlyingError=0x83891b8d0 {Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “f1.txt.sb-23e6090e-qqHT2x” in the folder “." UserInfo={NSURL=file:///Volumes/myfs/f1.txt.sb-23e6090e-qqHT2x, NSFilePath=/Volumes/myfs/f1.txt.sb-23e6090e-qqHT2x, NSUnderlyingError=0x83891bc00 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}}}}, NSLocalizedDescription=The document “f1.txt” could not be autosaved. You don’t have permission., NSURL=file:///Volumes/myfs/f1.txt, NSLocalizedFailure

Do you have any further suggestions to make in this respect?

I still don't understand how to debug TextEdit and Pages not being permitted to save the modified file contents.

First, as a general comment, as a file system vendor, this isn't necessarily your problem to debug. The file system’s role is to enforce the permissions that have been applied to it, not to ensure that those configurations are actually "useful". There can be some grey area if you're specifically creating that permission configuration or defining the mechanism used to control it, but if the user set it up, then it's their job to debug it.

In any case, moving to here:

In the system log, I've found the following error message:

So, the most useful data point here is this:

"You don’t have permission to save the file “f1.txt.sb-23e6090e-qqHT2x” in the folder"

In a previous post, I said:

Now, it's POSSIBLE that's failing because it's safe save semantics mean that it ends up trying to do a copy, which then fails due to the ACL.

Assuming you don't have an unusual approach to file naming[1], f1.txt.sb-23e6090e-qqHT2x is the temporary file TextEdit was trying to create so that it could write the new contents to your server. Once it had finished writing the data out, it would then have replaced f1.txt with f1.txt.sb-23e6090e-qqHT2x, completing the save. However, I don't think it actually supports in-place editing, so if it can't write that file, it will fail the save.

What are the permissions of the parent directory, and do they allow object creation?

[1] The extension sb-23e6090e-qqHT2x was "random" data attached to make the name unique.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

What are the permissions of the parent directory, and do they allow object creation?

The parent directory that houses the test file is the root of the filesystem which uses the most liberal POSIX permissions and has no ACL permissions set:

% ls -led /Volumes/myfs/
drwxrwxrwx  8 root  wheel  15 Sep  4 16:53 /Volumes/myfs/

I've been able to identify the point of failure in TextEdit using dtrace(1) via the syscall provider.

When replacing the original file, /Volumes/myfs/f.txt, with the temporary copy, TextEdit queries the presence of the com.apple.system.Security EA in the copy, /Volumes/myfs/f.txt.sb-1f18ed33-4luthh/f.txt. It gets ENOATTR returned, because the attribute isn't there.

Next, the original file is queried to get the size of the EA.

Finally, an attempt is made to create the security EA for the copy. It's this call that returns EPERM.

Extended attributes are queried and set using the following execution path, NSDocument saveToURL -> NSDocument writeSafelyToURL -> NSFileManager replaceItemAtURL -> CoreServicesInternal _URLReplaceObject -> CoreServicesInternal TransferExtendedAttributes -> libsystem_kernel.dylib getxattr or libsystem_kernel.dylibsetxattr`

See the file attached, dtrace-textedit-xattr.txt, for the diagnostics emitted by dtrace(1).

I guess, the only options available to me are, apps like TextEdit, Pages and possibly some other apps like that as well shouldn't be used to modify files when ACLs are stored as the system security EA, or the ACL implementation in my filesystem kext should be redone using the ATTR_CMN_EXTENDED_SECURITY VFS file metadata attribute.

When replacing the original file, /Volumes/myfs/f.txt, with the temporary copy, TextEdit queries the presence of the com.apple.system.Security EA in the copy, /Volumes/myfs/f.txt.sb-1f18ed33-4luthh/f.txt. It gets ENOATTR returned, because the attribute isn't there.

Looking at our code, this sequence is part of our "generic" xattr transfer engine. It works just like you've described above— it checks of the EA exists and copies it if it doesn't.

However, the problem here is that this transfer happens AFTER we set the permission configuration of the destination, including setting ACLs using filesec_set_property. In other words, the problem above isn't that set failed, it's that getxattr didn't return the acl attribute which should have been created by an earlier acl set.

Are you seeing an ACL get on the source? Or the ACL set on the destination?

I guess, the only options available to me are, apps like TextEdit, Pages and possibly some other apps like that as well shouldn't be used to modify files when ACLs are stored as the system security EA,

Not exactly. The thing to keep in mind here is that ACL shouldn't be modified through the xattr interface AT ALL. As is done in a few other places, the architectural "idea" is to basically:

  1. Store the data in an xattr (because it's convenient).
  2. Route modifications/access to that data through the "normal" API route.
  3. Block modification to the xattr (because #2 is the way you're supposed to access the data).

That's what _URLReplaceObject is doing, except that it took the "shortcut" of checking for xattrs on the destination instead of explicitly skipping "com.apple.system.Security".

[1] There's actually a different function called "xattr_preserve_for_intent" (see "man xattr_name_with_flags"), which it uses to determine which xattrs it should ignore. If you're curious, you can actually look at its implementation here.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Are you seeing an ACL get on the source? Or the ACL set on the destination?

I was able to map this syscall returning ENOATTR:

2  => getxattr('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', value:0, size:0, position:0, options:1) TextEdit-3772
3  <= getxattr('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', value:0, size:0, position:0, options:1) -> -1 (93) TextEdit-3772 ;ENOATTR

To this vnop_getxattr call in my kext, with args->a_size=0 returned:

vnop_getxattr: TextEdit-3772 -> getxattr_rpc('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', fffffe2fbb998180, uio_resid(EAvalue):762)
vnop_getxattr: TextEdit-3772 <- getxattr_rpc('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', fffffe2fbb998180, uio_resid(EAvalue):762) -> 93 ;ENOATTR
vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt' 'com.apple.system.Security' *args->a_size=0
vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt' 'com.apple.system.Security' <- 93 ;ENOATTR

The next syscall succeeds in returning the com.apple.system.Security EA, with the EA size of 512 bytes returned. This differs from the size returned by my kext being 68 bytes. See below:

3  => getxattr('/Volumes/myfs/f.txt', 'com.apple.system.Security', value:16de4d390, size:512, position:0, options:1) TextEdit-3772
0  <= getxattr('/Volumes/myfs/f.txt', 'com.apple.system.Security', value:16de4d390, size:512, position:0, options:1) -> 512 (0) TextEdit-3772
vnop_getxattr: TextEdit-3772 -> getxattr_rpc('/Volumes/myfs/f.txt', 'com.apple.system.Security', fffffecff9e3ba80, uio_resid(EAvalue):512)
vnop_getxattr: TextEdit-3772 <- getxattr_rpc('/Volumes/myfs/f.txt', 'com.apple.system.Security', fffffecff9e3ba80, uio_resid(EAvalue):488) -> 68
vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt' 'com.apple.system.Security' *args->a_size=68
vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt' 'com.apple.system.Security' <- 0 ; KERN_SUCCESS

This syscall never calls into the vnop_setxattr in my kext:

0  => setxattr('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', value:16de4d390, size:512, position:0, options:1) TextEdit-3772
0  <= setxattr('/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt', 'com.apple.system.Security', value:16de4d390, size:512, position:0, options:1) -> -1 (1) TextEdit-3772 ;EPERM

EPERM possibly being returned from setxattr via the following execution path:

if (xattr_protected(sactx->attrname) &&
	(error = xattr_entitlement_check(sactx->attrname, ctx, true)) != 0) {
	goto out;
}

OK. SO, the critical thing to look at here is what exactly happened on that file leading up to the getxattr. The problem here is that a few different things don't really make sense. More specifically, this sequence within our code would have been to set the ACL, then look at the xattr list. That means this should never have happened:

vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt' 'com.apple.system.Security' *args->a_size=0
vnop_getxattr: TextEdit-3772 '/Volumes/myfs/f.txt.sb-94c724e7-egshwF/f.txt' 'com.apple.system.Security' <- 93 ;ENOATTR

...since setting the acl should have created the xattr above.

There are also a few issues here:

The next syscall succeeds in returning the com.apple.system.Security EA, with the EA size of 512 bytes returned.

  1. getxattr should have failed on both files, due to this entitlement check.

  2. TransferExtendedAttributes calls xattr_preserve_for_intent and com.apple.security is specifically configured as XATTR_FLAG_NEVER_PRESERVE. That means it should never have touched that xattr on either file.

  3. Even if neither of those points held, the fact that it existed on the new file should have meant that it got skipped also.

What system version are you testing on? All the code for _URLReplaceObject hasn't changed in any recent version, but it's possible that an old enough system version might be doing something different.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for looking into this.

What system version are you testing on?

% sw_vers   
ProductName:		macOS
ProductVersion:		26.6.2
BuildVersion:		25G83

% xcodebuild -version
Xcode 26.6
Build version 17F113

To better understand the situation I resorted to dynamic analisys of the following CoreServicesInternal functions using Ghidra, a software reverse engineering tool:

URLReplaceObject
CFURLCopySecurityEAs
TransferExtendedAttributes
copyExtendedAttribute

The following test case was considered: Source file mode: 0444 Source file ACL: user id 501 allow write,append,delete The temporary file, created by TextEdit, i.e. the destination file mode: 0644

srcUrlMode != dstUrlMode

Execution path

CFURLCopySecurityEAs(srcURL,dstURL,&local_8d0)

This function is called based on xpc_runtime_is_app_sandboxed() returning true and isSrcURLDirectory==isDstURLDirectory.

Internally, CFURLCopySecurityEAs():

  1. Opens both srcURL and dstURL
  2. Retrieves a list of EAs from srcURL via flistxattr(2):
% sudo xattr /Volumes/myfs/nf.txt
com.apple.quarantine
com.apple.TextEncoding
com.apple.lastuseddate#PS
com.apple.system.Security
  1. Iterates over the list of EAs returned
  2. Copies any EAs that match the com.apple.security. pattern, e.g.:
% kextstat 2>/dev/null | awk '$6~/com.apple.security./{print $6}'
com.apple.security.AppleImage4
com.apple.security.sandbox
com.apple.security.quarantine
com.apple.security.BootPolicy

The value returned is 1.

Next, the file security object associated with the source and destination URLs is obtained via CFURLCopyResourcePropertyForKey:

CFURLCopyResourcePropertyForKey(srcURL,NSURLFileSecurityKey,&srcFileSec,&error);
CFURLCopyResourcePropertyForKey(dstURL,NSURLFileSecurityKey,&dstFileSec,&error);

Next, a new file security object is created via CFFileSecurityCreate:

<FileSecurity 0xaf54acd10> [0xaf6a71980] {FILESEC_OWNER = (null), FILESEC_GROUP = (null), FILESEC_MODE = (null), FILESEC_UUID = (null), FILESEC_GRPUUID = (null), FILESEC_ACL = (null)}

Next, the file mode 33060==o100444 is extracted from srcFileSec via CFFileSecurityGetMode(srcFileSec,&srcUrlMode):

<FileSecurity 0xa3f5dee60> [0xa3fbf0a80] {FILESEC_OWNER = 501, FILESEC_GROUP = 20, FILESEC_MODE = 33060, FILESEC_UUID = 0000000
0-0000-0000-0000-000000000000, FILESEC_GRPUUID = 00000000-0000-0000-0000-000000000000, FILESEC_ACL = !#acl 1
user:ED27D762-80A1-496B-BD81-4AE8CE54CE82:developer:501:allow:write,delete,append
}

Next, the GID 20 (staff) of the owner of the source and the destination files is extracted from srcFileSec and dstFileSec respectively via CFFileSecurityGetGroup:

CFFileSecurityGetGroup(srcFileSec,(long)&srcgid)
CFFileSecurityGetGroup(dstFileSec,&dstgid)

Next, the mode of the source file is applied to the brand new file sec object via CFFileSecuritySetMode(newFileSec,srcUrlMode):

<FileSecurity 0xaf54acd10> [0xaf6a71980] {FILESEC_OWNER = (null), FILESEC_GROUP = (null), FILESEC_MODE = 33060, FILESEC_UUID = 
(null), FILESEC_GRPUUID = (null), FILESEC_ACL = (null)}

Next, the ACL is extracted from the source file sec object via CFFileSecurityCopyAccessControlList(srcFileSec,&tmpACL):

<FileSecurity 0xa3f5dee60> [0xa3fbf0a80] {FILESEC_OWNER = 501, FILESEC_GROUP = 20, FILESEC_MODE = 33060, FILESEC_UUID = 0000000
0-0000-0000-0000-000000000000, FILESEC_GRPUUID = 00000000-0000-0000-0000-000000000000, FILESEC_ACL = !#acl 1
user:ED27D762-80A1-496B-BD81-4AE8CE54CE82:developer:501:allow:write,delete,append
}
(lldb) expr char* $tmpACL = (char*)acl_to_text(*(void**)0x16d35a138, (long*)0)
(lldb) p (char*)$tmpACL
(lldb) "!#acl 1\nuser:ED27D762-80A1-496B-BD81-4AE8CE54CE82:developer:501:allow:write,delete,append\n"

Next, the ACL extracted from the source file sec object is applied to the brand new file sec object created via CFFileSecuritySetAccessControlList(newFileSec,tmpACL):

<FileSecurity 0xaf54acd10> [0xaf6a71980] {FILESEC_OWNER = (null), FILESEC_GROUP = (null), FILESEC_MODE = 33060, FILESEC_UUID = 
(null), FILESEC_GRPUUID = (null), FILESEC_ACL = !#acl 1
user:ED27D762-80A1-496B-BD81-4AE8CE54CE82:developer:501:allow:write,delete,append
}

Next, the destination file mode o100644 is extracted from the destination file sec object via CFFileSecurityGetMode(dstFileSec,&dstUrlMode):

<FileSecurity 0xa3f5def70> [0xa3fbf0200] {FILESEC_OWNER = 501, FILESEC_GROUP = 20, FILESEC_MODE = 33188 (o100644), FILESEC_UUID = (null),
 FILESEC_GRPUUID = (null), FILESEC_ACL = (null)}

Next, the brand new file sec object is added to a dictionary:

CFDictionaryAddValue(dict,NSURLFileSecurityKey,newFileSec)

Next, TransferExtendedAttributes(alloc,srcURL,dstURL,&error) is called.

Internally, TransferExtendedAttributes:

  1. Obtains a list of EAs via listxattr(2)
  2. Calls xattr_preserve_for_intent for each of the EAs iteratively passing XATTR_OPERATION_INTENT_SAVE.
  3. Calls copyExtendedAttribute to copy the ACL EA from the source to the destination file.

xattr_preserve_for_intent considers the following EAs worth saving:

com.apple.lastuseddate#PS
com.apple.system.Security

The EAs are saved by calling copyExtendedAttribute. com.apple.lastuseddate#PS is saved successfully. com.apple.system.Security fails to be saved.

Internally, copyExtendedAttribute:

  1. Calls getxattr(dstFile, 'com.apple.system.Security', NULL, 0, 0, XATTR_NOFOLLOW) to obtain the size of the attribute. -1 is returned.
  2. Calls getxattr(srcFile, 'com.apple.system.Security', buffer, 512, 0, XATTR_NOFOLLOW) to obtain the ACL EA. -1 is returned with errno==1 EPERM.

TransferExtendedAttributes returns 0 to the caller and the errno in the error parameter.

URLReplaceObject proceeds down the error path and ends up setting a CFError to:

Error Domain=NSCocoaErrorDomain Code=513 "You don’t have permission to save the file “nf.txt.sb-4b9a8883-7jQtqz” in the folder 
“myfs”." UserInfo={NSURL=file:///Volumes/myfs/nf.txt.sb-4b9a8883-7jQtqz, NSFilePath=/Volumes/myfs/nf.txt.sb-4b9a8883-7jQtqz, NS
UnderlyingError=0xaf76952c0 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

URLReplaceObject returns 0x201==513 to the caller.

If you have any further guidance to offer, I would greatly appreciate it.

This function is called based on xpc_runtime_is_app_sandboxed() returning true and isSrcURLDirectory==isDstURLDirectory.

Internally, CFURLCopySecurityEAs():

Correct, except that the calling app isn't what does this:

  1. Copies any EAs that match the com.apple.security. pattern, e.g.:

What actually happens is that the code in the gathers the xattr data, then uses XPC to send a command to ScopedBookmarkAgent, which then does the actual copy. Note that this is why the copy DIDN'T fail here, as ScopedBookmarkAgent has the right to set this xattr.

In any case, the real key point is here:

Next, the ACL extracted from the source file sec object is applied to the brand-new file sec object created via CFFileSecuritySetAccessControlList(newFileSec,tmpACL):

At the point this succeeded, the file should have acquired a "com.apple.system.Security" attribute, since that's where your file system should be storing the ACL.

Why didn't it?

That matters because the reason copyExtendedAttribute did this first:

  1. Calls getxattr(dstFile, 'com.apple.system.Security', NULL, 0, 0, XATTR_NOFOLLOW) to obtain the size of the attribute. -1 is returned.

...is because IF an xattr had been returned there, as it should have been, copyExtendedAttribute would have then skipped this step:

  1. Calls getxattr(srcFile, 'com.apple.system.Security', buffer, 512, 0, XATTR_NOFOLLOW) to obtain the ACL EA. -1 is returned with errno==1 EPERM.

...and simply continued normally without any error.

If you have any further guidance to offer, I would greatly appreciate it.

Expanding on what I said above, if your file system is using the "com.apple.system.Security" attributes, then setting the ACL should result in the creation of a new "com.apple.system.Security" attribute. Similarly, if you're NOT using "com.apple.system.Security", then I don't think any file should ever return a "com.apple.system.Security" attribute. Mixing the two systems won’t really work.

SO, the question here is how/why an ACL was set on the file but the xattr was not created.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Sanboxed Apps Reading Extended Security Information (ACL)
 
 
Q