[PATCH RFC 0/4] landlock: add LANDLOCK_SCOPE_MEMFD_EXEC execution

Abhinav Saxena posted 4 patches 2 months, 2 weeks ago
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(-)
[PATCH RFC 0/4] landlock: add LANDLOCK_SCOPE_MEMFD_EXEC execution
Posted by Abhinav Saxena 2 months, 2 weeks ago
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>
Re: [PATCH RFC 0/4] landlock: add LANDLOCK_SCOPE_MEMFD_EXEC execution
Posted by Mickaël Salaün 2 weeks, 3 days ago
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>
> 
>
Re: [PATCH RFC 0/4] landlock: add LANDLOCK_SCOPE_MEMFD_EXEC execution
Posted by Abhinav Saxena 1 week ago
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/>