Skip to content

Commit 0c95f02

Browse files
committed
Merge tag 'landlock-6.0-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux
Pull landlock fix from Mickaël Salaün: "This fixes a mis-handling of the LANDLOCK_ACCESS_FS_REFER right when multiple rulesets/domains are stacked. The expected behaviour was that an additional ruleset can only restrict the set of permitted operations, but in this particular case, it was potentially possible to re-gain the LANDLOCK_ACCESS_FS_REFER right" * tag 'landlock-6.0-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mic/linux: landlock: Fix file reparenting without explicit LANDLOCK_ACCESS_FS_REFER
2 parents b307e70 + 55e5592 commit 0c95f02

File tree

2 files changed

+170
-33
lines changed

2 files changed

+170
-33
lines changed

security/landlock/fs.c

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,16 @@ static struct landlock_object *get_inode_object(struct inode *const inode)
149149
LANDLOCK_ACCESS_FS_READ_FILE)
150150
/* clang-format on */
151151

152+
/*
153+
* All access rights that are denied by default whether they are handled or not
154+
* by a ruleset/layer. This must be ORed with all ruleset->fs_access_masks[]
155+
* entries when we need to get the absolute handled access masks.
156+
*/
157+
/* clang-format off */
158+
#define ACCESS_INITIALLY_DENIED ( \
159+
LANDLOCK_ACCESS_FS_REFER)
160+
/* clang-format on */
161+
152162
/*
153163
* @path: Should have been checked by get_path_from_fd().
154164
*/
@@ -167,7 +177,9 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
167177
return -EINVAL;
168178

169179
/* Transforms relative access rights to absolute ones. */
170-
access_rights |= LANDLOCK_MASK_ACCESS_FS & ~ruleset->fs_access_masks[0];
180+
access_rights |=
181+
LANDLOCK_MASK_ACCESS_FS &
182+
~(ruleset->fs_access_masks[0] | ACCESS_INITIALLY_DENIED);
171183
object = get_inode_object(d_backing_inode(path->dentry));
172184
if (IS_ERR(object))
173185
return PTR_ERR(object);
@@ -277,23 +289,12 @@ static inline bool is_nouser_or_private(const struct dentry *dentry)
277289
static inline access_mask_t
278290
get_handled_accesses(const struct landlock_ruleset *const domain)
279291
{
280-
access_mask_t access_dom = 0;
281-
unsigned long access_bit;
282-
283-
for (access_bit = 0; access_bit < LANDLOCK_NUM_ACCESS_FS;
284-
access_bit++) {
285-
size_t layer_level;
292+
access_mask_t access_dom = ACCESS_INITIALLY_DENIED;
293+
size_t layer_level;
286294

287-
for (layer_level = 0; layer_level < domain->num_layers;
288-
layer_level++) {
289-
if (domain->fs_access_masks[layer_level] &
290-
BIT_ULL(access_bit)) {
291-
access_dom |= BIT_ULL(access_bit);
292-
break;
293-
}
294-
}
295-
}
296-
return access_dom;
295+
for (layer_level = 0; layer_level < domain->num_layers; layer_level++)
296+
access_dom |= domain->fs_access_masks[layer_level];
297+
return access_dom & LANDLOCK_MASK_ACCESS_FS;
297298
}
298299

