[lvc-project] [PATCH v2] namespace: Added pointer check in copy_mnt_ns()

Al Viro viro at zeniv.linux.org.uk
Sat Nov 19 10:12:39 MSK 2022


On Fri, Nov 18, 2022 at 02:41:37PM +0300, Denis Arefev wrote:
> Return value of a function 'next_mnt' is dereferenced at
> namespace.c:3377 without checking for null,
> but it is usually checked for this function
> 
> Found by Linux Verification Center (linuxtesting.org) with SVACE.

NAK.

I see a bug in there, but it's not going to trigger a NULL pointer
dereference and your patch doesn't fix it at all.

That loop ought to be
		// skipped mntns binding?
                while (p->mnt.mnt_root != q->mnt.mnt_root)
			p = next_mnt(skip_mnt_tree(p), old);

and I suspect that it'll confuse your tool even worse.

What happens here is that new tree is congruent to the old one,
with some subtrees skipped.  Each node N in the new tree is a
clone of some node (Origin(N)) in the old one.  Copying preserves
node order.  We want to have p == Origin(q) on each iteration.

What we really have (due to the real bug) is

	p is no later than Origin(q) in node ordering

Initially it's trivially true (p points to root of the old tree,
and the only way it would *not* be copied would be to somehow get
mntns binding as root; in that case copy_tree() would've failed
and we wouldn't get to that loop at all).

Suppose it is true on some iteration.  What happens on the next
one?  q hadn't been the last node in the new tree, or we would've
found next_mnt(q, new) to be NULL and exited the loop.  But
that means that
	p "<=" Origin(q) "<" Origin(next_mnt(q, new))
("<" and "<=" in the node ordering, that is).  So p couldn't
have been the last node in the old tree and
	next_mnt(p, old) "<=" Origin(next_mnt(q, new))
After the
                p = next_mnt(p, old);
		q = next_mnt(q, new);
		if (!q)
			break;
we have
	p != NULL && p "<=" Origin(q)

Cloning preserves ->mnt_root, so the subsequent loop
                while (p->mnt.mnt_root != q->mnt.mnt_root)
			p = next_mnt(p, old);
could be rewritten as
		while (p->mnt.mnt_root != Origin(q)->mnt.mnt_root)
			p = next_mnt(p, old);
and in that form it's really obvious that p will not advance past
Origin(q), nevermind running out of nodes.

So on the next iteration the property still holds.  There's no way
for your added checks to trigger.

There *IS* a bug in that logics, though - mntns binding can have
a file bound on top of it.  In such case it is possible to have
p behind the Origin(q) for a (short) while.  It's not going to
cause serious problems, but that's certainly a non-obvious
behaviour and a comment needed to explain why it's not problem
is certainly longer than the one-liner change eliminating the
oddity.  Note that running into mnt_root mismatch means that p
is currently pointing to mntns binding we'd skipped when copying.
So let's skip the subtree in the same way copy_tree() did...

The bottom line:
	* your NULL pointer checks could never trigger; if you *do* have
a reproducer, please post it.
	* there's a (pretty harmless) bug in that code, but it is not
fixed by your patch.
	* see if your tool is any happier with the patch below; I would
be rather surprised if it did, but...

diff --git a/fs/namespace.c b/fs/namespace.c
index df137ba19d37..c80f422084eb 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3515,8 +3515,9 @@ struct mnt_namespace *copy_mnt_ns(unsigned long flags, struct mnt_namespace *ns,
 		q = next_mnt(q, new);
 		if (!q)
 			break;
+		// an mntns binding we'd skipped?
 		while (p->mnt.mnt_root != q->mnt.mnt_root)
-			p = next_mnt(p, old);
+			p = next_mnt(skip_mnt_tree(p), old);
 	}
 	namespace_unlock();
 



More information about the lvc-project mailing list