#include <asm/ioctls.h>
#include <kunit/test.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/compiler_types.h>
#include <linux/dcache.h>
#include <linux/err.h>
#include <linux/falloc.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/list.h>
#include <linux/lsm_audit.h>
#include <linux/lsm_hooks.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/path.h>
#include <linux/pid.h>
#include <linux/rcupdate.h>
#include <linux/sched/signal.h>
#include <linux/spinlock.h>
#include <linux/stat.h>
#include <linux/types.h>
#include <linux/wait_bit.h>
#include <linux/workqueue.h>
#include <uapi/linux/fiemap.h>
#include <uapi/linux/landlock.h>
#include "access.h"
#include "audit.h"
#include "common.h"
#include "cred.h"
#include "domain.h"
#include "fs.h"
#include "limits.h"
#include "object.h"
#include "ruleset.h"
#include "setup.h"
static void release_inode(struct landlock_object *const object)
__releases(object->lock)
{
struct inode *const inode = object->underobj;
struct super_block *sb;
if (!inode) {
spin_unlock(&object->lock);
return;
}
object->underobj = NULL;
sb = inode->i_sb;
atomic_long_inc(&landlock_superblock(sb)->inode_refs);
spin_unlock(&object->lock);
rcu_assign_pointer(landlock_inode(inode)->object, NULL);
iput(inode);
if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs))
wake_up_var(&landlock_superblock(sb)->inode_refs);
}
static const struct landlock_object_underops landlock_fs_underops = {
.release = release_inode
};
static __attribute_const__ bool is_masked_device_ioctl(const unsigned int cmd)
{
switch (cmd) {
case FIOCLEX:
case FIONCLEX:
case FIONBIO:
case FIOASYNC:
case FIOQSIZE:
case FIFREEZE:
case FITHAW:
case FS_IOC_FIEMAP:
case FIGETBSZ:
case FICLONE:
case FICLONERANGE:
case FIDEDUPERANGE:
case FS_IOC_GETFSUUID:
case FS_IOC_GETFSSYSFSPATH:
return true;
default:
return false;
}
}
static __attribute_const__ bool
is_masked_device_ioctl_compat(const unsigned int cmd)
{
switch (cmd) {
case FICLONE:
return true;
#if defined(CONFIG_X86_64)
case FS_IOC_RESVSP_32:
case FS_IOC_RESVSP64_32:
case FS_IOC_UNRESVSP_32:
case FS_IOC_UNRESVSP64_32:
case FS_IOC_ZERO_RANGE_32:
#endif
case FS_IOC32_GETFLAGS:
case FS_IOC32_SETFLAGS:
return false;
default:
return is_masked_device_ioctl(cmd);
}
}
static struct landlock_object *get_inode_object(struct inode *const inode)
{
struct landlock_object *object, *new_object;
struct landlock_inode_security *inode_sec = landlock_inode(inode);
rcu_read_lock();
retry:
object = rcu_dereference(inode_sec->object);
if (object) {
if (likely(refcount_inc_not_zero(&object->usage))) {
rcu_read_unlock();
return object;
}
spin_lock(&object->lock);
spin_unlock(&object->lock);
goto retry;
}
rcu_read_unlock();
new_object = landlock_create_object(&landlock_fs_underops, inode);
if (IS_ERR(new_object))
return new_object;
spin_lock(&inode->i_lock);
if (unlikely(rcu_access_pointer(inode_sec->object))) {
spin_unlock(&inode->i_lock);
kfree(new_object);
rcu_read_lock();
goto retry;
}
ihold(inode);
rcu_assign_pointer(inode_sec->object, new_object);
spin_unlock(&inode->i_lock);
return new_object;
}
#define ACCESS_FILE ( \
LANDLOCK_ACCESS_FS_EXECUTE | \
LANDLOCK_ACCESS_FS_WRITE_FILE | \
LANDLOCK_ACCESS_FS_READ_FILE | \
LANDLOCK_ACCESS_FS_TRUNCATE | \
LANDLOCK_ACCESS_FS_IOCTL_DEV)
int landlock_append_fs_rule(struct landlock_ruleset *const ruleset,
const struct path *const path,
access_mask_t access_rights)
{
int err;
struct landlock_id id = {
.type = LANDLOCK_KEY_INODE,
};
if (!d_is_dir(path->dentry) &&
(access_rights | ACCESS_FILE) != ACCESS_FILE)
return -EINVAL;
if (WARN_ON_ONCE(ruleset->num_layers != 1))
return -EINVAL;
access_rights |= LANDLOCK_MASK_ACCESS_FS &
~landlock_get_fs_access_mask(ruleset, 0);
id.key.object = get_inode_object(d_backing_inode(path->dentry));
if (IS_ERR(id.key.object))
return PTR_ERR(id.key.object);
mutex_lock(&ruleset->lock);
err = landlock_insert_rule(ruleset, id, access_rights);
mutex_unlock(&ruleset->lock);
landlock_put_object(id.key.object);
return err;
}
static const struct landlock_rule *
find_rule(const struct landlock_ruleset *const domain,
const struct dentry *const dentry)
{
const struct landlock_rule *rule;
const struct inode *inode;
struct landlock_id id = {
.type = LANDLOCK_KEY_INODE,
};
if (d_is_negative(dentry))
return NULL;
inode = d_backing_inode(dentry);
rcu_read_lock();
id.key.object = rcu_dereference(landlock_inode(inode)->object);
rule = landlock_find_rule(domain, id);
rcu_read_unlock();
return rule;
}
static bool is_nouser_or_private(const struct dentry *dentry)
{
return (dentry->d_sb->s_flags & SB_NOUSER) ||
(d_is_positive(dentry) &&
unlikely(IS_PRIVATE(d_backing_inode(dentry))));
}
static const struct access_masks any_fs = {
.fs = ~0,
};
static bool no_more_access(
const layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
const layer_mask_t (*const layer_masks_child1)[LANDLOCK_NUM_ACCESS_FS],
const bool child1_is_directory,
const layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
const layer_mask_t (*const layer_masks_child2)[LANDLOCK_NUM_ACCESS_FS],
const bool child2_is_directory)
{
unsigned long access_bit;
for (access_bit = 0; access_bit < ARRAY_SIZE(*layer_masks_parent2);
access_bit++) {
const bool is_file_access =
!!(BIT_ULL(access_bit) & ACCESS_FILE);
if (child1_is_directory || is_file_access) {
if ((((*layer_masks_parent1)[access_bit] &
(*layer_masks_child1)[access_bit]) |
(*layer_masks_parent2)[access_bit]) !=
(*layer_masks_parent2)[access_bit])
return false;
}
if (!layer_masks_child2)
continue;
if (child2_is_directory || is_file_access) {
if ((((*layer_masks_parent2)[access_bit] &
(*layer_masks_child2)[access_bit]) |
(*layer_masks_parent1)[access_bit]) !=
(*layer_masks_parent1)[access_bit])
return false;
}
}
return true;
}
#define NMA_TRUE(...) KUNIT_EXPECT_TRUE(test, no_more_access(__VA_ARGS__))
#define NMA_FALSE(...) KUNIT_EXPECT_FALSE(test, no_more_access(__VA_ARGS__))
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_no_more_access(struct kunit *const test)
{
const layer_mask_t rx0[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
[BIT_INDEX(LANDLOCK_ACCESS_FS_READ_FILE)] = BIT_ULL(0),
};
const layer_mask_t mx0[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
[BIT_INDEX(LANDLOCK_ACCESS_FS_MAKE_REG)] = BIT_ULL(0),
};
const layer_mask_t x0[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
};
const layer_mask_t x1[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(1),
};
const layer_mask_t x01[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0) |
BIT_ULL(1),
};
const layer_mask_t allows_all[LANDLOCK_NUM_ACCESS_FS] = {};
NMA_TRUE(&x0, &allows_all, false, &allows_all, NULL, false);
NMA_TRUE(&allows_all, &x0, false, &allows_all, NULL, false);
NMA_FALSE(&x0, &x0, false, &allows_all, NULL, false);
NMA_TRUE(&x0, &x0, false, &rx0, NULL, false);
NMA_TRUE(&rx0, &rx0, false, &rx0, NULL, false);
NMA_FALSE(&rx0, &rx0, false, &x0, NULL, false);
NMA_FALSE(&rx0, &rx0, false, &x1, NULL, false);
NMA_TRUE(&x0, &x1, false, &x0, NULL, false);
NMA_TRUE(&x1, &x0, false, &x0, NULL, false);
NMA_TRUE(&x0, &x01, false, &x0, NULL, false);
NMA_TRUE(&x0, &x01, false, &rx0, NULL, false);
NMA_TRUE(&x01, &x0, false, &x0, NULL, false);
NMA_TRUE(&x01, &x0, false, &rx0, NULL, false);
NMA_FALSE(&x01, &x01, false, &x0, NULL, false);
NMA_FALSE(&rx0, &rx0, true, &x0, NULL, false);
NMA_TRUE(&mx0, &mx0, false, &x0, NULL, false);
NMA_FALSE(&mx0, &mx0, true, &x0, NULL, false);
NMA_TRUE(&mx0, &mx0, true, &mx0, &mx0, true);
NMA_TRUE(&mx0, &mx0, true, &mx0, &x0, true);
NMA_FALSE(&mx0, &mx0, true, &x0, &mx0, true);
NMA_FALSE(&mx0, &mx0, true, &x0, &x0, true);
NMA_FALSE(&mx0, &mx0, true, &x1, &x1, true);
NMA_TRUE(&mx0, &mx0, false, &mx0, &mx0, false);
NMA_TRUE(&mx0, &mx0, false, &mx0, &x0, false);
NMA_TRUE(&mx0, &mx0, false, &x0, &mx0, false);
NMA_TRUE(&mx0, &mx0, false, &x0, &x0, false);
NMA_TRUE(&rx0, &rx0, false, &rx0, &rx0, false);
NMA_TRUE(&rx0, &rx0, false, &rx0, &x0, false);
NMA_FALSE(&rx0, &rx0, false, &x0, &rx0, false);
NMA_FALSE(&rx0, &rx0, false, &x0, &x0, false);
NMA_FALSE(&rx0, &rx0, false, &x1, &x1, false);
NMA_FALSE(&x1, &x1, false, &x0, NULL, false);
NMA_FALSE(&x1, &x1, false, &rx0, NULL, false);
NMA_FALSE(&x1, &x1, true, &x0, NULL, false);
NMA_FALSE(&x1, &x1, true, &rx0, NULL, false);
NMA_TRUE(&x1, &x1, false, &x01, NULL, false);
NMA_FALSE(&x1, &x1, false, &x01, &x0, false);
NMA_FALSE(&x1, &x1, false, &x01, &x01, false);
NMA_FALSE(&x1, &x1, false, &x0, &x0, false);
NMA_FALSE(&x1, &x1, false, &x0, &x0, true);
NMA_FALSE(&x1, &x1, true, &x0, &x0, false);
NMA_FALSE(&x1, &x1, true, &x0, &x0, true);
}
#endif
#undef NMA_TRUE
#undef NMA_FALSE
static bool is_layer_masks_allowed(
layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
{
return !memchr_inv(layer_masks, 0, sizeof(*layer_masks));
}
static bool
scope_to_request(const access_mask_t access_request,
layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS])
{
const unsigned long access_req = access_request;
unsigned long access_bit;
if (WARN_ON_ONCE(!layer_masks))
return true;
for_each_clear_bit(access_bit, &access_req, ARRAY_SIZE(*layer_masks))
(*layer_masks)[access_bit] = 0;
return is_layer_masks_allowed(layer_masks);
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_scope_to_request_with_exec_none(struct kunit *const test)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
KUNIT_EXPECT_TRUE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
&layer_masks));
KUNIT_EXPECT_EQ(test, 0,
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
KUNIT_EXPECT_EQ(test, 0,
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
}
static void test_scope_to_request_with_exec_some(struct kunit *const test)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
};
KUNIT_EXPECT_FALSE(test, scope_to_request(LANDLOCK_ACCESS_FS_EXECUTE,
&layer_masks));
KUNIT_EXPECT_EQ(test, BIT_ULL(0),
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
KUNIT_EXPECT_EQ(test, 0,
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
}
static void test_scope_to_request_without_access(struct kunit *const test)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)] = BIT_ULL(0),
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(1),
};
KUNIT_EXPECT_TRUE(test, scope_to_request(0, &layer_masks));
KUNIT_EXPECT_EQ(test, 0,
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_EXECUTE)]);
KUNIT_EXPECT_EQ(test, 0,
layer_masks[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)]);
}
#endif
static bool
is_eacces(const layer_mask_t (*const layer_masks)[LANDLOCK_NUM_ACCESS_FS],
const access_mask_t access_request)
{
unsigned long access_bit;
const unsigned long access_check = access_request &
~LANDLOCK_ACCESS_FS_REFER;
if (!layer_masks)
return false;
for_each_set_bit(access_bit, &access_check, ARRAY_SIZE(*layer_masks)) {
if ((*layer_masks)[access_bit])
return true;
}
return false;
}
#define IE_TRUE(...) KUNIT_EXPECT_TRUE(test, is_eacces(__VA_ARGS__))
#define IE_FALSE(...) KUNIT_EXPECT_FALSE(test, is_eacces(__VA_ARGS__))
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static void test_is_eacces_with_none(struct kunit *const test)
{
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
IE_FALSE(&layer_masks, 0);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
}
static void test_is_eacces_with_refer(struct kunit *const test)
{
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_REFER)] = BIT_ULL(0),
};
IE_FALSE(&layer_masks, 0);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
}
static void test_is_eacces_with_write(struct kunit *const test)
{
const layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {
[BIT_INDEX(LANDLOCK_ACCESS_FS_WRITE_FILE)] = BIT_ULL(0),
};
IE_FALSE(&layer_masks, 0);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_REFER);
IE_FALSE(&layer_masks, LANDLOCK_ACCESS_FS_EXECUTE);
IE_TRUE(&layer_masks, LANDLOCK_ACCESS_FS_WRITE_FILE);
}
#endif
#undef IE_TRUE
#undef IE_FALSE
static bool is_access_to_paths_allowed(
const struct landlock_ruleset *const domain,
const struct path *const path,
const access_mask_t access_request_parent1,
layer_mask_t (*const layer_masks_parent1)[LANDLOCK_NUM_ACCESS_FS],
struct landlock_request *const log_request_parent1,
struct dentry *const dentry_child1,
const access_mask_t access_request_parent2,
layer_mask_t (*const layer_masks_parent2)[LANDLOCK_NUM_ACCESS_FS],
struct landlock_request *const log_request_parent2,
struct dentry *const dentry_child2)
{
bool allowed_parent1 = false, allowed_parent2 = false, is_dom_check,
child1_is_directory = true, child2_is_directory = true;
struct path walker_path;
access_mask_t access_masked_parent1, access_masked_parent2;
layer_mask_t _layer_masks_child1[LANDLOCK_NUM_ACCESS_FS],
_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;
if (!access_request_parent1 && !access_request_parent2)
return true;
if (WARN_ON_ONCE(!path))
return true;
if (is_nouser_or_private(path->dentry))
return true;
if (WARN_ON_ONCE(!layer_masks_parent1))
return false;
allowed_parent1 = is_layer_masks_allowed(layer_masks_parent1);
if (unlikely(layer_masks_parent2)) {
if (WARN_ON_ONCE(!dentry_child1))
return false;
allowed_parent2 = is_layer_masks_allowed(layer_masks_parent2);
access_masked_parent1 = access_masked_parent2 =
landlock_union_access_masks(domain).fs;
is_dom_check = true;
} else {
if (WARN_ON_ONCE(dentry_child1 || dentry_child2))
return false;
access_masked_parent1 = access_request_parent1;
access_masked_parent2 = access_request_parent2;
is_dom_check = false;
}
if (unlikely(dentry_child1)) {
landlock_unmask_layers(
find_rule(domain, dentry_child1),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child1, LANDLOCK_KEY_INODE),
&_layer_masks_child1, ARRAY_SIZE(_layer_masks_child1));
layer_masks_child1 = &_layer_masks_child1;
child1_is_directory = d_is_dir(dentry_child1);
}
if (unlikely(dentry_child2)) {
landlock_unmask_layers(
find_rule(domain, dentry_child2),
landlock_init_layer_masks(
domain, LANDLOCK_MASK_ACCESS_FS,
&_layer_masks_child2, LANDLOCK_KEY_INODE),
&_layer_masks_child2, ARRAY_SIZE(_layer_masks_child2));
layer_masks_child2 = &_layer_masks_child2;
child2_is_directory = d_is_dir(dentry_child2);
}
walker_path = *path;
path_get(&walker_path);
while (true) {
struct dentry *parent_dentry;
const struct landlock_rule *rule;
if (unlikely(is_dom_check &&
no_more_access(
layer_masks_parent1, layer_masks_child1,
child1_is_directory, layer_masks_parent2,
layer_masks_child2,
child2_is_directory))) {
is_dom_check = false;
access_masked_parent1 = access_request_parent1;
access_masked_parent2 = access_request_parent2;
allowed_parent1 =
allowed_parent1 ||
scope_to_request(access_masked_parent1,
layer_masks_parent1);
allowed_parent2 =
allowed_parent2 ||
scope_to_request(access_masked_parent2,
layer_masks_parent2);
if (allowed_parent1 && allowed_parent2)
break;
}
rule = find_rule(domain, walker_path.dentry);
allowed_parent1 = allowed_parent1 ||
landlock_unmask_layers(
rule, access_masked_parent1,
layer_masks_parent1,
ARRAY_SIZE(*layer_masks_parent1));
allowed_parent2 = allowed_parent2 ||
landlock_unmask_layers(
rule, access_masked_parent2,
layer_masks_parent2,
ARRAY_SIZE(*layer_masks_parent2));
if (allowed_parent1 && allowed_parent2)
break;
jump_up:
if (walker_path.dentry == walker_path.mnt->mnt_root) {
if (follow_up(&walker_path)) {
goto jump_up;
} else {
break;
}
}
if (unlikely(IS_ROOT(walker_path.dentry))) {
if (walker_path.mnt->mnt_flags & MNT_INTERNAL) {
allowed_parent1 = true;
allowed_parent2 = true;
}
break;
}
parent_dentry = dget_parent(walker_path.dentry);
dput(walker_path.dentry);
walker_path.dentry = parent_dentry;
}
path_put(&walker_path);
if (!allowed_parent1) {
log_request_parent1->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent1->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent1->audit.u.path = *path;
log_request_parent1->access = access_masked_parent1;
log_request_parent1->layer_masks = layer_masks_parent1;
log_request_parent1->layer_masks_size =
ARRAY_SIZE(*layer_masks_parent1);
}
if (!allowed_parent2) {
log_request_parent2->type = LANDLOCK_REQUEST_FS_ACCESS;
log_request_parent2->audit.type = LSM_AUDIT_DATA_PATH;
log_request_parent2->audit.u.path = *path;
log_request_parent2->access = access_masked_parent2;
log_request_parent2->layer_masks = layer_masks_parent2;
log_request_parent2->layer_masks_size =
ARRAY_SIZE(*layer_masks_parent2);
}
return allowed_parent1 && allowed_parent2;
}
static int current_check_access_path(const struct path *const path,
access_mask_t access_request)
{
const struct access_masks masks = {
.fs = access_request,
};
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), masks, NULL);
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
struct landlock_request request = {};
if (!subject)
return 0;
access_request = landlock_init_layer_masks(subject->domain,
access_request, &layer_masks,
LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(subject->domain, path, access_request,
&layer_masks, &request, NULL, 0, NULL,
NULL, NULL))
return 0;
landlock_log_denial(subject, &request);
return -EACCES;
}
static __attribute_const__ access_mask_t get_mode_access(const umode_t mode)
{
switch (mode & S_IFMT) {
case S_IFLNK:
return LANDLOCK_ACCESS_FS_MAKE_SYM;
case S_IFDIR:
return LANDLOCK_ACCESS_FS_MAKE_DIR;
case S_IFCHR:
return LANDLOCK_ACCESS_FS_MAKE_CHAR;
case S_IFBLK:
return LANDLOCK_ACCESS_FS_MAKE_BLOCK;
case S_IFIFO:
return LANDLOCK_ACCESS_FS_MAKE_FIFO;
case S_IFSOCK:
return LANDLOCK_ACCESS_FS_MAKE_SOCK;
case S_IFREG:
case 0:
default:
return LANDLOCK_ACCESS_FS_MAKE_REG;
}
}
static access_mask_t maybe_remove(const struct dentry *const dentry)
{
if (d_is_negative(dentry))
return 0;
return d_is_dir(dentry) ? LANDLOCK_ACCESS_FS_REMOVE_DIR :
LANDLOCK_ACCESS_FS_REMOVE_FILE;
}
static bool collect_domain_accesses(
const struct landlock_ruleset *const domain,
const struct dentry *const mnt_root, struct dentry *dir,
layer_mask_t (*const layer_masks_dom)[LANDLOCK_NUM_ACCESS_FS])
{
unsigned long access_dom;
bool ret = false;
if (WARN_ON_ONCE(!domain || !mnt_root || !dir || !layer_masks_dom))
return true;
if (is_nouser_or_private(dir))
return true;
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;
if (landlock_unmask_layers(find_rule(domain, dir), access_dom,
layer_masks_dom,
ARRAY_SIZE(*layer_masks_dom))) {
ret = true;
break;
}
if (dir == mnt_root || WARN_ON_ONCE(IS_ROOT(dir)))
break;
parent_dentry = dget_parent(dir);
dput(dir);
dir = parent_dentry;
}
dput(dir);
return ret;
}
static int current_check_refer_path(struct dentry *const old_dentry,
const struct path *const new_dir,
struct dentry *const new_dentry,
const bool removable, const bool exchange)
{
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs, NULL);
bool allow_parent1, allow_parent2;
access_mask_t access_request_parent1, access_request_parent2;
struct path mnt_dir;
struct dentry *old_parent;
layer_mask_t layer_masks_parent1[LANDLOCK_NUM_ACCESS_FS] = {},
layer_masks_parent2[LANDLOCK_NUM_ACCESS_FS] = {};
struct landlock_request request1 = {}, request2 = {};
if (!subject)
return 0;
if (unlikely(d_is_negative(old_dentry)))
return -ENOENT;
if (exchange) {
if (unlikely(d_is_negative(new_dentry)))
return -ENOENT;
access_request_parent1 =
get_mode_access(d_backing_inode(new_dentry)->i_mode);
} else {
access_request_parent1 = 0;
}
access_request_parent2 =
get_mode_access(d_backing_inode(old_dentry)->i_mode);
if (removable) {
access_request_parent1 |= maybe_remove(old_dentry);
access_request_parent2 |= maybe_remove(new_dentry);
}
if (old_dentry->d_parent == new_dir->dentry) {
access_request_parent1 = landlock_init_layer_masks(
subject->domain,
access_request_parent1 | access_request_parent2,
&layer_masks_parent1, LANDLOCK_KEY_INODE);
if (is_access_to_paths_allowed(subject->domain, new_dir,
access_request_parent1,
&layer_masks_parent1, &request1,
NULL, 0, NULL, NULL, NULL))
return 0;
landlock_log_denial(subject, &request1);
return -EACCES;
}
access_request_parent1 |= LANDLOCK_ACCESS_FS_REFER;
access_request_parent2 |= LANDLOCK_ACCESS_FS_REFER;
mnt_dir.mnt = new_dir->mnt;
mnt_dir.dentry = new_dir->mnt->mnt_root;
old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry :
old_dentry->d_parent;
allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
old_parent,
&layer_masks_parent1);
allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry,
new_dir->dentry,
&layer_masks_parent2);
if (allow_parent1 && allow_parent2)
return 0;
if (is_access_to_paths_allowed(
subject->domain, &mnt_dir, access_request_parent1,
&layer_masks_parent1, &request1, old_dentry,
access_request_parent2, &layer_masks_parent2, &request2,
exchange ? new_dentry : NULL))
return 0;
if (request1.access) {
request1.audit.u.path.dentry = old_parent;
landlock_log_denial(subject, &request1);
}
if (request2.access) {
request2.audit.u.path.dentry = new_dir->dentry;
landlock_log_denial(subject, &request2);
}
if (likely(is_eacces(&layer_masks_parent1, access_request_parent1) ||
is_eacces(&layer_masks_parent2, access_request_parent2)))
return -EACCES;
return -EXDEV;
}
static void hook_inode_free_security_rcu(void *inode_security)
{
struct landlock_inode_security *inode_sec;
inode_sec = inode_security + landlock_blob_sizes.lbs_inode;
WARN_ON_ONCE(inode_sec->object);
}
static void hook_sb_delete(struct super_block *const sb)
{
struct inode *inode, *prev_inode = NULL;
if (!landlock_initialized)
return;
spin_lock(&sb->s_inode_list_lock);
list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
struct landlock_object *object;
if (!icount_read(inode))
continue;
spin_lock(&inode->i_lock);
if (inode->i_state & (I_FREEING | I_WILL_FREE | I_NEW)) {
spin_unlock(&inode->i_lock);
continue;
}
rcu_read_lock();
object = rcu_dereference(landlock_inode(inode)->object);
if (!object) {
rcu_read_unlock();
spin_unlock(&inode->i_lock);
continue;
}
__iget(inode);
spin_unlock(&inode->i_lock);
spin_lock(&object->lock);
if (object->underobj == inode) {
object->underobj = NULL;
spin_unlock(&object->lock);
rcu_read_unlock();
rcu_assign_pointer(landlock_inode(inode)->object, NULL);
iput(inode);
} else {
spin_unlock(&object->lock);
rcu_read_unlock();
}
if (prev_inode) {
spin_unlock(&sb->s_inode_list_lock);
iput(prev_inode);
cond_resched();
spin_lock(&sb->s_inode_list_lock);
}
prev_inode = inode;
}
spin_unlock(&sb->s_inode_list_lock);
if (prev_inode)
iput(prev_inode);
wait_var_event(&landlock_superblock(sb)->inode_refs,
!atomic_long_read(&landlock_superblock(sb)->inode_refs));
}
static void
log_fs_change_topology_path(const struct landlock_cred_security *const subject,
size_t handle_layer, const struct path *const path)
{
landlock_log_denial(subject, &(struct landlock_request) {
.type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
.audit = {
.type = LSM_AUDIT_DATA_PATH,
.u.path = *path,
},
.layer_plus_one = handle_layer + 1,
});
}
static void log_fs_change_topology_dentry(
const struct landlock_cred_security *const subject, size_t handle_layer,
struct dentry *const dentry)
{
landlock_log_denial(subject, &(struct landlock_request) {
.type = LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY,
.audit = {
.type = LSM_AUDIT_DATA_DENTRY,
.u.dentry = dentry,
},
.layer_plus_one = handle_layer + 1,
});
}
static int hook_sb_mount(const char *const dev_name,
const struct path *const path, const char *const type,
const unsigned long flags, void *const data)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs,
&handle_layer);
if (!subject)
return 0;
log_fs_change_topology_path(subject, handle_layer, path);
return -EPERM;
}
static int hook_move_mount(const struct path *const from_path,
const struct path *const to_path)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs,
&handle_layer);
if (!subject)
return 0;
log_fs_change_topology_path(subject, handle_layer, to_path);
return -EPERM;
}
static int hook_sb_umount(struct vfsmount *const mnt, const int flags)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs,
&handle_layer);
if (!subject)
return 0;
log_fs_change_topology_dentry(subject, handle_layer, mnt->mnt_root);
return -EPERM;
}
static int hook_sb_remount(struct super_block *const sb, void *const mnt_opts)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs,
&handle_layer);
if (!subject)
return 0;
log_fs_change_topology_dentry(subject, handle_layer, sb->s_root);
return -EPERM;
}
static int hook_sb_pivotroot(const struct path *const old_path,
const struct path *const new_path)
{
size_t handle_layer;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(current_cred(), any_fs,
&handle_layer);
if (!subject)
return 0;
log_fs_change_topology_path(subject, handle_layer, new_path);
return -EPERM;
}
static int hook_path_link(struct dentry *const old_dentry,
const struct path *const new_dir,
struct dentry *const new_dentry)
{
return current_check_refer_path(old_dentry, new_dir, new_dentry, false,
false);
}
static int hook_path_rename(const struct path *const old_dir,
struct dentry *const old_dentry,
const struct path *const new_dir,
struct dentry *const new_dentry,
const unsigned int flags)
{
return current_check_refer_path(old_dentry, new_dir, new_dentry, true,
!!(flags & RENAME_EXCHANGE));
}
static int hook_path_mkdir(const struct path *const dir,
struct dentry *const dentry, const umode_t mode)
{
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_DIR);
}
static int hook_path_mknod(const struct path *const dir,
struct dentry *const dentry, const umode_t mode,
const unsigned int dev)
{
return current_check_access_path(dir, get_mode_access(mode));
}
static int hook_path_symlink(const struct path *const dir,
struct dentry *const dentry,
const char *const old_name)
{
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_MAKE_SYM);
}
static int hook_path_unlink(const struct path *const dir,
struct dentry *const dentry)
{
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_FILE);
}
static int hook_path_rmdir(const struct path *const dir,
struct dentry *const dentry)
{
return current_check_access_path(dir, LANDLOCK_ACCESS_FS_REMOVE_DIR);
}
static int hook_path_truncate(const struct path *const path)
{
return current_check_access_path(path, LANDLOCK_ACCESS_FS_TRUNCATE);
}
static access_mask_t
get_required_file_open_access(const struct file *const file)
{
access_mask_t access = 0;
if (file->f_mode & FMODE_READ) {
if (S_ISDIR(file_inode(file)->i_mode))
return LANDLOCK_ACCESS_FS_READ_DIR;
access = LANDLOCK_ACCESS_FS_READ_FILE;
}
if (file->f_mode & FMODE_WRITE)
access |= LANDLOCK_ACCESS_FS_WRITE_FILE;
if (file->f_flags & __FMODE_EXEC)
access |= LANDLOCK_ACCESS_FS_EXECUTE;
return access;
}
static int hook_file_alloc_security(struct file *const file)
{
landlock_file(file)->allowed_access = LANDLOCK_MASK_ACCESS_FS;
return 0;
}
static bool is_device(const struct file *const file)
{
const struct inode *inode = file_inode(file);
return S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
}
static int hook_file_open(struct file *const file)
{
layer_mask_t layer_masks[LANDLOCK_NUM_ACCESS_FS] = {};
access_mask_t open_access_request, full_access_request, allowed_access,
optional_access;
const struct landlock_cred_security *const subject =
landlock_get_applicable_subject(file->f_cred, any_fs, NULL);
struct landlock_request request = {};
if (!subject)
return 0;
open_access_request = get_required_file_open_access(file);
optional_access = LANDLOCK_ACCESS_FS_TRUNCATE;
if (is_device(file))
optional_access |= LANDLOCK_ACCESS_FS_IOCTL_DEV;
full_access_request = open_access_request | optional_access;
if (is_access_to_paths_allowed(
subject->domain, &file->f_path,
landlock_init_layer_masks(subject->domain,
full_access_request, &layer_masks,
LANDLOCK_KEY_INODE),
&layer_masks, &request, NULL, 0, NULL, NULL, NULL)) {
allowed_access = full_access_request;
} else {
unsigned long access_bit;
const unsigned long access_req = full_access_request;
allowed_access = 0;
for_each_set_bit(access_bit, &access_req,
ARRAY_SIZE(layer_masks)) {
if (!layer_masks[access_bit])
allowed_access |= BIT_ULL(access_bit);
}
}
landlock_file(file)->allowed_access = allowed_access;
#ifdef CONFIG_AUDIT
landlock_file(file)->deny_masks = landlock_get_deny_masks(
_LANDLOCK_ACCESS_FS_OPTIONAL, optional_access, &layer_masks,
ARRAY_SIZE(layer_masks));
#endif
if ((open_access_request & allowed_access) == open_access_request)
return 0;
request.access = open_access_request;
landlock_log_denial(subject, &request);
return -EACCES;
}
static int hook_file_truncate(struct file *const file)
{
if (landlock_file(file)->allowed_access & LANDLOCK_ACCESS_FS_TRUNCATE)
return 0;
landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
.type = LANDLOCK_REQUEST_FS_ACCESS,
.audit = {
.type = LSM_AUDIT_DATA_FILE,
.u.file = file,
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_TRUNCATE,
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
#endif
});
return -EACCES;
}
static int hook_file_ioctl_common(const struct file *const file,
const unsigned int cmd, const bool is_compat)
{
access_mask_t allowed_access = landlock_file(file)->allowed_access;
if (allowed_access & LANDLOCK_ACCESS_FS_IOCTL_DEV)
return 0;
if (!is_device(file))
return 0;
if (unlikely(is_compat) ? is_masked_device_ioctl_compat(cmd) :
is_masked_device_ioctl(cmd))
return 0;
landlock_log_denial(landlock_cred(file->f_cred), &(struct landlock_request) {
.type = LANDLOCK_REQUEST_FS_ACCESS,
.audit = {
.type = LSM_AUDIT_DATA_IOCTL_OP,
.u.op = &(struct lsm_ioctlop_audit) {
.path = file->f_path,
.cmd = cmd,
},
},
.all_existing_optional_access = _LANDLOCK_ACCESS_FS_OPTIONAL,
.access = LANDLOCK_ACCESS_FS_IOCTL_DEV,
#ifdef CONFIG_AUDIT
.deny_masks = landlock_file(file)->deny_masks,
#endif
});
return -EACCES;
}
static int hook_file_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
return hook_file_ioctl_common(file, cmd, false);
}
static int hook_file_ioctl_compat(struct file *file, unsigned int cmd,
unsigned long arg)
{
return hook_file_ioctl_common(file, cmd, true);
}
static bool control_current_fowner(struct fown_struct *const fown)
{
struct task_struct *p;
lockdep_assert_held(&fown->lock);
guard(rcu)();
p = pid_task(fown->pid, fown->pid_type);
if (!p)
return true;
return !same_thread_group(p, current);
}
static void hook_file_set_fowner(struct file *file)
{
struct landlock_ruleset *prev_dom;
struct landlock_cred_security fown_subject = {};
size_t fown_layer = 0;
if (control_current_fowner(file_f_owner(file))) {
static const struct access_masks signal_scope = {
.scope = LANDLOCK_SCOPE_SIGNAL,
};
const struct landlock_cred_security *new_subject =
landlock_get_applicable_subject(
current_cred(), signal_scope, &fown_layer);
if (new_subject) {
landlock_get_ruleset(new_subject->domain);
fown_subject = *new_subject;
}
}
prev_dom = landlock_file(file)->fown_subject.domain;
landlock_file(file)->fown_subject = fown_subject;
#ifdef CONFIG_AUDIT
landlock_file(file)->fown_layer = fown_layer;
#endif
landlock_put_ruleset_deferred(prev_dom);
}
static void hook_file_free_security(struct file *file)
{
landlock_put_ruleset_deferred(landlock_file(file)->fown_subject.domain);
}
static struct security_hook_list landlock_hooks[] __ro_after_init = {
LSM_HOOK_INIT(inode_free_security_rcu, hook_inode_free_security_rcu),
LSM_HOOK_INIT(sb_delete, hook_sb_delete),
LSM_HOOK_INIT(sb_mount, hook_sb_mount),
LSM_HOOK_INIT(move_mount, hook_move_mount),
LSM_HOOK_INIT(sb_umount, hook_sb_umount),
LSM_HOOK_INIT(sb_remount, hook_sb_remount),
LSM_HOOK_INIT(sb_pivotroot, hook_sb_pivotroot),
LSM_HOOK_INIT(path_link, hook_path_link),
LSM_HOOK_INIT(path_rename, hook_path_rename),
LSM_HOOK_INIT(path_mkdir, hook_path_mkdir),
LSM_HOOK_INIT(path_mknod, hook_path_mknod),
LSM_HOOK_INIT(path_symlink, hook_path_symlink),
LSM_HOOK_INIT(path_unlink, hook_path_unlink),
LSM_HOOK_INIT(path_rmdir, hook_path_rmdir),
LSM_HOOK_INIT(path_truncate, hook_path_truncate),
LSM_HOOK_INIT(file_alloc_security, hook_file_alloc_security),
LSM_HOOK_INIT(file_open, hook_file_open),
LSM_HOOK_INIT(file_truncate, hook_file_truncate),
LSM_HOOK_INIT(file_ioctl, hook_file_ioctl),
LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat),
LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner),
LSM_HOOK_INIT(file_free_security, hook_file_free_security),
};
__init void landlock_add_fs_hooks(void)
{
security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks),
&landlock_lsmid);
}
#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST
static struct kunit_case test_cases[] = {
KUNIT_CASE(test_no_more_access),
KUNIT_CASE(test_scope_to_request_with_exec_none),
KUNIT_CASE(test_scope_to_request_with_exec_some),
KUNIT_CASE(test_scope_to_request_without_access),
KUNIT_CASE(test_is_eacces_with_none),
KUNIT_CASE(test_is_eacces_with_refer),
KUNIT_CASE(test_is_eacces_with_write),
{}
};
static struct kunit_suite test_suite = {
.name = "landlock_fs",
.test_cases = test_cases,
};
kunit_test_suite(test_suite);
#endif