299300
static inline access_mask_t
@@ -316,8 +317,13 @@ init_layer_masks(const struct landlock_ruleset *const domain,
316317

317318
for_each_set_bit(access_bit, &access_req,
318319
ARRAY_SIZE(*layer_masks)) {
319-
if (domain->fs_access_masks[layer_level] &
320-
BIT_ULL(access_bit)) {
320+
/*
321+
* Artificially handles all initially denied by default
322+
* access rights.
323+
*/
324+
if (BIT_ULL(access_bit) &
325+
(domain->fs_access_masks[layer_level] |
326+
ACCESS_INITIALLY_DENIED)) {
321327
(*layer_masks)[access_bit] |=
322328
BIT_ULL(layer_level);
323329
handled_accesses |= BIT_ULL(access_bit);
@@ -857,10 +863,6 @@ static int current_check_refer_path(struct dentry *const old_dentry,
857863
NULL, NULL);
858864
}
859865

860-
/* Backward compatibility: no reparenting support. */
861-
if (!(get_handled_accesses(dom) & LANDLOCK_ACCESS_FS_REFER))
862-
return -EXDEV;
863-
864866
access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
865867
access_request_parent2 |= LANDLOCK_ACCESS_FS_REFER;
866868

tools/testing/selftests/landlock/fs_test.c

Lines changed: 145 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
* Copyright © 2017-2020 Mickaël Salaün <[email protected]>
66
* Copyright © 2020 ANSSI
7-
* Copyright © 2020-2021 Microsoft Corporation
7+
* Copyright © 2020-2022 Microsoft Corporation
88
*/
99

1010
#define _GNU_SOURCE
@@ -371,6 +371,13 @@ TEST_F_FORK(layout1, inval)
371371
ASSERT_EQ(EINVAL, errno);
372372
path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_EXECUTE;
373373

374+
/* Tests with denied-by-default access right. */
375+
path_beneath.allowed_access |= LANDLOCK_ACCESS_FS_REFER;
376+
ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
377+
&path_beneath, 0));
378+
ASSERT_EQ(EINVAL, errno);
379+
path_beneath.allowed_access &= ~LANDLOCK_ACCESS_FS_REFER;
380+
374381
/* Test with unknown (64-bits) value. */
375382
path_beneath.allowed_access |= (1ULL << 60);
376383
ASSERT_EQ(-1, landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH,
@@ -1826,6 +1833,20 @@ TEST_F_FORK(layout1, link)
18261833
ASSERT_EQ(0, link(file1_s1d3, file2_s1d3));
18271834
}
18281835

1836+
static int test_rename(const char *const oldpath, const char *const newpath)
1837+
{
1838+
if (rename(oldpath, newpath))
1839+
return errno;
1840+
return 0;
1841+
}
1842+
1843+
static int test_exchange(const char *const oldpath, const char *const newpath)
1844+
{
1845+
if (renameat2(AT_FDCWD, oldpath, AT_FDCWD, newpath, RENAME_EXCHANGE))
1846+
return errno;
1847+
return 0;
1848+
}
1849+
18291850
TEST_F_FORK(layout1, rename_file)
18301851
{
18311852
const struct rule rules[] = {
@@ -1867,10 +1888,10 @@ TEST_F_FORK(layout1, rename_file)
18671888
* to a different directory (which allows file removal).
18681889
*/
18691890
ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
1870-
ASSERT_EQ(EXDEV, errno);
1891+
ASSERT_EQ(EACCES, errno);
18711892
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file1_s1d3,
18721893
RENAME_EXCHANGE));
1873-
ASSERT_EQ(EXDEV, errno);
1894+
ASSERT_EQ(EACCES, errno);
18741895
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d2, AT_FDCWD, file1_s1d3,
18751896
RENAME_EXCHANGE));
18761897
ASSERT_EQ(EXDEV, errno);
@@ -1894,7 +1915,7 @@ TEST_F_FORK(layout1, rename_file)
18941915
ASSERT_EQ(EXDEV, errno);
18951916
ASSERT_EQ(0, unlink(file1_s1d3));
18961917
ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
1897-
ASSERT_EQ(EXDEV, errno);
1918+
ASSERT_EQ(EACCES, errno);
18981919

18991920
/* Exchanges and renames files with same parent. */
19001921
ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s2d3,
@@ -2014,6 +2035,115 @@ TEST_F_FORK(layout1, reparent_refer)
20142035
ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3));
20152036
}
20162037

