macOS 27/APFS: Supported ACL Absence Verification When acl_get_fd_np Returns ENOENT

Subject: Supported fail-closed ACL absence verification on macOS 27.0.1 / APFS

Environment: macOS 27.0.1, build 26A434; APFS; installed macOS 27.0 SDK.

We need a supported, documented way to determine whether an existing filesystem object has no effective ACL entries, while rejecting genuine retrieval failures and unsupported or unavailable ACL evidence. We are seeking clarification of the public interface contract, not alleging an Apple defect.

For existing directory and regular-file fixtures, pathname metadata and descriptor metadata identify the same object. Errno is cleared immediately before each native call and captured immediately afterward. ACL retrieval uses ACL_TYPE_EXTENDED:

  • acl_get_file(path, ACL_TYPE_EXTENDED)
  • acl_get_fd_np(fd, ACL_TYPE_EXTENDED)

On fresh objects, both return NULL with ENOENT. Explicit one-entry fixture ACLs are retrieved successfully. Explicit zero-entry fixture ACLs can also yield NULL/ENOENT, while attribute queries return a serialized zero-entry ACL.

The attribute queries use getattrlist() and descriptor-bound fgetattrlist(), requesting ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_EXTENDED_SECURITY, with FSOPT_NOFOLLOW | FSOPT_REPORT_FULLSIZE and WITHOUT FSOPT_PACK_INVAL_ATTRS. Buffer lengths, variable-reference bounds and returned-attribute validity are checked. For the existing directories of interest, both calls succeed but omit the ATTR_CMN_EXTENDED_SECURITY returned bit. Explicit populated and zero-entry fixture records do set the bit. APFS reports valid VOL_CAP_INT_EXTENDED_SECURITY support; we do not assume that volume capability proves per-object ACL availability.

On macOS 27.0.1, build 26A434, using APFS, which supported interface and documented conditions allow software to distinguish an absent ACL from unavailable or unsupported ACL evidence when descriptor-bound attribute queries succeed but omit ATTR_CMN_EXTENDED_SECURITY and native ACL getters return ENOENT for an existing object?

Please clarify:

  1. Whether ENOENT has a supported ACL-absence interpretation, and under exactly which checked conditions.
  2. Whether an omitted extended-security attribute can establish ACL absence, rather than unavailable or unsupported evidence.
  3. How explicit zero-entry ACLs differ from absent ACL records through supported interfaces.
  4. How KAUTH_FILESEC_NOACL should be interpreted through supported APIs.
  5. How genuine retrieval failures, including permission, I/O, allocation and unsupported-operation errors, must remain distinguishable.
  6. Whether a supported descriptor-bound verification method exists that preserves fail-closed security checks and object identity binding; please identify its guarantees and limitations regarding pathname/ancestor changes and later operations.
  7. Whether relevant behavior is specific to this macOS/APFS version.

Please identify the applicable public documentation or supported verification sequence. We do not want to modify permissions, synthesize ACLs on target objects, treat invalid-attribute defaults as real evidence, or accept ambiguous omission/ENOENT as success. Existing ownership, mode and confinement checks must remain separate mandatory checks. If no public API can make this distinction reliably, please state that limitation.

I would treat NULL plus ENOENT as inconclusive for the security decision here. Apple’s acl_get_file man page documents ENOENT as the named object not existing (or an empty pathname); it does not document ENOENT as a portable signal that an existing object has no ACL: https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/acl_get_file.3.html

Likewise, I would not promote an omitted ATTR_CMN_EXTENDED_SECURITY returned bit to proof of absence unless the getattrlist contract for this attribute explicitly guarantees that interpretation. Your descriptor-bound query and fixture results are good evidence to include in a Feedback Assistant report, but I can’t find a public contract that resolves the APFS/macOS 27 case you describe. For a fail-closed check, keep treating both outcomes as “ACL state not established” and avoid using them to authorize the operation until Apple confirms a supported distinction.

Thank you, Pokpong, for the thoughtful response and for clarifying the limits of the documented API behavior.

Your interpretation aligns with the fail-closed approach we are taking. We will continue treating both NULL/ENOENT from the ACL getters and omission of ATTR_CMN_EXTENDED_SECURITY as inconclusive, rather than interpreting either as proof of ACL absence.

The key unresolved question is whether Apple provides a supported, descriptor-bound mechanism to distinguish an absent ACL from unavailable ACL evidence for an existing APFS object, particularly when explicit zero-entry ACLs can exhibit similar behavior.

We will preserve the fixture results and descriptor-bound observations for a potential Feedback Assistant submission and seek clarification of the supported API contract rather than relying on undocumented behavior.

