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

selftests/landlock: Test IOCTL support



Exercises Landlock's IOCTL feature in different combinations of
handling and permitting the LANDLOCK_ACCESS_FS_IOCTL_DEV right, and in
different combinations of using files and directories.

Signed-off-by: default avatarGünther Noack <gnoack@google.com>
Link: https://lore.kernel.org/r/20240419161122.2023765-3-gnoack@google.com


Signed-off-by: default avatarMickaël Salaün <mic@digikod.net>
parent b25f7415
Loading
Loading
Loading
Loading
+189 −3
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
 */

#define _GNU_SOURCE
#include <asm/termbits.h>
#include <fcntl.h>
#include <libgen.h>
#include <linux/landlock.h>
@@ -16,6 +17,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/capability.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/sendfile.h>
@@ -24,6 +26,12 @@
#include <sys/vfs.h>
#include <unistd.h>

/*
 * Intentionally included last to work around header conflict.
 * See https://sourceware.org/glibc/wiki/Synchronizing_Headers.
 */
#include <linux/fs.h>

#include "common.h"

#ifndef renameat2
@@ -744,6 +752,9 @@ static int create_ruleset(struct __test_metadata *const _metadata,
	}

	for (i = 0; rules[i].path; i++) {
		if (!rules[i].access)
			continue;

		add_path_beneath(_metadata, ruleset_fd, rules[i].access,
				 rules[i].path);
	}
@@ -3452,7 +3463,7 @@ TEST_F_FORK(layout1, truncate_unhandled)
			      LANDLOCK_ACCESS_FS_WRITE_FILE;
	int ruleset_fd;

	/* Enable Landlock. */
	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, handled, rules);

	ASSERT_LE(0, ruleset_fd);
@@ -3535,7 +3546,7 @@ TEST_F_FORK(layout1, truncate)
			      LANDLOCK_ACCESS_FS_TRUNCATE;
	int ruleset_fd;

	/* Enable Landlock. */
	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, handled, rules);

	ASSERT_LE(0, ruleset_fd);
@@ -3761,7 +3772,7 @@ TEST_F_FORK(ftruncate, open_and_ftruncate)
	};
	int fd, ruleset_fd;

	/* Enable Landlock. */
	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
	ASSERT_LE(0, ruleset_fd);
	enforce_ruleset(_metadata, ruleset_fd);
@@ -3854,6 +3865,181 @@ TEST(memfd_ftruncate)
	ASSERT_EQ(0, close(fd));
}

static int test_fionread_ioctl(int fd)
{
	size_t sz = 0;

	if (ioctl(fd, FIONREAD, &sz) < 0 && errno == EACCES)
		return errno;
	return 0;
}

/* clang-format off */
FIXTURE(ioctl) {};

FIXTURE_SETUP(ioctl) {};

FIXTURE_TEARDOWN(ioctl) {};
/* clang-format on */

FIXTURE_VARIANT(ioctl)
{
	const __u64 handled;
	const __u64 allowed;
	const mode_t open_mode;
	/*
	 * FIONREAD is used as a characteristic device-specific IOCTL command.
	 * It is implemented in fs/ioctl.c for regular files,
	 * but we do not blanket-permit it for devices.
	 */
	const int expected_fionread_result;
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_none) {
	/* clang-format on */
	.handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
	.allowed = 0,
	.open_mode = O_RDWR,
	.expected_fionread_result = EACCES,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, handled_i_allowed_i) {
	/* clang-format on */
	.handled = LANDLOCK_ACCESS_FS_IOCTL_DEV,
	.allowed = LANDLOCK_ACCESS_FS_IOCTL_DEV,
	.open_mode = O_RDWR,
	.expected_fionread_result = 0,
};

/* clang-format off */
FIXTURE_VARIANT_ADD(ioctl, unhandled) {
	/* clang-format on */
	.handled = LANDLOCK_ACCESS_FS_EXECUTE,
	.allowed = LANDLOCK_ACCESS_FS_EXECUTE,
	.open_mode = O_RDWR,
	.expected_fionread_result = 0,
};

