diff mbox series

[v1] audit: fix suffixed '/' filename matching in __audit_inode_child()

Message ID 20241105123807.1257948-1-rrobaina@redhat.com (mailing list archive)
State Under Review
Delegated to: Paul Moore
Headers show
Series [v1] audit: fix suffixed '/' filename matching in __audit_inode_child() | expand

Commit Message

Ricardo Robaina Nov. 5, 2024, 12:37 p.m. UTC
When the user specifies a directory to delete with the suffix '/',
the audit record fails to collect the filename, resulting in the
following logs:

 type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=2 name=(null)
 type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=1 name=(null)

It happens because the value of the variables dname, and n->name->name
in __audit_inode_child() differ only by the suffix '/'. This commit
treats this corner case by cleaning the input and passing the correct
filename to audit_compare_dname_path().

Steps to reproduce the issue:

 # auditctl -w /tmp
 $ mkdir /tmp/foo
 $ rm -r /tmp/foo/ or rmdir /tmp/foo/
 # ausearch -i | grep PATH | tail -3

This patch is based on a GitHub patch/PR by user @hqh2010.
https://github.com/linux-audit/audit-kernel/pull/148

Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
---
 kernel/auditsc.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

Comments

Paul Moore Nov. 11, 2024, 10:06 p.m. UTC | #1
On Nov  5, 2024 Ricardo Robaina <rrobaina@redhat.com> wrote:
> 
> When the user specifies a directory to delete with the suffix '/',
> the audit record fails to collect the filename, resulting in the
> following logs:
> 
>  type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=2 name=(null)
>  type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=1 name=(null)
> 
> It happens because the value of the variables dname, and n->name->name
> in __audit_inode_child() differ only by the suffix '/'. This commit
> treats this corner case by cleaning the input and passing the correct
> filename to audit_compare_dname_path().
> 
> Steps to reproduce the issue:
> 
>  # auditctl -w /tmp
>  $ mkdir /tmp/foo
>  $ rm -r /tmp/foo/ or rmdir /tmp/foo/
>  # ausearch -i | grep PATH | tail -3
> 
> This patch is based on a GitHub patch/PR by user @hqh2010.
> https://github.com/linux-audit/audit-kernel/pull/148
> 
> Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
> ---
>  kernel/auditsc.c | 18 ++++++++++++++++--
>  1 file changed, 16 insertions(+), 2 deletions(-)
> 
> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> index 6f0d6fb6523f..d4fbac6b71a8 100644
> --- a/kernel/auditsc.c
> +++ b/kernel/auditsc.c
> @@ -2419,7 +2419,8 @@ void __audit_inode_child(struct inode *parent,
>  	struct audit_names *n, *found_parent = NULL, *found_child = NULL;
>  	struct audit_entry *e;
>  	struct list_head *list = &audit_filter_list[AUDIT_FILTER_FS];
> -	int i;
> +	int i, dlen, nlen;
> +	char *fn = NULL;
>  
>  	if (context->context == AUDIT_CTX_UNUSED)
>  		return;
> @@ -2443,6 +2444,7 @@ void __audit_inode_child(struct inode *parent,
>  	if (inode)
>  		handle_one(inode);
>  
> +	dlen = strlen(dname->name);
>  	/* look for a parent entry first */
>  	list_for_each_entry(n, &context->names_list, list) {
>  		if (!n->name ||
> @@ -2450,15 +2452,27 @@ void __audit_inode_child(struct inode *parent,
>  		     n->type != AUDIT_TYPE_UNKNOWN))
>  			continue;
>  
> +		/* special case, entry name has the sufix "/" */

/sufix/suffix/

> +		nlen = strlen(n->name->name);
> +		if (dname->name[dlen - 1] != '/' && n->name->name[nlen - 1] == '/') {

I'm guessing @dname is never going to have a trailing slash so we don't
care about @n missing the trailing slash?

> +			fn = kmalloc(PATH_MAX, GFP_KERNEL);
> +			if (!fn) {
> +				audit_panic("out of memory in __audit_inode_child()");
> +				return;
> +			}
> +			strscpy(fn, n->name->name, nlen);
> +		}

I'm looking at the extra work involved above with the alloc/copy and I'm
wondering if we can't solve this a bit more generically (I suspect all
the audit_compare_dname_path() callers may have similar issues) and with
out the additional alloc/copy.

This is completely untested, I didn't even compile it, but what about
something like the following?  We do add an extra strlen(), but that is
going to be faster than the alloc/copy.

diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
index 470041c49a44..c30c2ee9fb77 100644
--- a/kernel/auditfilter.c
+++ b/kernel/auditfilter.c
@@ -1320,10 +1320,13 @@ int audit_compare_dname_path(const struct qstr *dname, const char *path, int par
                return 1;
 
        parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
-       if (pathlen - parentlen != dlen)
-               return 1;
-
        p = path + parentlen;
+       pathlen = strlen(p);
+       if (p[pathlen - 1] == '/')
+               pathlen--;
+
+       if (pathlen != dlen)
+               return 1;
 
        return strncmp(p, dname->name, dlen);
 }

>  		if (n->ino == parent->i_ino && n->dev == parent->i_sb->s_dev &&
>  		    !audit_compare_dname_path(dname,
> -					      n->name->name, n->name_len)) {
> +					      fn ? fn : n->name->name, n->name_len)) {
>  			if (n->type == AUDIT_TYPE_UNKNOWN)
>  				n->type = AUDIT_TYPE_PARENT;
>  			found_parent = n;
>  			break;
>  		}
>  	}
> +	kfree(fn);
>  
>  	cond_resched();
>  
> -- 
> 2.47.0

