@@ -20,6 +20,7 @@
#include <linux/fs_context.h>
#include <linux/mount.h>
#include <linux/mutex.h>
+#include <linux/namei.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/security.h>
@@ -361,7 +362,11 @@ static int sel_make_classes(struct selinux_policy *newpolicy,
static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
unsigned long *ino);
-/* declaration for sel_remove_old_policy_nodes */
+/* declaration for sel_make_policy_nodes */
+static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
+ unsigned long *ino);
+
+/* declaration for sel_make_policy_nodes */
static void sel_remove_entries(struct dentry *de);
static ssize_t sel_read_mls(struct file *filp, char __user *buf,
@@ -518,50 +523,109 @@ static const struct file_operations sel_policy_ops = {
.llseek = generic_file_llseek,
};
-static void sel_remove_old_policy_nodes(struct selinux_fs_info *fsi)
+static void sel_remove_old_bool_data(unsigned int bool_num, char **bool_names,
+ unsigned int *bool_values)
{
u32 i;
/* bool_dir cleanup */
- for (i = 0; i < fsi->bool_num; i++)
- kfree(fsi->bool_pending_names[i]);
- kfree(fsi->bool_pending_names);
- kfree(fsi->bool_pending_values);
- fsi->bool_num = 0;
- fsi->bool_pending_names = NULL;
- fsi->bool_pending_values = NULL;
-
- sel_remove_entries(fsi->bool_dir);
-
- /* class_dir cleanup */
- sel_remove_entries(fsi->class_dir);
-
- /* policycap_dir cleanup */
- sel_remove_entries(fsi->policycap_dir);
+ for (i = 0; i < bool_num; i++)
+ kfree(bool_names[i]);
+ kfree(bool_names);
+ kfree(bool_values);
}
static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
struct selinux_policy *newpolicy)
{
- int ret;
+ int ret = 0;
+ struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir, *tmp_policycap_dir, *old_dentry;
+ unsigned int tmp_bool_num, old_bool_num;
+ char **tmp_bool_names, **old_bool_names;
+ unsigned int *tmp_bool_values, *old_bool_values;
+
+ tmp_parent = sel_make_disconnected_dir(fsi->sb, &fsi->last_ino);
+ if (IS_ERR(tmp_parent)) {
+ return PTR_ERR(tmp_parent);
+ }
- sel_remove_old_policy_nodes(fsi);
+ tmp_bool_dir = sel_make_dir(tmp_parent, BOOL_DIR_NAME, &fsi->last_ino);
+ if (IS_ERR(tmp_bool_dir)) {
+ ret = PTR_ERR(tmp_bool_dir);
+ goto out;
+ }
- ret = sel_make_bools(newpolicy, fsi->bool_dir, &fsi->bool_num,
- &fsi->bool_pending_names, &fsi->bool_pending_values);
+ tmp_class_dir = sel_make_dir(tmp_parent, CLASS_DIR_NAME, &fsi->last_ino);
+ if (IS_ERR(tmp_class_dir)) {
+ ret = PTR_ERR(tmp_class_dir);
+ goto out;
+ }
+
+ tmp_policycap_dir = sel_make_dir(tmp_parent, POLICYCAP_DIR_NAME, &fsi->last_ino);
+ if (IS_ERR(tmp_policycap_dir)) {
+ ret = PTR_ERR(tmp_policycap_dir);
+ goto out;
+ }
+
+ ret = sel_make_bools(newpolicy, tmp_bool_dir, &tmp_bool_num,
+ &tmp_bool_names, &tmp_bool_values);
if (ret) {
pr_err("SELinux: failed to load policy booleans\n");
- return ret;
+ goto out;
}
- ret = sel_make_classes(newpolicy, fsi->class_dir,
+ ret = sel_make_classes(newpolicy, tmp_class_dir,
&fsi->last_class_ino);
if (ret) {
pr_err("SELinux: failed to load policy classes\n");
- return ret;
+ goto out;
}
- return 0;
+ // booleans
+ old_dentry = fsi->bool_dir;
+ lock_rename(tmp_bool_dir, old_dentry);
+ ret = vfs_rename(tmp_parent->d_inode, tmp_bool_dir, fsi->sb->s_root->d_inode,
+ fsi->bool_dir, NULL, RENAME_EXCHANGE);
+ if (ret) {
+ pr_err("Failed to update boolean directory\n");
+ unlock_rename(tmp_bool_dir, old_dentry);
+ goto out;
+ }
+
+ old_bool_num = fsi->bool_num;
+ old_bool_names = fsi->bool_pending_names;
+ old_bool_values = fsi->bool_pending_values;
+
+ fsi->bool_num = tmp_bool_num;
+ fsi->bool_pending_names = tmp_bool_names;
+ fsi->bool_pending_values = tmp_bool_values;
+
+ sel_remove_old_bool_data(old_bool_num, old_bool_names, old_bool_values);
+
+ fsi->bool_dir = tmp_bool_dir;
+ unlock_rename(tmp_bool_dir, old_dentry);
+
+ // classes
+ old_dentry = fsi->class_dir;
+ lock_rename(tmp_class_dir, old_dentry);
+ ret = vfs_rename(tmp_parent->d_inode, tmp_class_dir, fsi->sb->s_root->d_inode,
+ fsi->class_dir, NULL, RENAME_EXCHANGE);
+ if (ret) {
+ pr_err("Failed to update class directory\n");
+ unlock_rename(tmp_class_dir, old_dentry);
+ goto out;
+ }
+ fsi->class_dir = tmp_class_dir;
+ unlock_rename(tmp_class_dir, old_dentry);
+
+out:
+ // Since the other temporary dirs are children of tmp_parent
+ // this will handle all the cleanup in the case of a failure before
+ // the swapover
+ sel_remove_entries(tmp_parent);
+ dput(tmp_parent); // d_genocide() only handles the children
+
+ return ret;
}
static ssize_t sel_write_load(struct file *file, const char __user *buf,
@@ -1984,6 +2048,38 @@ static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
return dentry;
}
+// vfs_rename() requires a rename operation to be defined.
+int sel_rename(struct inode *old_dir, struct dentry *old_dentry,
+ struct inode *new_dir, struct dentry *new_dentry,
+ unsigned int flags)
+{
+ if (!(flags & RENAME_EXCHANGE))
+ return -EINVAL;
+
+ return 0;
+}
+
+const struct inode_operations sel_dir_inode_operations = {
+ .lookup = simple_lookup,
+ .rename = sel_rename,
+};
+
+static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
+ unsigned long *ino)
+{
+ struct inode *inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO);
+
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ inode->i_op = &sel_dir_inode_operations;
+ inode->i_fop = &simple_dir_operations;
+ inode->i_ino = ++(*ino);
+ /* directory inodes start off with i_nlink == 2 (for "." entry) */
+ inc_nlink(inode);
+ return d_obtain_alias(inode);
+}
+
#define NULL_FILE_NAME "null"
static int sel_fill_super(struct super_block *sb, struct fs_context *fc)