[PATCH 2/2] audit: Call path_{put,get}() in audit_alloc_name()/audit_free_names() only when necessary

Waiman Long
2026-01-21T02:35:45Z

It is found that on large SMP systems with a large number of CPUs and
auditing enabled, workloads that generate a massive amount of syscalls
(like open/close) in parallel on the same working directory can cause
significant spinlock contention of the lockref.lock of the working
directory's dentry.

One possible way to reduce such spinlock contention scenario is to
keep the pwd references in audit_free_names() and reuse it in the next
audit_alloc_name() call if there is no change in working directory.

A new pwd_reset field is added to audit_context to indicate, if set,
that the pwd has been reset but still hold mount and dentry references.
Another get_cond_fs_pwd() helper is added to fs_struct.h to conditionally
put the old references back and get the new ones if fs->pwd has been
changed.

By adding test code to count the number of effective audit_free_names()
and audit_alloc_name() calls and the number of relevant path_put() and
path_get() calls after rebooting a patched kernel with auditing enabled
on a 2-socket 96-CPU system, there were about 30k path_get()/path_put()
out of a total of about 1 million audit_free_names()/audit_alloc_name()
calls. It is about 3% of those before the patch for this particular case.

After resetting the counters and running a parallel kernel build,
the new figures were about 202k path_get()/path_put() out of about 56M
audit_free_names()/audit_alloc_name(). That is about 0.4%.

As auditing is increasingly used in production systems due to various
legal and commercial compliance requirements, it is important that we
should try to minimize performance overhead when auditing is enabled.

Signed-off-by: Waiman Long <longman@redhat.com>
---
include/linux/fs_struct.h | 14 ++++++++++++++
kernel/audit.h | 7 +++++++
kernel/auditsc.c | 17 +++++++++++------
3 files changed, 32 insertions(+), 6 deletions(-)

diff --git a/include/linux/fs_struct.h b/include/linux/fs_struct.h
index 0070764b790a..f173b49ad47e 100644
--- a/include/linux/fs_struct.h
+++ b/include/linux/fs_struct.h
@@ -40,6 +40,20 @@ static inline void get_fs_pwd(struct fs_struct *fs, struct path *pwd)
read_sequnlock_excl(&fs->seq);
}

+/*
+ * Conditionally get the current fs->pwd if it differs from the given pwd
+ */
+static inline void get_cond_fs_pwd(struct fs_struct *fs, struct path *pwd)
+{
+ read_seqlock_excl(&fs->seq);
+ if ((fs->pwd.dentry != pwd->dentry) || (fs->pwd.mnt != pwd->mnt)) {
+ path_put(pwd);
+ *pwd = fs->pwd;
+ path_get(pwd);
+ }
+ read_sequnlock_excl(&fs->seq);
+}
+
extern bool current_chrooted(void);

static inline int current_umask(void)
diff --git a/kernel/audit.h b/kernel/audit.h
index 7c401729e21b..03f3539b10e7 100644
--- a/kernel/audit.h
+++ b/kernel/audit.h
@@ -133,6 +133,13 @@ struct audit_context {
int name_count; /* total records in names_list */
struct list_head names_list; /* struct audit_names->list anchor */
char *filterkey; /* key for rule that triggered record */
+ /*
+ * pwd_reset is set if audit_free_names() has been called from
+ * audit_reset_context() to reset pwd, but pwd is still holding dentry
+ * and mount references to be used in later audit action without
+ * the need to reacqure the references again.
+ */
+ int pwd_reset;
struct path pwd;
struct audit_aux_data *aux;
struct audit_aux_data *aux_pids;
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 824b6fd98561..30d931d9b9a4 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -942,9 +942,7 @@ static inline void audit_free_names(struct audit_context *context)
kfree(n);
}
context->name_count = 0;
- path_put(&context->pwd);
- context->pwd.dentry = NULL;
- context->pwd.mnt = NULL;
+ context->pwd_reset = true;
}

static inline void audit_free_aux(struct audit_context *context)
@@ -1091,6 +1089,8 @@ static inline void audit_free_context(struct audit_context *context)
audit_reset_context(context);
audit_proctitle_free(context);
free_tree_refs(context);
+ if (context->pwd_reset)
+ path_put(&context->pwd);
kfree(context->filterkey);
kfree(context);
}
@@ -1522,7 +1522,8 @@ static void audit_log_name(struct audit_context *context, struct audit_names *n,
/* name was specified as a relative path and the
* directory component is the cwd
*/
- if (context->pwd.dentry && context->pwd.mnt)
+ if (context->pwd.dentry && context->pwd.mnt &&
+ !context->pwd_reset)
audit_log_d_path(ab, " name=", &context->pwd);
else
audit_log_format(ab, " name=(null)");
@@ -1770,7 +1771,7 @@ static void audit_log_exit(void)
context->target_comm))
call_panic = 1;

- if (context->pwd.dentry && context->pwd.mnt) {
+ if (context->pwd.dentry && context->pwd.mnt && !context->pwd_reset) {
ab = audit_log_start(context, GFP_KERNEL, AUDIT_CWD);
if (ab) {
audit_log_d_path(ab, "cwd=", &context->pwd);
@@ -2167,8 +2168,12 @@ static struct audit_names *audit_alloc_name(struct audit_context *context,
list_add_tail(&aname->list, &context->names_list);

context->name_count++;
- if (!context->pwd.dentry)
+ if (context->pwd_reset) {
+ get_cond_fs_pwd(current->fs, &context->pwd);
+ context->pwd_reset = false;
+ } else if (!context->pwd.dentry) {
get_fs_pwd(current->fs, &context->pwd);
+ }
return aname;
}

--
2.52.0