--
paul-moore.com
Richard Guy Briggs Nov. 12, 2024, 10:06 p.m. UTC | #2
On 2024-11-11 17:06, Paul Moore wrote:
> On Nov  5, 2024 Ricardo Robaina <rrobaina@redhat.com> wrote:
> > 
> > When the user specifies a directory to delete with the suffix '/',
> > the audit record fails to collect the filename, resulting in the
> > following logs:
> > 
> >  type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=2 name=(null)
> >  type=PATH msg=audit(10/30/2024 14:11:17.796:6304) : item=1 name=(null)
> > 
> > It happens because the value of the variables dname, and n->name->name
> > in __audit_inode_child() differ only by the suffix '/'. This commit
> > treats this corner case by cleaning the input and passing the correct
> > filename to audit_compare_dname_path().
> > 
> > Steps to reproduce the issue:
> > 
> >  # auditctl -w /tmp
> >  $ mkdir /tmp/foo
> >  $ rm -r /tmp/foo/ or rmdir /tmp/foo/
> >  # ausearch -i | grep PATH | tail -3
> > 
> > This patch is based on a GitHub patch/PR by user @hqh2010.
> > https://github.com/linux-audit/audit-kernel/pull/148
> > 
> > Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
> > ---
> >  kernel/auditsc.c | 18 ++++++++++++++++--
> >  1 file changed, 16 insertions(+), 2 deletions(-)
> > 
> > diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> > index 6f0d6fb6523f..d4fbac6b71a8 100644
> > --- a/kernel/auditsc.c
> > +++ b/kernel/auditsc.c
> > @@ -2419,7 +2419,8 @@ void __audit_inode_child(struct inode *parent,
> >  	struct audit_names *n, *found_parent = NULL, *found_child = NULL;
> >  	struct audit_entry *e;
> >  	struct list_head *list = &audit_filter_list[AUDIT_FILTER_FS];
> > -	int i;
> > +	int i, dlen, nlen;
> > +	char *fn = NULL;
> >  
> >  	if (context->context == AUDIT_CTX_UNUSED)
> >  		return;
> > @@ -2443,6 +2444,7 @@ void __audit_inode_child(struct inode *parent,
> >  	if (inode)
> >  		handle_one(inode);
> >  
> > +	dlen = strlen(dname->name);
> >  	/* look for a parent entry first */
> >  	list_for_each_entry(n, &context->names_list, list) {
> >  		if (!n->name ||
> > @@ -2450,15 +2452,27 @@ void __audit_inode_child(struct inode *parent,
> >  		     n->type != AUDIT_TYPE_UNKNOWN))
> >  			continue;
> >  
> > +		/* special case, entry name has the sufix "/" */
> 
> /sufix/suffix/
> 
> > +		nlen = strlen(n->name->name);
> > +		if (dname->name[dlen - 1] != '/' && n->name->name[nlen - 1] == '/') {
> 
> I'm guessing @dname is never going to have a trailing slash so we don't
> care about @n missing the trailing slash?
> 
> > +			fn = kmalloc(PATH_MAX, GFP_KERNEL);
> > +			if (!fn) {
> > +				audit_panic("out of memory in __audit_inode_child()");
> > +				return;
> > +			}
> > +			strscpy(fn, n->name->name, nlen);
> > +		}
> 
> I'm looking at the extra work involved above with the alloc/copy and I'm
> wondering if we can't solve this a bit more generically (I suspect all
> the audit_compare_dname_path() callers may have similar issues) and with
> out the additional alloc/copy.

I had similar concerns about the alloc/copy and using a fixed length
compare but hadn't thought of generalizing it.

> This is completely untested, I didn't even compile it, but what about
> something like the following?  We do add an extra strlen(), but that is
> going to be faster than the alloc/copy.

I agree this is a better approach.

> diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
> index 470041c49a44..c30c2ee9fb77 100644
> --- a/kernel/auditfilter.c
> +++ b/kernel/auditfilter.c
> @@ -1320,10 +1320,13 @@ int audit_compare_dname_path(const struct qstr *dname, const char *path, int par
>                 return 1;
>  
>         parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
> -       if (pathlen - parentlen != dlen)
> -               return 1;
> -
>         p = path + parentlen;
> +       pathlen = strlen(p);
> +       if (p[pathlen - 1] == '/')
> +               pathlen--;
> +
> +       if (pathlen != dlen)
> +               return 1;
>  
>         return strncmp(p, dname->name, dlen);
>  }
> 
> >  		if (n->ino == parent->i_ino && n->dev == parent->i_sb->s_dev &&
> >  		    !audit_compare_dname_path(dname,
> > -					      n->name->name, n->name_len)) {
> > +					      fn ? fn : n->name->name, n->name_len)) {
> >  			if (n->type == AUDIT_TYPE_UNKNOWN)
> >  				n->type = AUDIT_TYPE_PARENT;
> >  			found_parent = n;
> >  			break;
> >  		}
> >  	}
> > +	kfree(fn);
> >  
> >  	cond_resched();
> >  
> > -- 
> > 2.47.0
> 
> --
> paul-moore.com

