Thanks for the detailed reply Mickaël! Mickaël Salaün <[email protected]> writes:
> Thanks for this patch series Abhinav! The code looks good overall, but > we should clarify the design. Sorry for the delayed response, it is on > my radar now. > > CCing Jeff and Daniel > > On Sat, Jul 19, 2025 at 05:13:10AM -0600, Abhinav Saxena wrote: >> This patch series introduces LANDLOCK_SCOPE_MEMFD_EXEC, a new Landlock >> scoping mechanism that restricts execution of anonymous memory file >> descriptors (memfd) created via memfd_create(2). This addresses security >> gaps where processes can bypass W^X policies and execute arbitrary code >> through anonymous memory objects. >> >> Fixes: <https://github.com/landlock-lsm/linux/issues/37> >> >> SECURITY PROBLEM >> `==============' >> >> Current Landlock filesystem restrictions do not cover memfd objects, >> allowing processes to: >> >> 1. Read-to-execute bypass: Create writable memfd, inject code, >> then execute via mmap(PROT_EXEC) or direct execve() >> 2. Anonymous execution: Execute code without touching the filesystem via >> execve(“/proc/self/fd/N”) where N is a memfd descriptor > >> 3. Cross-domain access violations: Pass memfd between processes to >> bypass domain restrictions > > Landlock only restricts access at open time, which is a useful property. > This enables to create more restricted sandboxes but still get access to > outside resources via trusted processes. If the process passing the FDs > is not trusted, the sandboxed process could just ask to execute > arbitrary code outside the sandbox anyway. > > However, the Landlock scopes are designed to block IPC from within a > sandbox to outside the sandbox. We could have a new scope to forbid a > sandbox process to receive or inherit file descriptors, but that would > be a different and generic feature. For compatibility reasons, this > might not be easy to implement and I think there are more important > features to implement before that. > > Thinking more about it, restricting memfd should not be a “scoped” flag > because the semantic is not the same, but we should have a new ruleset > property instead, something like “ruleset.denied” with a related > LANDLOCK_DENY_EXECUTE_MEMFD flag. This flag will only have an impact on > newly created memfd from a sandboxed process with this restriction at > creation time. This could be implemented with hook_file_alloc_security() > by checking if the file is indeed a memfd and checking inode->i_mode for > executability bits (which would imply MFD_NOEXEC_SEAL). > Thanks for the clarification! So if I understood correctly we are proposing adding a `denied` field to the `landlock_ruleset_attr` struct struct landlock_ruleset_attr { __u64 handled_access_fs; __u64 handled_access_net; __u64 scoped; __u64 denied; /* New field */ }; which allows memfd_create() to be allowed by default unless LANDLOCK_DENY_EXECUTE_MEMFD bit is set. Also it seems Thiébaud Weksteen’s patch[1] will land, and maybe we can use security_inode_init_security_anon instead? What do you think? Apologies for my ignorance, do we have to wait till his patch has landed into Linus’s tree? >> >> These scenarios can occur in sandboxed environments where filesystem >> access is restricted but memfd creation remains possible. >> >> IMPLEMENTATION >> `============' >> >> The implementation adds hierarchical execution control through domain >> scoping: >> >> Core Components: >> - is_memfd_file(): Reliable memfd detection via “memfd:” dentry prefix >> - domain_is_scoped(): Cross-domain hierarchy checking (moved to domain.c) >> - LSM hooks: mmap_file, file_mprotect, bprm_creds_for_exec >> - Creation-time restrictions: hook_file_alloc_security >> >> Security Matrix: >> Execution decisions follow domain hierarchy rules preventing both >> same-domain bypass attempts and cross-domain access violations while >> preserving legitimate hierarchical access patterns. >> >> Domain Hierarchy with LANDLOCK_SCOPE_MEMFD_EXEC: >> `=============================================' >> >> Root (no domain) - No restrictions >> | >> +– Domain A [SCOPE_MEMFD_EXEC] Layer 1 >> | +– memfd_A (tagged with Domain A as creator) >> | | >> | +– Domain A1 (child) [NO SCOPE] Layer 2 >> | | +– Inherits Layer 1 restrictions from parent >> | | +– memfd_A1 (can create, inherits restrictions) >> | | +– Domain A1a [SCOPE_MEMFD_EXEC] Layer 3 >> | | +– memfd_A1a (tagged with Domain A1a) >> | | >> | +– Domain A2 (child) [SCOPE_MEMFD_EXEC] Layer 2 >> | +– memfd_A2 (tagged with Domain A2 as creator) >> | +– CANNOT access memfd_A1 (different subtree) >> | >> +– Domain B [SCOPE_MEMFD_EXEC] Layer 1 >> +– memfd_B (tagged with Domain B as creator) >> +– CANNOT access ANY memfd from Domain A subtree >> >> Execution Decision Matrix: >> `======================' >> Executor-> | A | A1 | A1a | A2 | B | Root >> Creator | | | | | | >> ————|—–|—-|—–|—-|—-|—– >> Domain A | X | X | X | X | X | Y >> Domain A1 | Y | X | X | X | X | Y >> Domain A1a | Y | Y | X | X | X | Y >> Domain A2 | Y | X | X | X | X | Y >> Domain B | X | X | X | X | X | Y >> Root | Y | Y | Y | Y | Y | Y >> >> Legend: Y = Execution allowed, X = Execution denied > > Because checks should not be related to scopes, this will be much > simpler. > >> >> Scenarios Covered: >> - Direct mmap(PROT_EXEC) on memfd files >> - Two-stage mmap(PROT_READ) + mprotect(PROT_EXEC) bypass attempts >> - execve("/proc/self/fd/N") anonymous execution >> - execveat() and fexecve() file descriptor execution >> - Cross-process memfd inheritance and IPC passing >> >> TESTING >> `=====' >> >> All patches have been validated with: >> - scripts/checkpatch.pl –strict (clean) >> - Selftests covering same-domain restrictions, cross-domain >> hierarchy enforcement, and regular file isolation >> - KUnit tests for memfd detection edge cases > > Thanks for all these tests! > >> >> DISCLAIMER >> `========' >> >> My understanding of Landlock scoping semantics may be limited, but this >> implementation reflects my current understanding based on available >> documentation and code analysis. I welcome feedback and corrections >> regarding the scoping logic and domain hierarchy enforcement. >> >> Signed-off-by: Abhinav Saxena <[email protected]> >> — >> Abhinav Saxena (4): >> landlock: add LANDLOCK_SCOPE_MEMFD_EXEC scope >> landlock: implement memfd detection >> landlock: add memfd exec LSM hooks and scoping >> selftests/landlock: add memfd execution tests >> >> include/uapi/linux/landlock.h | 5 + >> security/landlock/.kunitconfig | 1 + >> security/landlock/audit.c | 4 + >> security/landlock/audit.h | 1 + >> security/landlock/cred.c | 14 - >> security/landlock/domain.c | 67 ++++ >> security/landlock/domain.h | 4 + >> security/landlock/fs.c | 405 >> ++++++++++++++++++++- >> security/landlock/limits.h | 2 +- >> security/landlock/task.c | 67 —- >> …/selftests/landlock/scoped_memfd_exec_test.c | 325 +++++++++++++++++ >> 11 files changed, 812 insertions(+), 83 deletions(-) >> — >> base-commit: 5b74b2eff1eeefe43584e5b7b348c8cd3b723d38 >> change-id: 20250716-memfd-exec-ac0d582018c3 >> >> Best regards, >> – >> Abhinav Saxena <[email protected]> >> >> Best, Abhinav [1] - <https://lore.kernel.org/all/[email protected]/>