For now, the affected security-sensitive operation will remain blocked wherever ACL absence cannot be established reliably.

Thank you again for the guidance and the documentation reference. It is helpful in confirming the appropriate security boundary while the underlying API semantics remain unresolved.

Quinn recommended consolidating my related question into this thread.

I have a similar observation on a system reported as macOS Monterey 12.7.6 (21H1320), Intel, with the relevant volume reported as APFS; the filesystem type has not been independently reverified.

An existing empty regular file was opened read-only with O_NOFOLLOW and O_CLOEXEC. Before/after pathname and descriptor metadata matched. One acl_get_fd_np(fd, ACL_TYPE_EXTENDED) call returned NULL with ENOENT, captured immediately after clearing errno. This does not independently establish ACL absence.

I have not tested fgetattrlist or controlled ACL fixtures on this host, so I am not claiming the additional results described in the original post.

Could Apple clarify a supported descriptor-bound sequence for distinguishing absent ACLs, explicit zero-entry ACLs, and unavailable ACL evidence, including whether that guidance applies to Monterey?

My related question, for context:
https://developer.apple.com/forums/thread/855564

Thank you. I’ll keep further discussion here.

So, let me jump straight to the most critical question here:

How explicit zero-entry ACLs differ from absent ACL records through supported interfaces.

Practically speaking, I don't think they do. That is, as far as the security system is concerned, a zero-entry record and an absent record are the same thing. Have you found some difference in your own testing?

In terms of the difference in behavior between the two APIs, the difference is that you actually asked different parts of the system slightly different questions.

Starting with acl_get_file, you asked "does this file have an ACL“ The answer for both cases (absent and zero entry) is "no.” Some platforms do return the zero-entry case, but, as far as macOS is concerned, those are exactly the same situation, and returning NULL means user space has to do slightly less work, so it returned NULL.

Shifting to getattrlist/ATTR_CMN_EXTENDED_SECURITY, what you're now doing is asking the file system "what data is stored at this attribute," and apfs then responded by returning exactly the data it had stored, either returning "nothing" or returning the zero entry. The key point here is that APFS (and most other file systems) does this because it doesn't really know or care what the actual content of that acl data “is.” It manages the data as an opaque data blob with little if any additional interpretation.

As an aside, I believe there are file systems where getattrlist will ALWAYS return a record— if a file system has dedicated ACL metadata storage, then it can't be "empty.”

That then leads to here:

I would treat NULL plus ENOENT as inconclusive for the security decision here. Apple’s acl_get_file man page documents ENOENT as the named object not existing (or an empty pathname); it does not document ENOENT as a portable signal that an existing object has no ACL:

I'm double-checking with the engineering team, but my understanding of the situation is that ENOENT should be interpreted as the ACL not existing.

However, if you really want to differentiate between these two cases:

Likewise, I would not promote an omitted ATTR_CMN_EXTENDED_SECURITY returned bit to proof of absence unless the getattrlist contract for this attribute explicitly guarantees that interpretation.

Ummm... huh? As far as I can tell, these are the only two API paths that will "get" you to this data and, more to the point, both of them are getting the data from the ACL data attached to the vnode in the kernel. If the data returned by ATTR_CMN_EXTENDED_SECURITY doesn't qualify as "proof of absence", then nothing else will either.

Frankly, this is getting very close to the metaphysical question "Can a file have an ACL if its ACL data cannot be retrieved by any API and if its security permissions behave exactly like it did not have an ACL?". My personal opinion would be "no", but I'm willing to keep an open mind.

The key unresolved question is whether Apple provides a supported, descriptor-bound mechanism to distinguish an absent ACL from unavailable ACL evidence for an existing APFS object, particularly when explicit zero-entry ACLs can exhibit similar behavior.

I think this API is called "fgetattrlist".

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you, Kevin. Your explanation clarifies an important distinction.

We do not need to distinguish a physically absent ACL record from a zero-entry ACL. For our check, those cases can be treated equivalently. Our requirement is to determine whether the object has any ACL entries. POSIX ownership, mode bits and other access controls remain separate checks.

Our experiments established differences in API representation; they did not establish a difference in access-control behavior between absent and zero-entry ACLs.

Once your check with the engineering team is complete, could you please clarify:

  1. For acl_get_fd_np(fd, ACL_TYPE_EXTENDED), may NULL with immediately captured errno == ENOENT be interpreted as “no ACL entries” when fd is valid and identifies the same existing object throughout the observation? Are there additional conditions or exceptions we should check?
  2. When fgetattrlist succeeds and returns a structurally valid response, requesting ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_EXTENDED_SECURITY without FSOPT_PACK_INVAL_ATTRS, may omission of the extended-security returned bit be interpreted as no ACL entries, or are there other successful omission cases that must remain distinguishable?
  3. Does this guidance also apply to macOS 26.5 / build 25F71, used in our earlier tests, as well as macOS 27.0.1 / build 26A434 discussed here?
    We will continue to reject genuine retrieval errors and will not interpret default values of unreturned attributes as observed data.