- RGB

--
Richard Guy Briggs <rgb@redhat.com>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
Upstream IRC: SunRaycer
Voice: +1.613.860 2354 SMS: +1.613.518.6570
Al Viro Nov. 13, 2024, 11:04 p.m. UTC | #3
On Mon, Nov 11, 2024 at 05:06:43PM -0500, Paul Moore wrote:

> This is completely untested, I didn't even compile it, but what about
> something like the following?  We do add an extra strlen(), but that is
> going to be faster than the alloc/copy.
> 
> diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
> index 470041c49a44..c30c2ee9fb77 100644
> --- a/kernel/auditfilter.c
> +++ b/kernel/auditfilter.c
> @@ -1320,10 +1320,13 @@ int audit_compare_dname_path(const struct qstr *dname, const char *path, int par
>                 return 1;
>  
>         parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
> -       if (pathlen - parentlen != dlen)
> -               return 1;
> -
>         p = path + parentlen;
> +       pathlen = strlen(p);

Huh?  We have
        pathlen = strlen(path);
several lines prior to this.  So unless parentlen + path manages to exceed
strlen(path) (in which case your strlen() is really asking for trouble),
this is simply
	pathlen -= parentlen;

What am I missing here?  And while we are at it,
	parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
is a bloody awful way to spell
	if (parentlen == AUDIT_NAME_FULL)
		parentlen = parent_len(path);
What's more, parent_len(path) starts with *yet* *another* strlen(path),
followed by really awful crap - we trim the trailing slashes (if any),
then search for the last slash before that...  is that really worth
the chance to skip that strncmp()?


> +       if (p[pathlen - 1] == '/')
> +               pathlen--;
> +
> +       if (pathlen != dlen)
> +               return 1;
>  
>         return strncmp(p, dname->name, dlen);

... which really should've been memcmp(), at that.
Paul Moore Nov. 14, 2024, 3:23 a.m. UTC | #4
On November 13, 2024 6:04:27 PM Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Mon, Nov 11, 2024 at 05:06:43PM -0500, Paul Moore wrote:
>
>> This is completely untested, I didn't even compile it, but what about
>> something like the following?  We do add an extra strlen(), but that is
>> going to be faster than the alloc/copy.
>>
>> diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c
>> index 470041c49a44..c30c2ee9fb77 100644
>> --- a/kernel/auditfilter.c
>> +++ b/kernel/auditfilter.c
>> @@ -1320,10 +1320,13 @@ int audit_compare_dname_path(const struct qstr 
>> *dname, const char *path, int par
>>        return 1;
>>
>> parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
>> -       if (pathlen - parentlen != dlen)
>> -               return 1;
>> -
>> p = path + parentlen;
>> +       pathlen = strlen(p);
>
> Huh?  We have
>        pathlen = strlen(path);
> several lines prior to this.  So unless parentlen + path manages to exceed
> strlen(path) (in which case your strlen() is really asking for trouble),
> this is simply
> pathlen -= parentlen;
>
> What am I missing here?

[NOTE: network access is patchy right now so you're getting a phone reply 
without an opportunity to look more closely at the code]

To be fair, this was a quick example of "do something like this" to 
demonstrate a different idea than was proposed in the original patch.  The 
bit of code I shared was not a fully baked patch; I thought that would have 
been clear from the context, if not my comments.

Of course improvements are welcome, but in the future know that unless I'm 
submitting a proper patch, the code snippets I share during review are 
going to be *rough* and not fully developed.  Additional work by the 
original author is expected.

> And while we are at it,
> parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
> is a bloody awful way to spell
> if (parentlen == AUDIT_NAME_FULL)
>  parentlen = parent_len(path);
> What's more, parent_len(path) starts with *yet* *another* strlen(path),
> followed by really awful crap - we trim the trailing slashes (if any),
> then search for the last slash before that...  is that really worth
> the chance to skip that strncmp()?

Pretty much all of the audit code is awkward at best Al, you should know 
that. We're not going to fix it all in one patch, and considering the 
nature of this patch effort, I think there is going to be a lot of value in 
keeping the initial fix patch to a minimum to ease backporting.  We can 
improve on some of those other issues in a second patch or at a later time.

As a reminder to everyone, patches are always welcome.  Fixing things is a 
great way to channel disgust into something much more useful.

>
>> +       if (p[pathlen - 1] == '/')
>> +               pathlen--;
>> +
>> +       if (pathlen != dlen)
>> +               return 1;
>>
>> return strncmp(p, dname->name, dlen);
>
> ... which really should've been memcmp(), at that.

Agreed. See above.

--
paul-moore.com
Al Viro Nov. 14, 2024, 4:09 a.m. UTC | #5
On Wed, Nov 13, 2024 at 10:23:55PM -0500, Paul Moore wrote:

> > And while we are at it,
> > parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
> > is a bloody awful way to spell
> > if (parentlen == AUDIT_NAME_FULL)
> >  parentlen = parent_len(path);
> > What's more, parent_len(path) starts with *yet* *another* strlen(path),
> > followed by really awful crap - we trim the trailing slashes (if any),
> > then search for the last slash before that...  is that really worth
> > the chance to skip that strncmp()?
> 
> Pretty much all of the audit code is awkward at best Al, you should know
> that.

Do I ever...

> We're not going to fix it all in one patch, and considering the nature
> of this patch effort, I think there is going to be a lot of value in keeping
> the initial fix patch to a minimum to ease backporting.  We can improve on
> some of those other issues in a second patch or at a later time.
> 
> As a reminder to everyone, patches are always welcome.  Fixing things is a
> great way to channel disgust into something much more useful.

> > 
> > > +       if (p[pathlen - 1] == '/')
> > > +               pathlen--;
> > > +
> > > +       if (pathlen != dlen)
> > > +               return 1;
> > > 
> > > return strncmp(p, dname->name, dlen);
> > 
> > ... which really should've been memcmp(), at that.
> 
> Agreed. See above.

OK, my primary interest here is to separate struct filename from that stuff
as much as possible.  So we will end up stomping on the same ground for
a while here.  FWIW, my current filename-related pile is in #next.filename;
there will be a lot more on the VFS side, but one of the obvious targets is
->aname, so __audit_inode() and its vicinity will get affected.  We'll need
to coordinate that stuff.

Anyway, regarding audit_compare_dname_path(), handling just the last '/' is
not enough - there might be any number of trailing slashes, not just one.

Another fun issue with looking for matches is this:

mkdir /tmp/foo
mkdir /tmp/foo/bar
mkdir /tmp/blah
ln -s ../foo/bar/baz /tmp/blah/barf
echo crap > /tmp/blah/barf

The last one will create a regular file "baz" in /tmp/foo/bar and write
"crap\n" into it.  With the only pathname passed to open(2) being
"/tmp/blah/barf".  And there may be a longer chain of symlinks like that.

What do you want to see reported in such case?
Paul Moore Nov. 27, 2024, 5:42 a.m. UTC | #6
On Wed, Nov 13, 2024 at 11:09 PM Al Viro <viro@zeniv.linux.org.uk> wrote:
> On Wed, Nov 13, 2024 at 10:23:55PM -0500, Paul Moore wrote:
> > > And while we are at it,
> > > parentlen = parentlen == AUDIT_NAME_FULL ? parent_len(path) : parentlen;
> > > is a bloody awful way to spell
> > > if (parentlen == AUDIT_NAME_FULL)
> > >  parentlen = parent_len(path);
> > > What's more, parent_len(path) starts with *yet* *another* strlen(path),
> > > followed by really awful crap - we trim the trailing slashes (if any),
> > > then search for the last slash before that...  is that really worth
> > > the chance to skip that strncmp()?
> >
> > Pretty much all of the audit code is awkward at best Al, you should know
> > that.
>
> Do I ever...
>
> > We're not going to fix it all in one patch, and considering the nature
> > of this patch effort, I think there is going to be a lot of value in keeping
> > the initial fix patch to a minimum to ease backporting.  We can improve on
> > some of those other issues in a second patch or at a later time.
> >
> > As a reminder to everyone, patches are always welcome.  Fixing things is a
> > great way to channel disgust into something much more useful.
>
> > >
> > > > +       if (p[pathlen - 1] == '/')
> > > > +               pathlen--;
> > > > +
> > > > +       if (pathlen != dlen)
> > > > +               return 1;
> > > >
> > > > return strncmp(p, dname->name, dlen);
> > >
> > > ... which really should've been memcmp(), at that.
> >
> > Agreed. See above.
>
> OK, my primary interest here is to separate struct filename from that stuff
> as much as possible.  So we will end up stomping on the same ground for
> a while here.  FWIW, my current filename-related pile is in #next.filename;
> there will be a lot more on the VFS side, but one of the obvious targets is
> ->aname, so __audit_inode() and its vicinity will get affected.  We'll need
> to coordinate that stuff.

[Sorry for the delay, it took me a bit longer than expected to get
through my inbox upon returning home, but I should be mostly caught up
now.]

We've talked a bit in other threads about the struct filename changes
you're working on, and while I haven't looked at your next.filename
branch, the stuff you were talking about doing made sense.  As far as
cross-tree conflicts go, I don't think we'll have too many problems
there as I don't foresee any major audit work happening in the
immediate future; sure we'll likely have a small number of fixes but
those should be easy enough to manage with your changes.  I'm assuming
your next.filename finds its way into linux-next?  If so, that should
give us a heads-up regarding conflicts.  If there is a nasty
collision, I'm confident we can figure something out.

> Anyway, regarding audit_compare_dname_path(), handling just the last '/' is
> not enough - there might be any number of trailing slashes, not just one.

Good reminder, thanks.  I see that Ricardo has sent an updated patch,
I haven't looked at it yet (waiting for the merge window to close),
but hopefully he has taken that into account (hint: now would be a
good time to verify that Ricardo <g>).

> Another fun issue with looking for matches is this:
>
> mkdir /tmp/foo
> mkdir /tmp/foo/bar
> mkdir /tmp/blah
> ln -s ../foo/bar/baz /tmp/blah/barf
> echo crap > /tmp/blah/barf
>
> The last one will create a regular file "baz" in /tmp/foo/bar and write
> "crap\n" into it.  With the only pathname passed to open(2) being
> "/tmp/blah/barf".  And there may be a longer chain of symlinks like that.
>
> What do you want to see reported in such case?

In the absence of anyone pushing third party requirements on the audit
subsystem to meet various security certification goals, my guiding
light has been to try and do the simplest thing that makes sense.
Looking at this quickly, there are three different types of events:
mkdir, symlink, and a file lookup/write.

The mkdir events in the example above are trivial, you log the usual
info when walking the pathname and call it good.

The symlink is mostly the same as mkdir, but you've got the relative
path to contend with so the current working directory is more
important and should be logged as well.

The file lookup/write can be a bit more complicated if we have all of
the symlink information as part of the pathname lookup/walk, but if we
don't have that I think we can manage with just the "/tmp/blah/barf".
Users/admins that really care about links will likely be logging the
various link*/symlink*/unlink* syscalls for the directories/files they
care about.

Of course if anyone has any requirements or reasoned opinions about
what we should log here, I'd love to hear it.  This includes you Al.
diff mbox series

Patch

diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index 6f0d6fb6523f..d4fbac6b71a8 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -2419,7 +2419,8 @@  void __audit_inode_child(struct inode *parent,
 	struct audit_names *n, *found_parent = NULL, *found_child = NULL;
 	struct audit_entry *e;
 	struct list_head *list = &audit_filter_list[AUDIT_FILTER_FS];
-	int i;
+	int i, dlen, nlen;
+	char *fn = NULL;
 
 	if (context->context == AUDIT_CTX_UNUSED)
 		return;
@@ -2443,6 +2444,7 @@  void __audit_inode_child(struct inode *parent,
 	if (inode)
 		handle_one(inode);
 
+	dlen = strlen(dname->name);
 	/* look for a parent entry first */
 	list_for_each_entry(n, &context->names_list, list) {
 		if (!n->name ||
@@ -2450,15 +2452,27 @@  void __audit_inode_child(struct inode *parent,
 		     n->type != AUDIT_TYPE_UNKNOWN))
 			continue;
 
+		/* special case, entry name has the sufix "/" */
+		nlen = strlen(n->name->name);
+		if (dname->name[dlen - 1] != '/' && n->name->name[nlen - 1] == '/') {
+			fn = kmalloc(PATH_MAX, GFP_KERNEL);
+			if (!fn) {
+				audit_panic("out of memory in __audit_inode_child()");
+				return;
+			}
+			strscpy(fn, n->name->name, nlen);
+		}
+
 		if (n->ino == parent->i_ino && n->dev == parent->i_sb->s_dev &&
 		    !audit_compare_dname_path(dname,
-					      n->name->name, n->name_len)) {
+					      fn ? fn : n->name->name, n->name_len)) {
 			if (n->type == AUDIT_TYPE_UNKNOWN)
 				n->type = AUDIT_TYPE_PARENT;
 			found_parent = n;
 			break;
 		}
 	}
+	kfree(fn);
 
 	cond_resched();