fuse: expose POSIX ACLs on Linux mounts, fixes #1042#9954
Open
ThomasWaldmann wants to merge 2 commits into
Open
fuse: expose POSIX ACLs on Linux mounts, fixes #1042#9954ThomasWaldmann wants to merge 2 commits into
ThomasWaldmann wants to merge 2 commits into
Conversation
The FUSE mount did not expose the archived ACLs at all, so tools reading
from a mounted archive (getfacl, rsync -A, cp --preserve=all, ...) did not
see them.
On Linux, POSIX ACLs are exposed by the kernel via the special
system.posix_acl_access / system.posix_acl_default xattrs, which use a
binary format (see linux/include/uapi/linux/posix_acl_xattr.h): a 4 byte
little-endian version header, followed by 8 byte entries of
{__le16 e_tag, __le16 e_perm, __le32 e_id}.
Add acl_text_to_xattr() to convert from our stored ACL text representation
(acl_access / acl_default) to that binary format, honouring numeric_ids the
same way borg extract does, and serve these xattrs from listxattr/getxattr
in both FUSE implementations (fuse.py for llfuse/pyfuse3, hlfuse.py for
mfusepy).
Note that a previous attempt at this (borgbackup#8843) returned libacl's internal
acl_t structure (via acl_size), which is not the kernel's xattr wire
format - that is why it did not work. The new conversion is tested against
what the kernel itself produces: acl_text_to_xattr() output must be byte
identical to the system.posix_acl_access xattr the kernel returns for the
same ACL.
The ACLs are exposed read-only and are not enforced for permission checks
(we do not negotiate FUSE_POSIX_ACL). Also note that since kernel 6.2, the
kernel refuses ACL xattr passthrough for FUSE mounts whose superblock lives
in a non-initial user namespace (e.g. rootless containers) unless
FUSE_POSIX_ACL was negotiated, which none of the Python FUSE bindings
support - the test skips in that case.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #9954 +/- ##
==========================================
+ Coverage 85.49% 85.51% +0.02%
==========================================
Files 93 93
Lines 16122 16152 +30
Branches 2466 2470 +4
==========================================
+ Hits 13783 13813 +30
Misses 1632 1632
Partials 707 707 ☔ View full report in Codecov by Harness. |
The error paths of the ACL xattr emulation (no such ACL -> ENOATTR, unconvertible ACL -> EIO) are not reachable via a real mount, so they were not covered. Add tests that call listxattr/getxattr directly with a fake item - these also work where the kernel does not offer ACL xattrs on FUSE mounts (mounts made inside a user namespace). Also decide once, at import time, whether the ACL xattrs can be emulated: ACL_XATTRS is empty if not, so the handlers do not need to check is_linux.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1042.
The FUSE mount did not expose the archived ACLs at all, so tools reading from a mounted archive (
getfacl,rsync -A,cp --preserve=all, ...) did not see them.What it does
On Linux, POSIX ACLs are exposed by the kernel via the special
system.posix_acl_access/system.posix_acl_defaultxattrs, which use a binary format (seelinux/include/uapi/linux/posix_acl_xattr.h): a 4 byte little-endian version header, followed by 8 byte entries of{__le16 e_tag, __le16 e_perm, __le32 e_id}.acl_text_to_xattr()(platform/linux.pyx) converts from our stored ACL text representation (acl_access/acl_default) to that binary format. It reuses the existingacl_use_local_uid_gid/posix_acl_use_stored_uid_gidconverters, so--numeric-idsbehaves like it does forborg extract.listxattr/getxattrserve those xattrs in both FUSE implementations:fuse.py(llfuse/pyfuse3) andhlfuse.py(mfusepy).platform/base.py+ dispatch inplatform/__init__.py, so non-Linux platforms are unaffected.ValueError, which the FUSE layer turns into a warning +EIOinstead of killing the mount.Why the previous attempt did not work
#8843 returned libacl's internal
acl_tstructure (sized viaacl_size()). That is libacl's own representation, not the kernel's xattr wire format, so the kernel could not make sense of it.To make sure this does not happen again, the conversion is tested against what the kernel itself produces:
test_acl_text_to_xattr_matches_kernelsets an ACL on a real file, reads the rawsystem.posix_acl_accessxattr the kernel returns, and requiresacl_text_to_xattr()output to be byte identical to it (named and numeric mode).Limitations
FUSE_POSIX_ACL).facd61053cff), the kernel refuses ACL xattr passthrough for FUSE mounts whose superblock lives in a non-initial user namespace (e.g. a mount made inside a rootless container) unlessFUSE_POSIX_ACLwas negotiated - and none of the Python FUSE bindings expose that init flag. For init-userns mounts (normal hosts, CI) the kernel does the plain passthrough as before, so this works there. The e2e test skips with a clear reason in the userns case.Tests
test_acl_text_to_xattr- binary format / error handling unit test.test_acl_text_to_xattr_matches_kernel- byte-identity against the kernel, see above.test_fuse_acls- e2e: file + dir with access and default ACLs, archived and mounted, compared against the source fs objects both at raw xattr level and viaplatform.acl_get(), with and without--numeric-ids.Ran locally on Linux (container) with both
pyfuse3andmfusepy;mypy,ruffandblackare happy.Docs (mount epilog) updated - they claimed ACLs were not supported.