ima: hooks for directory integrity protection
Both IMA-appraisal and EVM protect the integrity of regular files.
IMA protects file data integrity, while EVM protects the file meta-data
integrity, such as file attributes and extended attributes. This patch
set adds hooks for offline directory integrity protection.
An inode itself does not have any file name associated with it. The
association of the file name to inode is done via directory entries.
On a running system, mandatory and/or discretionary access control prevent
unprivileged file deletion, file name change, or hardlink creation.
In an offline attack, without these protections, the association between
a file name and an inode is unprotected. Files can be deleted, renamed
or moved from one directory to another one. In all of these cases,
the integrity of the file data and metadata is good.
To prevent such attacks, it is necessary to protect integrity of directory
content.
This patch adds 2 new hooks for directory integrity protection:
ima_dir_check() and ima_dir_update().
ima_dir_check() is called to verify integrity of the the directory during
the initial path lookup.
ima_dir_update() is called from several places in namei.c, when the directory
content is changing, for updating the directory hash.
Signed-off-by: Dmitry Kasatkin <d.kasatkin@samsung.com>
diff --git a/fs/namei.c b/fs/namei.c
index 726d211..31854e1 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -1639,16 +1639,33 @@
return follow_managed(path, nd);
}
+static inline int may_lookup_ima(struct nameidata *nd, int err)
+{
+ if (err)
+ return err;
+ err = ima_dir_check(&nd->path, MAY_EXEC|MAY_NOT_BLOCK);
+ if (err != -ECHILD)
+ return err;
+ if (unlazy_walk(nd, NULL, 0))
+ return -ECHILD;
+ return ima_dir_check(&nd->path, MAY_EXEC);
+}
+
static inline int may_lookup(struct nameidata *nd)
{
+ int err = 0;
+
if (nd->flags & LOOKUP_RCU) {
- int err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
+ err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK);
if (err != -ECHILD)
- return err;
+ return may_lookup_ima(nd, err);
if (unlazy_walk(nd, NULL, 0))
return -ECHILD;
}
- return inode_permission(nd->inode, MAY_EXEC);
+ err = inode_permission(nd->inode, MAY_EXEC);
+ if (err)
+ return err;
+ return ima_dir_check(&nd->path, MAY_EXEC);
}
static inline int handle_dots(struct nameidata *nd, int type)
@@ -3081,6 +3098,8 @@
}
mutex_lock(&dir->d_inode->i_mutex);
error = lookup_open(nd, &path, file, op, got_write, opened);
+ if (error >= 0 && (*opened & FILE_CREATED))
+ ima_dir_update(&nd->path, NULL);
mutex_unlock(&dir->d_inode->i_mutex);
if (error <= 0) {
@@ -3566,6 +3585,8 @@
error = vfs_mknod(path.dentry->d_inode,dentry,mode,0);
break;
}
+ if (!error)
+ ima_dir_update(&path, dentry);
out:
done_path_create(&path, dentry);
if (retry_estale(error, lookup_flags)) {
@@ -3623,6 +3644,8 @@
error = security_path_mkdir(&path, dentry, mode);
if (!error)
error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
+ if (!error)
+ ima_dir_update(&path, dentry);
done_path_create(&path, dentry);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
@@ -3744,6 +3767,8 @@
if (error)
goto exit3;
error = vfs_rmdir(path.dentry->d_inode, dentry);
+ if (!error)
+ ima_dir_update(&path, NULL);
exit3:
dput(dentry);
exit2:
@@ -3868,6 +3893,8 @@
if (error)
goto exit2;
error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode);
+ if (!error)
+ ima_dir_update(&path, NULL);
exit2:
dput(dentry);
}
@@ -3959,6 +3986,8 @@
error = security_path_symlink(&path, dentry, from->name);
if (!error)
error = vfs_symlink(path.dentry->d_inode, dentry, from->name);
+ if (!error)
+ ima_dir_update(&path, dentry);
done_path_create(&path, dentry);
if (retry_estale(error, lookup_flags)) {
lookup_flags |= LOOKUP_REVAL;
@@ -4101,6 +4130,8 @@
if (error)
goto out_dput;
error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode);
+ if (!error)
+ ima_dir_update(&new_path, NULL);
out_dput:
done_path_create(&new_path, new_dentry);
if (delegated_inode) {
@@ -4423,6 +4454,11 @@
error = vfs_rename(old_path.dentry->d_inode, old_dentry,
new_path.dentry->d_inode, new_dentry,
&delegated_inode, flags);
+ if (!error) {
+ ima_dir_update(&old_path, NULL);
+ if (!path_equal(&old_path, &new_path))
+ ima_dir_update(&new_path, NULL);
+ }
exit5:
dput(new_dentry);
exit4:
diff --git a/fs/open.c b/fs/open.c
index b6f1e96..61000be 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -428,6 +428,9 @@
error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR);
if (error)
goto dput_and_out;
+ error = ima_dir_check(&path, MAY_EXEC);
+ if (error)
+ goto dput_and_out;
set_fs_pwd(current->fs, &path);
@@ -458,6 +461,9 @@
goto out_putf;
error = inode_permission(inode, MAY_EXEC | MAY_CHDIR);
+ if (error)
+ goto out_putf;
+ error = ima_dir_check(&f.file->f_path, MAY_EXEC);
if (!error)
set_fs_pwd(current->fs, &f.file->f_path);
out_putf:
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 3fce836..639d576 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -85,4 +85,27 @@
return 0;
}
#endif /* CONFIG_IMA_APPRAISE */
+
+#ifdef CONFIG_IMA_APPRAISE_DIRECTORIES
+extern int ima_dir_check(struct path *dir, int mask);
+extern int ima_special_check(struct file *file, int mask);
+extern void ima_dir_update(struct path *dir, struct dentry *dentry);
+#else
+static inline int ima_dir_check(struct path *dir, int mask)
+{
+ return 0;
+}
+
+static inline int ima_special_check(struct file *file, int mask)
+{
+ return 0;
+}
+
+static inline void ima_dir_update(struct path *dir, struct dentry *dentry)
+{
+ return;
+}
+
+#endif /* CONFIG_IMA_APPRAISE_DIRECTORIES */
+
#endif /* _LINUX_IMA_H */
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index ef31b40..964d0b6 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -117,6 +117,7 @@
#include <net/checksum.h>
#include <linux/security.h>
#include <linux/freezer.h>
+#include <linux/ima.h>
struct hlist_head unix_socket_table[2 * UNIX_HASH_SIZE];
EXPORT_SYMBOL_GPL(unix_socket_table);
@@ -859,6 +860,7 @@
if (!err) {
res->mnt = mntget(path.mnt);
res->dentry = dget(dentry);
+ ima_dir_update(&path, dentry);
}
}
done_path_create(&path, dentry);
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 8162605..03b1829 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -362,6 +362,11 @@
*/
int ima_file_check(struct file *file, int mask, int opened)
{
+ struct inode *inode = d_backing_inode(file->f_path.dentry);
+
+ if (!S_ISREG(inode->i_mode))
+ return ima_special_check(file, mask);
+
return process_measurement(file,
mask & (MAY_READ | MAY_WRITE | MAY_EXEC),
FILE_CHECK, opened);