Re: [RFC PATCH v19 1/5] exec: Add a new AT_CHECK flag to execveat(2)
From: Mickaël Salaün <mic@digikod.net>
Date: 2024-07-05 17:53:30
Also in:
linux-fsdevel, linux-integrity, linux-security-module, lkml
On Thu, Jul 04, 2024 at 05:04:03PM -0700, Kees Cook wrote:
On Thu, Jul 04, 2024 at 09:01:33PM +0200, Mickaël Salaün wrote:quoted
Add a new AT_CHECK flag to execveat(2) to check if a file would be allowed for execution. The main use case is for script interpreters and dynamic linkers to check execution permission according to the kernel's security policy. Another use case is to add context to access logs e.g., which script (instead of interpreter) accessed a file. As any executable code, scripts could also use this check [1]. This is different than faccessat(2) which only checks file access rights, but not the full context e.g. mount point's noexec, stack limit, and all potential LSM extra checks (e.g. argv, envp, credentials). Since the use of AT_CHECK follows the exact kernel semantic as for a real execution, user space gets the same error codes.Nice! I much prefer this method of going through the exec machinery so we always have a single code path for these kinds of checks.quoted
Because AT_CHECK is dedicated to user space interpreters, it doesn't make sense for the kernel to parse the checked files, look for interpreters known to the kernel (e.g. ELF, shebang), and return ENOEXEC if the format is unknown. Because of that, security_bprm_check() is never called when AT_CHECK is used.I'd like some additional comments in the code that reminds us that access control checks have finished past a certain point.
Where in the code? Just before the bprm->is_check assignment?
[...]quoted
diff --git a/fs/exec.c b/fs/exec.c index 40073142288f..ea2a1867afdc 100644 --- a/fs/exec.c +++ b/fs/exec.c@@ -931,7 +931,7 @@ 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_CHECK)) != 0) return ERR_PTR(-EINVAL); if (flags & AT_SYMLINK_NOFOLLOW) open_exec_flags.lookup_flags &= ~LOOKUP_FOLLOW;[...]quoted
+ * To avoid race conditions leading to time-of-check to time-of-use issues, + * AT_CHECK should be used with AT_EMPTY_PATH to check against a file + * descriptor instead of a path.I want this enforced by the kernel. Let's not leave trivial ToCToU foot-guns around. i.e.: if ((flags & AT_CHECK) == AT_CHECK && (flags & AT_EMPTY_PATH) == 0) return ERR_PTR(-EBADF);
There are valid use cases relying on pathnames. See Linus's comment: https://lore.kernel.org/r/CAHk-=whb=XuU=LGKnJWaa7LOYQz9VwHs8SLfgLbT5sf2VAbX1A@mail.gmail.com (local)