TEST_F_FORK(ioctl, handle_dir_access_file)
{
	const int flag = 0;
	const struct rule rules[] = {
		{
			.path = "/dev",
			.access = variant->allowed,
		},
		{},
	};
	int file_fd, ruleset_fd;

	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
	ASSERT_LE(0, ruleset_fd);
	enforce_ruleset(_metadata, ruleset_fd);
	ASSERT_EQ(0, close(ruleset_fd));

	file_fd = open("/dev/zero", variant->open_mode);
	ASSERT_LE(0, file_fd);

	/* Checks that IOCTL commands return the expected errors. */
	EXPECT_EQ(variant->expected_fionread_result,
		  test_fionread_ioctl(file_fd));

	/* Checks that unrestrictable commands are unrestricted. */
	EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
	EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
	EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
	EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
	EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));

	ASSERT_EQ(0, close(file_fd));
}

TEST_F_FORK(ioctl, handle_dir_access_dir)
{
	const int flag = 0;
	const struct rule rules[] = {
		{
			.path = "/dev",
			.access = variant->allowed,
		},
		{},
	};
	int dir_fd, ruleset_fd;

	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
	ASSERT_LE(0, ruleset_fd);
	enforce_ruleset(_metadata, ruleset_fd);
	ASSERT_EQ(0, close(ruleset_fd));

	/*
	 * Ignore variant->open_mode for this test, as we intend to open a
	 * directory.  If the directory can not be opened, the variant is
	 * infeasible to test with an opened directory.
	 */
	dir_fd = open("/dev", O_RDONLY);
	if (dir_fd < 0)
		return;

	/*
	 * Checks that IOCTL commands return the expected errors.
	 * We do not use the expected values from the fixture here.
	 *
	 * When using IOCTL on a directory, no Landlock restrictions apply.
	 */
	EXPECT_EQ(0, test_fionread_ioctl(dir_fd));

	/* Checks that unrestrictable commands are unrestricted. */
	EXPECT_EQ(0, ioctl(dir_fd, FIOCLEX));
	EXPECT_EQ(0, ioctl(dir_fd, FIONCLEX));
	EXPECT_EQ(0, ioctl(dir_fd, FIONBIO, &flag));
	EXPECT_EQ(0, ioctl(dir_fd, FIOASYNC, &flag));
	EXPECT_EQ(0, ioctl(dir_fd, FIGETBSZ, &flag));

	ASSERT_EQ(0, close(dir_fd));
}

TEST_F_FORK(ioctl, handle_file_access_file)
{
	const int flag = 0;
	const struct rule rules[] = {
		{
			.path = "/dev/zero",
			.access = variant->allowed,
		},
		{},
	};
	int file_fd, ruleset_fd;

	/* Enables Landlock. */
	ruleset_fd = create_ruleset(_metadata, variant->handled, rules);
	ASSERT_LE(0, ruleset_fd);
	enforce_ruleset(_metadata, ruleset_fd);
	ASSERT_EQ(0, close(ruleset_fd));

	file_fd = open("/dev/zero", variant->open_mode);
	ASSERT_LE(0, file_fd)
	{
		TH_LOG("Failed to open /dev/zero: %s", strerror(errno));
	}

	/* Checks that IOCTL commands return the expected errors. */
	EXPECT_EQ(variant->expected_fionread_result,
		  test_fionread_ioctl(file_fd));

	/* Checks that unrestrictable commands are unrestricted. */
	EXPECT_EQ(0, ioctl(file_fd, FIOCLEX));
	EXPECT_EQ(0, ioctl(file_fd, FIONCLEX));
	EXPECT_EQ(0, ioctl(file_fd, FIONBIO, &flag));
	EXPECT_EQ(0, ioctl(file_fd, FIOASYNC, &flag));
	EXPECT_EQ(0, ioctl(file_fd, FIGETBSZ, &flag));

	ASSERT_EQ(0, close(file_fd));
}

/* clang-format off */
FIXTURE(layout1_bind) {};
/* clang-format on */