Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 117 additions & 21 deletions security/landlock/fs.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
* Returns NULL if no rule is found or if @dentry is negative.
*/
static const struct landlock_rule *
find_rule(const struct landlock_ruleset *const domain,
find_rule_rcu(const struct landlock_ruleset *const domain,
const struct dentry *const dentry)
{
const struct landlock_rule *rule;
Expand All @@ -375,10 +375,10 @@ find_rule(const struct landlock_ruleset *const domain,
return NULL;

inode = d_backing_inode(dentry);
rcu_read_lock();
if (unlikely(!inode))
return NULL;
id.key.object = rcu_dereference(landlock_inode(inode)->object);
rule = landlock_find_rule(domain, id);
rcu_read_unlock();
return rule;
}

Expand Down Expand Up @@ -771,6 +771,9 @@ static bool is_access_to_paths_allowed(
_layer_masks_child2[LANDLOCK_NUM_ACCESS_FS];
layer_mask_t(*layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS] = NULL,
(*layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS] = NULL;
unsigned int rename_seqcount;
bool pathwalk_ref = false;
const struct landlock_rule *rule;

if (!access_request_parent1 && !access_request_parent2)
return true;
Expand Down Expand Up @@ -809,9 +812,12 @@ static bool is_access_to_paths_allowed(
is_dom_check = false;
}

rcu_read_lock();

restart_pathwalk:
if (unlikely(dentry_child1)) {
landlock_unmask_layers(
find_rule(domain, dentry_child1),
find_rule_rcu(domain, dentry_child1),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child1, LANDLOCK_KEY_INODE),
Expand All @@ -821,7 +827,7 @@ static bool is_access_to_paths_allowed(
}
if (unlikely(dentry_child2)) {
landlock_unmask_layers(
find_rule(domain, dentry_child2),
find_rule_rcu(domain, dentry_child2),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child2, LANDLOCK_KEY_INODE),
Expand All @@ -831,14 +837,32 @@ static bool is_access_to_paths_allowed(
}

walker_path = *path;
path_get(&walker_path);

/*
* Attempt to do a pathwalk without taking dentry references first,
* but if any rename happens while we are doing this, give up and do a
* walk with dget_parent instead. See comments in
* collect_domain_accesses().
*/

if (!pathwalk_ref) {
rename_seqcount = raw_read_seqcount(&rename_lock.seqcount);
if (rename_seqcount & 1) {
pathwalk_ref = true;
path_get(&walker_path);
}
} else {
path_get(&walker_path);
}

rule = find_rule_rcu(domain, walker_path.dentry);

/*
* We need to walk through all the hierarchy to not miss any relevant
* restriction.
*/
while (true) {
struct dentry *parent_dentry;
const struct landlock_rule *rule;

/*
* If at least all accesses allowed on the destination are
Expand Down Expand Up @@ -880,7 +904,6 @@ static bool is_access_to_paths_allowed(
break;
}

rule = find_rule(domain, walker_path.dentry);
allowed_parent1 = allowed_parent1 ||
landlock_unmask_layers(
rule, access_masked_parent1,
Expand All @@ -897,10 +920,17 @@ static bool is_access_to_paths_allowed(
break;
jump_up:
if (walker_path.dentry == walker_path.mnt->mnt_root) {
/* follow_up gets the parent and puts the passed in path */
if (!pathwalk_ref)
path_get(&walker_path);
if (follow_up(&walker_path)) {
if (!pathwalk_ref)
path_put(&walker_path);
/* Ignores hidden mount points. */
goto jump_up;
} else {
if (!pathwalk_ref)
path_put(&walker_path);
/*
* Stops at the real root. Denies access
* because not all layers have granted access.
Expand All @@ -920,11 +950,28 @@ static bool is_access_to_paths_allowed(
}
break;
}
parent_dentry = dget_parent(walker_path.dentry);
dput(walker_path.dentry);
walker_path.dentry = parent_dentry;
if (!pathwalk_ref) {
parent_dentry = walker_path.dentry->d_parent;

rule = find_rule_rcu(domain, parent_dentry);
if (read_seqretry(&rename_lock, rename_seqcount)) {
pathwalk_ref = true;
goto restart_pathwalk;
} else {
walker_path.dentry = parent_dentry;
}
} else {
parent_dentry = dget_parent(walker_path.dentry);
dput(walker_path.dentry);
walker_path.dentry = parent_dentry;
rule = find_rule_rcu(domain, walker_path.dentry);
}
}
path_put(&walker_path);

if (pathwalk_ref)
path_put(&walker_path);

rcu_read_unlock();

if (!allowed_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
Expand Down Expand Up @@ -1035,23 +1082,55 @@ static bool collect_domain_accesses(
{
unsigned long access_dom;
bool ret = false;
bool pathwalk_ref = false;
unsigned int rename_seqcount;
const struct landlock_rule *rule;
struct dentry *parent_dentry;

if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
return true;
if (is_nouser_or_private(dir))
return true;

rcu_read_lock();

restart_pathwalk:
access_dom = landlock_init_layer_masks(domain, LANDLOCK_MASK_ACCESS_FS,
layer_masks_dom,
LANDLOCK_KEY_INODE);

dget(dir);
while (true) {
struct dentry *parent_dentry;
/*
* Attempt to do a pathwalk without taking dentry references first, but
* if any rename happens while we are doing this, give up and do a walk
* with dget_parent instead. This prevents wrong denials in the
* presence of a move followed by an immediate rmdir of the old parent,
* where even when both the original and the new parent has allow
* rules, we might still hit a negative dentry (the deleted old parent)
* and being unable to find either rules.
*/

if (!pathwalk_ref) {
rename_seqcount = raw_read_seqcount(&rename_lock.seqcount);
if (rename_seqcount & 1) {
pathwalk_ref = true;
dget(dir);
}
} else {
dget(dir);
}
rule = find_rule_rcu(domain, dir);
/*
* We don't need to check rename_seqcount here because we haven't
* followed any d_parent yet, and the d_inode of the path being
* accessed can't change under us as we have ref on path.dentry. But
* once we start walking up the path, we need to check the seqcount to
* make sure the rule we got isn't based on a wrong/changing/negative
* dentry.
*/

while (true) {
/* Gets all layers allowing all domain accesses. */
if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
layer_masks_dom,
if (landlock_unmask_layers(rule, access_dom, layer_masks_dom,
ARRAY_SIZE(*layer_masks_dom))) {
/*
* Stops when all handled accesses are allowed by at
Expand All @@ -1065,11 +1144,28 @@ static bool collect_domain_accesses(
if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir)))
break;

parent_dentry = dget_parent(dir);
dput(dir);
dir = parent_dentry;
if (!pathwalk_ref) {
parent_dentry = dir->d_parent;
rule = find_rule_rcu(domain, dir);
if (read_seqretry(&rename_lock, rename_seqcount)) {
pathwalk_ref = true;
goto restart_pathwalk;
} else {
dir = parent_dentry;
}
} else {
parent_dentry = dget_parent(dir);
dput(dir);
dir = parent_dentry;
rule = find_rule_rcu(domain, dir);
}
}
dput(dir);

if (pathwalk_ref)
dput(dir);

rcu_read_unlock();

return ret;
}

Expand Down
Loading