blob: 009debbc163909ef027c54b3780bc20ae389597a [file] [log] [blame]
/*
* Linux Security Module for Chromium OS
*
* Copyright 2016 Google Inc. All Rights Reserved
*
* Authors:
* Mattias Nissler <mnissler@chromium.org>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/atomic.h>
#include <linux/compiler.h>
#include <linux/dcache.h>
#include <linux/fs.h>
#include <linux/fsnotify_backend.h>
#include <linux/hash.h>
#include <linux/mutex.h>
#include <linux/rculist.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include "inode_mark.h"
/*
* This file implements facilities to pin inodes in core and attach some
* meta data to them. We use fsnotify inode marks as a vehicle to attach the
* meta data.
*/
struct chromiumos_inode_mark {
struct fsnotify_mark mark;
struct inode *inode;
enum chromiumos_inode_security_policy
policies[CHROMIUMOS_NUMBER_OF_POLICIES];
};
static inline struct chromiumos_inode_mark *
chromiumos_to_inode_mark(struct fsnotify_mark *mark)
{
return container_of(mark, struct chromiumos_inode_mark, mark);
}
/*
* Hashtable entry that contains tracking information specific to the file
* system identified by the corresponding super_block. This contains the
* fsnotify group that holds all the marks for inodes belonging to the
* super_block.
*/
struct chromiumos_super_block_mark {
atomic_t refcnt;
struct hlist_node node;
struct super_block *sb;
struct fsnotify_group *fsn_group;
};
#define CHROMIUMOS_SUPER_BLOCK_HASH_BITS 8
#define CHROMIUMOS_SUPER_BLOCK_HASH_SIZE (1 << CHROMIUMOS_SUPER_BLOCK_HASH_BITS)
static struct hlist_head chromiumos_super_block_hash_table
[CHROMIUMOS_SUPER_BLOCK_HASH_SIZE] __read_mostly;
static DEFINE_MUTEX(chromiumos_super_block_hash_lock);
static struct hlist_head *chromiumos_super_block_hlist(struct super_block *sb)
{
return &chromiumos_super_block_hash_table[hash_ptr(
sb, CHROMIUMOS_SUPER_BLOCK_HASH_BITS)];
}
static void chromiumos_super_block_put(struct chromiumos_super_block_mark *sbm)
{
if (atomic_dec_and_test(&sbm->refcnt)) {
mutex_lock(&chromiumos_super_block_hash_lock);
hlist_del_rcu(&sbm->node);
mutex_unlock(&chromiumos_super_block_hash_lock);
synchronize_rcu();
fsnotify_destroy_group(sbm->fsn_group);
kfree(sbm);
}
}
static struct chromiumos_super_block_mark *
chromiumos_super_block_lookup(struct super_block *sb)
{
struct hlist_head *hlist = chromiumos_super_block_hlist(sb);
struct chromiumos_super_block_mark *sbm;
struct chromiumos_super_block_mark *matching_sbm = NULL;
rcu_read_lock();
hlist_for_each_entry_rcu(sbm, hlist, node) {
if (sbm->sb == sb && atomic_inc_not_zero(&sbm->refcnt)) {
matching_sbm = sbm;
break;
}
}
rcu_read_unlock();
return matching_sbm;
}
static int chromiumos_handle_fsnotify_event(struct fsnotify_group *group,
struct inode *inode,
u32 mask, const void *data,
int data_type,
const unsigned char *file_name,
u32 cookie,
struct fsnotify_iter_info *iter_info)
{
/*
* This should never get called because a zero mask is set on the inode
* marks. All cases of marks going away (inode deletion, unmount,
* explicit removal) are handled in chromiumos_freeing_mark.
*/
WARN_ON_ONCE(1);
return 0;
}
static void chromiumos_freeing_mark(struct fsnotify_mark *mark,
struct fsnotify_group *group)
{
struct chromiumos_inode_mark *inode_mark =
chromiumos_to_inode_mark(mark);
iput(inode_mark->inode);
inode_mark->inode = NULL;
chromiumos_super_block_put(group->private);
}
static void chromiumos_free_mark(struct fsnotify_mark *mark)
{
iput(chromiumos_to_inode_mark(mark)->inode);
kfree(mark);
}
static const struct fsnotify_ops chromiumos_fsn_ops = {
.handle_event = chromiumos_handle_fsnotify_event,
.freeing_mark = chromiumos_freeing_mark,
.free_mark = chromiumos_free_mark,
};
static struct chromiumos_super_block_mark *
chromiumos_super_block_create(struct super_block *sb)
{
struct hlist_head *hlist = chromiumos_super_block_hlist(sb);
struct chromiumos_super_block_mark *sbm = NULL;
WARN_ON(!mutex_is_locked(&chromiumos_super_block_hash_lock));
/* No match found, create a new entry. */
sbm = kzalloc(sizeof(*sbm), GFP_KERNEL);
if (!sbm)
return ERR_PTR(-ENOMEM);
atomic_set(&sbm->refcnt, 1);
sbm->sb = sb;
sbm->fsn_group = fsnotify_alloc_group(&chromiumos_fsn_ops);
if (IS_ERR(sbm->fsn_group)) {
int ret = PTR_ERR(sbm->fsn_group);
kfree(sbm);
return ERR_PTR(ret);
}
sbm->fsn_group->private = sbm;
hlist_add_head_rcu(&sbm->node, hlist);
return sbm;
}
static struct chromiumos_super_block_mark *
chromiumos_super_block_get(struct super_block *sb)
{
struct chromiumos_super_block_mark *sbm;
mutex_lock(&chromiumos_super_block_hash_lock);
sbm = chromiumos_super_block_lookup(sb);
if (!sbm)
sbm = chromiumos_super_block_create(sb);
mutex_unlock(&chromiumos_super_block_hash_lock);
return sbm;
}
/*
* This will only ever get called if the metadata does not already exist for
* an inode, so no need to worry about freeing an existing mark.
*/
static int
chromiumos_inode_mark_create(
struct chromiumos_super_block_mark *sbm,
struct inode *inode,
enum chromiumos_inode_security_policy_type type,
enum chromiumos_inode_security_policy policy)
{
struct chromiumos_inode_mark *inode_mark;
int ret;
size_t i;
WARN_ON(!mutex_is_locked(&sbm->fsn_group->mark_mutex));
inode_mark = kzalloc(sizeof(*inode_mark), GFP_KERNEL);
if (!inode_mark)
return -ENOMEM;
fsnotify_init_mark(&inode_mark->mark, sbm->fsn_group);
inode_mark->inode = igrab(inode);
if (!inode_mark->inode) {
ret = -ENOENT;
goto out;
}
/* Initialize all policies to inherit. */
for (i = 0; i < CHROMIUMOS_NUMBER_OF_POLICIES; i++)
inode_mark->policies[i] = CHROMIUMOS_INODE_POLICY_INHERIT;
inode_mark->policies[type] = policy;
ret = fsnotify_add_mark_locked(&inode_mark->mark, &inode->i_fsnotify_marks,
type, false);
if (ret)
goto out;
/* Take an sbm reference so the created mark is accounted for. */
atomic_inc(&sbm->refcnt);
out:
fsnotify_put_mark(&inode_mark->mark);
return ret;
}
int chromiumos_update_inode_security_policy(
struct inode *inode,
enum chromiumos_inode_security_policy_type type,
enum chromiumos_inode_security_policy policy)
{
struct chromiumos_super_block_mark *sbm;
struct fsnotify_mark *mark;
bool free_mark = false;
int ret;
size_t i;
sbm = chromiumos_super_block_get(inode->i_sb);
if (IS_ERR(sbm))
return PTR_ERR(sbm);
mutex_lock(&sbm->fsn_group->mark_mutex);
mark = fsnotify_find_mark(&inode->i_fsnotify_marks, sbm->fsn_group);
if (mark) {
WRITE_ONCE(chromiumos_to_inode_mark(mark)->policies[type],
policy);
/*
* Frees mark if all policies are
* CHROMIUM_INODE_POLICY_INHERIT.
*/
free_mark = true;
for (i = 0; i < CHROMIUMOS_NUMBER_OF_POLICIES; i++) {
if (chromiumos_to_inode_mark(mark)->policies[i]
!= CHROMIUMOS_INODE_POLICY_INHERIT) {
free_mark = false;
break;
}
}
if (free_mark)
fsnotify_detach_mark(mark);
ret = 0;
} else {
ret = chromiumos_inode_mark_create(sbm, inode, type, policy);
}
mutex_unlock(&sbm->fsn_group->mark_mutex);
chromiumos_super_block_put(sbm);
/* This must happen after dropping the mark mutex. */
if (free_mark)
fsnotify_free_mark(mark);
if (mark)
fsnotify_put_mark(mark);
return ret;
}
/* Flushes all inode security policies. */
int chromiumos_flush_inode_security_policies(struct super_block *sb)
{
struct chromiumos_super_block_mark *sbm;
sbm = chromiumos_super_block_lookup(sb);
if (sbm) {
fsnotify_clear_marks_by_group(sbm->fsn_group,
FSNOTIFY_OBJ_ALL_TYPES_MASK);
chromiumos_super_block_put(sbm);
}
return 0;
}
enum chromiumos_inode_security_policy chromiumos_get_inode_security_policy(
struct dentry *dentry, struct inode *inode,
enum chromiumos_inode_security_policy_type type)
{
struct chromiumos_super_block_mark *sbm;
/*
* Initializes policy to CHROMIUM_INODE_POLICY_INHERIT, which is
* the value that will be returned if neither |dentry| nor any
* directory in its path has been asigned an inode security policy
* value for the given type.
*/
enum chromiumos_inode_security_policy policy =
CHROMIUMOS_INODE_POLICY_INHERIT;
if (!dentry || !inode || type >= CHROMIUMOS_NUMBER_OF_POLICIES)
return policy;
sbm = chromiumos_super_block_lookup(inode->i_sb);
if (!sbm)
return policy;
/* Walk the dentry path and look for a traversal policy. */
rcu_read_lock();
while (1) {
struct fsnotify_mark *mark = fsnotify_find_mark(
&inode->i_fsnotify_marks, sbm->fsn_group);
if (mark) {
struct chromiumos_inode_mark *inode_mark =
chromiumos_to_inode_mark(mark);
policy = READ_ONCE(inode_mark->policies[type]);
fsnotify_put_mark(mark);
if (policy != CHROMIUMOS_INODE_POLICY_INHERIT)
break;
}
if (IS_ROOT(dentry))
break;
dentry = READ_ONCE(dentry->d_parent);
if (!dentry)
break;
inode = d_inode_rcu(dentry);
if (!inode)
break;
}
rcu_read_unlock();
chromiumos_super_block_put(sbm);
return policy;
}