summary refs log tree commit diff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2021-02-22 13:05:30 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2021-02-22 13:05:30 -0800
commit250a25e7a1d71da06213aa354ece44fb8faa73f7 (patch)
tree53bce39e79d1501eb275b5ad3ff780f009894db1
parent205f92d7f2106fb248d65d2abce943ed6002598f (diff)
parent23d8f5b684fc30126b7708cad38b753eaa078b3e (diff)
downloadlinux-250a25e7a1d71da06213aa354ece44fb8faa73f7.tar.gz
Merge branch 'work.audit' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
Pull RCU-safe common_lsm_audit() from Al Viro:
 "Make common_lsm_audit() non-blocking and usable from RCU pathwalk
  context.

  We don't really need to grab/drop dentry in there - rcu_read_lock() is
  enough. There's a couple of followups using that to simplify the
  logics in selinux, but those hadn't soaked in -next yet, so they'll
  have to go in next window"

* 'work.audit' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  make dump_common_audit_data() safe to be called from RCU pathwalk
  new helper: d_find_alias_rcu()
-rw-r--r--fs/dcache.c25
-rw-r--r--include/linux/dcache.h2
-rw-r--r--security/lsm_audit.c5
3 files changed, 30 insertions, 2 deletions
diff --git a/fs/dcache.c b/fs/dcache.c
index c17fd15b01d4..799d9e4f0bcd 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1044,6 +1044,31 @@ struct dentry *d_find_alias(struct inode *inode)
 EXPORT_SYMBOL(d_find_alias);
 
 /*
+ *  Caller MUST be holding rcu_read_lock() and be guaranteed
+ *  that inode won't get freed until rcu_read_unlock().
+ */
+struct dentry *d_find_alias_rcu(struct inode *inode)
+{
+	struct hlist_head *l = &inode->i_dentry;
+	struct dentry *de = NULL;
+
+	spin_lock(&inode->i_lock);
+	// ->i_dentry and ->i_rcu are colocated, but the latter won't be
+	// used without having I_FREEING set, which means no aliases left
+	if (likely(!(inode->i_state & I_FREEING) && !hlist_empty(l))) {
+		if (S_ISDIR(inode->i_mode)) {
+			de = hlist_entry(l->first, struct dentry, d_u.d_alias);
+		} else {
+			hlist_for_each_entry(de, l, d_u.d_alias)
+				if (!d_unhashed(de))
+					break;
+		}
+	}
+	spin_unlock(&inode->i_lock);
+	return de;
+}
+
+/*
  *	Try to kill dentries associated with this inode.
  * WARNING: you must own a reference to inode.
  */
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index d7b369fc15d3..c1e48014106f 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -262,6 +262,8 @@ extern void d_tmpfile(struct dentry *, struct inode *);
 extern struct dentry *d_find_alias(struct inode *);
 extern void d_prune_aliases(struct inode *);
 
+extern struct dentry *d_find_alias_rcu(struct inode *);
+
 /* test whether we have any submounts in a subdir tree */
 extern int path_has_submounts(const struct path *);
 
diff --git a/security/lsm_audit.c b/security/lsm_audit.c
index a0cd28cd31a8..82ce14933513 100644
--- a/security/lsm_audit.c
+++ b/security/lsm_audit.c
@@ -291,18 +291,19 @@ static void dump_common_audit_data(struct audit_buffer *ab,
 		struct dentry *dentry;
 		struct inode *inode;
 
+		rcu_read_lock();
 		inode = a->u.inode;
-		dentry = d_find_alias(inode);
+		dentry = d_find_alias_rcu(inode);
 		if (dentry) {
 			audit_log_format(ab, " name=");
 			spin_lock(&dentry->d_lock);
 			audit_log_untrustedstring(ab, dentry->d_name.name);
 			spin_unlock(&dentry->d_lock);
-			dput(dentry);
 		}
 		audit_log_format(ab, " dev=");
 		audit_log_untrustedstring(ab, inode->i_sb->s_id);
 		audit_log_format(ab, " ino=%lu", inode->i_ino);
+		rcu_read_unlock();
 		break;
 	}
 	case LSM_AUDIT_DATA_TASK: {