#include <asm/current.h>
#include <linux/anon_inodes.h>
#include <linux/bitops.h>
#include <linux/build_bug.h>
#include <linux/capability.h>
#include <linux/cleanup.h>
#include <linux/compiler_types.h>
#include <linux/dcache.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/limits.h>
#include <linux/mount.h>
#include <linux/path.h>
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/stddef.h>
#include <linux/syscalls.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <uapi/linux/landlock.h>
#include "cred.h"
#include "domain.h"
#include "fs.h"
#include "limits.h"
#include "net.h"
#include "ruleset.h"
#include "setup.h"
#include "tsync.h"
static bool is_initialized(void)
{
if (likely(landlock_initialized))
return true;
pr_warn_once(
"Disabled but requested by user space. "
"You should enable Landlock at boot time: "
"https://docs.kernel.org/userspace-api/landlock.html#boot-time-configuration\n");
return false;
}
static __always_inline int
copy_min_struct_from_user(void *const dst, const size_t ksize,
const size_t ksize_min, const void __user *const src,
const size_t usize)
{
BUILD_BUG_ON(!dst);
if (!src)
return -EFAULT;
BUILD_BUG_ON(ksize <= 0);
BUILD_BUG_ON(ksize < ksize_min);
if (usize < ksize_min)
return -EINVAL;
if (usize > PAGE_SIZE)
return -E2BIG;
return copy_struct_from_user(dst, ksize, src, usize);
}
static void build_check_abi(void)
{
struct landlock_ruleset_attr ruleset_attr;
struct landlock_path_beneath_attr path_beneath_attr;
struct landlock_net_port_attr net_port_attr;
size_t ruleset_size, path_beneath_size, net_port_size;
ruleset_size = sizeof(ruleset_attr.handled_access_fs);
ruleset_size += sizeof(ruleset_attr.handled_access_net);
ruleset_size += sizeof(ruleset_attr.scoped);
BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size);
BUILD_BUG_ON(sizeof(ruleset_attr) != 24);
path_beneath_size = sizeof(path_beneath_attr.allowed_access);
path_beneath_size += sizeof(path_beneath_attr.parent_fd);
BUILD_BUG_ON(sizeof(path_beneath_attr) != path_beneath_size);
BUILD_BUG_ON(sizeof(path_beneath_attr) != 12);
net_port_size = sizeof(net_port_attr.allowed_access);
net_port_size += sizeof(net_port_attr.port);
BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size);
BUILD_BUG_ON(sizeof(net_port_attr) != 16);
}
static int fop_ruleset_release(struct inode *const inode,
struct file *const filp)
{
struct landlock_ruleset *ruleset = filp->private_data;
landlock_put_ruleset(ruleset);
return 0;
}
static ssize_t fop_dummy_read(struct file *const filp, char __user *const buf,
const size_t size, loff_t *const ppos)
{
return -EINVAL;
}
static ssize_t fop_dummy_write(struct file *const filp,
const char __user *const buf, const size_t size,
loff_t *const ppos)
{
return -EINVAL;
}
static const struct file_operations ruleset_fops = {
.release = fop_ruleset_release,
.read = fop_dummy_read,
.write = fop_dummy_write,
};
const int landlock_abi_version = 8;
SYSCALL_DEFINE3(landlock_create_ruleset,
const struct landlock_ruleset_attr __user *const, attr,
const size_t, size, const __u32, flags)
{
struct landlock_ruleset_attr ruleset_attr;
struct landlock_ruleset *ruleset;
int err, ruleset_fd;
build_check_abi();
if (!is_initialized())
return -EOPNOTSUPP;
if (flags) {
if (attr || size)
return -EINVAL;
if (flags == LANDLOCK_CREATE_RULESET_VERSION)
return landlock_abi_version;
if (flags == LANDLOCK_CREATE_RULESET_ERRATA)
return landlock_errata;
return -EINVAL;
}
err = copy_min_struct_from_user(&ruleset_attr, sizeof(ruleset_attr),
offsetofend(typeof(ruleset_attr),
handled_access_fs),
attr, size);
if (err)
return err;
if ((ruleset_attr.handled_access_fs | LANDLOCK_MASK_ACCESS_FS) !=
LANDLOCK_MASK_ACCESS_FS)
return -EINVAL;
if ((ruleset_attr.handled_access_net | LANDLOCK_MASK_ACCESS_NET) !=
LANDLOCK_MASK_ACCESS_NET)
return -EINVAL;
if ((ruleset_attr.scoped | LANDLOCK_MASK_SCOPE) != LANDLOCK_MASK_SCOPE)
return -EINVAL;
ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs,
ruleset_attr.handled_access_net,
ruleset_attr.scoped);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
ruleset_fd = anon_inode_getfd("[landlock-ruleset]", &ruleset_fops,
ruleset, O_RDWR | O_CLOEXEC);
if (ruleset_fd < 0)
landlock_put_ruleset(ruleset);
return ruleset_fd;
}
static struct landlock_ruleset *get_ruleset_from_fd(const int fd,
const fmode_t mode)
{
CLASS(fd, ruleset_f)(fd);
struct landlock_ruleset *ruleset;
if (fd_empty(ruleset_f))
return ERR_PTR(-EBADF);
if (fd_file(ruleset_f)->f_op != &ruleset_fops)
return ERR_PTR(-EBADFD);
if (!(fd_file(ruleset_f)->f_mode & mode))
return ERR_PTR(-EPERM);
ruleset = fd_file(ruleset_f)->private_data;
if (WARN_ON_ONCE(ruleset->num_layers != 1))
return ERR_PTR(-EINVAL);
landlock_get_ruleset(ruleset);
return ruleset;
}
static int get_path_from_fd(const s32 fd, struct path *const path)
{
CLASS(fd_raw, f)(fd);
BUILD_BUG_ON(!__same_type(
fd, ((struct landlock_path_beneath_attr *)NULL)->parent_fd));
if (fd_empty(f))
return -EBADF;
if ((fd_file(f)->f_op == &ruleset_fops) ||
(fd_file(f)->f_path.mnt->mnt_flags & MNT_INTERNAL) ||
(fd_file(f)->f_path.dentry->d_sb->s_flags & SB_NOUSER) ||
IS_PRIVATE(d_backing_inode(fd_file(f)->f_path.dentry)))
return -EBADFD;
*path = fd_file(f)->f_path;
path_get(path);
return 0;
}
static int add_rule_path_beneath(struct landlock_ruleset *const ruleset,
const void __user *const rule_attr)
{
struct landlock_path_beneath_attr path_beneath_attr;
struct path path;
int res, err;
access_mask_t mask;
res = copy_from_user(&path_beneath_attr, rule_attr,
sizeof(path_beneath_attr));
if (res)
return -EFAULT;
if (!path_beneath_attr.allowed_access)
return -ENOMSG;
mask = ruleset->access_masks[0].fs;
if ((path_beneath_attr.allowed_access | mask) != mask)
return -EINVAL;
err = get_path_from_fd(path_beneath_attr.parent_fd, &path);
if (err)
return err;
err = landlock_append_fs_rule(ruleset, &path,
path_beneath_attr.allowed_access);
path_put(&path);
return err;
}
static int add_rule_net_port(struct landlock_ruleset *ruleset,
const void __user *const rule_attr)
{
struct landlock_net_port_attr net_port_attr;
int res;
access_mask_t mask;
res = copy_from_user(&net_port_attr, rule_attr, sizeof(net_port_attr));
if (res)
return -EFAULT;
if (!net_port_attr.allowed_access)
return -ENOMSG;
mask = landlock_get_net_access_mask(ruleset, 0);
if ((net_port_attr.allowed_access | mask) != mask)
return -EINVAL;
if (net_port_attr.port > U16_MAX)
return -EINVAL;
return landlock_append_net_rule(ruleset, net_port_attr.port,
net_port_attr.allowed_access);
}
SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd,
const enum landlock_rule_type, rule_type,
const void __user *const, rule_attr, const __u32, flags)
{
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
if (!is_initialized())
return -EOPNOTSUPP;
if (flags)
return -EINVAL;
ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_WRITE);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
switch (rule_type) {
case LANDLOCK_RULE_PATH_BENEATH:
return add_rule_path_beneath(ruleset, rule_attr);
case LANDLOCK_RULE_NET_PORT:
return add_rule_net_port(ruleset, rule_attr);
default:
return -EINVAL;
}
}
SYSCALL_DEFINE2(landlock_restrict_self, const int, ruleset_fd, const __u32,
flags)
{
struct landlock_ruleset *ruleset __free(landlock_put_ruleset) = NULL;
struct cred *new_cred;
struct landlock_cred_security *new_llcred;
bool __maybe_unused log_same_exec, log_new_exec, log_subdomains,
prev_log_subdomains;
if (!is_initialized())
return -EOPNOTSUPP;
if (!task_no_new_privs(current) &&
!ns_capable_noaudit(current_user_ns(), CAP_SYS_ADMIN))
return -EPERM;
if ((flags | LANDLOCK_MASK_RESTRICT_SELF) !=
LANDLOCK_MASK_RESTRICT_SELF)
return -EINVAL;
log_same_exec = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SAME_EXEC_OFF);
log_new_exec = !!(flags & LANDLOCK_RESTRICT_SELF_LOG_NEW_EXEC_ON);
log_subdomains = !(flags & LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF);
if (!(ruleset_fd == -1 &&
flags == LANDLOCK_RESTRICT_SELF_LOG_SUBDOMAINS_OFF)) {
ruleset = get_ruleset_from_fd(ruleset_fd, FMODE_CAN_READ);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
}
new_cred = prepare_creds();
if (!new_cred)
return -ENOMEM;
new_llcred = landlock_cred(new_cred);
#ifdef CONFIG_AUDIT
prev_log_subdomains = !new_llcred->log_subdomains_off;
new_llcred->log_subdomains_off = !prev_log_subdomains ||
!log_subdomains;
#endif
if (ruleset) {
struct landlock_ruleset *const new_dom =
landlock_merge_ruleset(new_llcred->domain, ruleset);
if (IS_ERR(new_dom)) {
abort_creds(new_cred);
return PTR_ERR(new_dom);
}
#ifdef CONFIG_AUDIT
new_dom->hierarchy->log_same_exec = log_same_exec;
new_dom->hierarchy->log_new_exec = log_new_exec;
if ((!log_same_exec && !log_new_exec) || !prev_log_subdomains)
new_dom->hierarchy->log_status = LANDLOCK_LOG_DISABLED;
#endif
landlock_put_ruleset(new_llcred->domain);
new_llcred->domain = new_dom;
#ifdef CONFIG_AUDIT
new_llcred->domain_exec |= BIT(new_dom->num_layers - 1);
#endif
}
if (flags & LANDLOCK_RESTRICT_SELF_TSYNC) {
const int err = landlock_restrict_sibling_threads(
current_cred(), new_cred);
if (err) {
abort_creds(new_cred);
return err;
}
}
return commit_creds(new_cred);
}