Thank you for checking this with engineering.

ps. more info here: https://developer.apple.com/forums/thread/855333

For acl_get_fd_np(fd, ACL_TYPE_EXTENDED), may NULL with immediately captured errno == ENOENT be interpreted as “no ACL entries” when fd is valid and identifies the same existing object throughout the observation?

Yes. Putting that another way, there isn't any situation where:

  • acl_get* returns ENOENT.

BUT

  • Some secret/special/hidden acl state would alter the security behavior of the file.

The system has to be able to return an ACL entry for that ACL entry to impact the permissions of a given file.

Are there additional conditions or exceptions we should check?

No?

When fgetattrlist succeeds and returns a structurally valid response, requesting ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_EXTENDED_SECURITY without FSOPT_PACK_INVAL_ATTRS, may omission of the extended-security returned bit be interpreted as no ACL entries?

Yes. getattrlist is retrieving exactly the same underlying data as acl_get, so if it doesn’t return data it's because the data isn't there. The fact it returns a zeroed record while acl_get returns NULL is a minor divergence in the details of their implementation, not a meaningful distinction.

or are there other successful omission cases that must remain distinguishable?

No?

Does this guidance also apply to macOS 26.5 / build 25F71, used in our earlier tests, as well as macOS 27.0.1 / build 26A434 discussed here?

While I have not gone to the trouble of validating this in any detail, I'd expect everything I've said to be true going all the way back to the point we introduced ACLs in MacOS X 10.4. The only difference you MIGHT find is that in the VERY distant past is that I think acl_get* used to return an empty record (getattrlist) instead of NULL.

I've been unable to find the bug again, but I have a very vague memory that acl_get* was actually modified to return "NULL" (instead of the empty record) because empty ACL records had become common enough that they were having a small but measurable impact[1] on some kinds of file copies. None of those impacts were particular significant in absolute terms, but small things do add up. In any case, given that the work was effectively pointless, the simplest solution was to simply bypass the entire acl processing path by returning NULL.

[1] When copying large volumes of very small files, the syscall impact and memory impact eventually add up, as does the minor additional storage cost. This is exacerbated by the fact that ACL support was retrofitted onto some file systems in less than optimal ways.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you for sharing your Monterey observations and consolidating the discussion here.

Your descriptor-bound acl_get_fd_np result is relevant because it suggests that the NULL/ENOENT behavior is not limited to macOS 27.

Kevin Elliott has since explained that he expects the underlying ACL semantics to have remained consistent across macOS versions, potentially dating back to Mac OS X 10.4, although he has not independently validated that full version range.

Your Monterey observation is therefore useful supporting evidence, but it should remain distinct from a controlled cross-version verification.

If you conduct additional tests, comparing acl_get_fd_np with a structurally validated fgetattrlist query on the same existing descriptor would be particularly useful.

Thank you for contributing your findings.

Thank you, Kevin. Your direct answers resolve the central ACL-absence interpretation questions for our implementation.

We understand your guidance as follows:

  1. For a valid descriptor identifying the same existing object, acl_get_fd_np(fd, ACL_TYPE_EXTENDED) returning NULL with immediately captured errno == ENOENT may be interpreted as indicating no ACL entries.

  2. When fgetattrlist succeeds with a structurally valid response requesting ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_EXTENDED_SECURITY, without FSOPT_PACK_INVAL_ATTRS, omission of the extended-security returned bit may likewise indicate no ACL entries.

  3. Absent and explicit zero-entry ACL records need not be distinguished for the ACL security behavior discussed.

  4. You expect these semantics to be consistent across macOS versions, although the historical version range has not been independently validated.

This gives us a much clearer basis for interpreting successful ACL-absence observations while continuing to reject genuine API failures, malformed responses, invalid descriptors, and other unverified conditions.

We will also retain separate checks for object identity, ownership, mode bits, and the relevant filesystem-operation boundaries.

One final question, specifically about the scope of this guidance:

Would you consider these interpretations supported macOS API semantics that developers may rely on for security-sensitive checks, or should they be treated as current implementation behavior pending formal documentation?

We are not requesting an additional API or a distinction between absent and zero-entry ACL storage. We simply want to record the appropriate assurance level of your clarification.

Thank you again for your time and detailed assistance.