Skip to content

Commit

Permalink
Merge tag 'AT_EXECVE_CHECK-v6.14-rc1' of git://git.kernel.org/pub/scm…
Browse files Browse the repository at this point in the history
…/linux/kernel/git/kees/linux

Pull AT_EXECVE_CHECK from Kees Cook:

 - Implement AT_EXECVE_CHECK flag to execveat(2) (Mickaël Salaün)

 - Implement EXEC_RESTRICT_FILE and EXEC_DENY_INTERACTIVE securebits
   (Mickaël Salaün)

 - Add selftests and samples for AT_EXECVE_CHECK (Mickaël Salaün)

* tag 'AT_EXECVE_CHECK-v6.14-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux:
  ima: instantiate the bprm_creds_for_exec() hook
  samples/check-exec: Add an enlighten "inc" interpreter and 28 tests
  selftests: ktap_helpers: Fix uninitialized variable
  samples/check-exec: Add set-exec
  selftests/landlock: Add tests for execveat + AT_EXECVE_CHECK
  selftests/exec: Add 32 tests for AT_EXECVE_CHECK and exec securebits
  security: Add EXEC_RESTRICT_FILE and EXEC_DENY_INTERACTIVE securebits
  exec: Add a new AT_EXECVE_CHECK flag to execveat(2)
  • Loading branch information
torvalds committed Jan 23, 2025
2 parents 5ab889f + 95b3cda commit 21266b8
Show file tree
Hide file tree
Showing 29 changed files with 1,341 additions and 14 deletions.
144 changes: 144 additions & 0 deletions Documentation/userspace-api/check_exec.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
.. SPDX-License-Identifier: GPL-2.0
.. Copyright © 2024 Microsoft Corporation
===================
Executability check
===================

The ``AT_EXECVE_CHECK`` :manpage:`execveat(2)` flag, and the
``SECBIT_EXEC_RESTRICT_FILE`` and ``SECBIT_EXEC_DENY_INTERACTIVE`` securebits
are intended for script interpreters and dynamic linkers to enforce a
consistent execution security policy handled by the kernel. See the
`samples/check-exec/inc.c`_ example.

Whether an interpreter should check these securebits or not depends on the
security risk of running malicious scripts with respect to the execution
environment, and whether the kernel can check if a script is trustworthy or
not. For instance, Python scripts running on a server can use arbitrary
syscalls and access arbitrary files. Such interpreters should then be
enlighten to use these securebits and let users define their security policy.
However, a JavaScript engine running in a web browser should already be
sandboxed and then should not be able to harm the user's environment.

Script interpreters or dynamic linkers built for tailored execution environments
(e.g. hardened Linux distributions or hermetic container images) could use
``AT_EXECVE_CHECK`` without checking the related securebits if backward
compatibility is handled by something else (e.g. atomic update ensuring that
all legitimate libraries are allowed to be executed). It is then recommended
for script interpreters and dynamic linkers to check the securebits at run time
by default, but also to provide the ability for custom builds to behave like if
``SECBIT_EXEC_RESTRICT_FILE`` or ``SECBIT_EXEC_DENY_INTERACTIVE`` were always
set to 1 (i.e. always enforce restrictions).

AT_EXECVE_CHECK
===============

