349 lines
9.1 KiB
C
349 lines
9.1 KiB
C
/*
|
|
* 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 fsnotify_mark *inode_mark,
|
|
struct fsnotify_mark *vfsmount_mark,
|
|
struct fsnotify_event *event)
|
|
{
|
|
/*
|
|
* 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 const struct fsnotify_ops chromiumos_fsn_ops = {
|
|
.handle_event = chromiumos_handle_fsnotify_event,
|
|
.freeing_mark = chromiumos_freeing_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;
|
|
}
|
|
|
|
static void chromiumos_free_mark(struct fsnotify_mark *mark)
|
|
{
|
|
iput(chromiumos_to_inode_mark(mark)->inode);
|
|
kfree(mark);
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* Initialize chromiumos_free_mark() to be the routine that will be
|
|
* called when the mark is freed.
|
|
*/
|
|
fsnotify_init_mark(&inode_mark->mark, chromiumos_free_mark);
|
|
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, sbm->fsn_group,
|
|
inode_mark->inode, NULL, 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_inode_mark(sbm->fsn_group, inode);
|
|
if (mark) {
|
|
ACCESS_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_destroy_mark_locked(mark, sbm->fsn_group);
|
|
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 (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);
|
|
chromiumos_super_block_put(sbm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum chromiumos_inode_security_policy chromiumos_get_inode_security_policy(
|
|
struct dentry *dentry,
|
|
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 || !dentry->d_inode ||
|
|
type >= CHROMIUMOS_NUMBER_OF_POLICIES)
|
|
return policy;
|
|
|
|
sbm = chromiumos_super_block_lookup(dentry->d_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_inode_mark(
|
|
sbm->fsn_group, dentry->d_inode);
|
|
if (mark) {
|
|
struct chromiumos_inode_mark *inode_mark =
|
|
chromiumos_to_inode_mark(mark);
|
|
policy = ACCESS_ONCE(inode_mark->policies[type]);
|
|
fsnotify_put_mark(mark);
|
|
|
|
if (policy != CHROMIUMOS_INODE_POLICY_INHERIT)
|
|
break;
|
|
}
|
|
|
|
if (IS_ROOT(dentry))
|
|
break;
|
|
dentry = dentry->d_parent;
|
|
}
|
|
rcu_read_unlock();
|
|
|
|
chromiumos_super_block_put(sbm);
|
|
|
|
return policy;
|
|
}
|