Skip to content

initramfs: Inherit SELinux label on transient root tmpfs#2071

Open
andrewdunndev wants to merge 1 commit intobootc-dev:mainfrom
andrewdunndev:fix/selinux-transient-root-context
Open

initramfs: Inherit SELinux label on transient root tmpfs#2071
andrewdunndev wants to merge 1 commit intobootc-dev:mainfrom
andrewdunndev:fix/selinux-transient-root-context

Conversation

@andrewdunndev
Copy link
Contributor

@andrewdunndev andrewdunndev commented Mar 16, 2026

Summary

When root.transient is enabled, the overlay root at / gets SELinux label
tmpfs_t instead of root_t, causing services like systemd-networkd to crash.

This PR fixes the composefs backend by reading the SELinux label from the lower
(composefs) filesystem and setting it as rootcontext on the tmpfs used for
the overlay upper layer.

What changed

  • New mount_tmpfs_for_overlay() that reads security.selinux xattr from the
    base filesystem and sets rootcontext on the tmpfs before overlay creation
  • Uses the two-call fgetxattr pattern (query size → allocate → read) for
    correct handling of arbitrarily long MLS/MCS contexts
  • Uses CString (not str) to avoid requiring UTF-8 labels
  • Gracefully handles SELinux-disabled systems (fgetxattr error → no context set)
  • overlay_transient() passes the base fd through to inherit the correct label
  • Removed the now-unused mount_tmpfs() wrapper

Design decisions

  • rootcontext= over context=: rootcontext sets only the root inode's
    label, letting new files inherit labels from SELinux policy. context= would
    force a uniform label on all inodes in the tmpfs, which is too broad.
    (systemd uses context= in nspawn and has had issues with it — see
    nspawn volatile=overlay tmpfs labeled with "selinux-context" instead of "selinux-apifs-context" systemd/systemd#12292.)

  • Read from lower layer, not hardcode: The label is read from the composefs
    base filesystem rather than hardcoding root_t. This works regardless of the
    SELinux policy in use.

  • Silent fallback: If SELinux is not enabled or the xattr read fails, no
    context is set and behavior is unchanged from before this patch.

Related issues

Testing

This runs in the initramfs before the real root is mounted, so testing requires
a VM with SELinux enforcing and root.transient enabled. Verification:

# Before fix: / has tmpfs_t
ls -Zd /
# system_u:object_r:tmpfs_t:s0 /

# After fix: / has root_t
ls -Zd /
# system_u:object_r:root_t:s0 /

Closes #1992

Signed-off-by: Andrew Dunn andrew@dunn.dev

@bootc-bot bootc-bot bot requested a review from gursewak1997 March 16, 2026 13:41
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request correctly addresses an issue with SELinux labels on transient root filesystems by inheriting the label from the base filesystem. The implementation is well-structured, introducing a new function mount_tmpfs_for_overlay to handle the logic. My review includes a suggestion to refactor the extended attribute reading logic to be more robust and idiomatic by avoiding hardcoded buffer sizes.

@andrewdunndev andrewdunndev force-pushed the fix/selinux-transient-root-context branch 3 times, most recently from 25092c4 to 0c64f52 Compare March 16, 2026 14:30
gursewak1997
gursewak1997 previously approved these changes Mar 17, 2026
Copy link
Contributor

@gursewak1997 gursewak1997 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Collaborator

@cgwalters cgwalters left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is all OK, however...and this is not something you need to fix as part of this PR, but we are missing testing transient root at all I believe as part of gating CI here by default.

It's tricky because it would be another entry in our ever-growing matrix, I'll try to write something up for this.

let tmpfs = FsHandle::open("tmpfs")?;

if let Some(base_fd) = base {
// Read the SELinux label from the base filesystem root using the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to cap-std-ext::getxattr via Dir::reopen_dir. Force-pushed.

Comment on lines +244 to +245
// Construct a CString for fsconfig_set_string. This avoids
// requiring the label to be valid UTF-8 (per maintainer feedback).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me this is a perfect example of a "very obvious" LLM generated (I'm sure) comment that's just unnecessary. I'll try to update our AGENTS.md and such to try to suppress some of this, I have it in my personal one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, will keep replies terse going forward (and we adjusted our harness, you're detecting LLM assist correctly).

};

/// The SELinux extended attribute name.
const SELINUX_XATTR: &str = "security.selinux";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have this const elsewhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. Using the string literal inline since the existing consts in lsm.rs aren't reachable from initramfs.

Comment on lines +239 to +243
// The kernel may return a null-terminated byte string; strip it
// before constructing a CString (which adds its own terminator).
if label.last() == Some(&0) {
label.pop();
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, here be some 🐉 - we messed something up like this in ostree IIRC

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cap-std-ext::getxattr handles the buffer management now, so the manual null-terminator stripping is gone.

When root.transient is enabled, the tmpfs used as the overlay upper layer
has no SELinux context set, so its root directory gets the default tmpfs_t
label. This propagates to the overlay mountpoint at /, causing services
like systemd-networkd to refuse to start because they expect root_t.

Fix this by reading the SELinux label from the composefs lower layer via
fgetxattr(security.selinux) and setting it as rootcontext on the tmpfs
before creating the overlay. This ensures the overlay root inherits the
correct label from the base filesystem.

The rootcontext mount option sets only the root inode's label, preserving
per-file labeling from SELinux policy via type_transition rules. This is
preferable to context= which would force a uniform label on all inodes.

When SELinux is not enabled, fgetxattr returns an error and no context is
set, preserving the existing behavior.

Note: the ostree backend (otcore-prepare-root.c in ostreedev/ostree) has
the same issue but uses /run as the tmpfs backing store rather than
creating its own tmpfs. That fix would need to go through libcomposefs
mount options or the ostree repo directly.

Closes: bootc-dev#1992

AI-Assisted: yes
AI-Tools: GitLab Duo, OpenCode
Signed-off-by: Andrew Dunn <andrew@dunn.dev>
@andrewdunndev
Copy link
Contributor Author

Happy to help wire up a transient-root test or file an issue to track.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bad selinux tag for root dir when using transient root

3 participants