/* * Linux Security Module for Chromium OS * * Copyright 2016 Google Inc. All Rights Reserved * * Authors: * Mattias Nissler * * 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 #include #include #include #include #include #include #include #include #include #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; }