2038+
/* Checks renames beneath dir_s1d1. */
2039+
static void refer_denied_by_default(struct __test_metadata *const _metadata,
2040+
const struct rule layer1[],
2041+
const int layer1_err,
2042+
const struct rule layer2[])
2043+
{
2044+
int ruleset_fd;
2045+
2046+
ASSERT_EQ(0, unlink(file1_s1d2));
2047+
2048+
ruleset_fd = create_ruleset(_metadata, layer1[0].access, layer1);
2049+
ASSERT_LE(0, ruleset_fd);
2050+
enforce_ruleset(_metadata, ruleset_fd);
2051+
ASSERT_EQ(0, close(ruleset_fd));
2052+
2053+
/*
2054+
* If the first layer handles LANDLOCK_ACCESS_FS_REFER (according to
2055+
* layer1_err), then it allows some different-parent renames and links.
2056+
*/
2057+
ASSERT_EQ(layer1_err, test_rename(file1_s1d1, file1_s1d2));
2058+
if (layer1_err == 0)
2059+
ASSERT_EQ(layer1_err, test_rename(file1_s1d2, file1_s1d1));
2060+
ASSERT_EQ(layer1_err, test_exchange(file2_s1d1, file2_s1d2));
2061+
ASSERT_EQ(layer1_err, test_exchange(file2_s1d2, file2_s1d1));
2062+
2063+
ruleset_fd = create_ruleset(_metadata, layer2[0].access, layer2);
2064+
ASSERT_LE(0, ruleset_fd);
2065+
enforce_ruleset(_metadata, ruleset_fd);
2066+
ASSERT_EQ(0, close(ruleset_fd));
2067+
2068+
/*
2069+
* Now, either the first or the second layer does not handle
2070+
* LANDLOCK_ACCESS_FS_REFER, which means that any different-parent
2071+
* renames and links are denied, thus making the layer handling
2072+
* LANDLOCK_ACCESS_FS_REFER null and void.
2073+
*/
2074+
ASSERT_EQ(EXDEV, test_rename(file1_s1d1, file1_s1d2));
2075+
ASSERT_EQ(EXDEV, test_exchange(file2_s1d1, file2_s1d2));
2076+
ASSERT_EQ(EXDEV, test_exchange(file2_s1d2, file2_s1d1));
2077+
}
2078+
2079+
const struct rule layer_dir_s1d1_refer[] = {
2080+
{
2081+
.path = dir_s1d1,
2082+
.access = LANDLOCK_ACCESS_FS_REFER,
2083+
},
2084+
{},
2085+
};
2086+
2087+
const struct rule layer_dir_s1d1_execute[] = {
2088+
{
2089+
/* Matches a parent directory. */
2090+
.path = dir_s1d1,
2091+
.access = LANDLOCK_ACCESS_FS_EXECUTE,
2092+
},
2093+
{},
2094+
};
2095+
2096+
const struct rule layer_dir_s2d1_execute[] = {
2097+
{
2098+
/* Does not match a parent directory. */
2099+
.path = dir_s2d1,
2100+
.access = LANDLOCK_ACCESS_FS_EXECUTE,
2101+
},
2102+
{},
2103+
};
2104+
2105+
/*
2106+
* Tests precedence over renames: denied by default for different parent
2107+
* directories, *with* a rule matching a parent directory, but not directly
2108+
* denying access (with MAKE_REG nor REMOVE).
2109+
*/
2110+
TEST_F_FORK(layout1, refer_denied_by_default1)
2111+
{
2112+
refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
2113+
layer_dir_s1d1_execute);
2114+
}
2115+
2116+
/*
2117+
* Same test but this time turning around the ABI version order: the first
2118+
* layer does not handle LANDLOCK_ACCESS_FS_REFER.
2119+
*/
2120+
TEST_F_FORK(layout1, refer_denied_by_default2)
2121+
{
2122+
refer_denied_by_default(_metadata, layer_dir_s1d1_execute, EXDEV,
2123+
layer_dir_s1d1_refer);
2124+
}
2125+
2126+
/*
2127+
* Tests precedence over renames: denied by default for different parent
2128+
* directories, *without* a rule matching a parent directory, but not directly
2129+
* denying access (with MAKE_REG nor REMOVE).
2130+
*/
2131+
TEST_F_FORK(layout1, refer_denied_by_default3)
2132+
{
2133+
refer_denied_by_default(_metadata, layer_dir_s1d1_refer, 0,
2134+
layer_dir_s2d1_execute);
2135+
}
2136+
2137+
/*
2138+
* Same test but this time turning around the ABI version order: the first
2139+
* layer does not handle LANDLOCK_ACCESS_FS_REFER.
2140+
*/
2141+
TEST_F_FORK(layout1, refer_denied_by_default4)
2142+
{
2143+
refer_denied_by_default(_metadata, layer_dir_s2d1_execute, EXDEV,
2144+
layer_dir_s1d1_refer);
2145+
}
2146+
20172147
TEST_F_FORK(layout1, reparent_link)
20182148
{
20192149
const struct rule layer1[] = {
@@ -2336,11 +2466,12 @@ TEST_F_FORK(layout1, reparent_exdev_layers_rename1)
23362466
ASSERT_EQ(EXDEV, errno);
23372467

23382468
/*
2339-
* However, moving the file2_s1d3 file below dir_s2d3 is allowed
2340-
* because it cannot inherit MAKE_REG nor MAKE_DIR rights (which are
2341-
* dedicated to directories).
2469+
* Moving the file2_s1d3 file below dir_s2d3 is denied because the
2470+
* second layer does not handle REFER, which is always denied by
2471+
* default.
23422472
*/
2343-
ASSERT_EQ(0, rename(file2_s1d3, file1_s2d3));
2473+
ASSERT_EQ(-1, rename(file2_s1d3, file1_s2d3));
2474+
ASSERT_EQ(EXDEV, errno);
23442475
}
23452476

23462477
TEST_F_FORK(layout1, reparent_exdev_layers_rename2)
@@ -2373,8 +2504,12 @@ TEST_F_FORK(layout1, reparent_exdev_layers_rename2)
23732504
ASSERT_EQ(EACCES, errno);
23742505
ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
23752506
ASSERT_EQ(EXDEV, errno);
2376-
/* Modify layout! */
2377-
ASSERT_EQ(0, rename(file2_s1d2, file1_s2d3));
2507+
/*
2508+
* Modifying the layout is now denied because the second layer does not
2509+
* handle REFER, which is always denied by default.
2510+
*/
2511+
ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
2512+
ASSERT_EQ(EXDEV, errno);
23782513

23792514
/* Without REFER source, EACCES wins over EXDEV. */
23802515
ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));

0 commit comments

Comments
 (0)