From patchwork Fri Sep 30 16:01:38 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995730 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 06155C433FE for ; Fri, 30 Sep 2022 16:02:52 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231821AbiI3QCu (ORCPT ); Fri, 30 Sep 2022 12:02:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43160 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231950AbiI3QCL (ORCPT ); Fri, 30 Sep 2022 12:02:11 -0400 Received: from mail-ej1-x633.google.com (mail-ej1-x633.google.com [IPv6:2a00:1450:4864:20::633]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0FE871490A9; Fri, 30 Sep 2022 09:02:10 -0700 (PDT) Received: by mail-ej1-x633.google.com with SMTP id lh5so9920055ejb.10; Fri, 30 Sep 2022 09:02:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=BqSMEDKxbGMlwxe80OEMyoAkyzureFE4EFxXeZJXsHg=; b=SoiRpjDi1VcKofeJFg+rnnYDqCYv50XpXDCXVC+Zb066Tyr2grVR6ME5Mb+RYvO66K 8D0eD31gKbFl8Q+OHuC4y5gwWqqIduWdshkz3+w9PyzCNzL7qma3vFxzX+qkAyMn607H 3EyDmmLDi65T/ycy40NDO8H9LK2i1xy1fU6Iee5HrLszRlRmcb6iQ9i/w3sjeyHl4q5O uNOf2EFMkSyAgclNlLYbeX8kMqoFotbYP7Iw3/X77an7VC8qrsb1YYiWJ2NzHIqjtIlX 60oAOWIYO+5cTm7kRQ0HkQYU3t5NMNTvwY/70N6UfAwtjM34Vex/J7GA+zkvux5UNdrm Kfug== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=BqSMEDKxbGMlwxe80OEMyoAkyzureFE4EFxXeZJXsHg=; b=I28g9utbsSyF+My2os3FnN1wdd9tS43dK7Sr5GG9nHgjtbplUxsm25i/1VTMIN4wzx 13N+3ejtrkMBzYc9oDpIlP3TWSJDenu1WkK6IdILFWxTH8b/LNrg8kQ4n3NtrYkGMhvE BsC3Fp+fp2NoW0+OzCbJG+MXvhrXi52Oqnpl9unw8SSUq50MbS2GURew0Y9Jm43xqZ3F RVDFP54p5QOstMI4L5uPxMmq08KXu+5Dh0/cc/6VX+jO3R5ZBRXnNyRwbQCpGbhcymoq pzxVGHGROK/yTprcT1XU0+TyDhleWlLQ1e/LK8YNXpnbU0y365Z7+VjPjhQQfISGQ91V x/CA== X-Gm-Message-State: ACrzQf0zgwVImTbljER2RNw9hwO5vPHfcUz6uSIX9zWWovjtZCqG3ay7 5S3NF9pR+6roRTVFb85ytDLJCsyRhcQ= X-Google-Smtp-Source: AMsMyM6Vp653jbVBSEHgtQAJGgrmNbyVyTFUCkC0ZU9GxLqgIuoVRPDhZHnxelLBvdmXpbaAjilbTQ== X-Received: by 2002:a17:907:60c6:b0:787:af55:87c2 with SMTP id hv6-20020a17090760c600b00787af5587c2mr7014154ejc.52.1664553728533; Fri, 30 Sep 2022 09:02:08 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:08 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= , Tetsuo Handa , John Johansen Subject: [PATCH v7 1/7] security: Create file_truncate hook from path_truncate hook Date: Fri, 30 Sep 2022 18:01:38 +0200 Message-Id: <20220930160144.141504-2-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: Like path_truncate, the file_truncate hook also restricts file truncation, but is called in the cases where truncation is attempted on an already-opened file. This is required in a subsequent commit to handle ftruncate() operations differently to truncate() operations. Acked-by: Tetsuo Handa Acked-by: John Johansen Signed-off-by: Günther Noack --- fs/namei.c | 2 +- fs/open.c | 2 +- include/linux/lsm_hook_defs.h | 1 + include/linux/lsm_hooks.h | 10 +++++++++- include/linux/security.h | 6 ++++++ security/apparmor/lsm.c | 6 ++++++ security/security.c | 5 +++++ security/tomoyo/tomoyo.c | 13 +++++++++++++ 8 files changed, 42 insertions(+), 3 deletions(-) diff --git a/fs/namei.c b/fs/namei.c index 53b4bc094db2..0e419bd30f8e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3211,7 +3211,7 @@ static int handle_truncate(struct user_namespace *mnt_userns, struct file *filp) if (error) return error; - error = security_path_truncate(path); + error = security_file_truncate(filp); if (!error) { error = do_truncate(mnt_userns, path->dentry, 0, ATTR_MTIME|ATTR_CTIME|ATTR_OPEN, diff --git a/fs/open.c b/fs/open.c index cf7e5c350a54..0fa861873245 100644 --- a/fs/open.c +++ b/fs/open.c @@ -188,7 +188,7 @@ long do_sys_ftruncate(unsigned int fd, loff_t length, int small) if (IS_APPEND(file_inode(f.file))) goto out_putf; sb_start_write(inode->i_sb); - error = security_path_truncate(&f.file->f_path); + error = security_file_truncate(f.file); if (!error) error = do_truncate(file_mnt_user_ns(f.file), dentry, length, ATTR_MTIME | ATTR_CTIME, f.file); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 60fff133c0b1..dee35ab253ba 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -177,6 +177,7 @@ LSM_HOOK(int, 0, file_send_sigiotask, struct task_struct *tsk, struct fown_struct *fown, int sig) LSM_HOOK(int, 0, file_receive, struct file *file) LSM_HOOK(int, 0, file_open, struct file *file) +LSM_HOOK(int, 0, file_truncate, struct file *file) LSM_HOOK(int, 0, task_alloc, struct task_struct *task, unsigned long clone_flags) LSM_HOOK(void, LSM_RET_VOID, task_free, struct task_struct *task) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index 3aa6030302f5..4acc975f28d9 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -409,7 +409,9 @@ * @attr is the iattr structure containing the new file attributes. * Return 0 if permission is granted. * @path_truncate: - * Check permission before truncating a file. + * Check permission before truncating the file indicated by path. + * Note that truncation permissions may also be checked based on + * already opened files, using the @file_truncate hook. * @path contains the path structure for the file. * Return 0 if permission is granted. * @inode_getattr: @@ -598,6 +600,12 @@ * to receive an open file descriptor via socket IPC. * @file contains the file structure being received. * Return 0 if permission is granted. + * @file_truncate: + * Check permission before truncating a file, i.e. using ftruncate. + * Note that truncation permission may also be checked based on the path, + * using the @path_truncate hook. + * @file contains the file structure for the file. + * Return 0 if permission is granted. * @file_open: * Save open-time permission checking state for later use upon * file_permission, and recheck access if anything has changed diff --git a/include/linux/security.h b/include/linux/security.h index 7bd0c490703d..f80b23382dd9 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -394,6 +394,7 @@ int security_file_send_sigiotask(struct task_struct *tsk, struct fown_struct *fown, int sig); int security_file_receive(struct file *file); int security_file_open(struct file *file); +int security_file_truncate(struct file *file); int security_task_alloc(struct task_struct *task, unsigned long clone_flags); void security_task_free(struct task_struct *task); int security_cred_alloc_blank(struct cred *cred, gfp_t gfp); @@ -1011,6 +1012,11 @@ static inline int security_file_open(struct file *file) return 0; } +static inline int security_file_truncate(struct file *file) +{ + return 0; +} + static inline int security_task_alloc(struct task_struct *task, unsigned long clone_flags) { diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index e29cade7b662..98ecb7f221b8 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -329,6 +329,11 @@ static int apparmor_path_truncate(const struct path *path) return common_perm_cond(OP_TRUNC, path, MAY_WRITE | AA_MAY_SETATTR); } +static int apparmor_file_truncate(struct file *file) +{ + return apparmor_path_truncate(&file->f_path); +} + static int apparmor_path_symlink(const struct path *dir, struct dentry *dentry, const char *old_name) { @@ -1232,6 +1237,7 @@ static struct security_hook_list apparmor_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(mmap_file, apparmor_mmap_file), LSM_HOOK_INIT(file_mprotect, apparmor_file_mprotect), LSM_HOOK_INIT(file_lock, apparmor_file_lock), + LSM_HOOK_INIT(file_truncate, apparmor_file_truncate), LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), diff --git a/security/security.c b/security/security.c index 4b95de24bc8d..d73e423005c3 100644 --- a/security/security.c +++ b/security/security.c @@ -1650,6 +1650,11 @@ int security_file_open(struct file *file) return fsnotify_perm(file, MAY_OPEN); } +int security_file_truncate(struct file *file) +{ + return call_int_hook(file_truncate, 0, file); +} + int security_task_alloc(struct task_struct *task, unsigned long clone_flags) { int rc = lsm_task_alloc(task); diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index 71e82d855ebf..af04a7b7eb28 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -134,6 +134,18 @@ static int tomoyo_path_truncate(const struct path *path) return tomoyo_path_perm(TOMOYO_TYPE_TRUNCATE, path, NULL); } +/** + * tomoyo_file_truncate - Target for security_file_truncate(). + * + * @file: Pointer to "struct file". + * + * Returns 0 on success, negative value otherwise. + */ +static int tomoyo_file_truncate(struct file *file) +{ + return tomoyo_path_truncate(&file->f_path); +} + /** * tomoyo_path_unlink - Target for security_path_unlink(). * @@ -545,6 +557,7 @@ static struct security_hook_list tomoyo_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(bprm_check_security, tomoyo_bprm_check_security), LSM_HOOK_INIT(file_fcntl, tomoyo_file_fcntl), LSM_HOOK_INIT(file_open, tomoyo_file_open), + LSM_HOOK_INIT(file_truncate, tomoyo_file_truncate), LSM_HOOK_INIT(path_truncate, tomoyo_path_truncate), LSM_HOOK_INIT(path_unlink, tomoyo_path_unlink), LSM_HOOK_INIT(path_mkdir, tomoyo_path_mkdir), From patchwork Fri Sep 30 16:01:39 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995732 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 51E71C4332F for ; Fri, 30 Sep 2022 16:02:53 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231905AbiI3QCv (ORCPT ); Fri, 30 Sep 2022 12:02:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43372 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231964AbiI3QCQ (ORCPT ); Fri, 30 Sep 2022 12:02:16 -0400 Received: from mail-ed1-x52d.google.com (mail-ed1-x52d.google.com [IPv6:2a00:1450:4864:20::52d]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 5307A1B0E3B; Fri, 30 Sep 2022 09:02:13 -0700 (PDT) Received: by mail-ed1-x52d.google.com with SMTP id e18so6580899edj.3; Fri, 30 Sep 2022 09:02:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=fFHqZM+qxOTHDqmvrcbqWGIUVNhQ10GtkvY7OBLU1uk=; b=Eh6WvLCL5FYAMTtTaxAGNMll/2o8srl5Z7R/I446Wa9ufdgY3mJtLK46fdeB3XbsHa yBQhup0olCQtY2frYhYVL/R/wQH/8xJvifRbMYNKUvWaAv+bx+I2iNsjrQDPJsW/pk8/ XDGFNlRff6V0YQ3mF0Nfl3JBgpFWzR4x3sAQ9pXtmKvgDaYe864tRyBG5v1Ulp2pgPLu s65SQp866OYCmsy8CwMPyjSbzaWj/iQ66hSFHu1R1EOyqB0rONDJo93VM/Uo7hfKb0n5 m1rk0XwSNFaTX9uW/7iMpUn1JgTK/5rusTWSnMBUZsidiEYJf7LPzE97SJ9iAO7/GU7O 9gWA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=fFHqZM+qxOTHDqmvrcbqWGIUVNhQ10GtkvY7OBLU1uk=; b=bqvZJ1/yRMwpmuO3GCUXWGkve7q8gpgvb9qMdXoUTGOMcwXY/M30faM+kK+0RFrZCA zTLI7bxFV6p2R+qXZoKmtXMYZ4usB01IG9LG8DT30jPtzLGZatGgPTVRk9SgWx/uEXGZ m4TaIriKXqXTL6gMrFDXrPNkm+3Xw7a1cL4CkjTG+ELBM9Ees3u9OcHjPRKq+0y6/hix YJGQ7s/AXZKogbM8Uw7VCU8WW7XhS2Frh/sQ15nKb4lp4VQeEZMT1h7HBgKFmgs9iv2v 9osUluIUVU9s4E64bWrGo+NvUymQuPKs2TRD/VWEE/mQIctrqAJ5TS4m2VwRFrxsmFHF ZhdQ== X-Gm-Message-State: ACrzQf3IAtZWYvcnCBLHUOSASbwwZWxbKdoYz6BB8r54ZqXuSQpr22Jq lifL3nMA1kGKY+dwn0U5bP7PfcY6hi4= X-Google-Smtp-Source: AMsMyM6+cISzeSv4+FdtwtSKvKEDIjeM+vMlF7DNwLFO9ZHcGGPJw5iegDox9ibvi6EBtQS+oxShEQ== X-Received: by 2002:a05:6402:11d0:b0:44e:ec42:e0b8 with SMTP id j16-20020a05640211d000b0044eec42e0b8mr8326703edw.131.1664553731435; Fri, 30 Sep 2022 09:02:11 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:11 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 2/7] landlock: Support file truncation Date: Fri, 30 Sep 2022 18:01:39 +0200 Message-Id: <20220930160144.141504-3-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: Introduce the LANDLOCK_ACCESS_FS_TRUNCATE flag for file truncation. This flag hooks into the path_truncate LSM hook and covers file truncation using truncate(2), ftruncate(2), open(2) with O_TRUNC, as well as creat(). This change also increments the Landlock ABI version, updates corresponding selftests, and updates code documentation to document the flag. The following operations are restricted: open(): requires the LANDLOCK_ACCESS_FS_TRUNCATE right if a file gets implicitly truncated as part of the open() (e.g. using O_TRUNC). Notable special cases: * open(..., O_RDONLY|O_TRUNC) can truncate files as well in Linux * open() with O_TRUNC does *not* need the TRUNCATE right when it creates a new file. truncate() (on a path): requires the LANDLOCK_ACCESS_FS_TRUNCATE right. ftruncate() (on a file): requires that the file had the TRUNCATE right when it was previously opened. Signed-off-by: Günther Noack --- include/uapi/linux/landlock.h | 21 +++- security/landlock/fs.c | 117 ++++++++++++++++++- security/landlock/fs.h | 24 ++++ security/landlock/limits.h | 2 +- security/landlock/setup.c | 1 + security/landlock/syscalls.c | 2 +- tools/testing/selftests/landlock/base_test.c | 2 +- tools/testing/selftests/landlock/fs_test.c | 7 +- 8 files changed, 159 insertions(+), 17 deletions(-) diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 23df4e0e8ace..d830cdfdbe56 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -95,8 +95,19 @@ struct landlock_path_beneath_attr { * A file can only receive these access rights: * * - %LANDLOCK_ACCESS_FS_EXECUTE: Execute a file. - * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. + * - %LANDLOCK_ACCESS_FS_WRITE_FILE: Open a file with write access. Note that + * you might additionally need the `LANDLOCK_ACCESS_FS_TRUNCATE` right in + * order to overwrite files with :manpage:`open(2)` using `O_TRUNC` or + * :manpage:`creat(2)`. * - %LANDLOCK_ACCESS_FS_READ_FILE: Open a file with read access. + * - %LANDLOCK_ACCESS_FS_TRUNCATE: Truncate a file with :manpage:`truncate(2)`, + * :manpage:`ftruncate(2)`, :manpage:`creat(2)`, or :manpage:`open(2)` with + * `O_TRUNC`. Whether an opened file can be truncated with + * :manpage:`ftruncate(2)` is determined during :manpage:`open(2)`, in the + * same way as read and write permissions are checked during + * :manpage:`open(2)` using %LANDLOCK_ACCESS_FS_READ_FILE and + * %LANDLOCK_ACCESS_FS_WRITE_FILE. This access right is available since the + * third version of the Landlock ABI. * * A directory can receive access rights related to files or directories. The * following access right is applied to the directory itself, and the @@ -139,10 +150,9 @@ struct landlock_path_beneath_attr { * * It is currently not possible to restrict some file-related actions * accessible through these syscall families: :manpage:`chdir(2)`, - * :manpage:`truncate(2)`, :manpage:`stat(2)`, :manpage:`flock(2)`, - * :manpage:`chmod(2)`, :manpage:`chown(2)`, :manpage:`setxattr(2)`, - * :manpage:`utime(2)`, :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, - * :manpage:`access(2)`. + * :manpage:`stat(2)`, :manpage:`flock(2)`, :manpage:`chmod(2)`, + * :manpage:`chown(2)`, :manpage:`setxattr(2)`, :manpage:`utime(2)`, + * :manpage:`ioctl(2)`, :manpage:`fcntl(2)`, :manpage:`access(2)`. * Future Landlock evolutions will enable to restrict them. */ /* clang-format off */ @@ -160,6 +170,7 @@ struct landlock_path_beneath_attr { #define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) #define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13) +#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14) /* clang-format on */ #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/security/landlock/fs.c b/security/landlock/fs.c index a9dbd99d9ee7..2d0085bb5ed2 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -146,7 +146,8 @@ static struct landlock_object *get_inode_object(struct inode *const inode) #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ - LANDLOCK_ACCESS_FS_READ_FILE) + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_TRUNCATE) /* clang-format on */ /* @@ -297,6 +298,18 @@ get_handled_accesses(const struct landlock_ruleset *const domain) return access_dom & LANDLOCK_MASK_ACCESS_FS; } +/* + * init_layer_masks - Populates @layer_masks such that for each access right in + * @access_request, the bits for all the layers are set where this access right + * is handled. + * + * @domain: The domain that defines the current restrictions. + * @access_request: The requested access rights to check. + * @layer_masks: The layer masks to populate. + * + * Returns: An access mask where each access right bit is set which is handled + * in any of the active layers in @domain. + */ static inline access_mask_t init_layer_masks(const struct landlock_ruleset *const domain, const access_mask_t access_request, @@ -761,6 +774,47 @@ static bool collect_domain_accesses( return ret; } +/** + * get_path_access - Returns the subset of access rights in access_request which + * are permitted for the given path. + * + * @domain: The domain that defines the current restrictions. + * @path: The path to get access rights for. + * @access_request: The rights we are interested in. + * + * Returns: The access mask of the rights that are permitted on the given path, + * which are also a subset of access_request (to save some calculation time). + */ +static inline access_mask_t +get_path_access(const struct landlock_ruleset *const domain, + const struct path *const path, access_mask_t access_request) +{ + layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {}; + unsigned long access_bit, access_req; + access_mask_t effective_access_request; + + effective_access_request = + init_layer_masks(domain, access_request, &layer_masks); + if (!check_access_path_dual(domain, path, effective_access_request, + &layer_masks, NULL, 0, NULL, NULL)) { + /* + * Returns immediately for successful accesses and for cases + * where everything is permitted because the path belongs to an + * internal filesystem. + */ + return access_request; + } + + access_req = access_request; + for_each_set_bit(access_bit, &access_req, ARRAY_SIZE(layer_masks)) { + if (layer_masks[access_bit]) { + /* If any layer vetoed the access right, remove it. */ + access_request &= ~BIT_ULL(access_bit); + } + } + return access_request; +} + /** * current_check_refer_path - Check if a rename or link action is allowed * @@ -1142,9 +1196,19 @@ static int hook_path_rmdir(const struct path *const dir, return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR); } +static int hook_path_truncate(const struct path *const path) +{ + return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE); +} + /* File hooks */ -static inline access_mask_t get_file_access(const struct file *const file) +/* + * get_required_file_open_access - Returns the access rights that are required + * for opening the file, depending on the file type and open mode. + */ +static inline access_mask_t +get_required_file_open_access(const struct file *const file) { access_mask_t access = 0; @@ -1164,17 +1228,56 @@ static inline access_mask_t get_file_access(const struct file *const file) static int hook_file_open(struct file *const file) { + access_mask_t access_request, access_rights; + const access_mask_t optional_access = LANDLOCK_ACCESS_FS_TRUNCATE; const struct landlock_ruleset *const dom = landlock_get_current_domain(); - if (!dom) + if (!dom) { + /* + * Grants all access rights, even if most of them are not + * checked later on. It is more consistent. + */ + landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS; return 0; + } + /* - * Because a file may be opened with O_PATH, get_file_access() may - * return 0. This case will be handled with a future Landlock + * Because a file may be opened with O_PATH, get_required_file_open_access() + * may return 0. This case will be handled with a future Landlock * evolution. */ - return check_access_path(dom, &file->f_path, get_file_access(file)); + access_request = get_required_file_open_access(file); + access_rights = get_path_access(dom, &file->f_path, + access_request | optional_access); + if (access_request & ~access_rights) + return -EACCES; + + /* + * For operations on already opened files (i.e. ftruncate()), it is the + * access rights at the time of open() which decide whether the + * operation is permitted. Therefore, we record the relevant subset of + * file access rights in the opened struct file. + */ + landlock_file(file)->allowed_access = access_rights; + return 0; +} + +static int hook_file_truncate(struct file *const file) +{ + /* + * Allows truncation if the truncate right was available at the time of + * opening the file, to get a consistent access check as for read, write + * and execute operations. + * + * Note: For checks done based on the file's Landlock rights, we enforce + * them independently of whether the current thread is in a Landlock + * domain, so that open files passed between independent processes + * retain their behaviour. + */ + if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE) + return 0; + return -EACCES; } static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { @@ -1194,6 +1297,8 @@ static struct security_hook_list landlock_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(path_symlink, hook_path_symlink), LSM_HOOK_INIT(path_unlink, hook_path_unlink), LSM_HOOK_INIT(path_rmdir, hook_path_rmdir), + LSM_HOOK_INIT(path_truncate, hook_path_truncate), + LSM_HOOK_INIT(file_truncate, hook_file_truncate), LSM_HOOK_INIT(file_open, hook_file_open), }; diff --git a/security/landlock/fs.h b/security/landlock/fs.h index 8db7acf9109b..488e4813680a 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -36,6 +36,24 @@ struct landlock_inode_security { struct landlock_object __rcu *object; }; +/** + * struct landlock_file_security - File security blob + * + * This information is populated when opening a file in hook_file_open, and + * tracks the relevant Landlock access rights that were available at the time + * of opening the file. Other LSM hooks use these rights in order to authorize + * operations on already opened files. + */ +struct landlock_file_security { + /** + * @allowed_access: Access rights that were available at the time of + * opening the file. This is not necessarily the full set of access + * rights available at that time, but it's the necessary subset as + * needed to authorize later operations on the open file. + */ + access_mask_t allowed_access; +}; + /** * struct landlock_superblock_security - Superblock security blob * @@ -50,6 +68,12 @@ struct landlock_superblock_security { atomic_long_t inode_refs; }; +static inline struct landlock_file_security * +landlock_file(const struct file *const file) +{ + return file->f_security + landlock_blob_sizes.lbs_file; +} + static inline struct landlock_inode_security * landlock_inode(const struct inode *const inode) { diff --git a/security/landlock/limits.h b/security/landlock/limits.h index b54184ab9439..82288f0e9e5e 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -18,7 +18,7 @@ #define LANDLOCK_MAX_NUM_LAYERS 16 #define LANDLOCK_MAX_NUM_RULES U32_MAX -#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_REFER +#define LANDLOCK_LAST_ACCESS_FS LANDLOCK_ACCESS_FS_TRUNCATE #define LANDLOCK_MASK_ACCESS_FS ((LANDLOCK_LAST_ACCESS_FS << 1) - 1) #define LANDLOCK_NUM_ACCESS_FS __const_hweight64(LANDLOCK_MASK_ACCESS_FS) diff --git a/security/landlock/setup.c b/security/landlock/setup.c index f8e8e980454c..3f196d2ce4f9 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -19,6 +19,7 @@ bool landlock_initialized __lsm_ro_after_init = false; struct lsm_blob_sizes landlock_blob_sizes __lsm_ro_after_init = { .lbs_cred = sizeof(struct landlock_cred_security), + .lbs_file = sizeof(struct landlock_file_security), .lbs_inode = sizeof(struct landlock_inode_security), .lbs_superblock = sizeof(struct landlock_superblock_security), }; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 735a0865ea11..f4d6fc7ed17f 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -129,7 +129,7 @@ static const struct file_operations ruleset_fops = { .write = fop_dummy_write, }; -#define LANDLOCK_ABI_VERSION 2 +#define LANDLOCK_ABI_VERSION 3 /** * sys_landlock_create_ruleset - Create a new ruleset diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index da9290817866..72cdae277b02 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -75,7 +75,7 @@ TEST(abi_version) const struct landlock_ruleset_attr ruleset_attr = { .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, }; - ASSERT_EQ(2, landlock_create_ruleset(NULL, 0, + ASSERT_EQ(3, landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION)); ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 45de42a027c5..87b28d14a1aa 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -406,9 +406,10 @@ TEST_F_FORK(layout1, inval) #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ - LANDLOCK_ACCESS_FS_READ_FILE) + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_TRUNCATE) -#define ACCESS_LAST LANDLOCK_ACCESS_FS_REFER +#define ACCESS_LAST LANDLOCK_ACCESS_FS_TRUNCATE #define ACCESS_ALL ( \ ACCESS_FILE | \ @@ -422,7 +423,7 @@ TEST_F_FORK(layout1, inval) LANDLOCK_ACCESS_FS_MAKE_FIFO | \ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ LANDLOCK_ACCESS_FS_MAKE_SYM | \ - ACCESS_LAST) + LANDLOCK_ACCESS_FS_REFER) /* clang-format on */ From patchwork Fri Sep 30 16:01:40 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995731 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 32F09C43219 for ; Fri, 30 Sep 2022 16:02:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231932AbiI3QCw (ORCPT ); Fri, 30 Sep 2022 12:02:52 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43374 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231965AbiI3QCQ (ORCPT ); Fri, 30 Sep 2022 12:02:16 -0400 Received: from mail-ej1-x635.google.com (mail-ej1-x635.google.com [IPv6:2a00:1450:4864:20::635]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 514DA1ABFE5; Fri, 30 Sep 2022 09:02:14 -0700 (PDT) Received: by mail-ej1-x635.google.com with SMTP id sd10so10014805ejc.2; Fri, 30 Sep 2022 09:02:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=cJw3hwey1O2Jc4M+fogvAhEqykdufCShzAsb2RY13QA=; b=Z+M8FZEvj4PB2n2jZo+BwZ5t6M/DObdYluC059vXYfVdrMd10UoxqCaIKbL/EHMHFh VBxED3HwPM81ab6ddm8UTT9hv0hGlXusLqqWJJckGlRLCgIK1y81TULIUYO9Kms0Bh3U MZcd/7T+FchNOuS93k0J1mLW0/phH05LI69eBvJX964aK0MQMWiqt51+ZKJeQ4DbKhyI 6lp8FB35lQtbnhRzjFs2e7ZlfwRNZ2rQjqGDmJw7JQsa2/m9SPZ0PKyZkxphcNLcr2dv cHXYC9CTYaT3vltdgU/JJijvbvwS9+gt8FK0+0t4TX8xvWp1/skJgWshTY9v9cx0lDq7 um+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=cJw3hwey1O2Jc4M+fogvAhEqykdufCShzAsb2RY13QA=; b=P76mUxC2xnhVNMX8R3Nw60MFHMgQmihtRtmRQ2heRM6JV64GQPYlpNj7DV7pQWfGv8 sqFQalYl3UFmZc4mYLkyvHgGacfS6nh+2BLsEzjUPMVj97YvlQdYLl94cqAkGhz6F8fG vwr6ASEIBWLMJiKs/UClwyafZTZC1y1CGpS8Oa2YQuEuUCaiiIsyuEW3JbCwH9GIEqg7 PNW/kUm3woU/gfa74aDTgovmuXGIs7lIZwM4VCFD0ioLi7UgVFw+TW5sXOYwmm6uIicM 4QybhYrlzSKKlHCMFsuYO3WJfOHHdH5BhSYYfVi+SjkMNVXdkRrDZlpwKrVxcWk2yr1D nP8A== X-Gm-Message-State: ACrzQf2cU7nnResrUXJz6lt3cJ3s8oWD9Yja3em9q3514xsGqhO2jEyP Cl9NEH5pvsqopkPfNVT5qODJ2+7oJxQ= X-Google-Smtp-Source: AMsMyM5XGbeVNn1UURnLUzkjkRHUNWfxKwKlmwoWlfdqj/nZTt7XRessvdxSR5Sfz3lwyJPZt1hHLQ== X-Received: by 2002:a17:907:3201:b0:741:94f2:aeaf with SMTP id xg1-20020a170907320100b0074194f2aeafmr6786186ejb.505.1664553732295; Fri, 30 Sep 2022 09:02:12 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.11 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:11 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 3/7] selftests/landlock: Selftests for file truncation support Date: Fri, 30 Sep 2022 18:01:40 +0200 Message-Id: <20220930160144.141504-4-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: These tests exercise the following truncation operations: * truncate() (truncate by path) * ftruncate() (truncate by file descriptor) * open with the O_TRUNC flag * special case: creat(), which is open with O_CREAT|O_WRONLY|O_TRUNC. in the following scenarios: * Files with read, write and truncate rights. * Files with read and truncate rights. * Files with the truncate right. * Files without the truncate right. In particular, the following scenarios are enforced with the test: * open() with O_TRUNC requires the truncate right, if it truncates a file. open() already checks security_path_truncate() in this case, and it required no additional check in the Landlock LSM's file_open hook. * creat() requires the truncate right when called with an existing filename. * creat() does *not* require the truncate right when it's creating a new file. * ftruncate() requires that the file was opened by a thread that had the truncate right for the file at the time of open(). (The rights are carried along with the opened file.) Signed-off-by: Günther Noack --- tools/testing/selftests/landlock/fs_test.c | 287 +++++++++++++++++++++ 1 file changed, 287 insertions(+) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 87b28d14a1aa..718543fd3dfc 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -58,6 +58,7 @@ static const char file1_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f1"; static const char file2_s2d3[] = TMP_DIR "/s2d1/s2d2/s2d3/f2"; static const char dir_s3d1[] = TMP_DIR "/s3d1"; +static const char file1_s3d1[] = TMP_DIR "/s3d1/f1"; /* dir_s3d2 is a mount point. */ static const char dir_s3d2[] = TMP_DIR "/s3d1/s3d2"; static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; @@ -83,6 +84,7 @@ static const char dir_s3d3[] = TMP_DIR "/s3d1/s3d2/s3d3"; * │   ├── f1 * │   └── f2 * └── s3d1 + *    ├── f1 * └── s3d2 * └── s3d3 */ @@ -208,6 +210,7 @@ static void create_layout1(struct __test_metadata *const _metadata) create_file(_metadata, file1_s2d3); create_file(_metadata, file2_s2d3); + create_file(_metadata, file1_s3d1); create_directory(_metadata, dir_s3d2); set_cap(_metadata, CAP_SYS_ADMIN); ASSERT_EQ(0, mount("tmp", dir_s3d2, "tmpfs", 0, "size=4m,mode=700")); @@ -230,6 +233,7 @@ static void remove_layout1(struct __test_metadata *const _metadata) EXPECT_EQ(0, remove_path(file1_s2d2)); EXPECT_EQ(0, remove_path(file1_s2d1)); + EXPECT_EQ(0, remove_path(file1_s3d1)); EXPECT_EQ(0, remove_path(dir_s3d3)); set_cap(_metadata, CAP_SYS_ADMIN); umount(dir_s3d2); @@ -3158,6 +3162,289 @@ TEST_F_FORK(layout1, proc_pipe) ASSERT_EQ(0, close(pipe_fds[1])); } +/* Invokes truncate(2) and returns its errno or 0. */ +static int test_truncate(const char *const path) +{ + if (truncate(path, 10) < 0) + return errno; + return 0; +} + +/* + * Invokes creat(2) and returns its errno or 0. + * Closes the opened file descriptor on success. + */ +static int test_creat(const char *const path) +{ + int fd = creat(path, 0600); + + if (fd < 0) + return errno; + + /* + * Mixing error codes from close(2) and creat(2) should not lead to any + * (access type) confusion for this test. + */ + if (close(fd) < 0) + return errno; + return 0; +} + +/* + * Exercises file truncation when it's not restricted, + * as it was the case before LANDLOCK_ACCESS_FS_TRUNCATE existed. + */ +TEST_F_FORK(layout1, truncate_unhandled) +{ + const char *const file_r = file1_s1d1; + const char *const file_w = file2_s1d1; + const char *const file_none = file1_s1d2; + const struct rule rules[] = { + { + .path = file_r, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + { + .path = file_w, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + /* Implicitly: No rights for file_none. */ + {}, + }; + + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + int ruleset_fd; + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, handled, rules); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* + * Checks read right: truncate and open with O_TRUNC work, unless the + * file is attempted to be opened for writing. + */ + EXPECT_EQ(0, test_truncate(file_r)); + EXPECT_EQ(0, test_open(file_r, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_r, O_WRONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_creat(file_r)); + + /* + * Checks write right: truncate and open with O_TRUNC work, unless the + * file is attempted to be opened for reading. + */ + EXPECT_EQ(0, test_truncate(file_w)); + EXPECT_EQ(EACCES, test_open(file_w, O_RDONLY | O_TRUNC)); + EXPECT_EQ(0, test_open(file_w, O_WRONLY | O_TRUNC)); + EXPECT_EQ(0, test_creat(file_w)); + + /* + * Checks "no rights" case: truncate works but all open attempts fail, + * including creat. + */ + EXPECT_EQ(0, test_truncate(file_none)); + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_creat(file_none)); +} + +TEST_F_FORK(layout1, truncate) +{ + const char *const file_rwt = file1_s1d1; + const char *const file_rw = file2_s1d1; + const char *const file_rt = file1_s1d2; + const char *const file_t = file2_s1d2; + const char *const file_none = file1_s1d3; + const char *const dir_t = dir_s2d1; + const char *const file_in_dir_t = file1_s2d1; + const char *const dir_w = dir_s3d1; + const char *const file_in_dir_w = file1_s3d1; + const struct rule rules[] = { + { + .path = file_rwt, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = file_rw, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + { + .path = file_rt, + .access = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = file_t, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, + }, + /* Implicitly: No access rights for file_none. */ + { + .path = dir_t, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, + }, + { + .path = dir_w, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + const __u64 handled = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE; + int ruleset_fd; + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, handled, rules); + + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + /* Checks read, write and truncate rights: truncation works. */ + EXPECT_EQ(0, test_truncate(file_rwt)); + EXPECT_EQ(0, test_open(file_rwt, O_RDONLY | O_TRUNC)); + EXPECT_EQ(0, test_open(file_rwt, O_WRONLY | O_TRUNC)); + + /* Checks read and write rights: no truncate variant works. */ + EXPECT_EQ(EACCES, test_truncate(file_rw)); + EXPECT_EQ(EACCES, test_open(file_rw, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_rw, O_WRONLY | O_TRUNC)); + + /* + * Checks read and truncate rights: truncation works. + * + * Note: Files can get truncated using open() even with O_RDONLY. + */ + EXPECT_EQ(0, test_truncate(file_rt)); + EXPECT_EQ(0, test_open(file_rt, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_rt, O_WRONLY | O_TRUNC)); + + /* Checks truncate right: truncate works, but can't open file. */ + EXPECT_EQ(0, test_truncate(file_t)); + EXPECT_EQ(EACCES, test_open(file_t, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_t, O_WRONLY | O_TRUNC)); + + /* Checks "no rights" case: No form of truncation works. */ + EXPECT_EQ(EACCES, test_truncate(file_none)); + EXPECT_EQ(EACCES, test_open(file_none, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_none, O_WRONLY | O_TRUNC)); + + /* + * Checks truncate right on directory: truncate works on contained + * files. + */ + EXPECT_EQ(0, test_truncate(file_in_dir_t)); + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_RDONLY | O_TRUNC)); + EXPECT_EQ(EACCES, test_open(file_in_dir_t, O_WRONLY | O_TRUNC)); + + /* + * Checks creat in dir_w: This requires the truncate right when + * overwriting an existing file, but does not require it when the file + * is new. + */ + EXPECT_EQ(EACCES, test_creat(file_in_dir_w)); + + ASSERT_EQ(0, unlink(file_in_dir_w)); + EXPECT_EQ(0, test_creat(file_in_dir_w)); +} + +/* Invokes ftruncate(2) and returns its errno or 0. */ +static int test_ftruncate(int fd) +{ + if (ftruncate(fd, 10) < 0) + return errno; + return 0; +} + +TEST_F_FORK(layout1, ftruncate) +{ + /* + * This test opens a new file descriptor at different stages of + * Landlock restriction: + * + * without restriction: ftruncate works + * something else but truncate restricted: ftruncate works + * truncate restricted and permitted: ftruncate works + * truncate restricted and not permitted: ftruncate fails + * + * Whether this works or not is expected to depend on the time when the + * FD was opened, not to depend on the time when ftruncate() was + * called. + */ + const char *const path = file1_s1d1; + const __u64 handled1 = LANDLOCK_ACCESS_FS_READ_FILE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + const struct rule layer1[] = { + { + .path = path, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + const __u64 handled2 = LANDLOCK_ACCESS_FS_TRUNCATE; + const struct rule layer2[] = { + { + .path = path, + .access = LANDLOCK_ACCESS_FS_TRUNCATE, + }, + {}, + }; + const __u64 handled3 = LANDLOCK_ACCESS_FS_TRUNCATE | + LANDLOCK_ACCESS_FS_WRITE_FILE; + const struct rule layer3[] = { + { + .path = path, + .access = LANDLOCK_ACCESS_FS_WRITE_FILE, + }, + {}, + }; + int fd_layer0, fd_layer1, fd_layer2, fd_layer3, ruleset_fd; + + fd_layer0 = open(path, O_WRONLY); + EXPECT_EQ(0, test_ftruncate(fd_layer0)); + + ruleset_fd = create_ruleset(_metadata, handled1, layer1); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + fd_layer1 = open(path, O_WRONLY); + EXPECT_EQ(0, test_ftruncate(fd_layer0)); + EXPECT_EQ(0, test_ftruncate(fd_layer1)); + + ruleset_fd = create_ruleset(_metadata, handled2, layer2); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + fd_layer2 = open(path, O_WRONLY); + EXPECT_EQ(0, test_ftruncate(fd_layer0)); + EXPECT_EQ(0, test_ftruncate(fd_layer1)); + EXPECT_EQ(0, test_ftruncate(fd_layer2)); + + ruleset_fd = create_ruleset(_metadata, handled3, layer3); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + fd_layer3 = open(path, O_WRONLY); + EXPECT_EQ(0, test_ftruncate(fd_layer0)); + EXPECT_EQ(0, test_ftruncate(fd_layer1)); + EXPECT_EQ(0, test_ftruncate(fd_layer2)); + EXPECT_EQ(EACCES, test_ftruncate(fd_layer3)); + + ASSERT_EQ(0, close(fd_layer0)); + ASSERT_EQ(0, close(fd_layer1)); + ASSERT_EQ(0, close(fd_layer2)); + ASSERT_EQ(0, close(fd_layer3)); +} + /* clang-format off */ FIXTURE(layout1_bind) {}; /* clang-format on */ From patchwork Fri Sep 30 16:01:41 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995734 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A5060C433F5 for ; Fri, 30 Sep 2022 16:02:58 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231945AbiI3QC4 (ORCPT ); Fri, 30 Sep 2022 12:02:56 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33628 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231775AbiI3QCS (ORCPT ); Fri, 30 Sep 2022 12:02:18 -0400 Received: from mail-ed1-x535.google.com (mail-ed1-x535.google.com [IPv6:2a00:1450:4864:20::535]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 054751B34B9; Fri, 30 Sep 2022 09:02:14 -0700 (PDT) Received: by mail-ed1-x535.google.com with SMTP id a13so6606044edj.0; Fri, 30 Sep 2022 09:02:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=7mkbYIT0lyMbYQ60i8bXJR6CMNT1pJkoeHZZ5DPZ4IM=; b=OaP+7KFzTlEnJt+iZNALX3EGzH6HiqJvDEEO8jC2klp8ppaRpH+kBqLgnAu2RkNsI7 Bg8BELHbdgH1fO0kkr1/Sw7R+A+EmyJgYfB5ihvi1gRr7+lNhwLCabcbnU933I/DvVYq aaa4x2VqKqlAcnKnn30HBFWjAbKXQbICpXOOLyoAB5e7oebeHCfzQP7QZnxEzyvB4tfX sKk0HqRxkzeBzHBDe5dQKQSSo5isMFHd9TC4fvoBxoC8MrkhCrQ9lEQKAN3bwdqKvOkV MRJ53mQT4V7Sd678ksvdBgAQpXOsLYdbp+M2BXbGzCGG+kUlnhcL5MeeOW+4WDFCKffD geAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=7mkbYIT0lyMbYQ60i8bXJR6CMNT1pJkoeHZZ5DPZ4IM=; b=IYIDVSimY+os7f8X2UmQVTlN51YwI3EqtTOsZSgD98clWxruG0UeqcjfmbLwR5V3L+ 5LrDf1mxIXVxpK2ZhaH2IA28swAicDgVTCaobCOK8RNWd4k0H3wq/js9bG2reNBEo4vK Gf2nwtAb9pIDKxQZeN66fgqbh7Iqnpbm2N6JCb9nAWsrUVmZeQNXzQUc/660RGpYFL3V 3i21+9d6dqNMc9Cot5dtbjUaQcig+V2ls7ZTyvPwRYgnN9WEu2r/lZQy3SBhzAx4OuK9 g90WKjjagNz+tvuTfyNUvw44i/AmCz8au7bFES52F5XYwZXf14OYBoRgyWf7z9e6LvX2 bSGw== X-Gm-Message-State: ACrzQf2CssB5fEhmT+P4x3x+c/YHhnJmvQLeP+jajA6aA+BzbOrFWtWd 2X9PzZwhSd1Y86S+HGGD4HKEC9/YnQI= X-Google-Smtp-Source: AMsMyM7wWy7UXrzkHV/brcq/gODLW68ZQaRz2XmrOK/WFpJ+QUarqJTevlqyAk99KQ9oBM2tjbfpgQ== X-Received: by 2002:a50:cdd8:0:b0:457:269a:decf with SMTP id h24-20020a50cdd8000000b00457269adecfmr8394285edj.359.1664553733081; Fri, 30 Sep 2022 09:02:13 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:12 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 4/7] selftests/landlock: Test open() and ftruncate() in multiple scenarios Date: Fri, 30 Sep 2022 18:01:41 +0200 Message-Id: <20220930160144.141504-5-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: This test uses multiple fixture variants to exercise a broader set of scnenarios. Signed-off-by: Günther Noack --- tools/testing/selftests/landlock/fs_test.c | 96 ++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 718543fd3dfc..308f6f36e8c0 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -3445,6 +3445,102 @@ TEST_F_FORK(layout1, ftruncate) ASSERT_EQ(0, close(fd_layer3)); } +/* clang-format off */ +FIXTURE(ftruncate) {}; +/* clang-format on */ + +FIXTURE_SETUP(ftruncate) +{ + prepare_layout(_metadata); + create_file(_metadata, file1_s1d1); +} + +FIXTURE_TEARDOWN(ftruncate) +{ + EXPECT_EQ(0, remove_path(file1_s1d1)); + cleanup_layout(_metadata); +} + +FIXTURE_VARIANT(ftruncate) +{ + const __u64 handled; + const __u64 permitted; + const int expected_open_result; + const int expected_ftruncate_result; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ftruncate, w_w) { + /* clang-format on */ + .handled = LANDLOCK_ACCESS_FS_WRITE_FILE, + .permitted = LANDLOCK_ACCESS_FS_WRITE_FILE, + .expected_open_result = 0, + .expected_ftruncate_result = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ftruncate, t_t) { + /* clang-format on */ + .handled = LANDLOCK_ACCESS_FS_TRUNCATE, + .permitted = LANDLOCK_ACCESS_FS_TRUNCATE, + .expected_open_result = 0, + .expected_ftruncate_result = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ftruncate, wt_w) { + /* clang-format on */ + .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, + .permitted = LANDLOCK_ACCESS_FS_WRITE_FILE, + .expected_open_result = 0, + .expected_ftruncate_result = EACCES, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ftruncate, wt_wt) { + /* clang-format on */ + .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, + .permitted = LANDLOCK_ACCESS_FS_WRITE_FILE | + LANDLOCK_ACCESS_FS_TRUNCATE, + .expected_open_result = 0, + .expected_ftruncate_result = 0, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(ftruncate, wt_t) { + /* clang-format on */ + .handled = LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_TRUNCATE, + .permitted = LANDLOCK_ACCESS_FS_TRUNCATE, + .expected_open_result = EACCES, +}; + +TEST_F_FORK(ftruncate, open_and_ftruncate) +{ + const char *const path = file1_s1d1; + const struct rule rules[] = { + { + .path = path, + .access = variant->permitted, + }, + {}, + }; + int fd, ruleset_fd; + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, variant->handled, rules); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + fd = open(path, O_WRONLY); + EXPECT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); + if (fd >= 0) { + EXPECT_EQ(variant->expected_ftruncate_result, + test_ftruncate(fd)); + ASSERT_EQ(0, close(fd)); + } +} + /* clang-format off */ FIXTURE(layout1_bind) {}; /* clang-format on */ From patchwork Fri Sep 30 16:01:42 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995733 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D790BC433F5 for ; Fri, 30 Sep 2022 16:02:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231934AbiI3QCx (ORCPT ); Fri, 30 Sep 2022 12:02:53 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35624 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231777AbiI3QCS (ORCPT ); Fri, 30 Sep 2022 12:02:18 -0400 Received: from mail-ed1-x534.google.com (mail-ed1-x534.google.com [IPv6:2a00:1450:4864:20::534]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A08911B0512; Fri, 30 Sep 2022 09:02:15 -0700 (PDT) Received: by mail-ed1-x534.google.com with SMTP id y8so6557537edc.10; Fri, 30 Sep 2022 09:02:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=6srsNO+PnD44pD9cuu4iqeCet2frHPF+z/NrQzZBp+E=; b=akMEACoKhQNR86trTa3gJuFenczxFAfJopi3gQz8C26bu8pgpl8TBH4piFfUpLPiCG ZjMT3it5aYF6KMt132fEEFUqF7TGkI3aWRZzB6153YcbK6OTIH2Xci49s1pjNr1Sp2VO VqD00Zo5Np8tO7zjDZ8vxuEK3OHciU1eeux37VgYyEOdJjMUDqmSzbJ4xYE1HrnVm4y3 owr8bt7IWzXvbriXCWioJ22QqKI5OZ3e0pp614qC4DPvalW+/VRWBuS8C1o8I3CN6eJM Y/2GmuflSoJtASmpXnVDfDzrma+noSUE4X3T3PyuLTwphPdEg54RUYi5FofswDby09c5 He+Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=6srsNO+PnD44pD9cuu4iqeCet2frHPF+z/NrQzZBp+E=; b=wkWZnxOUqn47YqwXeJLWE5d5QifFqKYD/ZulahiPmdgeoJRhM+/ygu+WpBVOvP/3P1 DFFsl6TXypRLzkbcGG5PfteI+ofjkw7bOqBT8oC6JCck1R04AuvB1G23rJU/uz2gwSR/ q0Wzc7vJzC7C6AnLKyVMEdUUgZ2u2N2yQ6+GwXbHVhztSkFT+iDZGufCj/DbXCMTvGCU Ppn5VFvDppTNCMz8KlC3ATtWWrPyzRfcGVcgBjsVD/nDPFKEYcZlNDEI/DkY6tsNGxhA M68leabfQ8m+oZrkWaikzbRmFJVm/rluZi7FQdxh2AHshysE2PD1EOB9Mfj5D4ZZT5Mj e4yg== X-Gm-Message-State: ACrzQf0Pf7V1QN5D/CByNaJOa594djen0ErGKC5kp67XhYFY4StFxV0g 0KfJwd/9vbR8h2fuwvMi4OkQZphEj5A= X-Google-Smtp-Source: AMsMyM6mO2TLigGKg2JkStpo3szvUkfM86c6hg1G+snKqedez1OhvCSTVKrK792UdTw9abe3Gg3Raw== X-Received: by 2002:aa7:cb0b:0:b0:456:e744:79e5 with SMTP id s11-20020aa7cb0b000000b00456e74479e5mr8468292edt.191.1664553733876; Fri, 30 Sep 2022 09:02:13 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:13 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 5/7] selftests/landlock: Test FD passing from a Landlock-restricted to an unrestricted process Date: Fri, 30 Sep 2022 18:01:42 +0200 Message-Id: <20220930160144.141504-6-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: A file descriptor created in a restricted process carries Landlock restrictions with it which will apply even if the same opened file is used from an unrestricted process. This change extracts suitable FD-passing helpers from base_test.c and moves them to common.h. We use the fixture variants from the ftruncate fixture to exercise the same scenarios as in the open_and_ftruncate test, but doing the Landlock restriction and open() in a different process than the ftruncate() call. Signed-off-by: Günther Noack --- tools/testing/selftests/landlock/base_test.c | 36 +---------- tools/testing/selftests/landlock/common.h | 67 ++++++++++++++++++++ tools/testing/selftests/landlock/fs_test.c | 62 ++++++++++++++++++ 3 files changed, 132 insertions(+), 33 deletions(-) diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 72cdae277b02..6d1b6eedb432 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -263,23 +263,6 @@ TEST(ruleset_fd_transfer) .allowed_access = LANDLOCK_ACCESS_FS_READ_DIR, }; int ruleset_fd_tx, dir_fd; - union { - /* Aligned ancillary data buffer. */ - char buf[CMSG_SPACE(sizeof(ruleset_fd_tx))]; - struct cmsghdr _align; - } cmsg_tx = {}; - char data_tx = '.'; - struct iovec io = { - .iov_base = &data_tx, - .iov_len = sizeof(data_tx), - }; - struct msghdr msg = { - .msg_iov = &io, - .msg_iovlen = 1, - .msg_control = &cmsg_tx.buf, - .msg_controllen = sizeof(cmsg_tx.buf), - }; - struct cmsghdr *cmsg; int socket_fds[2]; pid_t child; int status; @@ -298,33 +281,20 @@ TEST(ruleset_fd_transfer) &path_beneath_attr, 0)); ASSERT_EQ(0, close(path_beneath_attr.parent_fd)); - cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(NULL, cmsg); - cmsg->cmsg_len = CMSG_LEN(sizeof(ruleset_fd_tx)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(cmsg), &ruleset_fd_tx, sizeof(ruleset_fd_tx)); - /* Sends the ruleset FD over a socketpair and then close it. */ ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, socket_fds)); - ASSERT_EQ(sizeof(data_tx), sendmsg(socket_fds[0], &msg, 0)); + ASSERT_EQ(0, send_fd(socket_fds[0], ruleset_fd_tx)); ASSERT_EQ(0, close(socket_fds[0])); ASSERT_EQ(0, close(ruleset_fd_tx)); child = fork(); ASSERT_LE(0, child); if (child == 0) { - int ruleset_fd_rx; + int ruleset_fd_rx = recv_fd(socket_fds[1]); - *(char *)msg.msg_iov->iov_base = '\0'; - ASSERT_EQ(sizeof(data_tx), - recvmsg(socket_fds[1], &msg, MSG_CMSG_CLOEXEC)); - ASSERT_EQ('.', *(char *)msg.msg_iov->iov_base); + ASSERT_LE(0, ruleset_fd_rx); ASSERT_EQ(0, close(socket_fds[1])); - cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(ruleset_fd_tx))); - memcpy(&ruleset_fd_rx, CMSG_DATA(cmsg), sizeof(ruleset_fd_tx)); /* Enforces the received ruleset on the child. */ ASSERT_EQ(0, prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)); diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 7ba18eb23783..8ec8971e9580 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -187,3 +188,69 @@ clear_cap(struct __test_metadata *const _metadata, const cap_value_t caps) { _effective_cap(_metadata, caps, CAP_CLEAR); } + +/* Receives an FD from a UNIX socket. Returns the received FD, -1 on error. */ +__maybe_unused static int recv_fd(int usock) +{ + int fd_rx; + union { + /* Aligned ancillary data buffer. */ + char buf[CMSG_SPACE(sizeof(fd_rx))]; + struct cmsghdr _align; + } cmsg_rx = {}; + char data = '\0'; + struct iovec io = { + .iov_base = &data, + .iov_len = sizeof(data), + }; + struct msghdr msg = { + .msg_iov = &io, + .msg_iovlen = 1, + .msg_control = &cmsg_rx.buf, + .msg_controllen = sizeof(cmsg_rx.buf), + }; + struct cmsghdr *cmsg; + int res; + + res = recvmsg(usock, &msg, MSG_CMSG_CLOEXEC); + if (res < 0) + return -1; + + cmsg = CMSG_FIRSTHDR(&msg); + if (cmsg->cmsg_len != CMSG_LEN(sizeof(fd_rx))) + return -1; + + memcpy(&fd_rx, CMSG_DATA(cmsg), sizeof(fd_rx)); + return fd_rx; +} + +/* Sends an FD on a UNIX socket. Returns 0 on success or -1 on error. */ +__maybe_unused static int send_fd(int usock, int fd_tx) +{ + union { + /* Aligned ancillary data buffer. */ + char buf[CMSG_SPACE(sizeof(fd_tx))]; + struct cmsghdr _align; + } cmsg_tx = {}; + char data_tx = '.'; + struct iovec io = { + .iov_base = &data_tx, + .iov_len = sizeof(data_tx), + }; + struct msghdr msg = { + .msg_iov = &io, + .msg_iovlen = 1, + .msg_control = &cmsg_tx.buf, + .msg_controllen = sizeof(cmsg_tx.buf), + }; + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_len = CMSG_LEN(sizeof(fd_tx)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(cmsg), &fd_tx, sizeof(fd_tx)); + + if (sendmsg(usock, &msg, 0) < 0) + return -1; + return 0; +} diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 308f6f36e8c0..93ed80a25a74 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -3541,6 +3541,68 @@ TEST_F_FORK(ftruncate, open_and_ftruncate) } } +TEST_F_FORK(ftruncate, open_and_ftruncate_in_different_processes) +{ + int child, fd, status; + int socket_fds[2]; + + ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, + socket_fds)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + /* + * Enable Landlock in the child process, open a file descriptor + * where truncation is forbidden and send it to the + * non-landlocked parent process. + */ + const char *const path = file1_s1d1; + const struct rule rules[] = { + { + .path = path, + .access = variant->permitted, + }, + {}, + }; + int fd, ruleset_fd; + + /* Enable Landlock. */ + ruleset_fd = create_ruleset(_metadata, variant->handled, rules); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + ASSERT_EQ(0, close(ruleset_fd)); + + fd = open(path, O_WRONLY); + ASSERT_EQ(variant->expected_open_result, (fd < 0 ? errno : 0)); + + if (fd >= 0) { + ASSERT_EQ(0, send_fd(socket_fds[0], fd)); + ASSERT_EQ(0, close(fd)); + } + + ASSERT_EQ(0, close(socket_fds[0])); + + _exit(_metadata->passed ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if (variant->expected_open_result == 0) { + fd = recv_fd(socket_fds[1]); + ASSERT_LE(0, fd); + + EXPECT_EQ(variant->expected_ftruncate_result, + test_ftruncate(fd)); + ASSERT_EQ(0, close(fd)); + } + + ASSERT_EQ(child, waitpid(child, &status, 0)); + ASSERT_EQ(1, WIFEXITED(status)); + ASSERT_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); + + ASSERT_EQ(0, close(socket_fds[0])); + ASSERT_EQ(0, close(socket_fds[1])); +} + /* clang-format off */ FIXTURE(layout1_bind) {}; /* clang-format on */ From patchwork Fri Sep 30 16:01:43 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995735 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A9A3CC4332F for ; Fri, 30 Sep 2022 16:02:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231946AbiI3QC5 (ORCPT ); Fri, 30 Sep 2022 12:02:57 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35640 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231853AbiI3QCS (ORCPT ); Fri, 30 Sep 2022 12:02:18 -0400 Received: from mail-ej1-x62e.google.com (mail-ej1-x62e.google.com [IPv6:2a00:1450:4864:20::62e]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7DA0C1B3A45; Fri, 30 Sep 2022 09:02:16 -0700 (PDT) Received: by mail-ej1-x62e.google.com with SMTP id nb11so9974317ejc.5; Fri, 30 Sep 2022 09:02:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=C+GyJq5HgV0Z7OWkJYmIbswU12VCmy8zCOP8Qkw/Jxk=; b=CCRGRJOufx+mg2aoyxpmDIdQ9ZbPwRbNqH7uNo9Ul+rzcw89FssJZfByRUozRyKGBf Ks7JXx3H5xjIzBh8gj1369ysjCcFlxeoAvLs9Pd8LjLHMELF2wr+0vppKbJZzjoJ6Twe kzLAnpaAd1w/D1exrxeK35/OJEFJKs5Tof0kOjn6Wkm+VFtE9zH1MEzISk7fKt8SG0QH PI2TVHUdTwwDJtCzK9Mq/C95m/gk2exYhEXo/qiVqoVbUL1neOBw/BUSU5JCstHSyFUS udiLj+8y7aC38LhL8wKiFlsRbREbAKwH1EPvUFK6VoMRcIhJN9XRTp4WhV97+LA4L5pt CSzA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=C+GyJq5HgV0Z7OWkJYmIbswU12VCmy8zCOP8Qkw/Jxk=; b=54aVk462zhl7m9c6MB4e6MA413mfrrdOh1C+7ylFqPiqS5DGVGP5920MmxKJVqcRwL F91bKz6kGLSnOWZwyYDoO1KxLBEckNJDOr03K4mcbb1XWr/Cst2+HWUPuHcylgpEaGJE itJngoXaBP8wpb8C0yvSOEhAMIphpUO1qlFK+gI82DHUoZmY4BX5Cqzeqd/2zj+0AoKC oLpM7hJKumVbH+Ed8xzTXyHWpF9qNcl7Bsug0guUfJyQW/jan4nglUI73ru/3ciCILFW jJryMou1CVjybAFdvu824FUUiBy5s47tH1WNWFElkJcaiPUiAVQMAfjww6LVFMwQVokI fLqg== X-Gm-Message-State: ACrzQf3BnFErVER+6RqWDAW8c8UI8SD84rg9gkbH4wOmZfuogbCouzI3 EXCHEf9nuMKBmV3p6RpfPCdMTvc6pkk= X-Google-Smtp-Source: AMsMyM7029mwtxLlTXW+vuLatElzrzmMciDXtOc+5zIDzcDsh9BJqoNohMfrWA6In0YfUlUz4BNktg== X-Received: by 2002:a17:906:9b8b:b0:782:20fd:8597 with SMTP id dd11-20020a1709069b8b00b0078220fd8597mr6849275ejc.423.1664553734640; Fri, 30 Sep 2022 09:02:14 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:14 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 6/7] samples/landlock: Extend sample tool to support LANDLOCK_ACCESS_FS_TRUNCATE Date: Fri, 30 Sep 2022 18:01:43 +0200 Message-Id: <20220930160144.141504-7-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: Update the sandboxer sample to restrict truncate actions. This is automatically enabled by default if the running kernel supports LANDLOCK_ACCESS_FS_TRUNCATE, except for the paths listed in the LL_FS_RW environment variable. Signed-off-by: Günther Noack --- samples/landlock/sandboxer.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/samples/landlock/sandboxer.c b/samples/landlock/sandboxer.c index 3e404e51ec64..771b6b10d519 100644 --- a/samples/landlock/sandboxer.c +++ b/samples/landlock/sandboxer.c @@ -76,7 +76,8 @@ static int parse_path(char *env_path, const char ***const path_list) #define ACCESS_FILE ( \ LANDLOCK_ACCESS_FS_EXECUTE | \ LANDLOCK_ACCESS_FS_WRITE_FILE | \ - LANDLOCK_ACCESS_FS_READ_FILE) + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_TRUNCATE) /* clang-format on */ @@ -160,10 +161,8 @@ static int populate_ruleset(const char *const env_var, const int ruleset_fd, LANDLOCK_ACCESS_FS_MAKE_FIFO | \ LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ LANDLOCK_ACCESS_FS_MAKE_SYM | \ - LANDLOCK_ACCESS_FS_REFER) - -#define ACCESS_ABI_2 ( \ - LANDLOCK_ACCESS_FS_REFER) + LANDLOCK_ACCESS_FS_REFER | \ + LANDLOCK_ACCESS_FS_TRUNCATE) /* clang-format on */ @@ -226,11 +225,17 @@ int main(const int argc, char *const argv[], char *const *const envp) return 1; } /* Best-effort security. */ - if (abi < 2) { - ruleset_attr.handled_access_fs &= ~ACCESS_ABI_2; - access_fs_ro &= ~ACCESS_ABI_2; - access_fs_rw &= ~ACCESS_ABI_2; + switch (abi) { + case 1: + /* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */ + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + __attribute__((fallthrough)); + case 2: + /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; } + access_fs_ro &= ruleset_attr.handled_access_fs; + access_fs_rw &= ruleset_attr.handled_access_fs; ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); From patchwork Fri Sep 30 16:01:44 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?G=C3=BCnther_Noack?= X-Patchwork-Id: 12995736 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9F3ADC433F5 for ; Fri, 30 Sep 2022 16:03:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231950AbiI3QC6 (ORCPT ); Fri, 30 Sep 2022 12:02:58 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:35630 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231332AbiI3QCT (ORCPT ); Fri, 30 Sep 2022 12:02:19 -0400 Received: from mail-ej1-x631.google.com (mail-ej1-x631.google.com [IPv6:2a00:1450:4864:20::631]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 268C11B0E31; Fri, 30 Sep 2022 09:02:17 -0700 (PDT) Received: by mail-ej1-x631.google.com with SMTP id nb11so9974419ejc.5; Fri, 30 Sep 2022 09:02:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date; bh=3yKms8AKRzFfDi33PhwXrFrwuGPdadmIT5ZGKIDnFV8=; b=HUjcbO5qLJL7WhMNzFFYSeT2a5hNyYd+VbCLDZ1E5WE/w7qTCbjxBZKzrz1JE1e/PP LEAxmLnyHCyHEfR1V0sydyzOLCi8/dbxOChArQS3vigArM6l1WFOOVVDkthvJpx0EbJb 8N9OssMrfXdCPOZ83LQwdyllDXYN46jsvb8i93lX3RbXQQZ05rnWWiqk2XW63JuBma9U xG9CfQsLuLRvGE830+40tQ0SGvGj98NQozDeyx2fEhprWoa7YJhn72sGtEsfY0KgaM3t 1IYww6hGXGXN8Z4iUc/NQsl0/9KVowa5lLSPsi9hxMwk6u1g1SM+BgDpph9rNp6Me8Xn bi0g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date; bh=3yKms8AKRzFfDi33PhwXrFrwuGPdadmIT5ZGKIDnFV8=; b=ShgbkU51mJHaOKSSEod8WJ+9kUSIbxR8j2njqQN+hVrVfDAzls+RnryhhY5X3/xpjI P1SktwXnw2a02jsMSU2dRVCdGcckN/HSl06St90TlznPYlk7Mvz+lZa9Z0ZpKBrgKPxl FabTD1MNRp+jhYMLuIqD07oHIJmpNfhji4WaX1Y1zMT9H8vjrGaRzqMPbGBEtlpzdwP5 ustZHzfHGMpeRLX0Fd2nhvW11NPqP4sJ54EyKaaFT7Gi3kVIaWKBY9/tRbv7nGQLfxUM wBZaPS215hkEG8nwHZSCoRSvkjI2sulQk/0CqTNihMrzsr6Nrh7JqfCJo4vwaiYkhm0Q Gyxw== X-Gm-Message-State: ACrzQf2DpGIPfP5oJNflhq5QNn+qogwd0A+jaYX1aH2Y/B9IwTiymZ+C pLQzNV0fURIdaca5M02N+bR0v/xnXnc= X-Google-Smtp-Source: AMsMyM71Xfnl7pGLFZKvWI7hVFP4JDPanUWCO92/4jNSgbGKb48qeuV1lNCErW1UCKK/z2ODbm9hYQ== X-Received: by 2002:a17:906:8a57:b0:781:9705:df89 with SMTP id gx23-20020a1709068a5700b007819705df89mr7067153ejc.266.1664553735400; Fri, 30 Sep 2022 09:02:15 -0700 (PDT) Received: from nuc.i.gnoack.org ([2a02:168:633b:1:1e69:7aff:fe05:97e6]) by smtp.gmail.com with ESMTPSA id f18-20020a05640214d200b004588ef795easm927583edx.34.2022.09.30.09.02.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 30 Sep 2022 09:02:15 -0700 (PDT) From: =?utf-8?q?G=C3=BCnther_Noack?= To: linux-security-module@vger.kernel.org Cc: =?utf-8?q?Micka=C3=ABl_Sala=C3=BCn?= , James Morris , Paul Moore , "Serge E . Hallyn" , linux-fsdevel@vger.kernel.org, Konstantin Meskhidze , =?utf-8?q?G=C3=BCnt?= =?utf-8?q?her_Noack?= Subject: [PATCH v7 7/7] landlock: Document Landlock's file truncation support Date: Fri, 30 Sep 2022 18:01:44 +0200 Message-Id: <20220930160144.141504-8-gnoack3000@gmail.com> X-Mailer: git-send-email 2.37.3 In-Reply-To: <20220930160144.141504-1-gnoack3000@gmail.com> References: <20220930160144.141504-1-gnoack3000@gmail.com> MIME-Version: 1.0 Precedence: bulk List-ID: Use the LANDLOCK_ACCESS_FS_TRUNCATE flag in the tutorial. Adapt the backwards compatibility example and discussion to remove the truncation flag where needed. Point out potential surprising behaviour related to truncate. Signed-off-by: Günther Noack --- Documentation/userspace-api/landlock.rst | 66 +++++++++++++++++++++--- 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/Documentation/userspace-api/landlock.rst b/Documentation/userspace-api/landlock.rst index b8ea59493964..408029b120bd 100644 --- a/Documentation/userspace-api/landlock.rst +++ b/Documentation/userspace-api/landlock.rst @@ -8,7 +8,7 @@ Landlock: unprivileged access control ===================================== :Author: Mickaël Salaün -:Date: May 2022 +:Date: September 2022 The goal of Landlock is to enable to restrict ambient rights (e.g. global filesystem access) for a set of processes. Because Landlock is a stackable @@ -60,7 +60,8 @@ the need to be explicit about the denied-by-default access rights. LANDLOCK_ACCESS_FS_MAKE_FIFO | LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | - LANDLOCK_ACCESS_FS_REFER, + LANDLOCK_ACCESS_FS_REFER | + LANDLOCK_ACCESS_FS_TRUNCATE, }; Because we may not know on which kernel version an application will be @@ -69,16 +70,27 @@ should try to protect users as much as possible whatever the kernel they are using. To avoid binary enforcement (i.e. either all security features or none), we can leverage a dedicated Landlock command to get the current version of the Landlock ABI and adapt the handled accesses. Let's check if we should -remove the `LANDLOCK_ACCESS_FS_REFER` access right which is only supported -starting with the second version of the ABI. +remove the ``LANDLOCK_ACCESS_FS_REFER`` or ``LANDLOCK_ACCESS_FS_TRUNCATE`` +access rights, which are only supported starting with the second and third +version of the ABI. .. code-block:: c int abi; abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); - if (abi < 2) { + if (abi < 0) { + perror("The running kernel does not enable to use Landlock"); + return 0; /* Degrade gracefully if Landlock is not handled. */ + } + switch (abi) { + case 1: + /* Removes LANDLOCK_ACCESS_FS_REFER for ABI < 2 */ ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; + __attribute__((fallthrough)); + case 2: + /* Removes LANDLOCK_ACCESS_FS_TRUNCATE for ABI < 3 */ + ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; } This enables to create an inclusive ruleset that will contain our rules. @@ -127,8 +139,8 @@ descriptor. It may also be required to create rules following the same logic as explained for the ruleset creation, by filtering access rights according to the Landlock -ABI version. In this example, this is not required because -`LANDLOCK_ACCESS_FS_REFER` is not allowed by any rule. +ABI version. In this example, this is not required because all of the requested +``allowed_access`` rights are already available in ABI 1. We now have a ruleset with one rule allowing read access to ``/usr`` while denying all other handled accesses for the filesystem. The next step is to @@ -251,6 +263,37 @@ To be allowed to use :manpage:`ptrace(2)` and related syscalls on a target process, a sandboxed process should have a subset of the target process rules, which means the tracee must be in a sub-domain of the tracer. +Truncating files +---------------- + +The operations covered by ``LANDLOCK_ACCESS_FS_WRITE_FILE`` and +``LANDLOCK_ACCESS_FS_TRUNCATE`` both change the contents of a file and sometimes +overlap in non-intuitive ways. It is recommended to always specify both of +these together. + +A particularly surprising example is :manpage:`creat(2)`. The name suggests +that this system call requires the rights to create and write files. However, +it also requires the truncate right if an existing file under the same name is +already present. + +It should also be noted that truncating files does not require the +``LANDLOCK_ACCESS_FS_WRITE_FILE`` right. Apart from the :manpage:`truncate(2)` +system call, this can also be done through :manpage:`open(2)` with the flags +``O_RDONLY | O_TRUNC``. + +When opening a file, the availability of the ``LANDLOCK_ACCESS_FS_TRUNCATE`` +right is associated with the newly created file descriptor and will be used for +subsequent truncation attempts using :manpage:`ftruncate(2)`. The behavior is +similar to opening a file for reading or writing, where permissions are checked +during :manpage:`open(2)`, but not during the subsequent :manpage:`read(2)` and +:manpage:`write(2)` calls. + +As a consequence, it is possible to have multiple open file descriptors for the +same file, where one grants the right to truncate the file and the other does +not. It is also possible to pass such file descriptors between processes, +keeping their Landlock properties, even when these processes do not have an +enforced Landlock ruleset. + Compatibility ============= @@ -397,6 +440,15 @@ Starting with the Landlock ABI version 2, it is now possible to securely control renaming and linking thanks to the new `LANDLOCK_ACCESS_FS_REFER` access right. +File truncation (ABI < 3) +------------------------- + +File truncation could not be denied before the third Landlock ABI, so it is +always allowed when using a kernel that only supports the first or second ABI. + +Starting with the Landlock ABI version 3, it is now possible to securely control +truncation thanks to the new ``LANDLOCK_ACCESS_FS_TRUNCATE`` access right. + .. _kernel_support: Kernel support