Received: by 2002:a05:6a10:16a7:0:0:0:0 with SMTP id gp39csp685366pxb; Wed, 11 Nov 2020 13:39:58 -0800 (PST) X-Google-Smtp-Source: ABdhPJy91mOYFXGSmNFm3gLjZtHvaZ+K/f7mEJhjhz4zTjz2cHohofk6/+QolezftOI94FJJI/zN X-Received: by 2002:aa7:d8c4:: with SMTP id k4mr1718620eds.248.1605130798506; Wed, 11 Nov 2020 13:39:58 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1605130798; cv=none; d=google.com; s=arc-20160816; b=atq/7/UJq+In6AXS2u7oM0CjmQmh5r1DZrNwO9YUS8txtwoHOoYBsRcEHUHjp+tRse tS+mIekBfQdJzEhGUf9OJNIBFWsOxeX0hjcEv6FhvpOGBZvWBz8NOWZF11lrZ7l+2x6x CBSbL8SjWrPaJEcbNkzYMbc648l3A5rwvCSzbGapqY3z2j9ElYyvqgiBGNrfDfnUZ20A 9QqlZJbTGHMcAKpcxJYup5rJh5VuYAfxmoqryHjorWm87t89LyTqGuAd8yNUaFge2PLI 9Uow/PNlZqFKzx+qNO5Umy7hXP9iYuYe6FhQ98FdhsMXR1KVf1o0HFuhxq7gDjUqMV/i mIrg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from; bh=oEyFhJmHr3clzrTunIENP6gRenn7yzY/Ll+jAMRQUmg=; b=VDaumNpXcKmPKr3FWZo91swMFDYCmFeCR1S9m2gT4E6JLuLbBQ8YzJcYhJYYg7SATc BjftiRWgWpQFWiaJYGrg0BFRJ4+FYnA5gZXaYrN9nUBztSjIYFZ77e6O63nKjjG/D4Vk Zpy9Z2qIm24LYPLZolCg8MA3NDv6X/sQYRsQYR4t0KM2cxQfnqlP9sZLEwBOZvmdF6z6 U5DM6VQ1uQkZKRSjsGTkwYAzgs3F2Kb2WTcfU5/JyIdZdJoR0m14pVo1/kB9tkns86nB RPfvR2KzDev2XVnw6RIlWkTjcWAUS+Cq65A/ZApyLJu4Njt3qax7TfeKMtVOdbJefKWd OADg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [23.128.96.18]) by mx.google.com with ESMTP id x9si2598473ejc.540.2020.11.11.13.39.34; Wed, 11 Nov 2020 13:39:58 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) client-ip=23.128.96.18; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 23.128.96.18 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727122AbgKKVfZ (ORCPT + 99 others); Wed, 11 Nov 2020 16:35:25 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:43962 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726510AbgKKVev (ORCPT ); Wed, 11 Nov 2020 16:34:51 -0500 Received: from smtp-8fad.mail.infomaniak.ch (smtp-8fad.mail.infomaniak.ch [IPv6:2001:1600:3:17::8fad]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EC97BC061A04 for ; Wed, 11 Nov 2020 13:34:50 -0800 (PST) Received: from smtp-3-0001.mail.infomaniak.ch (unknown [10.4.36.108]) by smtp-2-3000.mail.infomaniak.ch (Postfix) with ESMTPS id 4CWdL05tD5zlhS6t; Wed, 11 Nov 2020 22:34:48 +0100 (CET) Received: from localhost (unknown [94.23.54.103]) by smtp-3-0001.mail.infomaniak.ch (Postfix) with ESMTPA id 4CWdL02mhLzlh8T4; Wed, 11 Nov 2020 22:34:48 +0100 (CET) From: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= To: James Morris , Jann Horn , "Serge E . Hallyn" Cc: =?UTF-8?q?Micka=C3=ABl=20Sala=C3=BCn?= , Shuah Khan , Vincent Dagonneau , linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org, linux-security-module@vger.kernel.org Subject: [PATCH v1 3/9] landlock: Enforce deterministic interleaved path rules Date: Wed, 11 Nov 2020 22:34:36 +0100 Message-Id: <20201111213442.434639-4-mic@digikod.net> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201111213442.434639-1-mic@digikod.net> References: <20201111213442.434639-1-mic@digikod.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org To have consistent layered rules, granting access to a path implies that all accesses tied to inodes, from the requested file to the real root, must be checked. Otherwise, stacked rules may result to overzealous restrictions. By excluding the ability to add exceptions in the same layer (e.g. /a allowed, /a/b denied, and /a/b/c allowed), we get deterministic interleaved path rules. This removes an optimization which could be replaced by a proper cache mechanism. This also further simplifies and explain check_access_path_continue(). Add a layout1.interleaved_masked_accesses test to check corner-case layered rule combinations. Cc: James Morris Cc: Jann Horn Cc: Serge E. Hallyn Signed-off-by: Mickaël Salaün --- security/landlock/fs.c | 38 +++++---- tools/testing/selftests/landlock/fs_test.c | 95 ++++++++++++++++++++++ 2 files changed, 115 insertions(+), 18 deletions(-) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 33fc7ae17c7f..2ca4dce1e9ed 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -180,16 +180,24 @@ static bool check_access_path_continue( rcu_dereference(landlock_inode(inode)->object)); rcu_read_unlock(); - /* Checks for matching layers. */ - if (rule && (rule->layers | *layer_mask)) { - if ((rule->access & access_request) == access_request) { - *layer_mask &= ~rule->layers; - return true; - } else { - return false; - } + if (!rule) + /* Continues to walk if there is no rule for this inode. */ + return true; + /* + * We must check all layers for each inode because we may encounter + * multiple different accesses from the same layer in a walk. Each + * layer must at least allow the access request one time (i.e. with one + * inode). This enables to have a deterministic behavior whatever + * inode is tagged within interleaved layers. + */ + if ((rule->access & access_request) == access_request) { + /* Validates layers for which all accesses are allowed. */ + *layer_mask &= ~rule->layers; + /* Continues to walk until all layers are validated. */ + return true; } - return true; + /* Stops if a rule in the path don't allow all requested access. */ + return false; } static int check_access_path(const struct landlock_ruleset *const domain, @@ -231,12 +239,6 @@ static int check_access_path(const struct landlock_ruleset *const domain, &layer_mask)) { struct dentry *parent_dentry; - /* Stops when a rule from each layer granted access. */ - if (layer_mask == 0) { - allowed = true; - break; - } - jump_up: /* * Does not work with orphaned/private mounts like overlayfs @@ -248,10 +250,10 @@ static int check_access_path(const struct landlock_ruleset *const domain, goto jump_up; } else { /* - * Stops at the real root. Denies access - * because not all layers have granted access. + * Stops at the real root. Denies access if + * not all layers granted access. */ - allowed = false; + allowed = (layer_mask == 0); break; } } diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 8aed28081ec8..ade0ad8728d8 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -629,6 +629,101 @@ TEST_F(layout1, ruleset_overlap) EXPECT_EQ(0, close(open_fd)); } +TEST_F(layout1, interleaved_masked_accesses) +{ + /* + * Checks overly restrictive rules: + * layer 1: allows s1d1/s1d2/s1d3/file1 + * layer 2: allows s1d1/s1d2/s1d3 + * denies s1d1/s1d2 + * layer 3: allows s1d1 + * layer 4: allows s1d1/s1d2 + */ + const struct rule layer1[] = { + /* Allows access to file1_s1d3 with the first layer. */ + { + .path = file1_s1d3, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + {} + }; + const struct rule layer2[] = { + /* Start by granting access to file1_s1d3 with this rule... */ + { + .path = dir_s1d3, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + /* ...but finally denies access to file1_s1d3. */ + { + .path = dir_s1d2, + .access = 0, + }, + {} + }; + const struct rule layer3[] = { + /* Try to allows access to file1_s1d3. */ + { + .path = dir_s1d1, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + {} + }; + const struct rule layer4[] = { + /* Try to bypass layer2. */ + { + .path = dir_s1d2, + .access = LANDLOCK_ACCESS_FS_READ_FILE, + }, + {} + }; + int open_fd, ruleset_fd; + + ruleset_fd = create_ruleset(_metadata, ACCESS_RO, layer1); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Checks that access is granted for file1_s1d3. */ + open_fd = open(file1_s1d3, O_RDONLY | O_CLOEXEC); + ASSERT_LE(0, open_fd); + EXPECT_EQ(0, close(open_fd)); + ASSERT_EQ(-1, open(file2_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + + ruleset_fd = create_ruleset(_metadata, ACCESS_RO, layer2); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Now, checks that access is denied for file1_s1d3. */ + ASSERT_EQ(-1, open(file1_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, open(file2_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + + ruleset_fd = create_ruleset(_metadata, ACCESS_RO, layer3); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Checks that access rights are unchanged with layer 3. */ + ASSERT_EQ(-1, open(file1_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, open(file2_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + + ruleset_fd = create_ruleset(_metadata, ACCESS_RO, layer4); + ASSERT_LE(0, ruleset_fd); + enforce_ruleset(_metadata, ruleset_fd); + EXPECT_EQ(0, close(ruleset_fd)); + + /* Checks that access rights are unchanged with layer 4. */ + ASSERT_EQ(-1, open(file1_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); + ASSERT_EQ(-1, open(file2_s1d3, O_RDONLY | O_CLOEXEC)); + ASSERT_EQ(EACCES, errno); +} + TEST_F(layout1, inherit_subset) { const struct rule rules[] = { -- 2.29.2