mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-01-23 16:53:58 -05:00
selftests/landlock: Add 11 new test suites dedicated to file reparenting
These test suites try to check all edge cases for directory and file renaming or linking involving a new parent directory, with and without LANDLOCK_ACCESS_FS_REFER and other access rights. layout1: * reparent_refer: Tests simple FS_REFER usage. * reparent_link: Tests a mix of FS_MAKE_REG and FS_REFER with links. * reparent_rename: Tests a mix of FS_MAKE_REG and FS_REFER with renames and RENAME_EXCHANGE. * reparent_exdev_layers_rename1/2: Tests renames with two layers. * reparent_exdev_layers_exchange1/2/3: Tests exchanges with two layers. * reparent_remove: Tests file and directory removal with rename. * reparent_dom_superset: Tests access partial ordering. layout1_bind: * reparent_cross_mount: Tests FS_REFER propagation across mount points. Test coverage for security/landlock is 95.4% of 604 lines according to gcc/gcov-11. Cc: Paul Moore <paul@paul-moore.com> Signed-off-by: Mickaël Salaün <mic@digikod.net> Link: https://lore.kernel.org/r/20220506161102.525323-9-mic@digikod.net
This commit is contained in:
parent
b91c3e4ea7
commit
f4056b9266
1 changed files with 754 additions and 1 deletions
|
@ -146,7 +146,7 @@ static int remove_path(const char *const path)
|
|||
goto out;
|
||||
}
|
||||
if (unlink(path) && rmdir(path)) {
|
||||
if (errno != ENOENT)
|
||||
if (errno != ENOENT && errno != ENOTDIR)
|
||||
err = errno;
|
||||
goto out;
|
||||
}
|
||||
|
@ -1972,6 +1972,721 @@ TEST_F_FORK(layout1, rename_dir)
|
|||
ASSERT_EQ(0, rmdir(dir_s1d3));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_refer)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int ruleset_fd =
|
||||
create_ruleset(_metadata, LANDLOCK_ACCESS_FS_REFER, layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d1));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, dir_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d1));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Moving should only be allowed when the source and the destination
|
||||
* parent directory have REFER.
|
||||
*/
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, dir_s2d3));
|
||||
ASSERT_EQ(ENOTEMPTY, errno);
|
||||
ASSERT_EQ(0, unlink(file1_s2d3));
|
||||
ASSERT_EQ(0, unlink(file2_s2d3));
|
||||
ASSERT_EQ(0, rename(dir_s1d3, dir_s2d3));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_link)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{
|
||||
.path = dir_s1d3,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{},
|
||||
};
|
||||
const int ruleset_fd = create_ruleset(
|
||||
_metadata,
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
ASSERT_EQ(0, unlink(file1_s1d1));
|
||||
ASSERT_EQ(0, unlink(file1_s1d2));
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
|
||||
/* Denies linking because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, link(file2_s1d1, file1_s1d1));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Denies linking because of missing source and destination REFER. */
|
||||
ASSERT_EQ(-1, link(file1_s2d1, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/* Denies linking because of missing source REFER. */
|
||||
ASSERT_EQ(-1, link(file1_s2d1, file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Denies linking because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, link(file1_s2d2, file1_s1d1));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Denies linking because of missing destination REFER. */
|
||||
ASSERT_EQ(-1, link(file1_s2d2, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Allows linking because of REFER and MAKE_REG. */
|
||||
ASSERT_EQ(0, link(file1_s2d2, file1_s1d3));
|
||||
ASSERT_EQ(0, unlink(file1_s2d2));
|
||||
/* Reverse linking denied because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, link(file1_s1d3, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(0, unlink(file1_s2d3));
|
||||
/* Checks reverse linking. */
|
||||
ASSERT_EQ(0, link(file1_s1d3, file1_s2d3));
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
|
||||
/*
|
||||
* This is OK for a file link, but it should not be allowed for a
|
||||
* directory rename (because of the superset of access rights.
|
||||
*/
|
||||
ASSERT_EQ(0, link(file1_s2d3, file1_s1d3));
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
|
||||
ASSERT_EQ(-1, link(file2_s1d2, file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, link(file2_s1d3, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
ASSERT_EQ(0, link(file2_s1d2, file1_s1d2));
|
||||
ASSERT_EQ(0, link(file2_s1d3, file1_s1d3));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_rename)
|
||||
{
|
||||
/* Same rules as for reparent_link. */
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{
|
||||
.path = dir_s1d3,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{},
|
||||
};
|
||||
const int ruleset_fd = create_ruleset(
|
||||
_metadata,
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
ASSERT_EQ(0, unlink(file1_s1d2));
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
|
||||
/* Denies renaming because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file2_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(0, unlink(file1_s1d1));
|
||||
ASSERT_EQ(-1, rename(file2_s1d1, file1_s1d1));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Even denies same file exchange. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file2_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Denies renaming because of missing source and destination REFER. */
|
||||
ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Denies renaming because of missing MAKE_REG, source and destination
|
||||
* REFER.
|
||||
*/
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file2_s1d1, AT_FDCWD, file1_s2d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Denies renaming because of missing source REFER. */
|
||||
ASSERT_EQ(-1, rename(file1_s2d1, file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/* Denies renaming because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d1, AT_FDCWD, file2_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Denies renaming because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d1));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Denies renaming because of missing destination REFER*/
|
||||
ASSERT_EQ(-1, rename(file1_s2d2, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Denies exchange because of one missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, file2_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Allows renaming because of REFER and MAKE_REG. */
|
||||
ASSERT_EQ(0, rename(file1_s2d2, file1_s1d3));
|
||||
|
||||
/* Reverse renaming denied because of missing MAKE_REG. */
|
||||
ASSERT_EQ(-1, rename(file1_s1d3, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(0, unlink(file1_s2d3));
|
||||
ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
|
||||
|
||||
/* Tests reverse renaming. */
|
||||
ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, file2_s2d3, AT_FDCWD, file1_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
|
||||
|
||||
/*
|
||||
* This is OK for a file rename, but it should not be allowed for a
|
||||
* directory rename (because of the superset of access rights).
|
||||
*/
|
||||
ASSERT_EQ(0, rename(file1_s2d3, file1_s1d3));
|
||||
ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
|
||||
|
||||
/*
|
||||
* Tests superset restrictions applied to directories. Not only the
|
||||
* dir_s2d3's parent (dir_s2d2) should be taken into account but also
|
||||
* access rights tied to dir_s2d3. dir_s2d2 is missing one access right
|
||||
* compared to dir_s1d3/file1_s1d3 (MAKE_REG) but it is provided
|
||||
* directly by the moved dir_s2d3.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(dir_s2d3, file1_s1d3));
|
||||
ASSERT_EQ(0, rename(file1_s1d3, dir_s2d3));
|
||||
/*
|
||||
* The first rename is allowed but not the exchange because dir_s1d3's
|
||||
* parent (dir_s1d2) doesn't have REFER.
|
||||
*/
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, dir_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, file1_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(file1_s2d3, dir_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
ASSERT_EQ(-1, rename(file2_s1d2, file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(file2_s1d3, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Renaming in the same directory is always allowed. */
|
||||
ASSERT_EQ(0, rename(file2_s1d2, file1_s1d2));
|
||||
ASSERT_EQ(0, rename(file2_s1d3, file1_s1d3));
|
||||
|
||||
ASSERT_EQ(0, unlink(file1_s1d2));
|
||||
/* Denies because of missing source MAKE_REG and destination REFER. */
|
||||
ASSERT_EQ(-1, rename(dir_s2d3, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
/* Denies because of missing source MAKE_REG and REFER. */
|
||||
ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
}
|
||||
|
||||
static void
|
||||
reparent_exdev_layers_enforce1(struct __test_metadata *const _metadata)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
/* Interesting for the layer2 tests. */
|
||||
.path = dir_s1d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||
},
|
||||
{},
|
||||
};
|
||||
const int ruleset_fd = create_ruleset(
|
||||
_metadata,
|
||||
LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_REFER, layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
}
|
||||
|
||||
static void
|
||||
reparent_exdev_layers_enforce2(struct __test_metadata *const _metadata)
|
||||
{
|
||||
const struct rule layer2[] = {
|
||||
{
|
||||
.path = dir_s2d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_DIR,
|
||||
},
|
||||
{},
|
||||
};
|
||||
/*
|
||||
* Same checks as before but with a second layer and a new MAKE_DIR
|
||||
* rule (and no explicit handling of REFER).
|
||||
*/
|
||||
const int ruleset_fd =
|
||||
create_ruleset(_metadata, LANDLOCK_ACCESS_FS_MAKE_DIR, layer2);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_exdev_layers_rename1)
|
||||
{
|
||||
ASSERT_EQ(0, unlink(file1_s2d2));
|
||||
ASSERT_EQ(0, unlink(file1_s2d3));
|
||||
|
||||
reparent_exdev_layers_enforce1(_metadata);
|
||||
|
||||
/*
|
||||
* Moving the dir_s1d3 directory below dir_s2d2 is allowed by Landlock
|
||||
* because it doesn't inherit new access rights.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
|
||||
ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));
|
||||
|
||||
/*
|
||||
* Moving the dir_s1d3 directory below dir_s2d3 is allowed, even if it
|
||||
* gets a new inherited access rights (MAKE_REG), because MAKE_REG is
|
||||
* already allowed for dir_s1d3.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(dir_s1d3, file1_s2d3));
|
||||
ASSERT_EQ(0, rename(file1_s2d3, dir_s1d3));
|
||||
|
||||
/*
|
||||
* However, moving the file1_s1d3 file below dir_s2d3 is allowed
|
||||
* because it cannot inherit MAKE_REG right (which is dedicated to
|
||||
* directories).
|
||||
*/
|
||||
ASSERT_EQ(0, rename(file1_s1d3, file1_s2d3));
|
||||
|
||||
reparent_exdev_layers_enforce2(_metadata);
|
||||
|
||||
/*
|
||||
* Moving the dir_s1d3 directory below dir_s2d2 is now denied because
|
||||
* MAKE_DIR is not tied to dir_s2d2.
|
||||
*/
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/*
|
||||
* Moving the dir_s1d3 directory below dir_s2d3 is forbidden because it
|
||||
* would grants MAKE_REG and MAKE_DIR rights to it.
|
||||
*/
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/*
|
||||
* However, moving the file2_s1d3 file below dir_s2d3 is allowed
|
||||
* because it cannot inherit MAKE_REG nor MAKE_DIR rights (which are
|
||||
* dedicated to directories).
|
||||
*/
|
||||
ASSERT_EQ(0, rename(file2_s1d3, file1_s2d3));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_exdev_layers_rename2)
|
||||
{
|
||||
reparent_exdev_layers_enforce1(_metadata);
|
||||
|
||||
/* Checks EACCES predominance over EXDEV. */
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/* Modify layout! */
|
||||
ASSERT_EQ(0, rename(file1_s1d2, file1_s2d3));
|
||||
|
||||
/* Without REFER source. */
|
||||
ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
reparent_exdev_layers_enforce2(_metadata);
|
||||
|
||||
/* Checks EACCES predominance over EXDEV. */
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
/* Checks with actual file2_s1d2. */
|
||||
ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/* Modify layout! */
|
||||
ASSERT_EQ(0, rename(file2_s1d2, file1_s2d3));
|
||||
|
||||
/* Without REFER source, EACCES wins over EXDEV. */
|
||||
ASSERT_EQ(-1, rename(dir_s1d1, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_exdev_layers_exchange1)
|
||||
{
|
||||
const char *const dir_file1_s1d2 = file1_s1d2, *const dir_file2_s2d3 =
|
||||
file2_s2d3;
|
||||
|
||||
ASSERT_EQ(0, unlink(file1_s1d2));
|
||||
ASSERT_EQ(0, mkdir(file1_s1d2, 0700));
|
||||
ASSERT_EQ(0, unlink(file2_s2d3));
|
||||
ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
|
||||
|
||||
reparent_exdev_layers_enforce1(_metadata);
|
||||
|
||||
/* Error predominance with file exchange: returns EXDEV and EACCES. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/*
|
||||
* Checks with directories which creation could be allowed, but denied
|
||||
* because of access rights that would be inherited.
|
||||
*/
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
|
||||
dir_file2_s2d3, RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
|
||||
dir_file1_s1d2, RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Checks with same access rights. */
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
|
||||
/* Checks with different (child-only) access rights. */
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
|
||||
/*
|
||||
* Checks that exchange between file and directory are consistent.
|
||||
*
|
||||
* Moving a file (file1_s2d2) to a directory which only grants more
|
||||
* directory-related access rights is allowed, and at the same time
|
||||
* moving a directory (dir_file2_s2d3) to another directory which
|
||||
* grants less access rights is allowed too.
|
||||
*
|
||||
* See layout1.reparent_exdev_layers_exchange3 for inverted arguments.
|
||||
*/
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
/*
|
||||
* However, moving back the directory is denied because it would get
|
||||
* more access rights than the current state and because file creation
|
||||
* is forbidden (in dir_s2d2).
|
||||
*/
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
reparent_exdev_layers_enforce2(_metadata);
|
||||
|
||||
/* Error predominance with file exchange: returns EXDEV and EACCES. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, file1_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d3, AT_FDCWD, file1_s1d1,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Checks with directories which creation is now denied. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD,
|
||||
dir_file2_s2d3, RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD,
|
||||
dir_file1_s1d2, RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Checks with different (child-only) access rights. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s1d3, AT_FDCWD, dir_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
/* Denied because of MAKE_DIR. */
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Checks with different (child-only) access rights. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_s2d3, AT_FDCWD, dir_file1_s1d2,
|
||||
RENAME_EXCHANGE));
|
||||
/* Denied because of MAKE_DIR. */
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file1_s1d2, AT_FDCWD, dir_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* See layout1.reparent_exdev_layers_exchange2 for complement. */
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_exdev_layers_exchange2)
|
||||
{
|
||||
const char *const dir_file2_s2d3 = file2_s2d3;
|
||||
|
||||
ASSERT_EQ(0, unlink(file2_s2d3));
|
||||
ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
|
||||
|
||||
reparent_exdev_layers_enforce1(_metadata);
|
||||
reparent_exdev_layers_enforce2(_metadata);
|
||||
|
||||
/* Checks that exchange between file and directory are consistent. */
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_exdev_layers_exchange3)
|
||||
{
|
||||
const char *const dir_file2_s2d3 = file2_s2d3;
|
||||
|
||||
ASSERT_EQ(0, unlink(file2_s2d3));
|
||||
ASSERT_EQ(0, mkdir(file2_s2d3, 0700));
|
||||
|
||||
reparent_exdev_layers_enforce1(_metadata);
|
||||
|
||||
/*
|
||||
* Checks that exchange between file and directory are consistent,
|
||||
* including with inverted arguments (see
|
||||
* layout1.reparent_exdev_layers_exchange1).
|
||||
*/
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_file2_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, dir_file2_s2d3, AT_FDCWD, file1_s2d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_remove)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d1,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||
},
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REMOVE_FILE,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d1,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_FILE,
|
||||
},
|
||||
{},
|
||||
};
|
||||
const int ruleset_fd = create_ruleset(
|
||||
_metadata,
|
||||
LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_REMOVE_DIR |
|
||||
LANDLOCK_ACCESS_FS_REMOVE_FILE,
|
||||
layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/* Access denied because of wrong/swapped remove file/dir. */
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, dir_s2d2));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s2d2, file1_s1d1));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d2,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s1d1, AT_FDCWD, dir_s2d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
|
||||
/* Access allowed thanks to the matching rights. */
|
||||
ASSERT_EQ(-1, rename(file1_s2d1, dir_s1d2));
|
||||
ASSERT_EQ(EISDIR, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d2, file1_s2d1));
|
||||
ASSERT_EQ(ENOTDIR, errno);
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
|
||||
ASSERT_EQ(ENOTDIR, errno);
|
||||
ASSERT_EQ(0, unlink(file1_s2d1));
|
||||
ASSERT_EQ(0, unlink(file1_s1d3));
|
||||
ASSERT_EQ(0, unlink(file2_s1d3));
|
||||
ASSERT_EQ(0, rename(dir_s1d3, file1_s2d1));
|
||||
|
||||
/* Effectively removes a file and a directory by exchanging them. */
|
||||
ASSERT_EQ(0, mkdir(dir_s1d3, 0700));
|
||||
ASSERT_EQ(0, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(-1, renameat2(AT_FDCWD, file1_s2d2, AT_FDCWD, dir_s1d3,
|
||||
RENAME_EXCHANGE));
|
||||
ASSERT_EQ(EACCES, errno);
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, reparent_dom_superset)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
.path = dir_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = file1_s1d2,
|
||||
.access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
},
|
||||
{
|
||||
.path = dir_s1d3,
|
||||
.access = LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d2,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER |
|
||||
LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SOCK,
|
||||
},
|
||||
{
|
||||
.path = dir_s2d3,
|
||||
.access = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_FIFO,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int ruleset_fd = create_ruleset(_metadata,
|
||||
LANDLOCK_ACCESS_FS_REFER |
|
||||
LANDLOCK_ACCESS_FS_EXECUTE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_SOCK |
|
||||
LANDLOCK_ACCESS_FS_READ_FILE |
|
||||
LANDLOCK_ACCESS_FS_MAKE_FIFO,
|
||||
layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d1));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Moving file1_s1d2 beneath dir_s2d3 would grant it the READ_FILE
|
||||
* access right.
|
||||
*/
|
||||
ASSERT_EQ(-1, rename(file1_s1d2, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Moving file1_s1d2 should be allowed even if dir_s2d2 grants a
|
||||
* superset of access rights compared to dir_s1d2, because file1_s1d2
|
||||
* already has these access rights anyway.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(file1_s1d2, file1_s2d2));
|
||||
ASSERT_EQ(0, rename(file1_s2d2, file1_s1d2));
|
||||
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d1));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Moving dir_s1d3 beneath dir_s2d3 would grant it the MAKE_FIFO access
|
||||
* right.
|
||||
*/
|
||||
ASSERT_EQ(-1, rename(dir_s1d3, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
/*
|
||||
* Moving dir_s1d3 should be allowed even if dir_s2d2 grants a superset
|
||||
* of access rights compared to dir_s1d2, because dir_s1d3 already has
|
||||
* these access rights anyway.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(dir_s1d3, file1_s2d2));
|
||||
ASSERT_EQ(0, rename(file1_s2d2, dir_s1d3));
|
||||
|
||||
/*
|
||||
* Moving file1_s2d3 beneath dir_s1d2 is allowed, but moving it back
|
||||
* will be denied because the new inherited access rights from dir_s1d2
|
||||
* will be less than the destination (original) dir_s2d3. This is a
|
||||
* sinkhole scenario where we cannot move back files or directories.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(file1_s2d3, file2_s1d2));
|
||||
ASSERT_EQ(-1, rename(file2_s1d2, file1_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
ASSERT_EQ(0, unlink(file2_s1d2));
|
||||
ASSERT_EQ(0, unlink(file2_s2d3));
|
||||
/*
|
||||
* Checks similar directory one-way move: dir_s2d3 loses EXECUTE and
|
||||
* MAKE_SOCK which were inherited from dir_s1d3.
|
||||
*/
|
||||
ASSERT_EQ(0, rename(dir_s2d3, file2_s1d2));
|
||||
ASSERT_EQ(-1, rename(file2_s1d2, dir_s2d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1, remove_dir)
|
||||
{
|
||||
const struct rule rules[] = {
|
||||
|
@ -2520,6 +3235,44 @@ TEST_F_FORK(layout1_bind, same_content_same_file)
|
|||
ASSERT_EQ(EACCES, test_open(bind_file1_s1d3, O_WRONLY));
|
||||
}
|
||||
|
||||
TEST_F_FORK(layout1_bind, reparent_cross_mount)
|
||||
{
|
||||
const struct rule layer1[] = {
|
||||
{
|
||||
/* dir_s2d1 is beneath the dir_s2d2 mount point. */
|
||||
.path = dir_s2d1,
|
||||
.access = LANDLOCK_ACCESS_FS_REFER,
|
||||
},
|
||||
{
|
||||
.path = bind_dir_s1d3,
|
||||
.access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||
},
|
||||
{},
|
||||
};
|
||||
int ruleset_fd = create_ruleset(
|
||||
_metadata,
|
||||
LANDLOCK_ACCESS_FS_REFER | LANDLOCK_ACCESS_FS_EXECUTE, layer1);
|
||||
|
||||
ASSERT_LE(0, ruleset_fd);
|
||||
enforce_ruleset(_metadata, ruleset_fd);
|
||||
ASSERT_EQ(0, close(ruleset_fd));
|
||||
|
||||
/* Checks basic denied move. */
|
||||
ASSERT_EQ(-1, rename(file1_s1d1, file1_s1d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Checks real cross-mount move (Landlock is not involved). */
|
||||
ASSERT_EQ(-1, rename(file1_s2d1, file1_s2d2));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Checks move that will give more accesses. */
|
||||
ASSERT_EQ(-1, rename(file1_s2d2, bind_file1_s1d3));
|
||||
ASSERT_EQ(EXDEV, errno);
|
||||
|
||||
/* Checks legitimate downgrade move. */
|
||||
ASSERT_EQ(0, rename(bind_file1_s1d3, file1_s2d2));
|
||||
}
|
||||
|
||||
#define LOWER_BASE TMP_DIR "/lower"
|
||||
#define LOWER_DATA LOWER_BASE "/data"
|
||||
static const char lower_fl1[] = LOWER_DATA "/fl1";
|
||||
|
|
Loading…
Add table
Reference in a new issue