Passing the ``AT_EXECVE_CHECK`` flag to :manpage:`execveat(2)` only performs a
check on a regular file and returns 0 if execution of this file would be
allowed, ignoring the file format and then the related interpreter dependencies
(e.g. ELF libraries, script's shebang).

Programs should always perform this check to apply kernel-level checks against
files that are not directly executed by the kernel but passed to a user space
interpreter instead. All files that contain executable code, from the point of
view of the interpreter, should be checked. However the result of this check
should only be enforced according to ``SECBIT_EXEC_RESTRICT_FILE`` or
``SECBIT_EXEC_DENY_INTERACTIVE.``.

The main purpose of this flag is to improve the security and consistency of an
execution environment to ensure that direct file execution (e.g.
``./script.sh``) and indirect file execution (e.g. ``sh script.sh``) lead to
the same result. For instance, this can be used to check if a file is
trustworthy according to the caller's environment.

In a secure environment, libraries and any executable dependencies should also
be checked. For instance, dynamic linking should make sure that all libraries
are allowed for execution to avoid trivial bypass (e.g. using ``LD_PRELOAD``).
For such secure execution environment to make sense, only trusted code should
be executable, which also requires integrity guarantees.

To avoid race conditions leading to time-of-check to time-of-use issues,
``AT_EXECVE_CHECK`` should be used with ``AT_EMPTY_PATH`` to check against a
file descriptor instead of a path.

SECBIT_EXEC_RESTRICT_FILE and SECBIT_EXEC_DENY_INTERACTIVE
==========================================================

When ``SECBIT_EXEC_RESTRICT_FILE`` is set, a process should only interpret or
execute a file if a call to :manpage:`execveat(2)` with the related file
descriptor and the ``AT_EXECVE_CHECK`` flag succeed.

This secure bit may be set by user session managers, service managers,
container runtimes, sandboxer tools... Except for test environments, the
related ``SECBIT_EXEC_RESTRICT_FILE_LOCKED`` bit should also be set.

Programs should only enforce consistent restrictions according to the
securebits but without relying on any other user-controlled configuration.
Indeed, the use case for these securebits is to only trust executable code
vetted by the system configuration (through the kernel), so we should be
careful to not let untrusted users control this configuration.

However, script interpreters may still use user configuration such as
environment variables as long as it is not a way to disable the securebits
checks. For instance, the ``PATH`` and ``LD_PRELOAD`` variables can be set by
a script's caller. Changing these variables may lead to unintended code
executions, but only from vetted executable programs, which is OK. For this to
make sense, the system should provide a consistent security policy to avoid
arbitrary code execution e.g., by enforcing a write xor execute policy.

When ``SECBIT_EXEC_DENY_INTERACTIVE`` is set, a process should never interpret
interactive user commands (e.g. scripts). However, if such commands are passed
through a file descriptor (e.g. stdin), its content should be interpreted if a
call to :manpage:`execveat(2)` with the related file descriptor and the
``AT_EXECVE_CHECK`` flag succeed.

For instance, script interpreters called with a script snippet as argument
should always deny such execution if ``SECBIT_EXEC_DENY_INTERACTIVE`` is set.

This secure bit may be set by user session managers, service managers,
container runtimes, sandboxer tools... Except for test environments, the
related ``SECBIT_EXEC_DENY_INTERACTIVE_LOCKED`` bit should also be set.

Here is the expected behavior for a script interpreter according to combination
of any exec securebits:

1. ``SECBIT_EXEC_RESTRICT_FILE=0`` and ``SECBIT_EXEC_DENY_INTERACTIVE=0``

Always interpret scripts, and allow arbitrary user commands (default).

No threat, everyone and everything is trusted, but we can get ahead of
potential issues thanks to the call to :manpage:`execveat(2)` with
``AT_EXECVE_CHECK`` which should always be performed but ignored by the
script interpreter. Indeed, this check is still important to enable systems
administrators to verify requests (e.g. with audit) and prepare for
migration to a secure mode.

2. ``SECBIT_EXEC_RESTRICT_FILE=1`` and ``SECBIT_EXEC_DENY_INTERACTIVE=0``

Deny script interpretation if they are not executable, but allow
arbitrary user commands.

The threat is (potential) malicious scripts run by trusted (and not fooled)
users. That can protect against unintended script executions (e.g. ``sh
/tmp/*.sh``). This makes sense for (semi-restricted) user sessions.

3. ``SECBIT_EXEC_RESTRICT_FILE=0`` and ``SECBIT_EXEC_DENY_INTERACTIVE=1``

Always interpret scripts, but deny arbitrary user commands.

This use case may be useful for secure services (i.e. without interactive
user session) where scripts' integrity is verified (e.g. with IMA/EVM or
dm-verity/IPE) but where access rights might not be ready yet. Indeed,
arbitrary interactive commands would be much more difficult to check.

4. ``SECBIT_EXEC_RESTRICT_FILE=1`` and ``SECBIT_EXEC_DENY_INTERACTIVE=1``

Deny script interpretation if they are not executable, and also deny
any arbitrary user commands.

The threat is malicious scripts run by untrusted users (but trusted code).
This makes sense for system services that may only execute trusted scripts.

.. Links
.. _samples/check-exec/inc.c:
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/samples/check-exec/inc.c
1 change: 1 addition & 0 deletions Documentation/userspace-api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Security-related interfaces
mfd_noexec
spec_ctrl
tee
check_exec

Devices and I/O
===============
Expand Down
20 changes: 18 additions & 2 deletions fs/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,8 @@ static struct file *do_open_execat(int fd, struct filename *name, int flags)
.lookup_flags = LOOKUP_FOLLOW,
};

if ((flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
if ((flags &
~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH | AT_EXECVE_CHECK)) != 0)
return ERR_PTR(-EINVAL);
if (flags & AT_SYMLINK_NOFOLLOW)
open_exec_flags.lookup_flags &= ~LOOKUP_FOLLOW;
Expand Down Expand Up @@ -1564,6 +1565,21 @@ static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int fl
}
bprm->interp = bprm->filename;

/*
* At this point, security_file_open() has already been called (with
* __FMODE_EXEC) and access control checks for AT_EXECVE_CHECK will
* stop just after the security_bprm_creds_for_exec() call in
* bprm_execve(). Indeed, the kernel should not try to parse the
* content of the file with exec_binprm() nor change the calling
* thread, which means that the following security functions will not
* be called:
* - security_bprm_check()
* - security_bprm_creds_from_file()
* - security_bprm_committing_creds()
* - security_bprm_committed_creds()
*/
bprm->is_check = !!(flags & AT_EXECVE_CHECK);

retval = bprm_mm_init(bprm);
if (!retval)
return bprm;
Expand Down Expand Up @@ -1845,7 +1861,7 @@ static int bprm_execve(struct linux_binprm *bprm)

/* Set the unchanging part of bprm->cred */
retval = security_bprm_creds_for_exec(bprm);
if (retval)
if (retval || bprm->is_check)
goto out;

retval = exec_binprm(bprm);
Expand Down
7 changes: 6 additions & 1 deletion include/linux/binfmts.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ struct linux_binprm {
*/
point_of_no_return:1,
/* Set when "comm" must come from the dentry. */
comm_from_dentry:1;
comm_from_dentry:1,
/*
* Set by user space to check executability according to the
* caller's environment.
*/
is_check:1;
struct file *executable; /* Executable to pass to the interpreter */
struct file *interpreter;
struct file *file;
Expand Down
1 change: 1 addition & 0 deletions include/uapi/linux/audit.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@
#define AUDIT_INTEGRITY_RULE 1805 /* policy rule */
#define AUDIT_INTEGRITY_EVM_XATTR 1806 /* New EVM-covered xattr */
#define AUDIT_INTEGRITY_POLICY_RULE 1807 /* IMA policy rules */
#define AUDIT_INTEGRITY_USERSPACE 1808 /* Userspace enforced data integrity */

#define AUDIT_KERNEL 2000 /* Asynchronous audit record. NOT A REQUEST. */

Expand Down
4 changes: 4 additions & 0 deletions include/uapi/linux/fcntl.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,8 @@
#define AT_HANDLE_MNT_ID_UNIQUE 0x001 /* Return the u64 unique mount ID. */
#define AT_HANDLE_CONNECTABLE 0x002 /* Request a connectable file handle */

/* Flags for execveat2(2). */
#define AT_EXECVE_CHECK 0x10000 /* Only perform a check if execution
would be allowed. */

#endif /* _UAPI_LINUX_FCNTL_H */
24 changes: 23 additions & 1 deletion include/uapi/linux/securebits.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,32 @@
#define SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED \
(issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE_LOCKED))

/* See Documentation/userspace-api/check_exec.rst */
#define SECURE_EXEC_RESTRICT_FILE 8
#define SECURE_EXEC_RESTRICT_FILE_LOCKED 9 /* make bit-8 immutable */

#define SECBIT_EXEC_RESTRICT_FILE (issecure_mask(SECURE_EXEC_RESTRICT_FILE))
#define SECBIT_EXEC_RESTRICT_FILE_LOCKED \
(issecure_mask(SECURE_EXEC_RESTRICT_FILE_LOCKED))

/* See Documentation/userspace-api/check_exec.rst */
#define SECURE_EXEC_DENY_INTERACTIVE 10
#define SECURE_EXEC_DENY_INTERACTIVE_LOCKED 11 /* make bit-10 immutable */

#define SECBIT_EXEC_DENY_INTERACTIVE \
(issecure_mask(SECURE_EXEC_DENY_INTERACTIVE))
#define SECBIT_EXEC_DENY_INTERACTIVE_LOCKED \
(issecure_mask(SECURE_EXEC_DENY_INTERACTIVE_LOCKED))

#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \
issecure_mask(SECURE_NO_SETUID_FIXUP) | \
issecure_mask(SECURE_KEEP_CAPS) | \
issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE))
issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE) | \
issecure_mask(SECURE_EXEC_RESTRICT_FILE) | \
issecure_mask(SECURE_EXEC_DENY_INTERACTIVE))
#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1)

