ima: special files integrity verification implementation

This patch implements special files integrity verification hooks.
It maintains a hashes of symbolic links, device nodes' major and
minor numbers.

Signed-off-by: Dmitry Kasatkin <d.kasatkin@samsung.com>
diff --git a/fs/namei.c b/fs/namei.c
index d0d858b..f3675e2 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2958,7 +2958,7 @@
 	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);
+		ima_dir_update(&nd->path, NULL, NULL);
 	mutex_unlock(&dir->d_inode->i_mutex);
 
 	if (error <= 0) {
@@ -3460,7 +3460,7 @@
 			break;
 	}
 	if (!error)
-		ima_dir_update(&path, dentry);
+		ima_dir_update(&path, dentry, NULL);
 out:
 	done_path_create(&path, dentry);
 	if (retry_estale(error, lookup_flags)) {
@@ -3519,7 +3519,7 @@
 	if (!error)
 		error = vfs_mkdir(path.dentry->d_inode, dentry, mode);
 	if (!error)
-		ima_dir_update(&path, dentry);
+		ima_dir_update(&path, dentry, NULL);
 	done_path_create(&path, dentry);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -3639,7 +3639,7 @@
 		goto exit3;
 	error = vfs_rmdir(nd.path.dentry->d_inode, dentry);
 	if (!error)
-		ima_dir_update(&nd.path, NULL);
+		ima_dir_update(&nd.path, NULL, NULL);
 exit3:
 	dput(dentry);
 exit2:
@@ -3761,7 +3761,7 @@
 			goto exit2;
 		error = vfs_unlink(nd.path.dentry->d_inode, dentry, &delegated_inode);
 		if (!error)
-			ima_dir_update(&nd.path, NULL);
+			ima_dir_update(&nd.path, NULL, NULL);
 exit2:
 		dput(dentry);
 	}
@@ -3854,7 +3854,7 @@
 	if (!error)
 		error = vfs_symlink(path.dentry->d_inode, dentry, from->name);
 	if (!error)
-		ima_dir_update(&path, dentry);
+		ima_dir_update(&path, dentry, from->name);
 	done_path_create(&path, dentry);
 	if (retry_estale(error, lookup_flags)) {
 		lookup_flags |= LOOKUP_REVAL;
@@ -3998,7 +3998,7 @@
 		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);
+		ima_dir_update(&new_path, NULL, NULL);
 out_dput:
 	done_path_create(&new_path, new_dentry);
 	if (delegated_inode) {
@@ -4317,9 +4317,9 @@
 			   new_dir->d_inode, new_dentry,
 			   &delegated_inode, flags);
 	if (!error) {
-		ima_dir_update(&oldnd.path, NULL);
+		ima_dir_update(&oldnd.path, NULL, NULL);
 		if (!path_equal(&oldnd.path, &newnd.path))
-			ima_dir_update(&newnd.path, NULL);
+			ima_dir_update(&newnd.path, NULL, NULL);
 	}
 exit5:
 	dput(new_dentry);
diff --git a/include/linux/ima.h b/include/linux/ima.h
index 704d5ef..a980e0e 100644
--- a/include/linux/ima.h
+++ b/include/linux/ima.h
@@ -83,7 +83,8 @@
 #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);
+extern void ima_dir_update(struct path *dir, struct dentry *dentry,
+			   const char *link);
 #else
 static inline int ima_dir_check(struct path *dir, int mask)
 {
@@ -95,7 +96,8 @@
 	return 0;
 }
 
-static inline void ima_dir_update(struct path *dir, struct dentry *dentry)
+static inline void ima_dir_update(struct path *dir, struct dentry *dentry,
+				  const char *link)
 {
 	return;
 }
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index ce84666..18c6412 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -849,7 +849,7 @@
 		if (!err) {
 			res->mnt = mntget(path.mnt);
 			res->dentry = dget(dentry);
-			ima_dir_update(&path, dentry);
+			ima_dir_update(&path, dentry, NULL);
 		}
 	}
 	done_path_create(&path, dentry);
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 38e243d..3af74e1 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -155,7 +155,7 @@
 
 /* IMA policy related functions */
 enum ima_hooks { FILE_CHECK = 1, MMAP_CHECK, BPRM_CHECK, MODULE_CHECK,
-		 FIRMWARE_CHECK, DIR_CHECK, POST_SETATTR};
+		 FIRMWARE_CHECK, DIR_CHECK, SPECIAL_CHECK, POST_SETATTR};
 
 int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask,
 		     int flags);
diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c
index a99eb6d..250c3c0 100644
--- a/security/integrity/ima/ima_api.c
+++ b/security/integrity/ima/ima_api.c
@@ -325,6 +325,9 @@
 {
 	char *pathname = NULL;
 
+	if (!path->mnt)
+		return NULL;
+
 	*pathbuf = __getname();
 	if (*pathbuf) {
 		pathname = d_absolute_path(path, *pathbuf, PATH_MAX);
diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
index a86332d..3857686 100644
--- a/security/integrity/ima/ima_appraise.c
+++ b/security/integrity/ima/ima_appraise.c
@@ -202,7 +202,7 @@
 
 		cause = "missing-hash";
 		status = INTEGRITY_NOLABEL;
-		if (opened & FILE_CREATED) {
+		if (S_ISREG(inode->i_mode) && (opened & FILE_CREATED)) {
 			iint->flags |= IMA_NEW_FILE;
 			status = INTEGRITY_PASS;
 		}
diff --git a/security/integrity/ima/ima_dir.c b/security/integrity/ima/ima_dir.c
index c3f9c1d..516bbd7 100644
--- a/security/integrity/ima/ima_dir.c
+++ b/security/integrity/ima/ima_dir.c
@@ -131,11 +131,12 @@
 static int ima_dir_collect(struct integrity_iint_cache *iint,
 			  struct path *path, struct file *file,
 			  struct evm_ima_xattr_data **xattr_value,
-			  int *xattr_len)
+			  int *xattr_len, const char *link)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	int rc = -EINVAL;
+	u32 dev;
 	struct {
 		struct ima_digest_data hdr;
 		char digest[IMA_MAX_DIGEST_SIZE];
@@ -157,6 +158,14 @@
 	case S_IFDIR:
 		rc = ima_calc_dir_hash(path, file, &hash.hdr);
 		break;
+	case S_IFIFO: case S_IFSOCK:
+	case S_IFCHR: case S_IFBLK:
+		dev = new_encode_dev(inode->i_rdev);
+		rc = ima_calc_buffer_hash(&dev, sizeof(dev), &hash.hdr);
+		break;
+	case S_IFLNK:
+		rc = ima_calc_buffer_hash(link, strlen(link), &hash.hdr);
+		break;
 	default:
 		pr_debug("UKNOWN: dentry: %s, 0%o\n",
 			 dentry->d_name.name, inode->i_mode & S_IFMT);
@@ -182,14 +191,16 @@
 	return rc;
 }
 
-static int dir_measurement(struct path *path, struct file *file, int mask)
+static int dir_measurement(struct path *path, struct file *file, int mask,
+			   const char *link)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = dentry->d_inode;
 	struct integrity_iint_cache *iint;
 	char *pathbuf = NULL;
 	const char *pathname;
-	int rc = 0, action, xattr_len = 0, func = DIR_CHECK;
+	int rc = 0, action, xattr_len = 0;
+	int func = S_ISDIR(inode->i_mode) ? DIR_CHECK : SPECIAL_CHECK;
 	struct evm_ima_xattr_data *xattr_value = NULL;
 
 	if (!ima_dir_enabled || !ima_initialized)
@@ -211,9 +222,10 @@
 
 		mutex_lock(&inode->i_mutex);
 	} else {
+		int func = S_ISDIR(inode->i_mode) ? DIR_CHECK : SPECIAL_CHECK;
 		/* Determine if in appraise/measurement policy,
 		* returns IMA_MEASURE, IMA_APPRAISE bitmask.  */
-		action = ima_must_appraise(inode, mask, DIR_CHECK);
+		action = ima_must_appraise(inode, mask, func);
 		if (!action)
 			return 0;
 
@@ -240,7 +252,7 @@
 	if (!action)
 		goto out_locked;
 
-	rc = ima_dir_collect(iint, path, file, &xattr_value, &xattr_len);
+	rc = ima_dir_collect(iint, path, file, &xattr_value, &xattr_len, link);
 	if (rc)
 		goto out_locked;
 
@@ -267,19 +279,17 @@
 {
 	BUG_ON(!S_ISDIR(dir->dentry->d_inode->i_mode));
 
-	return dir_measurement(dir, NULL, mask);
+	return dir_measurement(dir, NULL, mask, NULL);
 }
 EXPORT_SYMBOL_GPL(ima_dir_check);
 
 int ima_special_check(struct file *file, int mask)
 {
-	if (!S_ISDIR(file->f_dentry->d_inode->i_mode))
-		return 0;
-	return dir_measurement(&file->f_path, file, mask);
+	return dir_measurement(&file->f_path, file, mask, NULL);
 }
 
 static void ima_dir_update_xattr(struct integrity_iint_cache *iint,
-				 struct path *path)
+				 struct path *path, const char *link)
 {
 	struct dentry *dentry = path->dentry;
 	struct inode *inode = NULL;
@@ -287,12 +297,13 @@
 
 	if (!iint) {
 		/* if iint is NULL, then we allocated iint for new directory */
-		int action;
+		int action, func;
 
 		inode = dentry->d_inode;
+		func = S_ISDIR(inode->i_mode) ? DIR_CHECK : SPECIAL_CHECK;
 
 		/* Determine if in appraise/measurement policy */
-		action = ima_must_appraise(inode, MAY_READ, DIR_CHECK);
+		action = ima_must_appraise(inode, MAY_READ, func);
 		if (!action)
 			return;
 
@@ -307,7 +318,7 @@
 		iint->ima_file_status = INTEGRITY_PASS;
 	}
 
-	rc = ima_dir_collect(iint, path, NULL, NULL, NULL);
+	rc = ima_dir_collect(iint, path, NULL, NULL, NULL, link);
 	if (!rc)
 		ima_fix_xattr(dentry, iint);
 out:
@@ -324,7 +335,7 @@
  * and is used to re-calculate and update integrity data.
  * It is called with dir i_mutex locked.
  */
-void ima_dir_update(struct path *dir, struct dentry *dentry)
+void ima_dir_update(struct path *dir, struct dentry *dentry, const char *link)
 {
 	struct inode *inode = dir->dentry->d_inode;
 	struct integrity_iint_cache *iint;
@@ -345,7 +356,7 @@
 		/* new entry -> set initial security.ima value */
 		struct path path = { .mnt = dir->mnt, .dentry = dentry };
 		BUG_ON(!dentry->d_inode);
-		ima_dir_update_xattr(NULL, &path);
+		ima_dir_update_xattr(NULL, &path, link);
 	}
 
 	/* do not reset flags for directories, correct ?
@@ -353,6 +364,12 @@
 	*/
 	iint->flags &= ~IMA_COLLECTED;
 	if (iint->flags & IMA_APPRAISE)
-		ima_dir_update_xattr(iint, dir);
+		ima_dir_update_xattr(iint, dir, NULL);
 }
 EXPORT_SYMBOL_GPL(ima_dir_update);
+
+int ima_link_check(struct dentry *dentry, const char *link)
+{
+	struct path path = { .mnt = NULL, .dentry = dentry };
+	return dir_measurement(&path, NULL, MAY_READ, link);
+}
diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c
index 296f4de..f36cf35 100644
--- a/security/integrity/ima/ima_policy.c
+++ b/security/integrity/ima/ima_policy.c
@@ -521,6 +521,8 @@
 				entry->func = BPRM_CHECK;
 			else if (strcmp(args[0].from, "DIR_CHECK") == 0)
 				entry->func = DIR_CHECK;
+			else if (strcmp(args[0].from, "SPECIAL_CHECK") == 0)
+				entry->func = SPECIAL_CHECK;
 			else
 				result = -EINVAL;
 			if (!result)