selftests/landlock: Exhaustive test for the IOCTL allow-list

This test checks all IOCTL commands implemented in do_vfs_ioctl().

Test coverage for security/landlock is 90.9% of 722 lines according to
gcc/gcov-13.

Suggested-by: Mickaël Salaün <mic@digikod.net>
Signed-off-by: Günther Noack <gnoack@google.com>
Link: https://lore.kernel.org/r/20240419161122.2023765-8-gnoack@google.com
[mic: Add test coverage]
Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
Günther Noack 2024-04-19 16:11:18 +00:00 committed by Mickaël Salaün
parent f83d51a5bd
commit bce605e0cf
No known key found for this signature in database
GPG Key ID: E5E3D0E88C82F6D2

View File

@ -11,6 +11,7 @@
#include <asm/termbits.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/fiemap.h>
#include <linux/landlock.h>
#include <linux/magic.h>
#include <sched.h>
@ -3945,6 +3946,119 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl)
ASSERT_EQ(0, close(fd));
}
/*
* ioctl_error - generically call the given ioctl with a pointer to a
* sufficiently large zeroed-out memory region.
*
* Returns the IOCTLs error, or 0.
*/
static int ioctl_error(struct __test_metadata *const _metadata, int fd,
unsigned int cmd)
{
char buf[128]; /* sufficiently large */
int res, stdinbak_fd;
/*
* Depending on the IOCTL command, parts of the zeroed-out buffer might
* be interpreted as file descriptor numbers. We do not want to
* accidentally operate on file descriptor 0 (stdin), so we temporarily
* move stdin to a different FD and close FD 0 for the IOCTL call.
*/
stdinbak_fd = dup(0);
ASSERT_LT(0, stdinbak_fd);
ASSERT_EQ(0, close(0));
/* Invokes the IOCTL with a zeroed-out buffer. */
bzero(&buf, sizeof(buf));
res = ioctl(fd, cmd, &buf);
/* Restores the old FD 0 and closes the backup FD. */
ASSERT_EQ(0, dup2(stdinbak_fd, 0));
ASSERT_EQ(0, close(stdinbak_fd));
if (res < 0)
return errno;
return 0;
}
/* Define some linux/falloc.h IOCTL commands which are not available in uapi headers. */
struct space_resv {
__s16 l_type;
__s16 l_whence;
__s64 l_start;
__s64 l_len; /* len == 0 means until end of file */
__s32 l_sysid;
__u32 l_pid;
__s32 l_pad[4]; /* reserved area */
};
#define FS_IOC_RESVSP _IOW('X', 40, struct space_resv)
#define FS_IOC_UNRESVSP _IOW('X', 41, struct space_resv)
#define FS_IOC_RESVSP64 _IOW('X', 42, struct space_resv)
#define FS_IOC_UNRESVSP64 _IOW('X', 43, struct space_resv)
#define FS_IOC_ZERO_RANGE _IOW('X', 57, struct space_resv)
/*
* Tests a series of blanket-permitted and denied IOCTLs.
*/
TEST_F_FORK(layout1, blanket_permitted_ioctls)
{
const struct landlock_ruleset_attr attr = {
.handled_access_fs = LANDLOCK_ACCESS_FS_IOCTL_DEV,
};
int ruleset_fd, fd;
/* Enables Landlock. */
ruleset_fd = landlock_create_ruleset(&attr, sizeof(attr), 0);
ASSERT_LE(0, ruleset_fd);
enforce_ruleset(_metadata, ruleset_fd);
ASSERT_EQ(0, close(ruleset_fd));
fd = open("/dev/null", O_RDWR | O_CLOEXEC);
ASSERT_LE(0, fd);
/*
* Checks permitted commands.
* These ones may return errors, but should not be blocked by Landlock.
*/
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOCLEX));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONCLEX));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIONBIO));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOASYNC));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIOQSIZE));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIFREEZE));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FITHAW));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_FIEMAP));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIGETBSZ));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONE));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FICLONERANGE));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FIDEDUPERANGE));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSUUID));
EXPECT_NE(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFSSYSFSPATH));
/*
* Checks blocked commands.
* A call to a blocked IOCTL command always returns EACCES.
*/
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_GETFLAGS));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_SETFLAGS));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSGETXATTR));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_FSSETXATTR));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIBMAP));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_RESVSP64));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_UNRESVSP64));
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE));
/* Default case is also blocked. */
EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee));
ASSERT_EQ(0, close(fd));
}
/*
* Named pipes are not governed by the LANDLOCK_ACCESS_FS_IOCTL_DEV right,
* because they are not character or block devices.