#define SECURE_ALL_UNPRIVILEGED (issecure_mask(SECURE_EXEC_RESTRICT_FILE) | \
issecure_mask(SECURE_EXEC_DENY_INTERACTIVE))

#endif /* _UAPI_LINUX_SECUREBITS_H */
9 changes: 9 additions & 0 deletions samples/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ config SAMPLE_CGROUP
help
Build samples that demonstrate the usage of the cgroup API.

config SAMPLE_CHECK_EXEC
bool "Exec secure bits examples"
depends on CC_CAN_LINK && HEADERS_INSTALL
help
Build a tool to easily configure SECBIT_EXEC_RESTRICT_FILE and
SECBIT_EXEC_DENY_INTERACTIVE, and a simple script interpreter to
demonstrate how they should be used with execveat(2) +
AT_EXECVE_CHECK.

source "samples/rust/Kconfig"

endif # SAMPLES
Expand Down
1 change: 1 addition & 0 deletions samples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

subdir-$(CONFIG_SAMPLE_AUXDISPLAY) += auxdisplay
subdir-$(CONFIG_SAMPLE_ANDROID_BINDERFS) += binderfs
subdir-$(CONFIG_SAMPLE_CHECK_EXEC) += check-exec
subdir-$(CONFIG_SAMPLE_CGROUP) += cgroup
obj-$(CONFIG_SAMPLE_CONFIGFS) += configfs/
obj-$(CONFIG_SAMPLE_CONNECTOR) += connector/
Expand Down
2 changes: 2 additions & 0 deletions samples/check-exec/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/inc
/set-exec
15 changes: 15 additions & 0 deletions samples/check-exec/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: BSD-3-Clause

userprogs-always-y := \
inc \
set-exec

userccflags += -I usr/include

.PHONY: all clean

all:
$(MAKE) -C ../.. samples/check-exec/

clean:
$(MAKE) -C ../.. M=samples/check-exec/ clean
Loading

0 comments on commit 21266b8

Please sign in to comment.