Skip to content
LinuxDeep Dive Published Updated 6 min readViews unavailable

Linux ID-Mapped Mounts: Ownership Views Without Recursive chown

Use a user-namespace mapping to present filesystem ownership through a mount-specific view, with explicit prerequisites, lifecycle, and failure checks.

A container may need a host directory to appear owned by a different UID/GID range without rewriting every inode on disk. A recursive chown is a poor general solution: it mutates shared data, can be expensive, and may be wrong when another consumer expects the original ownership. An ID-mapped mount offers a different tool: it applies a UID/GID mapping to one mount’s view of a filesystem.

The important distinction is that an ID-mapped mount changes how the VFS interprets ownership and performs permission checks through that mount; it does not recursively rewrite inode ownership on disk. It is not an authorization bypass, and the mapping does not make every filesystem eligible. Linux’s ID-mapping documentation explains how IDs are translated, while mount_setattr(2) defines the mount operation and its constraints.

Separate stored ownership from the view

Filesystem inodes carry kernel UID and GID values. A process observes IDs translated into its user namespace, and a mount can supply another mapping for interpreting IDs while accessing that filesystem. The same inode can therefore be reported with different numeric ownership through two differently mapped mounts, even though no ownership-changing operation has rewritten it. Unmapped IDs may appear as the configured overflow UID/GID rather than the original number.

User-namespace maps use rows of the form inside-ID outside-ID length. For example, a row 1000 1001 1 maps one ID: namespace UID 1000 corresponds to UID 1001 in the parent namespace. This notation is a mapping, not a request to recursively change file owners. Keep the direction of the mapping explicit when designing an application; comparing stat output from two namespaces without first understanding their maps can make unchanged data appear to have changed owners. See user_namespaces(7) for the map files and namespace rules.

This is useful for bind-mounting a directory into a container whose account numbering differs from the host, or for making a filesystem tree usable in a separate namespace without a one-time ownership migration. It is scoped to the mount object. Another mount of the same filesystem need not present the same ownership view, and processes that reach the tree through a different mount do not inherit this mount’s mapping.

The mount has to be configured before it is exposed

The file-descriptor mount API makes the lifecycle explicit. A typical flow is:

  1. Create or obtain a user namespace with the intended UID/GID map, and retain its namespace file descriptor.
  2. Create a detached clone of the source mount with open_tree() and OPEN_TREE_CLONE.
  3. Call mount_setattr() on that detached mount with MOUNT_ATTR_IDMAP, passing the user-namespace descriptor in userns_fd.
  4. Attach the configured mount at the destination with move_mount().

open_tree() creates a mount file descriptor; it does not by itself make the clone visible at a target path. mount_setattr() requires an ID-mapped mount to be detached (that is, not previously visible in a mount namespace), and the mapping on that mount object cannot later be cleared or replaced with mount_setattr(). Since Linux 6.15, open_tree_attr() can create a new detached clone with a different mapping; see the open_tree(2) documentation. The mount API’s own descriptors and attachment rules are covered by open_tree(2), mount_setattr(2), and move_mount(2).

Conceptually, the critical call is not a path-based ownership rewrite. It is a property change on the mount descriptor:

struct mount_attr attr = {
    .attr_set = MOUNT_ATTR_IDMAP,
    .userns_fd = userns_fd,
};

/* Apply only to a detached mount descriptor. Check the syscall result. */
mount_setattr(mount_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr));

This is an illustrative fragment, not a complete mount program: it omits user-namespace creation, UID/GID-map setup, descriptor ownership, error handling, and the final move_mount(). A real caller must initialize the whole structure to zero, check every return value, and close descriptors on all paths. The kernel manual documents AT_EMPTY_PATH for operating on the mount referenced by a descriptor.

Check support and privilege before designing around it

An ID-mapped mount requires kernel support, filesystem support, and a mapping that is valid for the intended user namespace. Filesystem support varies; a kernel that recognizes MOUNT_ATTR_IDMAP can still reject a particular filesystem. The caller also needs the capabilities required in the relevant user-namespace and mount context. Do not infer any of this from a distribution name or from the fact that ordinary mounts work.

Common failure clues include:

  • EBADF: userns_fd is not a valid file descriptor.
  • EINVAL: userns_fd is valid but does not refer to a user namespace, the mount is not detached, the filesystem does not support ID-mapped mounts, or the requested attribute is unsupported.
  • EBUSY: the mount still has files open for writing when the caller tries to apply the mapping.
  • EPERM: the caller lacks the required authority for the mount or namespace operation.

These are examples, not a complete errno table; consult the target kernel’s mount_setattr(2) error list before building a diagnostic parser. In particular, do not translate every EINVAL into “old kernel”: it may be a lifecycle, descriptor, or filesystem problem.

Validate the view without mutating the tree

Start with read-only observations. Record the namespace maps and compare a known file through the intended mount and through the original path:

cat /proc/self/uid_map
cat /proc/self/gid_map
stat -c '%u:%g %a %n' /host/source/known-file
stat -c '%u:%g %a %n' /container/view/known-file

The two stat commands are meaningful only when the paths actually resolve through the two mount views and the process namespaces are known. Use a disposable test file under an unprivileged account to test the expected read/write operation through the mapped mount. Both mount paths reach the same underlying file: a write through either view changes shared content visible through the other, while ownership reporting can differ by mount. Compare the expected ownership view and permission result; do not expect a write to leave the original file’s contents unchanged. Do not use chown -R as a diagnostic; it destroys the before/after evidence and changes shared state.

If access fails, inspect in a fixed order: confirm that the process sees the intended UID/GID map; verify the mount’s actual source and destination with mount-table tooling; check that the filesystem and running kernel support the operation; then inspect the syscall’s exact errno and capability context. If the IDs show the expected view but access is denied, remember that mode bits, ACLs, capabilities, LSM policy, mount flags, and directory traversal permissions still participate. An ID mapping alone does not grant access.

Boundaries and portability

ID-mapped mounts are a Linux VFS feature, not a portable POSIX contract. Container runtimes and filesystems may expose different user-facing controls, and support varies by kernel version and filesystem implementation. A mapping also cannot represent arbitrary per-file policy: it translates ranges of IDs. Validate the exact source filesystem, namespace arrangement, and host kernel used in production.

The syscall fragment above was not compiled or executed here, and no kernel/filesystem matrix was tested. Treat it as a lifecycle sketch; a deployment should run a non-destructive integration test on its target kernel and filesystem before relying on this behavior.

Related:

Sources:

Comments