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(-)
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 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 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 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 <xandfury@gmail.com> --- 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 <xandfury@gmail.com>
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). > > 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 <xandfury@gmail.com> > --- > 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 <xandfury@gmail.com> > >
Thanks for the detailed reply Mickaël! Mickaël Salaün <mic@digikod.net> 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 <xandfury@gmail.com> >> — >> 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 <xandfury@gmail.com> >> >> Best, Abhinav [1] - <https://lore.kernel.org/all/20250918020434.1612137-1-tweek@google.com/>
© 2016 - 2025 Red Hat, Inc.