Unverified Commit bce605e0 authored by Günther Noack's avatar Günther Noack Committed by Mickaël Salaün
Browse files

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: default avatarMickaël Salaün <mic@digikod.net>
Signed-off-by: default avatarGünther Noack <gnoack@google.com>
Link: https://lore.kernel.org/r/20240419161122.2023765-8-gnoack@google.com


[mic: Add test coverage]
Signed-off-by: default avatarMickaël Salaün <mic@digikod.net>
parent f83d51a5
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
@@ -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.