mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
landlock: Add a place for flags to layer rules
To avoid unnecessarily increasing the size of struct landlock_layer, we make the layer level a u8 and use the space to store the flags struct. struct layer_access_masks is renamed to struct layer_masks, and a new field is added to track whether a quiet flag rule is seen for each layer. Through use of bitfields, this does not increase the size of the struct. Cc: Justin Suess <utilityemal77@gmail.com> Assisted-by: GitHub-Copilot:claude-opus-4.8 copilot-review Signed-off-by: Tingmao Wang <m@maowtm.org> Co-developed-by: Justin Suess <utilityemal77@gmail.com> Signed-off-by: Justin Suess <utilityemal77@gmail.com> Tested-by: Justin Suess <utilityemal77@gmail.com> Link: https://patch.msgid.link/be3fec3927bc9faaacd4ce0e7f0d1ff5474e2210.1781228815.git.m@maowtm.org [mic: Fix comment formatting] Signed-off-by: Mickaël Salaün <mic@digikod.net>
This commit is contained in:
parent
299eccf996
commit
a260c00556
|
|
@ -62,18 +62,41 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) ==
|
||||||
sizeof(typeof_member(union access_masks_all, all)));
|
sizeof(typeof_member(union access_masks_all, all)));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* struct layer_access_masks - A boolean matrix of layers and access rights
|
* struct layer_mask - The access rights and rule flags for a layer.
|
||||||
*
|
*
|
||||||
* This has a bit for each combination of layer numbers and access rights.
|
* This has a bit for each access rights and rule flags. During access checks,
|
||||||
* During access checks, it is used to represent the access rights for each
|
* it is used to represent the access rights for each layer which still need to
|
||||||
* layer which still need to be fulfilled. When all bits are 0, the access
|
* be fulfilled. When all bits are 0, the access request is considered to be
|
||||||
* request is considered to be fulfilled.
|
* fulfilled.
|
||||||
*/
|
*/
|
||||||
struct layer_access_masks {
|
struct layer_mask {
|
||||||
/**
|
/**
|
||||||
* @access: The unfulfilled access rights for each layer.
|
* @access: The unfulfilled access rights for this layer.
|
||||||
*/
|
*/
|
||||||
access_mask_t access[LANDLOCK_MAX_NUM_LAYERS];
|
access_mask_t access : LANDLOCK_NUM_ACCESS_MAX;
|
||||||
|
#ifdef CONFIG_AUDIT
|
||||||
|
/**
|
||||||
|
* @quiet: Whether we have encountered a rule with the quiet flag for
|
||||||
|
* this layer. Used to control logging.
|
||||||
|
*/
|
||||||
|
access_mask_t quiet : 1;
|
||||||
|
#endif /* CONFIG_AUDIT */
|
||||||
|
} __packed __aligned(sizeof(access_mask_t));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure that we don't increase the size of struct layer_mask when storing
|
||||||
|
* rule flags.
|
||||||
|
*/
|
||||||
|
static_assert(sizeof(struct layer_mask) == sizeof(access_mask_t));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct layer_masks - An array of struct layer_mask, one per layer.
|
||||||
|
*/
|
||||||
|
struct layer_masks {
|
||||||
|
/**
|
||||||
|
* @layers: The unfulfilled access rights for each layer.
|
||||||
|
*/
|
||||||
|
struct layer_mask layers[LANDLOCK_MAX_NUM_LAYERS];
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -187,11 +187,11 @@ static void test_get_hierarchy(struct kunit *const test)
|
||||||
/* Get the youngest layer that denied the access_request. */
|
/* Get the youngest layer that denied the access_request. */
|
||||||
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
|
static size_t get_denied_layer(const struct landlock_ruleset *const domain,
|
||||||
access_mask_t *const access_request,
|
access_mask_t *const access_request,
|
||||||
const struct layer_access_masks *masks)
|
const struct layer_masks *masks)
|
||||||
{
|
{
|
||||||
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
|
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||||
if (masks->access[i] & *access_request) {
|
if (masks->layers[i].access & *access_request) {
|
||||||
*access_request &= masks->access[i];
|
*access_request &= masks->layers[i].access;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,12 +208,12 @@ static void test_get_denied_layer(struct kunit *const test)
|
||||||
const struct landlock_ruleset dom = {
|
const struct landlock_ruleset dom = {
|
||||||
.num_layers = 5,
|
.num_layers = 5,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks masks = {
|
const struct layer_masks masks = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_READ_FILE |
|
.layers[1].access = LANDLOCK_ACCESS_FS_READ_FILE |
|
||||||
LANDLOCK_ACCESS_FS_READ_DIR,
|
LANDLOCK_ACCESS_FS_READ_DIR,
|
||||||
.access[2] = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
.layers[2].access = LANDLOCK_ACCESS_FS_REMOVE_DIR,
|
||||||
};
|
};
|
||||||
access_mask_t access;
|
access_mask_t access;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ struct landlock_request {
|
||||||
access_mask_t access;
|
access_mask_t access;
|
||||||
|
|
||||||
/* Required fields for requests with layer masks. */
|
/* Required fields for requests with layer masks. */
|
||||||
const struct layer_access_masks *layer_masks;
|
const struct layer_masks *layer_masks;
|
||||||
|
|
||||||
/* Required fields for requests with deny masks. */
|
/* Required fields for requests with deny masks. */
|
||||||
const access_mask_t all_existing_optional_access;
|
const access_mask_t all_existing_optional_access;
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ static void test_get_layer_deny_mask(struct kunit *const test)
|
||||||
deny_masks_t
|
deny_masks_t
|
||||||
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||||
const access_mask_t optional_access,
|
const access_mask_t optional_access,
|
||||||
const struct layer_access_masks *const masks)
|
const struct layer_masks *const masks)
|
||||||
{
|
{
|
||||||
const unsigned long access_opt = optional_access;
|
const unsigned long access_opt = optional_access;
|
||||||
unsigned long access_bit;
|
unsigned long access_bit;
|
||||||
|
|
@ -201,8 +201,9 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||||
if (WARN_ON_ONCE(!access_opt))
|
if (WARN_ON_ONCE(!access_opt))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
for (ssize_t i = ARRAY_SIZE(masks->access) - 1; i >= 0; i--) {
|
for (ssize_t i = ARRAY_SIZE(masks->layers) - 1; i >= 0; i--) {
|
||||||
const access_mask_t denied = masks->access[i] & optional_access;
|
const access_mask_t denied = masks->layers[i].access &
|
||||||
|
optional_access;
|
||||||
const unsigned long newly_denied = denied & ~all_denied;
|
const unsigned long newly_denied = denied & ~all_denied;
|
||||||
|
|
||||||
if (!newly_denied)
|
if (!newly_denied)
|
||||||
|
|
@ -222,12 +223,12 @@ landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||||
|
|
||||||
static void test_landlock_get_deny_masks(struct kunit *const test)
|
static void test_landlock_get_deny_masks(struct kunit *const test)
|
||||||
{
|
{
|
||||||
const struct layer_access_masks layers1 = {
|
const struct layer_masks layers1 = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_TRUNCATE,
|
.layers[1].access = LANDLOCK_ACCESS_FS_TRUNCATE,
|
||||||
.access[2] = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
.layers[2].access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
|
||||||
.access[9] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[9].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
};
|
};
|
||||||
|
|
||||||
KUNIT_EXPECT_EQ(test, 0x1,
|
KUNIT_EXPECT_EQ(test, 0x1,
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ struct landlock_hierarchy {
|
||||||
deny_masks_t
|
deny_masks_t
|
||||||
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
landlock_get_deny_masks(const access_mask_t all_existing_optional_access,
|
||||||
const access_mask_t optional_access,
|
const access_mask_t optional_access,
|
||||||
const struct layer_access_masks *const masks);
|
const struct layer_masks *const masks);
|
||||||
|
|
||||||
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
|
int landlock_init_hierarchy_log(struct landlock_hierarchy *const hierarchy);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -406,15 +406,15 @@ static const struct access_masks any_fs = {
|
||||||
* src_parent would result in having the same or fewer access rights if it were
|
* src_parent would result in having the same or fewer access rights if it were
|
||||||
* moved under new_parent.
|
* moved under new_parent.
|
||||||
*/
|
*/
|
||||||
static bool may_refer(const struct layer_access_masks *const src_parent,
|
static bool may_refer(const struct layer_masks *const src_parent,
|
||||||
const struct layer_access_masks *const src_child,
|
const struct layer_masks *const src_child,
|
||||||
const struct layer_access_masks *const new_parent,
|
const struct layer_masks *const new_parent,
|
||||||
const bool child_is_dir)
|
const bool child_is_dir)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(new_parent->access); i++) {
|
for (size_t i = 0; i < ARRAY_SIZE(new_parent->layers); i++) {
|
||||||
access_mask_t child_access = src_parent->access[i] &
|
access_mask_t child_access = src_parent->layers[i].access &
|
||||||
src_child->access[i];
|
src_child->layers[i].access;
|
||||||
access_mask_t parent_access = new_parent->access[i];
|
access_mask_t parent_access = new_parent->layers[i].access;
|
||||||
|
|
||||||
if (!child_is_dir) {
|
if (!child_is_dir) {
|
||||||
child_access &= ACCESS_FILE;
|
child_access &= ACCESS_FILE;
|
||||||
|
|
@ -436,11 +436,11 @@ static bool may_refer(const struct layer_access_masks *const src_parent,
|
||||||
* that child2 may be used from parent2 to parent1 without increasing its access
|
* that child2 may be used from parent2 to parent1 without increasing its access
|
||||||
* rights), false otherwise.
|
* rights), false otherwise.
|
||||||
*/
|
*/
|
||||||
static bool no_more_access(const struct layer_access_masks *const parent1,
|
static bool no_more_access(const struct layer_masks *const parent1,
|
||||||
const struct layer_access_masks *const child1,
|
const struct layer_masks *const child1,
|
||||||
const bool child1_is_dir,
|
const bool child1_is_dir,
|
||||||
const struct layer_access_masks *const parent2,
|
const struct layer_masks *const parent2,
|
||||||
const struct layer_access_masks *const child2,
|
const struct layer_masks *const child2,
|
||||||
const bool child2_is_dir)
|
const bool child2_is_dir)
|
||||||
{
|
{
|
||||||
if (!may_refer(parent1, child1, parent2, child1_is_dir))
|
if (!may_refer(parent1, child1, parent2, child1_is_dir))
|
||||||
|
|
@ -459,25 +459,25 @@ static bool no_more_access(const struct layer_access_masks *const parent1,
|
||||||
|
|
||||||
static void test_no_more_access(struct kunit *const test)
|
static void test_no_more_access(struct kunit *const test)
|
||||||
{
|
{
|
||||||
const struct layer_access_masks rx0 = {
|
const struct layer_masks rx0 = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
LANDLOCK_ACCESS_FS_READ_FILE,
|
LANDLOCK_ACCESS_FS_READ_FILE,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks mx0 = {
|
const struct layer_masks mx0 = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE |
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE |
|
||||||
LANDLOCK_ACCESS_FS_MAKE_REG,
|
LANDLOCK_ACCESS_FS_MAKE_REG,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks x0 = {
|
const struct layer_masks x0 = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks x1 = {
|
const struct layer_masks x1 = {
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks x01 = {
|
const struct layer_masks x01 = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[1].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
};
|
};
|
||||||
const struct layer_access_masks allows_all = {};
|
const struct layer_masks allows_all = {};
|
||||||
|
|
||||||
/* Checks without restriction. */
|
/* Checks without restriction. */
|
||||||
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
|
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
|
||||||
|
|
@ -565,9 +565,13 @@ static void test_no_more_access(struct kunit *const test)
|
||||||
#undef NMA_TRUE
|
#undef NMA_TRUE
|
||||||
#undef NMA_FALSE
|
#undef NMA_FALSE
|
||||||
|
|
||||||
static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
|
static bool is_layer_masks_allowed(const struct layer_masks *masks)
|
||||||
{
|
{
|
||||||
return mem_is_zero(&masks->access, sizeof(masks->access));
|
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||||
|
if (masks->layers[i].access)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -576,16 +580,16 @@ static bool is_layer_masks_allowed(const struct layer_access_masks *masks)
|
||||||
* Returns true if the request is allowed, false otherwise.
|
* Returns true if the request is allowed, false otherwise.
|
||||||
*/
|
*/
|
||||||
static bool scope_to_request(const access_mask_t access_request,
|
static bool scope_to_request(const access_mask_t access_request,
|
||||||
struct layer_access_masks *masks)
|
struct layer_masks *masks)
|
||||||
{
|
{
|
||||||
bool saw_unfulfilled_access = false;
|
bool saw_unfulfilled_access = false;
|
||||||
|
|
||||||
if (WARN_ON_ONCE(!masks))
|
if (WARN_ON_ONCE(!masks))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||||
masks->access[i] &= access_request;
|
masks->layers[i].access &= access_request;
|
||||||
if (masks->access[i])
|
if (masks->layers[i].access)
|
||||||
saw_unfulfilled_access = true;
|
saw_unfulfilled_access = true;
|
||||||
}
|
}
|
||||||
return !saw_unfulfilled_access;
|
return !saw_unfulfilled_access;
|
||||||
|
|
@ -596,41 +600,46 @@ static bool scope_to_request(const access_mask_t access_request,
|
||||||
static void test_scope_to_request_with_exec_none(struct kunit *const test)
|
static void test_scope_to_request_with_exec_none(struct kunit *const test)
|
||||||
{
|
{
|
||||||
/* Allows everything. */
|
/* Allows everything. */
|
||||||
struct layer_access_masks masks = {};
|
struct layer_masks masks = {};
|
||||||
|
|
||||||
/* Checks and scopes with execute. */
|
/* Checks and scopes with execute. */
|
||||||
KUNIT_EXPECT_TRUE(test,
|
KUNIT_EXPECT_TRUE(test,
|
||||||
scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks));
|
scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE, &masks));
|
||||||
KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
|
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_scope_to_request_with_exec_some(struct kunit *const test)
|
static void test_scope_to_request_with_exec_some(struct kunit *const test)
|
||||||
{
|
{
|
||||||
/* Denies execute and write. */
|
/* Denies execute and write. */
|
||||||
struct layer_access_masks masks = {
|
struct layer_masks masks = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
.layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Checks and scopes with execute. */
|
/* Checks and scopes with execute. */
|
||||||
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
|
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
&masks));
|
&masks));
|
||||||
KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE, masks.access[0]);
|
/*
|
||||||
KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
|
* These casts to access_mask_t are needed because typeof(), used in
|
||||||
|
* KUNIT_EXPECT_EQ(), does not work on bitfields.
|
||||||
|
*/
|
||||||
|
KUNIT_EXPECT_EQ(test, LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
|
(access_mask_t)masks.layers[0].access);
|
||||||
|
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_scope_to_request_without_access(struct kunit *const test)
|
static void test_scope_to_request_without_access(struct kunit *const test)
|
||||||
{
|
{
|
||||||
/* Denies execute and write. */
|
/* Denies execute and write. */
|
||||||
struct layer_access_masks masks = {
|
struct layer_masks masks = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_EXECUTE,
|
.layers[0].access = LANDLOCK_ACCESS_FS_EXECUTE,
|
||||||
.access[1] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
.layers[1].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Checks and scopes without access request. */
|
/* Checks and scopes without access request. */
|
||||||
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks));
|
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &masks));
|
||||||
KUNIT_EXPECT_EQ(test, 0, masks.access[0]);
|
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[0].access);
|
||||||
KUNIT_EXPECT_EQ(test, 0, masks.access[1]);
|
KUNIT_EXPECT_EQ(test, 0, (access_mask_t)masks.layers[1].access);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */
|
||||||
|
|
@ -639,15 +648,15 @@ static void test_scope_to_request_without_access(struct kunit *const test)
|
||||||
* Returns true if there is at least one access right different than
|
* Returns true if there is at least one access right different than
|
||||||
* LANDLOCK_ACCESS_FS_REFER.
|
* LANDLOCK_ACCESS_FS_REFER.
|
||||||
*/
|
*/
|
||||||
static bool is_eacces(const struct layer_access_masks *masks,
|
static bool is_eacces(const struct layer_masks *masks,
|
||||||
const access_mask_t access_request)
|
const access_mask_t access_request)
|
||||||
{
|
{
|
||||||
if (!masks)
|
if (!masks)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||||
/* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */
|
/* LANDLOCK_ACCESS_FS_REFER alone must return -EXDEV. */
|
||||||
if (masks->access[i] & access_request &
|
if (masks->layers[i].access & access_request &
|
||||||
~LANDLOCK_ACCESS_FS_REFER)
|
~LANDLOCK_ACCESS_FS_REFER)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -661,7 +670,7 @@ static bool is_eacces(const struct layer_access_masks *masks,
|
||||||
|
|
||||||
static void test_is_eacces_with_none(struct kunit *const test)
|
static void test_is_eacces_with_none(struct kunit *const test)
|
||||||
{
|
{
|
||||||
const struct layer_access_masks masks = {};
|
const struct layer_masks masks = {};
|
||||||
|
|
||||||
IE_FALSE(&masks, 0);
|
IE_FALSE(&masks, 0);
|
||||||
IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER);
|
IE_FALSE(&masks, LANDLOCK_ACCESS_FS_REFER);
|
||||||
|
|
@ -671,8 +680,8 @@ static void test_is_eacces_with_none(struct kunit *const test)
|
||||||
|
|
||||||
static void test_is_eacces_with_refer(struct kunit *const test)
|
static void test_is_eacces_with_refer(struct kunit *const test)
|
||||||
{
|
{
|
||||||
const struct layer_access_masks masks = {
|
const struct layer_masks masks = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_REFER,
|
.layers[0].access = LANDLOCK_ACCESS_FS_REFER,
|
||||||
};
|
};
|
||||||
|
|
||||||
IE_FALSE(&masks, 0);
|
IE_FALSE(&masks, 0);
|
||||||
|
|
@ -683,8 +692,8 @@ static void test_is_eacces_with_refer(struct kunit *const test)
|
||||||
|
|
||||||
static void test_is_eacces_with_write(struct kunit *const test)
|
static void test_is_eacces_with_write(struct kunit *const test)
|
||||||
{
|
{
|
||||||
const struct layer_access_masks masks = {
|
const struct layer_masks masks = {
|
||||||
.access[0] = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
.layers[0].access = LANDLOCK_ACCESS_FS_WRITE_FILE,
|
||||||
};
|
};
|
||||||
|
|
||||||
IE_FALSE(&masks, 0);
|
IE_FALSE(&masks, 0);
|
||||||
|
|
@ -743,11 +752,11 @@ static bool
|
||||||
is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
||||||
const struct path *const path,
|
const struct path *const path,
|
||||||
const access_mask_t access_request_parent1,
|
const access_mask_t access_request_parent1,
|
||||||
struct layer_access_masks *layer_masks_parent1,
|
struct layer_masks *layer_masks_parent1,
|
||||||
struct landlock_request *const log_request_parent1,
|
struct landlock_request *const log_request_parent1,
|
||||||
struct dentry *const dentry_child1,
|
struct dentry *const dentry_child1,
|
||||||
const access_mask_t access_request_parent2,
|
const access_mask_t access_request_parent2,
|
||||||
struct layer_access_masks *layer_masks_parent2,
|
struct layer_masks *layer_masks_parent2,
|
||||||
struct landlock_request *const log_request_parent2,
|
struct landlock_request *const log_request_parent2,
|
||||||
struct dentry *const dentry_child2)
|
struct dentry *const dentry_child2)
|
||||||
{
|
{
|
||||||
|
|
@ -755,9 +764,9 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
||||||
child1_is_directory = true, child2_is_directory = true;
|
child1_is_directory = true, child2_is_directory = true;
|
||||||
struct path walker_path;
|
struct path walker_path;
|
||||||
access_mask_t access_masked_parent1, access_masked_parent2;
|
access_mask_t access_masked_parent1, access_masked_parent2;
|
||||||
struct layer_access_masks _layer_masks_child1, _layer_masks_child2;
|
struct layer_masks _layer_masks_child1, _layer_masks_child2;
|
||||||
struct layer_access_masks *layer_masks_child1 = NULL,
|
struct layer_masks *layer_masks_child1 = NULL,
|
||||||
*layer_masks_child2 = NULL;
|
*layer_masks_child2 = NULL;
|
||||||
|
|
||||||
if (!access_request_parent1 && !access_request_parent2)
|
if (!access_request_parent1 && !access_request_parent2)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -797,6 +806,10 @@ is_access_to_paths_allowed(const struct landlock_ruleset *const domain,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlikely(dentry_child1)) {
|
if (unlikely(dentry_child1)) {
|
||||||
|
/*
|
||||||
|
* Get the layer masks for the child dentries for use by domain
|
||||||
|
* check later.
|
||||||
|
*/
|
||||||
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
if (landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
|
||||||
&_layer_masks_child1,
|
&_layer_masks_child1,
|
||||||
LANDLOCK_KEY_INODE))
|
LANDLOCK_KEY_INODE))
|
||||||
|
|
@ -952,7 +965,7 @@ static int current_check_access_path(const struct path *const path,
|
||||||
};
|
};
|
||||||
const struct landlock_cred_security *const subject =
|
const struct landlock_cred_security *const subject =
|
||||||
landlock_get_applicable_subject(current_cred(), masks, NULL);
|
landlock_get_applicable_subject(current_cred(), masks, NULL);
|
||||||
struct layer_access_masks layer_masks;
|
struct layer_masks layer_masks;
|
||||||
struct landlock_request request = {};
|
struct landlock_request request = {};
|
||||||
|
|
||||||
if (!subject)
|
if (!subject)
|
||||||
|
|
@ -1029,7 +1042,7 @@ static access_mask_t maybe_remove(const struct dentry *const dentry)
|
||||||
static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
|
static bool collect_domain_accesses(const struct landlock_ruleset *const domain,
|
||||||
const struct dentry *const mnt_root,
|
const struct dentry *const mnt_root,
|
||||||
struct dentry *dir,
|
struct dentry *dir,
|
||||||
struct layer_access_masks *layer_masks_dom)
|
struct layer_masks *layer_masks_dom)
|
||||||
{
|
{
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
|
|
@ -1135,8 +1148,7 @@ static int current_check_refer_path(struct dentry *const old_dentry,
|
||||||
access_mask_t access_request_parent1, access_request_parent2;
|
access_mask_t access_request_parent1, access_request_parent2;
|
||||||
struct path mnt_dir;
|
struct path mnt_dir;
|
||||||
struct dentry *old_parent;
|
struct dentry *old_parent;
|
||||||
struct layer_access_masks layer_masks_parent1 = {},
|
struct layer_masks layer_masks_parent1 = {}, layer_masks_parent2 = {};
|
||||||
layer_masks_parent2 = {};
|
|
||||||
struct landlock_request request1 = {}, request2 = {};
|
struct landlock_request request1 = {}, request2 = {};
|
||||||
|
|
||||||
if (!subject)
|
if (!subject)
|
||||||
|
|
@ -1202,7 +1214,6 @@ static int current_check_refer_path(struct dentry *const old_dentry,
|
||||||
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
|
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
|
||||||
new_dir->dentry,
|
new_dir->dentry,
|
||||||
&layer_masks_parent2);
|
&layer_masks_parent2);
|
||||||
|
|
||||||
if (allow_parent1 && allow_parent2)
|
if (allow_parent1 && allow_parent2)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -1580,7 +1591,7 @@ static int hook_path_truncate(const struct path *const path)
|
||||||
*/
|
*/
|
||||||
static void unmask_scoped_access(const struct landlock_ruleset *const client,
|
static void unmask_scoped_access(const struct landlock_ruleset *const client,
|
||||||
const struct landlock_ruleset *const server,
|
const struct landlock_ruleset *const server,
|
||||||
struct layer_access_masks *const masks,
|
struct layer_masks *const masks,
|
||||||
const access_mask_t access)
|
const access_mask_t access)
|
||||||
{
|
{
|
||||||
int client_layer, server_layer;
|
int client_layer, server_layer;
|
||||||
|
|
@ -1621,9 +1632,9 @@ static void unmask_scoped_access(const struct landlock_ruleset *const client,
|
||||||
server_walker = server_walker->parent;
|
server_walker = server_walker->parent;
|
||||||
|
|
||||||
for (; client_layer >= 0; client_layer--) {
|
for (; client_layer >= 0; client_layer--) {
|
||||||
if (masks->access[client_layer] & access &&
|
if (masks->layers[client_layer].access & access &&
|
||||||
client_walker == server_walker)
|
client_walker == server_walker)
|
||||||
masks->access[client_layer] &= ~access;
|
masks->layers[client_layer].access &= ~access;
|
||||||
|
|
||||||
client_walker = client_walker->parent;
|
client_walker = client_walker->parent;
|
||||||
server_walker = server_walker->parent;
|
server_walker = server_walker->parent;
|
||||||
|
|
@ -1635,7 +1646,7 @@ static int hook_unix_find(const struct path *const path, struct sock *other,
|
||||||
{
|
{
|
||||||
const struct landlock_ruleset *dom_other;
|
const struct landlock_ruleset *dom_other;
|
||||||
const struct landlock_cred_security *subject;
|
const struct landlock_cred_security *subject;
|
||||||
struct layer_access_masks layer_masks;
|
struct layer_masks layer_masks;
|
||||||
struct landlock_request request = {};
|
struct landlock_request request = {};
|
||||||
static const struct access_masks fs_resolve_unix = {
|
static const struct access_masks fs_resolve_unix = {
|
||||||
.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
|
.fs = LANDLOCK_ACCESS_FS_RESOLVE_UNIX,
|
||||||
|
|
@ -1739,7 +1750,7 @@ static bool is_device(const struct file *const file)
|
||||||
|
|
||||||
static int hook_file_open(struct file *const file)
|
static int hook_file_open(struct file *const file)
|
||||||
{
|
{
|
||||||
struct layer_access_masks layer_masks = {};
|
struct layer_masks layer_masks = {};
|
||||||
access_mask_t open_access_request, full_access_request, allowed_access,
|
access_mask_t open_access_request, full_access_request, allowed_access,
|
||||||
optional_access;
|
optional_access;
|
||||||
const struct landlock_cred_security *const subject =
|
const struct landlock_cred_security *const subject =
|
||||||
|
|
@ -1780,8 +1791,8 @@ static int hook_file_open(struct file *const file)
|
||||||
* are still unfulfilled in any of the layers.
|
* are still unfulfilled in any of the layers.
|
||||||
*/
|
*/
|
||||||
allowed_access = full_access_request;
|
allowed_access = full_access_request;
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(layer_masks.access); i++)
|
for (size_t i = 0; i < ARRAY_SIZE(layer_masks.layers); i++)
|
||||||
allowed_access &= ~layer_masks.access[i];
|
allowed_access &= ~layer_masks.layers[i].access;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@
|
||||||
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
|
#define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1)
|
||||||
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
|
#define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE)
|
||||||
|
|
||||||
|
#define LANDLOCK_NUM_ACCESS_MAX \
|
||||||
|
MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE)
|
||||||
|
|
||||||
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
|
#define LANDLOCK_LAST_RESTRICT_SELF LANDLOCK_RESTRICT_SELF_TSYNC
|
||||||
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
|
#define LANDLOCK_MASK_RESTRICT_SELF ((LANDLOCK_LAST_RESTRICT_SELF << 1) - 1)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ static int current_check_access_socket(struct socket *const sock,
|
||||||
{
|
{
|
||||||
unsigned short sock_family;
|
unsigned short sock_family;
|
||||||
__be16 port;
|
__be16 port;
|
||||||
struct layer_access_masks layer_masks = {};
|
struct layer_masks layer_masks = {};
|
||||||
const struct landlock_rule *rule;
|
const struct landlock_rule *rule;
|
||||||
struct landlock_id id = {
|
struct landlock_id id = {
|
||||||
.type = LANDLOCK_KEY_NET_PORT,
|
.type = LANDLOCK_KEY_NET_PORT,
|
||||||
|
|
|
||||||
|
|
@ -628,7 +628,7 @@ landlock_find_rule(const struct landlock_ruleset *const ruleset,
|
||||||
* remaining unfulfilled access rights and masks has no leftover set bits).
|
* remaining unfulfilled access rights and masks has no leftover set bits).
|
||||||
*/
|
*/
|
||||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||||
struct layer_access_masks *masks)
|
struct layer_masks *masks)
|
||||||
{
|
{
|
||||||
if (!masks)
|
if (!masks)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -649,11 +649,17 @@ bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||||
const struct landlock_layer *const layer = &rule->layers[i];
|
const struct landlock_layer *const layer = &rule->layers[i];
|
||||||
|
|
||||||
/* Clear the bits where the layer in the rule grants access. */
|
/* Clear the bits where the layer in the rule grants access. */
|
||||||
masks->access[layer->level - 1] &= ~layer->access;
|
masks->layers[layer->level - 1].access &= ~layer->access;
|
||||||
|
|
||||||
|
#ifdef CONFIG_AUDIT
|
||||||
|
/* Collect rule flags for each layer. */
|
||||||
|
if (layer->flags.quiet)
|
||||||
|
masks->layers[layer->level - 1].quiet = true;
|
||||||
|
#endif /* CONFIG_AUDIT */
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < ARRAY_SIZE(masks->access); i++) {
|
for (size_t i = 0; i < ARRAY_SIZE(masks->layers); i++) {
|
||||||
if (masks->access[i])
|
if (masks->layers[i].access)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -666,8 +672,9 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
|
||||||
/**
|
/**
|
||||||
* landlock_init_layer_masks - Initialize layer masks from an access request
|
* landlock_init_layer_masks - Initialize layer masks from an access request
|
||||||
*
|
*
|
||||||
* Populates @masks such that for each access right in @access_request,
|
* Populates @masks such that for each access right in @access_request, the bits
|
||||||
* the bits for all the layers are set where this access right is handled.
|
* for all the layers are set where this access right is handled. Rule flags
|
||||||
|
* are also zeroed.
|
||||||
*
|
*
|
||||||
* @domain: The domain that defines the current restrictions.
|
* @domain: The domain that defines the current restrictions.
|
||||||
* @access_request: The requested access rights to check.
|
* @access_request: The requested access rights to check.
|
||||||
|
|
@ -680,7 +687,7 @@ get_access_mask_t(const struct landlock_ruleset *const ruleset,
|
||||||
access_mask_t
|
access_mask_t
|
||||||
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
||||||
const access_mask_t access_request,
|
const access_mask_t access_request,
|
||||||
struct layer_access_masks *const masks,
|
struct layer_masks *const masks,
|
||||||
const enum landlock_key_type key_type)
|
const enum landlock_key_type key_type)
|
||||||
{
|
{
|
||||||
access_mask_t handled_accesses = 0;
|
access_mask_t handled_accesses = 0;
|
||||||
|
|
@ -709,11 +716,19 @@ landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
||||||
for (size_t i = 0; i < domain->num_layers; i++) {
|
for (size_t i = 0; i < domain->num_layers; i++) {
|
||||||
const access_mask_t handled = get_access_mask(domain, i);
|
const access_mask_t handled = get_access_mask(domain, i);
|
||||||
|
|
||||||
masks->access[i] = access_request & handled;
|
masks->layers[i].access = access_request & handled;
|
||||||
handled_accesses |= masks->access[i];
|
handled_accesses |= masks->layers[i].access;
|
||||||
|
#ifdef CONFIG_AUDIT
|
||||||
|
masks->layers[i].quiet = false;
|
||||||
|
#endif /* CONFIG_AUDIT */
|
||||||
|
}
|
||||||
|
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->layers);
|
||||||
|
i++) {
|
||||||
|
masks->layers[i].access = 0;
|
||||||
|
#ifdef CONFIG_AUDIT
|
||||||
|
masks->layers[i].quiet = false;
|
||||||
|
#endif /* CONFIG_AUDIT */
|
||||||
}
|
}
|
||||||
for (size_t i = domain->num_layers; i < ARRAY_SIZE(masks->access); i++)
|
|
||||||
masks->access[i] = 0;
|
|
||||||
|
|
||||||
return handled_accesses;
|
return handled_accesses;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,18 @@ struct landlock_layer {
|
||||||
/**
|
/**
|
||||||
* @level: Position of this layer in the layer stack. Starts from 1.
|
* @level: Position of this layer in the layer stack. Starts from 1.
|
||||||
*/
|
*/
|
||||||
u16 level;
|
u8 level;
|
||||||
|
/**
|
||||||
|
* @flags: Bitfield for special flags attached to this rule.
|
||||||
|
*/
|
||||||
|
struct {
|
||||||
|
/**
|
||||||
|
* @quiet: Suppresses denial logs for the object covered by this
|
||||||
|
* rule in this domain. For filesystem rules, this inherits
|
||||||
|
* down the file hierarchy.
|
||||||
|
*/
|
||||||
|
u8 quiet : 1;
|
||||||
|
} flags;
|
||||||
/**
|
/**
|
||||||
* @access: Bitfield of allowed actions on the kernel object. They are
|
* @access: Bitfield of allowed actions on the kernel object. They are
|
||||||
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
|
* relative to the object type (e.g. %LANDLOCK_ACTION_FS_READ).
|
||||||
|
|
@ -302,12 +313,12 @@ landlock_get_scope_mask(const struct landlock_ruleset *const ruleset,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
bool landlock_unmask_layers(const struct landlock_rule *const rule,
|
||||||
struct layer_access_masks *masks);
|
struct layer_masks *masks);
|
||||||
|
|
||||||
access_mask_t
|
access_mask_t
|
||||||
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
landlock_init_layer_masks(const struct landlock_ruleset *const domain,
|
||||||
const access_mask_t access_request,
|
const access_mask_t access_request,
|
||||||
struct layer_access_masks *masks,
|
struct layer_masks *masks,
|
||||||
const enum landlock_key_type key_type);
|
const enum landlock_key_type key_type);
|
||||||
|
|
||||||
#endif /* _SECURITY_LANDLOCK_RULESET_H */
|
#endif /* _SECURITY_LANDLOCK_RULESET_H */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user