Files
libfuse/doc/README.extensions
Bernd Schubert 183e21e23b Document security and other extension format
Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
2026-06-06 10:15:20 +02:00

186 lines
5.8 KiB
Plaintext

FUSE Extensions
================
Overview
--------
FUSE extensions are a mechanism for passing additional metadata alongside regular
FUSE requests. Extensions are placed at the end of the request payload and can
contain various types of supplementary information such as security contexts or
supplementary group IDs.
Wire Protocol
-------------
Extensions are appended to the end of FUSE request messages. Their presence and
total size is indicated by the `total_extlen` field in the fuse_in_header.
struct fuse_in_header {
uint32_t len;
uint32_t opcode;
uint64_t unique;
uint64_t nodeid;
uint32_t uid;
uint32_t gid;
uint32_t pid;
uint16_t total_extlen; /* Total size of all extensions in 8-byte units */
uint16_t padding;
};
The total_extlen field:
- Measured in 8-byte units (not bytes)
- Allows encoding up to 512 KB of extensions in a 16-bit field
- Convert to bytes: total_bytes = total_extlen * 8
- All extensions combined are always 8-byte aligned (FUSE_REC_ALIGN)
Extension Location
------------------
Extensions are located at the END of the request buffer:
Non-io_uring:
[fuse_in_header][operation header][operation payload][extensions]
^
ext_start = buffer_end - (total_extlen * 8)
io_uring:
Headers: [fuse_in_header][operation header]
Payload: [operation payload][extensions]
^
ext_start = payload_end - (total_extlen * 8)
Extension Header Format
-----------------------
Extensions use a polymorphic header design where the same 8-byte structure
is interpreted differently based on the second field's value:
For security contexts (nr_secctx = 1..31):
struct fuse_secctx_header {
uint32_t size; /* Total size of this extension in bytes */
uint32_t nr_secctx; /* Number of security contexts (1-31) */
};
For other extensions (type >= 32):
struct fuse_ext_header {
uint32_t size; /* Total size of this extension in bytes */
uint32_t type; /* Extension type ID (32+) */
};
The second field determines interpretation:
- Value 0: Invalid / reserved
- Value 1-31: Security context header (nr_secctx)
- Value 32+: Other extension type (type ID)
Extension Types
---------------
Defined in enum fuse_ext_type:
FUSE_MAX_NR_SECCTX = 31
Maximum number of security contexts in a single extension.
Values 0-31 are reserved for fuse_secctx_header.
FUSE_EXT_GROUPS = 32
Supplementary groups extension (fuse_supp_groups).
Multiple Extensions
-------------------
A single request can contain multiple extensions. They are concatenated
sequentially, each 8-byte aligned:
[fuse_secctx_header + secctx data][padding][fuse_ext_header + groups data][padding]
To iterate:
1. Start at ext_start = buffer_end - (total_extlen * 8)
2. Read header (8 bytes)
3. Process extension based on second field value
4. Advance: next_ext = current_ext + FUSE_REC_ALIGN(header.size)
5. Repeat until reaching ext_end
Security Context Extension
--------------------------
Wire format for security context (type 0-31):
Offset 0-7: fuse_secctx_header { size, nr_secctx }
Offset 8: First fuse_secctx structure (8 bytes)
{
uint32_t size; /* Size of context VALUE only */
uint32_t padding; /* Always 0 */
}
Offset 16: Context name (null-terminated string)
Example: "security.selinux\0"
Offset N: Context value (size bytes)
Example: "unconfined_u:object_r:user_home_t:s0"
[Padding to 8-byte boundary]
[Additional contexts if nr_secctx > 1]
Example (72 bytes total):
Offset 0-7: { size=72, nr_secctx=1 }
Offset 8-15: { size=38, padding=0 }
Offset 16-32: "security.selinux\0" (17 bytes)
Offset 33-70: "unconfined_u:object_r:user_home_t:s0" (38 bytes)
Offset 71: 0x00 (padding)
Kernel Guarantees
-----------------
The kernel guarantees:
1. Extension data is always at the end of the buffer
2. total_extlen accurately represents the extension size
3. All extensions are 8-byte aligned (FUSE_REC_ALIGN)
4. Security context name strings are null-terminated (via strscpy)
5. Extension headers are well-formed
Userspace Requirements
----------------------
Userspace filesystem daemons must:
1. Check total_extlen in fuse_in_header
2. Calculate extension location: buffer_end - (total_extlen * 8)
3. Iterate through extensions checking the second header field
4. For security contexts (1-31): interpret as fuse_secctx_header
5. For other types (32+): interpret as fuse_ext_header
6. Validate all size fields to prevent buffer overruns
7. Use FUSE_REC_ALIGN when advancing to next extension
Capability Negotiation
----------------------
Extensions are only sent when negotiated during FUSE_INIT:
For security contexts:
Kernel flag: FUSE_SECURITY_CTX (1ULL << 32)
Libfuse flag: FUSE_CAP_SECURITY_CTX
The kernel only sends security context extensions for operations where
security labels can be atomically set during creation:
- FUSE_CREATE
- FUSE_MKNOD
- FUSE_MKDIR
- FUSE_SYMLINK
Alignment Considerations
------------------------
While extensions are 8-byte aligned relative to each other, their absolute
position in the buffer may not be 8-byte aligned because they follow
variable-length operation data (e.g., filenames).
Example:
[header:40][create_in:16][filename:9][extension:72]
^
Offset 65 - NOT 8-byte aligned!
Therefore, userspace must use memcpy() when reading extension headers to
avoid unaligned access issues on architectures that don't support it.
References
----------
- include/fuse_kernel.h: Extension structure definitions
- Linux kernel: fs/fuse/dir.c: get_security_context()
- Linux kernel: fs/fuse/dev.c: fuse_args_to_req()