From patchwork Thu Jul 4 19:01:33 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 13724209 Received: from smtp-190b.mail.infomaniak.ch (smtp-190b.mail.infomaniak.ch [185.125.25.11]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 852253219F for ; Thu, 4 Jul 2024 19:07:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.125.25.11 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720120057; cv=none; b=Kaq5EZ2D29+QA7mqWw/x40wldAUpGWXpmwMof+s/rfC+ArSbRhYVCfq8JcTcz2tO0pNm+7L+MIiQgga/EfjiBu9vTLnYcYc9QytHHXsO4uO4NS3KV3UpKiTDyfAHucDzAcZipTTddsQCNsQQJbtR5aTn5jUaa1ABXVIZYlS0iuM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720120057; c=relaxed/simple; bh=QkSeR/S7n24t9rwdBxmCu1WbP7aeAIrwOrKa2VVZbjA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=TYYJNVp00J6jNa/2HWlK5Gu5UOvk0Uboz02DnWb1xQE2B4BsY3ZJdRPnTVhA79Ft2WFBaKmXaiNxtgNpMEGOA0VCYpMxRDqcg/WsMW5zIjX2DHAdbUx9bw9USH8XVGLOKJkR5/eFz72wZ19hjFdvZ17/VwJUWU0fHZijFJRemts= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=RoJpMmrw; arc=none smtp.client-ip=185.125.25.11 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="RoJpMmrw" Received: from smtp-3-0001.mail.infomaniak.ch (smtp-3-0001.mail.infomaniak.ch [10.4.36.108]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4WFQxW5nlpzC3q; Thu, 4 Jul 2024 21:02:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1720119727; bh=Y9d96OsDMPb7+yCSisfZWoz+qtMBDpJ4FX9JzX/byVk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RoJpMmrwajgMIZzj+FDE8d2fMbX6V4zlKWa7FHgUGq82BsioXPo8PzYQfoHIFVUkz TV8A2QFNZM2+q+h0r43ok920fl0wvuPue9FRnVPait38jkHnUbIUXrLRWLEaLimXlR 1rX+c3eFRYDpywr2XT9ozXTEknnIa3ZHKGwnQiAQ= Received: from unknown by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4WFQxR1bl1z3JG; Thu, 4 Jul 2024 21:02:03 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Linus Torvalds , Paul Moore , Theodore Ts'o Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Alejandro Colomar , Aleksa Sarai , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , Christian Heimes , Dmitry Vyukov , Eric Biggers , Eric Chiang , Fan Wu , Florian Weimer , Geert Uytterhoeven , James Morris , Jan Kara , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Luis Chamberlain , "Madhavan T . Venkataraman" , Matt Bobrowski , Matthew Garrett , Matthew Wilcox , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Scott Shell , Shuah Khan , Stephen Rothwell , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Xiaoming Ni , Yin Fengwei , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org Subject: [RFC PATCH v19 1/5] exec: Add a new AT_CHECK flag to execveat(2) Date: Thu, 4 Jul 2024 21:01:33 +0200 Message-ID: <20240704190137.696169-2-mic@digikod.net> In-Reply-To: <20240704190137.696169-1-mic@digikod.net> References: <20240704190137.696169-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-integrity@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Infomaniak-Routing: alpha 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. With the information that a script interpreter is about to interpret a script, an LSM security policy can adjust caller's access rights or log execution request as for native script execution (e.g. role transition). This is possible thanks to the call to security_bprm_creds_for_exec(). Because LSMs may only change bprm's credentials, use of AT_CHECK with current kernel code should not be a security issue (e.g. unexpected role transition). LSMs willing to update the caller's credential could now do so when bprm->is_check is set. Of course, such policy change should be in line with the new user space code. 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. It should be noted that script interpreters cannot directly use execveat(2) (without this new AT_CHECK flag) because this could lead to unexpected behaviors e.g., `python script.sh` could lead to Bash being executed to interpret the script. Unlike the kernel, script interpreters may just interpret the shebang as a simple comment, which should not change for backward compatibility reasons. Because scripts or libraries files might not currently have the executable permission set, or because we might want specific users to be allowed to run arbitrary scripts, the following patch provides a dynamic configuration mechanism with the SECBIT_SHOULD_EXEC_CHECK and SECBIT_SHOULD_EXEC_RESTRICT securebits. This is a redesign of the CLIP OS 4's O_MAYEXEC: https://github.com/clipos-archive/src_platform_clip-patches/blob/f5cb330d6b684752e403b4e41b39f7004d88e561/1901_open_mayexec.patch This patch has been used for more than a decade with customized script interpreters. Some examples can be found here: https://github.com/clipos-archive/clipos4_portage-overlay/search?q=O_MAYEXEC Cc: Al Viro Cc: Christian Brauner Cc: Kees Cook Cc: Paul Moore Link: https://docs.python.org/3/library/io.html#io.open_code [1] Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240704190137.696169-2-mic@digikod.net --- New design since v18: https://lore.kernel.org/r/20220104155024.48023-3-mic@digikod.net --- fs/exec.c | 5 +++-- include/linux/binfmts.h | 7 ++++++- include/uapi/linux/fcntl.h | 30 ++++++++++++++++++++++++++++++ kernel/audit.h | 1 + kernel/auditsc.c | 1 + 5 files changed, 41 insertions(+), 3 deletions(-) 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; @@ -1595,6 +1595,7 @@ static struct linux_binprm *alloc_bprm(int fd, struct filename *filename, int fl bprm->filename = bprm->fdpath; } bprm->interp = bprm->filename; + bprm->is_check = !!(flags & AT_CHECK); retval = bprm_mm_init(bprm); if (!retval) @@ -1885,7 +1886,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); diff --git a/include/linux/binfmts.h b/include/linux/binfmts.h index 70f97f685bff..8ff9c9e33aed 100644 --- a/include/linux/binfmts.h +++ b/include/linux/binfmts.h @@ -42,7 +42,12 @@ struct linux_binprm { * Set when errors can no longer be returned to the * original userspace. */ - point_of_no_return:1; + point_of_no_return: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; diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h index c0bcc185fa48..bcd05c59b7df 100644 --- a/include/uapi/linux/fcntl.h +++ b/include/uapi/linux/fcntl.h @@ -118,6 +118,36 @@ #define AT_HANDLE_FID AT_REMOVEDIR /* file handle is needed to compare object identity and may not be usable to open_by_handle_at(2) */ + +/* + * AT_CHECK 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). AT_CHECK + * should only be used if SECBIT_SHOULD_EXEC_CHECK is set for the calling + * thread. See securebits.h documentation. + * + * Programs should use this check to apply kernel-level checks against files + * that are not directly executed by the kernel but directly passed to a user + * space interpreter instead. All files that contain executable code, from the + * point of view of the interpreter, should be checked. 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_CHECK should be used with AT_EMPTY_PATH to check against a file + * descriptor instead of a path. + */ +#define AT_CHECK 0x10000 + #if defined(__KERNEL__) #define AT_GETATTR_NOSEC 0x80000000 #endif diff --git a/kernel/audit.h b/kernel/audit.h index a60d2840559e..8ebdabd2ab81 100644 --- a/kernel/audit.h +++ b/kernel/audit.h @@ -197,6 +197,7 @@ struct audit_context { struct open_how openat2; struct { int argc; + bool is_check; } execve; struct { char *name; diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 6f0d6fb6523f..b6316e284342 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -2662,6 +2662,7 @@ void __audit_bprm(struct linux_binprm *bprm) context->type = AUDIT_EXECVE; context->execve.argc = bprm->argc; + context->execve.is_check = bprm->is_check; } From patchwork Thu Jul 4 19:01:34 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 13724204 Received: from smtp-bc08.mail.infomaniak.ch (smtp-bc08.mail.infomaniak.ch [45.157.188.8]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 08A5713A252; Thu, 4 Jul 2024 19:02:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.157.188.8 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119735; cv=none; b=eqRQa+P9RTmhJZNBdAsgsasuYaOYeB+L1ftTPKaz0d9ag4tzQhU/TpiSIoQsLvgar8BQSiB9lmVMuTghfQIz7L+PFzc7/20GaQQxW8V1xSsyjecrwUQRnykFB62IdyBB+n2zcn33bxOH4YSsAcypxCxHcucevka3u5XTQ8TrPz4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119735; c=relaxed/simple; bh=EBHdqI81Shfe7Ji2XgN1x+T8QX1m9WCcGRjbASaPEi8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=IkMDR/2MDPcEa6FClGVStHkwqlewr3+vH8mlq6FFNwkJkU+RwAngDjTo3r/EbXuabx+e+STL9aUpqdTKlfPNzceTo5d9tIr9LHZfBSEgnG/gdMYErMDbfq1kM+DGmwJjeRhPA9QaXxgsaD5aW1kXdYjS6OtnGW/OMwlW8BmMOTc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=qDP+FohS; arc=none smtp.client-ip=45.157.188.8 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="qDP+FohS" Received: from smtp-3-0000.mail.infomaniak.ch (smtp-3-0000.mail.infomaniak.ch [10.4.36.107]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4WFQxX6ncnz157l; Thu, 4 Jul 2024 21:02:08 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1720119728; bh=vlgO1yPB2S138oeh4MchQUq5CiNsXtuZszsawNERxBQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qDP+FohSa9f5xw0vJOaviMSAXsyQWam0Xx9SyLOrW3xaNe305z7EM3RN2aP6++27k CQnxUkS0Ytdxe17Vk7PtTTw6QNab0pAqj4c7bTaN2u31LglVcO4K80jbxaFCj2lhIq yUyequX2BN91Fu4sQv5yZEiM9rhXzXijUDXRkzf4= Received: from unknown by smtp-3-0000.mail.infomaniak.ch (Postfix) with ESMTPA id 4WFQxX1QNszVW3; Thu, 4 Jul 2024 21:02:08 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Linus Torvalds , Paul Moore , Theodore Ts'o Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Alejandro Colomar , Aleksa Sarai , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , Christian Heimes , Dmitry Vyukov , Eric Biggers , Eric Chiang , Fan Wu , Florian Weimer , Geert Uytterhoeven , James Morris , Jan Kara , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Luis Chamberlain , "Madhavan T . Venkataraman" , Matt Bobrowski , Matthew Garrett , Matthew Wilcox , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Scott Shell , Shuah Khan , Stephen Rothwell , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Xiaoming Ni , Yin Fengwei , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org Subject: [RFC PATCH v19 2/5] security: Add new SHOULD_EXEC_CHECK and SHOULD_EXEC_RESTRICT securebits Date: Thu, 4 Jul 2024 21:01:34 +0200 Message-ID: <20240704190137.696169-3-mic@digikod.net> In-Reply-To: <20240704190137.696169-1-mic@digikod.net> References: <20240704190137.696169-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-integrity@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Infomaniak-Routing: alpha These new SECBIT_SHOULD_EXEC_CHECK, SECBIT_SHOULD_EXEC_RESTRICT, and their *_LOCKED counterparts are designed to be set by processes setting up an execution environment, such as a user session, a container, or a security sandbox. Like seccomp filters or Landlock domains, the securebits are inherited across proceses. When SECBIT_SHOULD_EXEC_CHECK is set, programs interpreting code should check executable resources with execveat(2) + AT_CHECK (see previous patch). When SECBIT_SHOULD_EXEC_RESTRICT is set, a process should only allow execution of approved resources, if any (see SECBIT_SHOULD_EXEC_CHECK). For a secure environment, we might also want SECBIT_SHOULD_EXEC_CHECK_LOCKED and SECBIT_SHOULD_EXEC_RESTRICT_LOCKED to be set. For a test environment (e.g. testing on a fleet to identify potential issues), only the SECBIT_SHOULD_EXEC_CHECK* bits can be set to still be able to identify potential issues (e.g. with interpreters logs or LSMs audit entries). It should be noted that unlike other security bits, the SECBIT_SHOULD_EXEC_CHECK and SECBIT_SHOULD_EXEC_RESTRICT bits are dedicated to user space willing to restrict itself. Because of that, they only make sense in the context of a trusted environment (e.g. sandbox, container, user session, full system) where the process changing its behavior (according to these bits) and all its parent processes are trusted. Otherwise, any parent process could just execute its own malicious code (interpreting a script or not), or even enforce a seccomp filter to mask these bits. Such a secure environment can be achieved with an appropriate access control policy (e.g. mount's noexec option, file access rights, LSM configuration) and an enlighten ld.so checking that libraries are allowed for execution e.g., to protect against illegitimate use of LD_PRELOAD. Scripts may need some changes to deal with untrusted data (e.g. stdin, environment variables), but that is outside the scope of the kernel. The only restriction enforced by the kernel is the right to ptrace another process. Processes are denied to ptrace less restricted ones, unless the tracer has CAP_SYS_PTRACE. This is mainly a safeguard to avoid trivial privilege escalations e.g., by a debugging process being abused with a confused deputy attack. Cc: Al Viro Cc: Christian Brauner Cc: Kees Cook Cc: Paul Moore Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240704190137.696169-3-mic@digikod.net --- New design since v18: https://lore.kernel.org/r/20220104155024.48023-3-mic@digikod.net --- include/uapi/linux/securebits.h | 56 ++++++++++++++++++++++++++++- security/commoncap.c | 63 ++++++++++++++++++++++++++++----- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/include/uapi/linux/securebits.h b/include/uapi/linux/securebits.h index d6d98877ff1a..3fdb0382718b 100644 --- a/include/uapi/linux/securebits.h +++ b/include/uapi/linux/securebits.h @@ -52,10 +52,64 @@ #define SECBIT_NO_CAP_AMBIENT_RAISE_LOCKED \ (issecure_mask(SECURE_NO_CAP_AMBIENT_RAISE_LOCKED)) +/* + * When SECBIT_SHOULD_EXEC_CHECK is set, a process should check all executable + * files with execveat(2) + AT_CHECK. However, such check should only be + * performed if all to-be-executed code only comes from regular files. For + * instance, if a script interpreter is called with both a script snipped as + * argument and a regular file, the interpreter should not check any file. + * Doing otherwise would mislead the kernel to think that only the script file + * is being executed, which could for instance lead to unexpected permission + * change and break current use cases. + * + * This secure bit may be set by user session managers, service managers, + * container runtimes, sandboxer tools... Except for test environments, the + * related SECBIT_SHOULD_EXEC_CHECK_LOCKED bit should also be set. + * + * Ptracing another process is deny if the tracer has SECBIT_SHOULD_EXEC_CHECK + * but not the tracee. SECBIT_SHOULD_EXEC_CHECK_LOCKED also checked. + */ +#define SECURE_SHOULD_EXEC_CHECK 8 +#define SECURE_SHOULD_EXEC_CHECK_LOCKED 9 /* make bit-8 immutable */ + +#define SECBIT_SHOULD_EXEC_CHECK (issecure_mask(SECURE_SHOULD_EXEC_CHECK)) +#define SECBIT_SHOULD_EXEC_CHECK_LOCKED \ + (issecure_mask(SECURE_SHOULD_EXEC_CHECK_LOCKED)) + +/* + * When SECBIT_SHOULD_EXEC_RESTRICT is set, a process should only allow + * execution of approved files, if any (see SECBIT_SHOULD_EXEC_CHECK). For + * instance, script interpreters called with a script snippet as argument + * should always deny such execution if SECBIT_SHOULD_EXEC_RESTRICT is set. + * However, if a script interpreter is called with both + * SECBIT_SHOULD_EXEC_CHECK and SECBIT_SHOULD_EXEC_RESTRICT, they should + * interpret the provided script files if no unchecked code is also provided + * (e.g. directly as argument). + * + * This secure bit may be set by user session managers, service managers, + * container runtimes, sandboxer tools... Except for test environments, the + * related SECBIT_SHOULD_EXEC_RESTRICT_LOCKED bit should also be set. + * + * Ptracing another process is deny if the tracer has + * SECBIT_SHOULD_EXEC_RESTRICT but not the tracee. + * SECBIT_SHOULD_EXEC_RESTRICT_LOCKED is also checked. + */ +#define SECURE_SHOULD_EXEC_RESTRICT 10 +#define SECURE_SHOULD_EXEC_RESTRICT_LOCKED 11 /* make bit-8 immutable */ + +#define SECBIT_SHOULD_EXEC_RESTRICT (issecure_mask(SECURE_SHOULD_EXEC_RESTRICT)) +#define SECBIT_SHOULD_EXEC_RESTRICT_LOCKED \ + (issecure_mask(SECURE_SHOULD_EXEC_RESTRICT_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_SHOULD_EXEC_CHECK) | \ + issecure_mask(SECURE_SHOULD_EXEC_RESTRICT)) #define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) +#define SECURE_ALL_UNPRIVILEGED (issecure_mask(SECURE_SHOULD_EXEC_CHECK) | \ + issecure_mask(SECURE_SHOULD_EXEC_RESTRICT)) + #endif /* _UAPI_LINUX_SECUREBITS_H */ diff --git a/security/commoncap.c b/security/commoncap.c index 162d96b3a676..34b4493e2a69 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -117,6 +117,33 @@ int cap_settime(const struct timespec64 *ts, const struct timezone *tz) return 0; } +static bool ptrace_secbits_allowed(const struct cred *tracer, + const struct cred *tracee) +{ + const unsigned long tracer_secbits = SECURE_ALL_UNPRIVILEGED & + tracer->securebits; + const unsigned long tracee_secbits = SECURE_ALL_UNPRIVILEGED & + tracee->securebits; + /* Ignores locking of unset secure bits (cf. SECURE_ALL_LOCKS). */ + const unsigned long tracer_locked = (tracer_secbits << 1) & + tracer->securebits; + const unsigned long tracee_locked = (tracee_secbits << 1) & + tracee->securebits; + + /* The tracee must not have less constraints than the tracer. */ + if ((tracer_secbits | tracee_secbits) != tracee_secbits) + return false; + + /* + * Makes sure that the tracer's locks for restrictions are the same for + * the tracee. + */ + if ((tracer_locked | tracee_locked) != tracee_locked) + return false; + + return true; +} + /** * cap_ptrace_access_check - Determine whether the current process may access * another @@ -146,7 +173,8 @@ int cap_ptrace_access_check(struct task_struct *child, unsigned int mode) else caller_caps = &cred->cap_permitted; if (cred->user_ns == child_cred->user_ns && - cap_issubset(child_cred->cap_permitted, *caller_caps)) + cap_issubset(child_cred->cap_permitted, *caller_caps) && + ptrace_secbits_allowed(cred, child_cred)) goto out; if (ns_capable(child_cred->user_ns, CAP_SYS_PTRACE)) goto out; @@ -178,7 +206,8 @@ int cap_ptrace_traceme(struct task_struct *parent) cred = __task_cred(parent); child_cred = current_cred(); if (cred->user_ns == child_cred->user_ns && - cap_issubset(child_cred->cap_permitted, cred->cap_permitted)) + cap_issubset(child_cred->cap_permitted, cred->cap_permitted) && + ptrace_secbits_allowed(cred, child_cred)) goto out; if (has_ns_capability(parent, child_cred->user_ns, CAP_SYS_PTRACE)) goto out; @@ -1302,21 +1331,39 @@ int cap_task_prctl(int option, unsigned long arg2, unsigned long arg3, & (old->securebits ^ arg2)) /*[1]*/ || ((old->securebits & SECURE_ALL_LOCKS & ~arg2)) /*[2]*/ || (arg2 & ~(SECURE_ALL_LOCKS | SECURE_ALL_BITS)) /*[3]*/ - || (cap_capable(current_cred(), - current_cred()->user_ns, - CAP_SETPCAP, - CAP_OPT_NONE) != 0) /*[4]*/ /* * [1] no changing of bits that are locked * [2] no unlocking of locks * [3] no setting of unsupported bits - * [4] doing anything requires privilege (go read about - * the "sendmail capabilities bug") */ ) /* cannot change a locked bit */ return -EPERM; + /* + * Doing anything requires privilege (go read about the + * "sendmail capabilities bug"), except for unprivileged bits. + * Indeed, the SECURE_ALL_UNPRIVILEGED bits are not + * restrictions enforced by the kernel but by user space on + * itself. The kernel is only in charge of protecting against + * privilege escalation with ptrace protections. + */ + if (cap_capable(current_cred(), current_cred()->user_ns, + CAP_SETPCAP, CAP_OPT_NONE) != 0) { + const unsigned long unpriv_and_locks = + SECURE_ALL_UNPRIVILEGED | + SECURE_ALL_UNPRIVILEGED << 1; + const unsigned long changed = old->securebits ^ arg2; + + /* For legacy reason, denies non-change. */ + if (!changed) + return -EPERM; + + /* Denies privileged changes. */ + if (changed & ~unpriv_and_locks) + return -EPERM; + } + new = prepare_creds(); if (!new) return -ENOMEM; From patchwork Thu Jul 4 19:01:35 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 13724210 Received: from smtp-bc0d.mail.infomaniak.ch (smtp-bc0d.mail.infomaniak.ch [45.157.188.13]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0ECF77346D for ; Thu, 4 Jul 2024 19:07:34 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.157.188.13 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720120058; cv=none; b=Y6o8UEwUgF0+DWz8KyplusFbVM9HOCV+mRKOhCC9T86mFsLEBZoET5cLYPnaooHueKQdBSFV44PBS6EFXAwSP/RxEvEyZHohdnrWjcG8+IBZv0IYlqRdmHtKHeJi5zTqHDLvgjvOYxVfJDA6p9J8g/yiBhZ+H+34OSGEkVlV4sk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720120058; c=relaxed/simple; bh=ptPL5Y7LsG0j2z5Q1/Ky/IaneqRgLDbKXOWSzgDU8aU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=mDHvaokVu2DJGc2udjJwimwqLo/tlr4JhoL2IJeNEPiiBZI2onHqlF+3SAuCcUf5nuK3c4g8wZLAJt6BOBhzYC1bv9dh8fkqXwYvp6p/syi7LSPqWKiE1xuD8NHUm1m1/O3iZXG2JloH9h+DXsF2+zcMdLit0g5ZB9Nfwddex6I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=CmucNG2/; arc=none smtp.client-ip=45.157.188.13 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="CmucNG2/" Received: from smtp-4-0001.mail.infomaniak.ch (smtp-4-0001.mail.infomaniak.ch [10.7.10.108]) by smtp-4-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4WFQxb1B4fzBWp; Thu, 4 Jul 2024 21:02:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1720119731; bh=AUhi8anywy4RhQSH5ewJ+lFNdqrjTzLVRPfBkBYYM98=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=CmucNG2/x01v7szxMt8Zn5vhZCb4Swm3sO7FETMJCZ/9VNbPj4oa+mpK8qoIGSVuz rC0FZAQhVbfqPZNqZtUYP6kJU+EKUSe6bsBVxCZ8EDknVh6c8bWH4fTZrGElc1+1b9 p8kjm0p5FBkZGuJtUBEmkZUp5/DDNpVGxEJ0tZTo= Received: from unknown by smtp-4-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4WFQxY3CB3zWh5; Thu, 4 Jul 2024 21:02:09 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Linus Torvalds , Paul Moore , Theodore Ts'o Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Alejandro Colomar , Aleksa Sarai , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , Christian Heimes , Dmitry Vyukov , Eric Biggers , Eric Chiang , Fan Wu , Florian Weimer , Geert Uytterhoeven , James Morris , Jan Kara , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Luis Chamberlain , "Madhavan T . Venkataraman" , Matt Bobrowski , Matthew Garrett , Matthew Wilcox , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Scott Shell , Shuah Khan , Stephen Rothwell , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Xiaoming Ni , Yin Fengwei , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org Subject: [RFC PATCH v19 3/5] selftests/exec: Add tests for AT_CHECK and related securebits Date: Thu, 4 Jul 2024 21:01:35 +0200 Message-ID: <20240704190137.696169-4-mic@digikod.net> In-Reply-To: <20240704190137.696169-1-mic@digikod.net> References: <20240704190137.696169-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-integrity@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Infomaniak-Routing: alpha Test that checks performed by execveat(..., AT_CHECK) are consistent with noexec mount points and file execute permissions. Test that SECBIT_SHOULD_EXEC_CHECK and SECBIT_SHOULD_EXEC_RESTRICT are inherited by child processes and that they can be pinned with the appropriate SECBIT_SHOULD_EXEC_CHECK_LOCKED and SECBIT_SHOULD_EXEC_RESTRICT_LOCKED bits. Cc: Al Viro Cc: Christian Brauner Cc: Kees Cook Cc: Paul Moore Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240704190137.696169-4-mic@digikod.net --- Changes since v18: * Rewrite tests with the new design: execveat/AT_CHECK and securebits. * Simplify the capability dropping and improve it with the NOROOT securebits. * Replace most ASSERT with EXPECT. * Fix NULL execve's argv to avoid kernel warning. * Move tests to exec/ * Build a "false" static binary to test full execution path. Changes since v14: * Add Reviewed-by Kees Cook. Changes since v13: * Move -I to CFLAGS (suggested by Kees Cook). * Update sysctl name. Changes since v12: * Fix Makefile's license. Changes since v10: * Update selftest Makefile. Changes since v9: * Rename the syscall and the sysctl. * Update tests for enum trusted_for_usage Changes since v8: * Update with the dedicated syscall introspect_access(2) and the renamed fs.introspection_policy sysctl. * Remove check symlink which can't be use as is anymore. * Use socketpair(2) to test UNIX socket. Changes since v7: * Update tests with faccessat2/AT_INTERPRETED, including new ones to check that setting R_OK or W_OK returns EINVAL. * Add tests for memfd, pipefs and nsfs. * Rename and move back tests to a standalone directory. Changes since v6: * Add full combination tests for all file types, including block devices, character devices, fifos, sockets and symlinks. * Properly save and restore initial sysctl value for all tests. Changes since v5: * Refactor with FIXTURE_VARIANT, which make the tests much more easy to read and maintain. * Save and restore initial sysctl value (suggested by Kees Cook). * Test with a sysctl value of 0. * Check errno in sysctl_access_write test. * Update tests for the CAP_SYS_ADMIN switch. * Update tests to check -EISDIR (replacing -EACCES). * Replace FIXTURE_DATA() with FIXTURE() (spotted by Kees Cook). * Use global const strings. Changes since v3: * Replace RESOLVE_MAYEXEC with O_MAYEXEC. * Add tests to check that O_MAYEXEC is ignored by open(2) and openat(2). Changes since v2: * Move tests from exec/ to openat2/ . * Replace O_MAYEXEC with RESOLVE_MAYEXEC from openat2(2). * Cleanup tests. Changes since v1: * Move tests from yama/ to exec/ . * Fix _GNU_SOURCE in kselftest_harness.h . * Add a new test sysctl_access_write to check if CAP_MAC_ADMIN is taken into account. * Test directory execution which is always forbidden since commit 73601ea5b7b1 ("fs/open.c: allow opening only regular files during execve()"), and also check that even the root user can not bypass file execution checks. * Make sure delete_workspace() always as enough right to succeed. * Cosmetic cleanup. --- tools/testing/selftests/exec/.gitignore | 2 + tools/testing/selftests/exec/Makefile | 8 + tools/testing/selftests/exec/config | 2 + tools/testing/selftests/exec/false.c | 5 + tools/testing/selftests/exec/should-exec.c | 449 +++++++++++++++++++++ 5 files changed, 466 insertions(+) create mode 100644 tools/testing/selftests/exec/config create mode 100644 tools/testing/selftests/exec/false.c create mode 100644 tools/testing/selftests/exec/should-exec.c diff --git a/tools/testing/selftests/exec/.gitignore b/tools/testing/selftests/exec/.gitignore index 90c238ba6a4b..20e965dcc98e 100644 --- a/tools/testing/selftests/exec/.gitignore +++ b/tools/testing/selftests/exec/.gitignore @@ -9,8 +9,10 @@ execveat.ephemeral execveat.denatured non-regular null-argv +/false /load_address_* /recursion-depth +/should-exec xxxxxxxx* pipe S_I*.test diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile index fb4472ddffd8..fc0cb8925b02 100644 --- a/tools/testing/selftests/exec/Makefile +++ b/tools/testing/selftests/exec/Makefile @@ -2,15 +2,20 @@ CFLAGS = -Wall CFLAGS += -Wno-nonnull CFLAGS += -D_GNU_SOURCE +CFLAGS += $(KHDR_INCLUDES) + +LDLIBS += -lcap TEST_PROGS := binfmt_script.py TEST_GEN_PROGS := execveat load_address_4096 load_address_2097152 load_address_16777216 non-regular +TEST_GEN_PROGS_EXTENDED := false TEST_GEN_FILES := execveat.symlink execveat.denatured script subdir # Makefile is a run-time dependency, since it's accessed by the execveat test TEST_FILES := Makefile TEST_GEN_PROGS += recursion-depth TEST_GEN_PROGS += null-argv +TEST_GEN_PROGS += should-exec EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \ $(OUTPUT)/S_I*.test @@ -34,3 +39,6 @@ $(OUTPUT)/load_address_2097152: load_address.c $(CC) $(CFLAGS) $(LDFLAGS) -Wl,-z,max-page-size=0x200000 -pie -static $< -o $@ $(OUTPUT)/load_address_16777216: load_address.c $(CC) $(CFLAGS) $(LDFLAGS) -Wl,-z,max-page-size=0x1000000 -pie -static $< -o $@ + +$(OUTPUT)/false: false.c + $(CC) $(CFLAGS) $(LDFLAGS) -static $< -o $@ diff --git a/tools/testing/selftests/exec/config b/tools/testing/selftests/exec/config new file mode 100644 index 000000000000..c308079867b3 --- /dev/null +++ b/tools/testing/selftests/exec/config @@ -0,0 +1,2 @@ +CONFIG_BLK_DEV=y +CONFIG_BLK_DEV_LOOP=y diff --git a/tools/testing/selftests/exec/false.c b/tools/testing/selftests/exec/false.c new file mode 100644 index 000000000000..104383ec3a79 --- /dev/null +++ b/tools/testing/selftests/exec/false.c @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: GPL-2.0 +int main(void) +{ + return 1; +} diff --git a/tools/testing/selftests/exec/should-exec.c b/tools/testing/selftests/exec/should-exec.c new file mode 100644 index 000000000000..166276a39b4e --- /dev/null +++ b/tools/testing/selftests/exec/should-exec.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Test execveat(2) with AT_CHECK, and prctl(2) with SECBIT_SHOULD_EXEC_CHECK, + * SECBIT_SHOULD_EXEC_RESTRIC, and their locked counterparts. + * + * Copyright © 2018-2020 ANSSI + * Copyright © 2024 Microsoft Corporation + * + * Author: Mickaël Salaün + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Defines AT_CHECK without type conflicts. */ +#define _ASM_GENERIC_FCNTL_H +#include + +#include "../kselftest_harness.h" + +static void drop_privileges(struct __test_metadata *const _metadata) +{ + const unsigned int noroot = SECBIT_NOROOT | SECBIT_NOROOT_LOCKED; + cap_t cap_p; + + if ((cap_get_secbits() & noroot) != noroot) + EXPECT_EQ(0, cap_set_secbits(noroot)); + + cap_p = cap_get_proc(); + EXPECT_NE(NULL, cap_p); + EXPECT_NE(-1, cap_clear(cap_p)); + + /* + * Drops everything, especially CAP_SETPCAP, CAP_DAC_OVERRIDE, and + * CAP_DAC_READ_SEARCH. + */ + EXPECT_NE(-1, cap_set_proc(cap_p)); + EXPECT_NE(-1, cap_free(cap_p)); +} + +static int test_secbits_set(const unsigned int secbits) +{ + int err; + + err = prctl(PR_SET_SECUREBITS, secbits); + if (err) + return errno; + return 0; +} + +FIXTURE(access) +{ + int memfd, pipefd; + int pipe_fds[2], socket_fds[2]; +}; + +FIXTURE_VARIANT(access) +{ + const bool mount_exec; + const bool file_exec; +}; + +FIXTURE_VARIANT_ADD(access, mount_exec_file_exec){ + .mount_exec = true, + .file_exec = true, +}; + +FIXTURE_VARIANT_ADD(access, mount_exec_file_noexec){ + .mount_exec = true, + .file_exec = false, +}; + +FIXTURE_VARIANT_ADD(access, mount_noexec_file_exec){ + .mount_exec = false, + .file_exec = true, +}; + +FIXTURE_VARIANT_ADD(access, mount_noexec_file_noexec){ + .mount_exec = false, + .file_exec = false, +}; + +static const char binary_path[] = "./false"; +static const char workdir_path[] = "./test-mount"; +static const char reg_file_path[] = "./test-mount/regular_file"; +static const char dir_path[] = "./test-mount/directory"; +static const char block_dev_path[] = "./test-mount/block_device"; +static const char char_dev_path[] = "./test-mount/character_device"; +static const char fifo_path[] = "./test-mount/fifo"; + +FIXTURE_SETUP(access) +{ + int procfd_path_size; + static const char path_template[] = "/proc/self/fd/%d"; + char procfd_path[sizeof(path_template) + 10]; + + /* Makes sure we are not already restricted nor locked. */ + EXPECT_EQ(0, test_secbits_set(0)); + + /* + * Cleans previous workspace if any error previously happened (don't + * check errors). + */ + umount(workdir_path); + rmdir(workdir_path); + + /* Creates a clean mount point. */ + ASSERT_EQ(0, mkdir(workdir_path, 00700)); + ASSERT_EQ(0, mount("test", workdir_path, "tmpfs", + MS_MGC_VAL | (variant->mount_exec ? 0 : MS_NOEXEC), + "mode=0700,size=9m")); + + /* Creates a regular file. */ + ASSERT_EQ(0, mknod(reg_file_path, + S_IFREG | (variant->file_exec ? 0700 : 0600), 0)); + /* Creates a directory. */ + ASSERT_EQ(0, mkdir(dir_path, variant->file_exec ? 0700 : 0600)); + /* Creates a character device: /dev/null. */ + ASSERT_EQ(0, mknod(char_dev_path, S_IFCHR | 0400, makedev(1, 3))); + /* Creates a block device: /dev/loop0 */ + ASSERT_EQ(0, mknod(block_dev_path, S_IFBLK | 0400, makedev(7, 0))); + /* Creates a fifo. */ + ASSERT_EQ(0, mknod(fifo_path, S_IFIFO | 0600, 0)); + + /* Creates a regular file without user mount point. */ + self->memfd = memfd_create("test-exec-probe", MFD_CLOEXEC); + ASSERT_LE(0, self->memfd); + /* Sets mode, which must be ignored by the exec check. */ + ASSERT_EQ(0, fchmod(self->memfd, variant->file_exec ? 0700 : 0600)); + + /* Creates a pipefs file descriptor. */ + ASSERT_EQ(0, pipe(self->pipe_fds)); + procfd_path_size = snprintf(procfd_path, sizeof(procfd_path), + path_template, self->pipe_fds[0]); + ASSERT_LT(procfd_path_size, sizeof(procfd_path)); + self->pipefd = open(procfd_path, O_RDWR | O_CLOEXEC); + ASSERT_LE(0, self->pipefd); + ASSERT_EQ(0, fchmod(self->pipefd, variant->file_exec ? 0700 : 0600)); + + /* Creates a socket file descriptor. */ + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0, + self->socket_fds)); +} + +FIXTURE_TEARDOWN_PARENT(access) +{ + /* There is no need to unlink the test files. */ + EXPECT_EQ(0, umount(workdir_path)); + EXPECT_EQ(0, rmdir(workdir_path)); +} + +static void fill_exec_fd(struct __test_metadata *_metadata, const int fd_out) +{ + char buf[1024]; + size_t len; + int fd_in; + + fd_in = open(binary_path, O_CLOEXEC | O_RDONLY); + ASSERT_LE(0, fd_in); + /* Cannot use copy_file_range(2) because of EXDEV. */ + len = read(fd_in, buf, sizeof(buf)); + EXPECT_LE(0, len); + while (len > 0) { + EXPECT_EQ(len, write(fd_out, buf, len)) + { + TH_LOG("Failed to write: %s (%d)", strerror(errno), + errno); + } + len = read(fd_in, buf, sizeof(buf)); + EXPECT_LE(0, len); + } + EXPECT_EQ(0, close(fd_in)); +} + +static void fill_exec_path(struct __test_metadata *_metadata, + const char *const path) +{ + int fd_out; + + fd_out = open(path, O_CLOEXEC | O_WRONLY); + ASSERT_LE(0, fd_out) + { + TH_LOG("Failed to open %s: %s", path, strerror(errno)); + } + fill_exec_fd(_metadata, fd_out); + EXPECT_EQ(0, close(fd_out)); +} + +static void test_exec_fd(struct __test_metadata *_metadata, const int fd, + const int err_code) +{ + char *const argv[] = { "", NULL }; + int access_ret, access_errno; + + /* + * If we really execute fd, filled with the "false" binary, the current + * thread will exits with an error, which will be interpreted by the + * test framework as an error. With AT_CHECK, we only check a + * potential successful execution. + */ + access_ret = execveat(fd, "", argv, NULL, AT_EMPTY_PATH | AT_CHECK); + access_errno = errno; + if (err_code) { + EXPECT_EQ(-1, access_ret); + EXPECT_EQ(err_code, access_errno) + { + TH_LOG("Wrong error for execveat(2): %s (%d)", + strerror(access_errno), errno); + } + } else { + EXPECT_EQ(0, access_ret) + { + TH_LOG("Access denied: %s", strerror(access_errno)); + } + } +} + +static void test_exec_path(struct __test_metadata *_metadata, + const char *const path, const int err_code) +{ + int flags = O_CLOEXEC; + int fd; + + /* Do not block on pipes. */ + if (path == fifo_path) + flags |= O_NONBLOCK; + + fd = open(path, flags | O_RDONLY); + ASSERT_LE(0, fd) + { + TH_LOG("Failed to open %s: %s", path, strerror(errno)); + } + test_exec_fd(_metadata, fd, err_code); + EXPECT_EQ(0, close(fd)); +} + +/* Tests that we don't get ENOEXEC. */ +TEST_F(access, regular_file_empty) +{ + const int exec = variant->mount_exec && variant->file_exec; + + test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES); + + drop_privileges(_metadata); + test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES); +} + +TEST_F(access, regular_file_elf) +{ + const int exec = variant->mount_exec && variant->file_exec; + + fill_exec_path(_metadata, reg_file_path); + + test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES); + + drop_privileges(_metadata); + test_exec_path(_metadata, reg_file_path, exec ? 0 : EACCES); +} + +/* Tests that we don't get ENOEXEC. */ +TEST_F(access, memfd_empty) +{ + const int exec = variant->file_exec; + + test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES); + + drop_privileges(_metadata); + test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES); +} + +TEST_F(access, memfd_elf) +{ + const int exec = variant->file_exec; + + fill_exec_fd(_metadata, self->memfd); + + test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES); + + drop_privileges(_metadata); + test_exec_fd(_metadata, self->memfd, exec ? 0 : EACCES); +} + +TEST_F(access, non_regular_files) +{ + test_exec_path(_metadata, dir_path, EACCES); + test_exec_path(_metadata, block_dev_path, EACCES); + test_exec_path(_metadata, char_dev_path, EACCES); + test_exec_path(_metadata, fifo_path, EACCES); + test_exec_fd(_metadata, self->socket_fds[0], EACCES); + test_exec_fd(_metadata, self->pipefd, EACCES); +} + + +/* clang-format off */ +FIXTURE(secbits) {}; +/* clang-format on */ + +FIXTURE_VARIANT(secbits) +{ + const bool is_privileged; + const int error; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(secbits, priv) { + /* clang-format on */ + .is_privileged = true, + .error = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(secbits, unpriv) { + /* clang-format on */ + .is_privileged = false, + .error = EPERM, +}; + +FIXTURE_SETUP(secbits) +{ + /* Makes sure no should-exec bits are set. */ + EXPECT_EQ(0, test_secbits_set(0)); + EXPECT_EQ(0, prctl(PR_GET_SECUREBITS)); + + if (!variant->is_privileged) + drop_privileges(_metadata); +} + +FIXTURE_TEARDOWN(secbits) +{ +} + +TEST_F(secbits, legacy) +{ + EXPECT_EQ(variant->error, test_secbits_set(0)); +} + +#define CHILD(...) \ + do { \ + pid_t child = vfork(); \ + EXPECT_LE(0, child); \ + if (child == 0) { \ + __VA_ARGS__; \ + _exit(0); \ + } \ + } while (0) + +TEST_F(secbits, should_exec) +{ + unsigned int secbits = prctl(PR_GET_SECUREBITS); + + secbits |= SECBIT_SHOULD_EXEC_CHECK; + EXPECT_EQ(0, test_secbits_set(secbits)); + EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)); + CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS))); + + secbits |= SECBIT_SHOULD_EXEC_RESTRICT; + EXPECT_EQ(0, test_secbits_set(secbits)); + EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)); + CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS))); + + secbits &= ~(SECBIT_SHOULD_EXEC_CHECK | SECBIT_SHOULD_EXEC_RESTRICT); + EXPECT_EQ(0, test_secbits_set(secbits)); + EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS)); + CHILD(EXPECT_EQ(secbits, prctl(PR_GET_SECUREBITS))); +} + +TEST_F(secbits, check_locked_set) +{ + unsigned int secbits = prctl(PR_GET_SECUREBITS); + + secbits |= SECBIT_SHOULD_EXEC_CHECK; + EXPECT_EQ(0, test_secbits_set(secbits)); + secbits |= SECBIT_SHOULD_EXEC_CHECK_LOCKED; + EXPECT_EQ(0, test_secbits_set(secbits)); + + /* Checks lock set but unchanged. */ + EXPECT_EQ(variant->error, test_secbits_set(secbits)); + CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits))); + + secbits &= ~SECBIT_SHOULD_EXEC_CHECK; + EXPECT_EQ(EPERM, test_secbits_set(0)); + CHILD(EXPECT_EQ(EPERM, test_secbits_set(0))); +} + +TEST_F(secbits, check_locked_unset) +{ + unsigned int secbits = prctl(PR_GET_SECUREBITS); + + secbits |= SECBIT_SHOULD_EXEC_CHECK_LOCKED; + EXPECT_EQ(0, test_secbits_set(secbits)); + + /* Checks lock unset but unchanged. */ + EXPECT_EQ(variant->error, test_secbits_set(secbits)); + CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits))); + + secbits &= ~SECBIT_SHOULD_EXEC_CHECK; + EXPECT_EQ(EPERM, test_secbits_set(0)); + CHILD(EXPECT_EQ(EPERM, test_secbits_set(0))); +} + +TEST_F(secbits, restrict_locked_set) +{ + unsigned int secbits = prctl(PR_GET_SECUREBITS); + + secbits |= SECBIT_SHOULD_EXEC_RESTRICT; + EXPECT_EQ(0, test_secbits_set(secbits)); + secbits |= SECBIT_SHOULD_EXEC_RESTRICT_LOCKED; + EXPECT_EQ(0, test_secbits_set(secbits)); + + /* Checks lock set but unchanged. */ + EXPECT_EQ(variant->error, test_secbits_set(secbits)); + CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits))); + + secbits &= ~SECBIT_SHOULD_EXEC_RESTRICT; + EXPECT_EQ(EPERM, test_secbits_set(0)); + CHILD(EXPECT_EQ(EPERM, test_secbits_set(0))); +} + +TEST_F(secbits, restrict_locked_unset) +{ + unsigned int secbits = prctl(PR_GET_SECUREBITS); + + secbits |= SECBIT_SHOULD_EXEC_RESTRICT_LOCKED; + EXPECT_EQ(0, test_secbits_set(secbits)); + + /* Checks lock unset but unchanged. */ + EXPECT_EQ(variant->error, test_secbits_set(secbits)); + CHILD(EXPECT_EQ(variant->error, test_secbits_set(secbits))); + + secbits &= ~SECBIT_SHOULD_EXEC_RESTRICT; + EXPECT_EQ(EPERM, test_secbits_set(0)); + CHILD(EXPECT_EQ(EPERM, test_secbits_set(0))); +} + +/* TODO: Add ptrace tests */ + +TEST_HARNESS_MAIN From patchwork Thu Jul 4 19:01:36 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 13724205 Received: from smtp-8fa8.mail.infomaniak.ch (smtp-8fa8.mail.infomaniak.ch [83.166.143.168]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 86CF313D276; Thu, 4 Jul 2024 19:02:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=83.166.143.168 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119737; cv=none; b=QIPsYtPIDe2h+UjYLRiJUyskW7IgVbKl/ZrcabqehWO0xpnlQ7aXt4FQ1LfuoIUS9Hr8yWxab0Qa3hJJE3u/53JkVMJBhahLolqo/lg+f3yqCbwjo2x+OGDIVsWrIRdbqBlBus3nA33wAgnquHC3fvXY28KVmdvxyR0MxrYWsC4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119737; c=relaxed/simple; bh=9kEvfh40DrzVZsKjxTdNWubNxHzlStfeHN1CIvOxWb4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=nFnhRdHSbshQ2pA2CEVT00RsLGy3xKN4IIAgcJ7ARIetW/y51OiNMLQL18ZmZY9VQP2sGCV0bKHyCDzyocA3C74K+bIpu/kLI2p3YyWECDLBC01XfrUdRxuRGdSKUYFP/vsDhOXp1/RB8438vcsg8AJ65oPFw4rldLM/BV2w/wY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=ThRs7c9X; arc=none smtp.client-ip=83.166.143.168 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="ThRs7c9X" Received: from smtp-3-0001.mail.infomaniak.ch (smtp-3-0001.mail.infomaniak.ch [10.4.36.108]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4WFQxc2r9Kz12Vn; Thu, 4 Jul 2024 21:02:12 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1720119732; bh=daNi8bj+uI1GV3NFPcnb4qSetCZapGCSAhuBCp9VMuo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ThRs7c9XxrJXhsxBCBf1BgkjK/BWahL0xO9HmmtyK9bdLiJ4fpswPBizU28ADiD+8 aOIDrvtiPJgENur37wuN+viYCGh0J5TAJo4Qi4GciV6Mt82ZKsC1Dvp1sweWWvL/Na FkxyZLz0b+yHt23kspra1Bb7GYYXewOJOH2LhNuo= Received: from unknown by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4WFQxb45Fpz2mC; Thu, 4 Jul 2024 21:02:11 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Linus Torvalds , Paul Moore , Theodore Ts'o Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Alejandro Colomar , Aleksa Sarai , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , Christian Heimes , Dmitry Vyukov , Eric Biggers , Eric Chiang , Fan Wu , Florian Weimer , Geert Uytterhoeven , James Morris , Jan Kara , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Luis Chamberlain , "Madhavan T . Venkataraman" , Matt Bobrowski , Matthew Garrett , Matthew Wilcox , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Scott Shell , Shuah Khan , Stephen Rothwell , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Xiaoming Ni , Yin Fengwei , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org, =?utf-8?q?G=C3=BCnther_Noack?= Subject: [RFC PATCH v19 4/5] selftests/landlock: Add tests for execveat + AT_CHECK Date: Thu, 4 Jul 2024 21:01:36 +0200 Message-ID: <20240704190137.696169-5-mic@digikod.net> In-Reply-To: <20240704190137.696169-1-mic@digikod.net> References: <20240704190137.696169-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-integrity@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Infomaniak-Routing: alpha Extend layout1.execute with the new AT_CHECK flag. The semantic with AT_CHECK is the same as with a simple execve(2), LANDLOCK_ACCESS_FS_EXECUTE is enforced the same way. Cc: Günther Noack Cc: Kees Cook Cc: Paul Moore Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240704190137.696169-5-mic@digikod.net --- tools/testing/selftests/landlock/fs_test.c | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 7d063c652be1..85ef36b09a37 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -37,6 +37,10 @@ #include #include +/* Defines AT_CHECK without type conflicts. */ +#define _ASM_GENERIC_FCNTL_H +#include + #include "common.h" #ifndef renameat2 @@ -2009,6 +2013,21 @@ static void test_execute(struct __test_metadata *const _metadata, const int err, }; } +static void test_check_exec(struct __test_metadata *const _metadata, + const int err, const char *const path) +{ + int ret; + char *const argv[] = { (char *)path, NULL }; + + ret = execveat(AT_FDCWD, path, argv, NULL, AT_EMPTY_PATH | AT_CHECK); + if (err) { + EXPECT_EQ(-1, ret); + EXPECT_EQ(errno, err); + } else { + EXPECT_EQ(0, ret); + } +} + TEST_F_FORK(layout1, execute) { const struct rule rules[] = { @@ -2026,20 +2045,27 @@ TEST_F_FORK(layout1, execute) copy_binary(_metadata, file1_s1d2); copy_binary(_metadata, file1_s1d3); + /* Checks before file1_s1d1 being denied. */ + test_execute(_metadata, 0, file1_s1d1); + test_check_exec(_metadata, 0, file1_s1d1); + enforce_ruleset(_metadata, ruleset_fd); ASSERT_EQ(0, close(ruleset_fd)); ASSERT_EQ(0, test_open(dir_s1d1, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d1, O_RDONLY)); test_execute(_metadata, EACCES, file1_s1d1); + test_check_exec(_metadata, EACCES, file1_s1d1); ASSERT_EQ(0, test_open(dir_s1d2, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d2, O_RDONLY)); test_execute(_metadata, 0, file1_s1d2); + test_check_exec(_metadata, 0, file1_s1d2); ASSERT_EQ(0, test_open(dir_s1d3, O_RDONLY)); ASSERT_EQ(0, test_open(file1_s1d3, O_RDONLY)); test_execute(_metadata, 0, file1_s1d3); + test_check_exec(_metadata, 0, file1_s1d3); } TEST_F_FORK(layout1, link) From patchwork Thu Jul 4 19:01:37 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= X-Patchwork-Id: 13724206 Received: from smtp-bc0a.mail.infomaniak.ch (smtp-bc0a.mail.infomaniak.ch [45.157.188.10]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4EAD113D512 for ; Thu, 4 Jul 2024 19:02:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=45.157.188.10 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119737; cv=none; b=Y4dgb/ieJQstnqm3AmcZvMz62150f1blf/1OvEY3k0SbRJ0elL0ZsNYR5DsGNz8uLhXh62R9QsFVmg1F4YKXF7psLK9I421cFVqCFitq/fzLJln5pUmwl7kyK/08xxPivwBY+cGK5jCjL+1c7/B0G2Lijy5eymam6k2W8OBdoxU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1720119737; c=relaxed/simple; bh=oF6rZ0nuBxG6YDixlM9cat48r0Oum2JdP1zOs4FQ5/c=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=HeIMYiIO4uT1JeSem3bn42Wzxj29RyujAyDjHgcwMiKwzOVqkVmTH0N7hgLoIaPNqn4Zm0IuxtCTfTHj46edM1EVpPNW9tfbwjTXtGVlcc585oaV1S/GWKFltf2bUBT40PBXwsbBDVC/xsHtQ5t+i54/fvXyo663J0vgy7LV4FY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net; spf=pass smtp.mailfrom=digikod.net; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b=sz775JKN; arc=none smtp.client-ip=45.157.188.10 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=digikod.net Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=digikod.net Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=digikod.net header.i=@digikod.net header.b="sz775JKN" Received: from smtp-4-0001.mail.infomaniak.ch (smtp-4-0001.mail.infomaniak.ch [10.7.10.108]) by smtp-3-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4WFQxd4WXSz114p; Thu, 4 Jul 2024 21:02:13 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=digikod.net; s=20191114; t=1720119733; bh=v0fxaXOdLtc/Ju12bV32vY14OcdVSThIgNKjl9+i++A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sz775JKNODOr34yQ+Cv6KYcDvi+5ptHtkytGJ4G92ILUZoxk4zmBBIN1RY/1M1tms ZO3bjuRTATAsvIlizxMEmtFYYYIpG6mKsV5ckhQqA6RKyNXIXh9DUoYxtxxVrb/Yc1 MGjtdlShlQavT56SM7tKhkep7CajraMh20U4V77E= Received: from unknown by smtp-4-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4WFQxc5zxzzTK7; Thu, 4 Jul 2024 21:02:12 +0200 (CEST) From: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= To: Al Viro , Christian Brauner , Kees Cook , Linus Torvalds , Paul Moore , Theodore Ts'o Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , Alejandro Colomar , Aleksa Sarai , Andrew Morton , Andy Lutomirski , Arnd Bergmann , Casey Schaufler , Christian Heimes , Dmitry Vyukov , Eric Biggers , Eric Chiang , Fan Wu , Florian Weimer , Geert Uytterhoeven , James Morris , Jan Kara , Jann Horn , Jeff Xu , Jonathan Corbet , Jordan R Abrahams , Lakshmi Ramasubramanian , Luca Boccassi , Luis Chamberlain , "Madhavan T . Venkataraman" , Matt Bobrowski , Matthew Garrett , Matthew Wilcox , Miklos Szeredi , Mimi Zohar , Nicolas Bouchinet , Scott Shell , Shuah Khan , Stephen Rothwell , Steve Dower , Steve Grubb , Thibaut Sautereau , Vincent Strubel , Xiaoming Ni , Yin Fengwei , kernel-hardening@lists.openwall.com, linux-api@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-integrity@vger.kernel.org, linux-kernel@vger.kernel.org, linux-security-module@vger.kernel.org Subject: [RFC PATCH v19 5/5] samples/should-exec: Add set-should-exec Date: Thu, 4 Jul 2024 21:01:37 +0200 Message-ID: <20240704190137.696169-6-mic@digikod.net> In-Reply-To: <20240704190137.696169-1-mic@digikod.net> References: <20240704190137.696169-1-mic@digikod.net> Precedence: bulk X-Mailing-List: linux-integrity@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-Infomaniak-Routing: alpha Add a simple tool to set SECBIT_SHOULD_EXEC_CHECK, SECBIT_SHOULD_EXEC_RESTRICT, and their lock counterparts before executing a command. This should be useful to easily test against script interpreters. Cc: Al Viro Cc: Christian Brauner Cc: Kees Cook Cc: Paul Moore Signed-off-by: Mickaël Salaün Link: https://lore.kernel.org/r/20240704190137.696169-6-mic@digikod.net --- samples/Kconfig | 7 +++ samples/Makefile | 1 + samples/should-exec/.gitignore | 1 + samples/should-exec/Makefile | 13 ++++ samples/should-exec/set-should-exec.c | 88 +++++++++++++++++++++++++++ 5 files changed, 110 insertions(+) create mode 100644 samples/should-exec/.gitignore create mode 100644 samples/should-exec/Makefile create mode 100644 samples/should-exec/set-should-exec.c diff --git a/samples/Kconfig b/samples/Kconfig index b288d9991d27..d8f2639bc830 100644 --- a/samples/Kconfig +++ b/samples/Kconfig @@ -180,6 +180,13 @@ config SAMPLE_SECCOMP Build samples of seccomp filters using various methods of BPF filter construction. +config SAMPLE_SHOULD_EXEC + bool "Should-exec secure bits examples" + depends on CC_CAN_LINK && HEADERS_INSTALL + help + Build a tool to easily configure SECBIT_SHOULD_EXEC_CHECK, + SECBIT_SHOULD_EXEC_RESTRICT and their lock counterparts. + config SAMPLE_TIMER bool "Timer sample" depends on CC_CAN_LINK && HEADERS_INSTALL diff --git a/samples/Makefile b/samples/Makefile index b85fa64390c5..0e7a97fb222d 100644 --- a/samples/Makefile +++ b/samples/Makefile @@ -19,6 +19,7 @@ subdir-$(CONFIG_SAMPLE_PIDFD) += pidfd obj-$(CONFIG_SAMPLE_QMI_CLIENT) += qmi/ obj-$(CONFIG_SAMPLE_RPMSG_CLIENT) += rpmsg/ subdir-$(CONFIG_SAMPLE_SECCOMP) += seccomp +subdir-$(CONFIG_SAMPLE_SHOULD_EXEC) += should-exec subdir-$(CONFIG_SAMPLE_TIMER) += timers obj-$(CONFIG_SAMPLE_TRACE_EVENTS) += trace_events/ obj-$(CONFIG_SAMPLE_TRACE_CUSTOM_EVENTS) += trace_events/ diff --git a/samples/should-exec/.gitignore b/samples/should-exec/.gitignore new file mode 100644 index 000000000000..ac46c614ec80 --- /dev/null +++ b/samples/should-exec/.gitignore @@ -0,0 +1 @@ +/set-should-exec diff --git a/samples/should-exec/Makefile b/samples/should-exec/Makefile new file mode 100644 index 000000000000..c4294278dd07 --- /dev/null +++ b/samples/should-exec/Makefile @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: BSD-3-Clause + +userprogs-always-y := set-should-exec + +userccflags += -I usr/include + +.PHONY: all clean + +all: + $(MAKE) -C ../.. samples/should-exec/ + +clean: + $(MAKE) -C ../.. M=samples/should-exec/ clean diff --git a/samples/should-exec/set-should-exec.c b/samples/should-exec/set-should-exec.c new file mode 100644 index 000000000000..b3c31106d916 --- /dev/null +++ b/samples/should-exec/set-should-exec.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Simple tool to set SECBIT_SHOULD_EXEC_CHECK, SECBIT_SHOULD_EXEC_RESTRICT, + * and their lock counterparts before executing a command. + * + * Copyright © 2024 Microsoft Corporation + */ + +#define _GNU_SOURCE +#define __SANE_USERSPACE_TYPES__ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void print_usage(const char *argv0) +{ + fprintf(stderr, "usage: %s -c|-r [-l] -- [args]...\n\n", argv0); + fprintf(stderr, "Execute a command with\n"); + fprintf(stderr, "- SECBIT_SHOULD_EXEC_CHECK set: -c\n"); + fprintf(stderr, "- SECBIT_SHOULD_EXEC_RESTRICT set: -r\n"); + fprintf(stderr, "- SECBIT_SHOULD_EXEC_*_LOCKED set: -l\n"); +} + +int main(const int argc, char *const argv[], char *const *const envp) +{ + const char *cmd_path; + char *const *cmd_argv; + int opt, secbits, err; + bool has_policy = false; + + secbits = prctl(PR_GET_SECUREBITS); + + while ((opt = getopt(argc, argv, "crl")) != -1) { + switch (opt) { + case 'c': + secbits |= SECBIT_SHOULD_EXEC_CHECK; + has_policy = true; + break; + case 'r': + secbits |= SECBIT_SHOULD_EXEC_RESTRICT; + has_policy = true; + break; + case 'l': + secbits |= SECBIT_SHOULD_EXEC_CHECK_LOCKED; + secbits |= SECBIT_SHOULD_EXEC_RESTRICT_LOCKED; + break; + default: + print_usage(argv[0]); + return 1; + } + } + + if (!argv[optind] || !has_policy) { + print_usage(argv[0]); + return 1; + } + + err = prctl(PR_SET_SECUREBITS, secbits); + if (err) { + perror("Failed to set secure bit(s)."); + fprintf(stderr, + "Hint: The running kernel may not support this feature.\n"); + return 1; + } + + fprintf(stderr, "SECBIT_SHOULD_EXEC_CHECK: %d\n", + !!(secbits & SECBIT_SHOULD_EXEC_CHECK)); + fprintf(stderr, "SECBIT_SHOULD_EXEC_CHECK_LOCKED: %d\n", + !!(secbits & SECBIT_SHOULD_EXEC_CHECK_LOCKED)); + fprintf(stderr, "SECBIT_SHOULD_EXEC_RESTRICT: %d\n", + !!(secbits & SECBIT_SHOULD_EXEC_RESTRICT)); + fprintf(stderr, "SECBIT_SHOULD_EXEC_RESTRICT_LOCKED: %d\n", + !!(secbits & SECBIT_SHOULD_EXEC_RESTRICT_LOCKED)); + + cmd_path = argv[optind]; + cmd_argv = argv + optind; + fprintf(stderr, "Executing command...\n"); + execvpe(cmd_path, cmd_argv, envp); + fprintf(stderr, "Failed to execute \"%s\": %s\n", cmd_path, + strerror(errno)); + return 1; +}