Re: [PATCHES] Bind mount escape fixes (CVE-2015-2925)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, 2015-10-01 at 11:15 -0500, Eric W. Biederman wrote:
> With a strategically placed rename bind mounts can be tricked into
> giving processes access to the entire filesystem instead of just a piece
> of it.  This misfeature has existed since bind mounts were introduced
> into the kernel.  This issue has been fixed in Linus's tree and below
> are my tested backports of the fixes to 4.2.1, 4.1.8, 3.18.21, 3.14.53,
> 3.12.48, 3.10.89, 3.4.109, 3.2.71, 2.6.32.68.  All of the kernels 
> currently listed as being active.
> 
> The fixes backported are:
> cde93be45a8a90d8c264c776fab63487b5038a65 dcache: Handle escaped paths in prepend_path
> 397d425dc26da728396e66d392d5dcb8dac30c37 vfs: Test for and handle paths that are unreachable from their mnt_root
> 
> As I backported the patches the logical work remained the same but the
> exact implemenation details changed to fit in with the vfs present in
> the older kernels.  Minor changes were needed for every the backport to
> every kernel except 4.2.1.
> 
> Please queue these changes for the appropriate stable trees.

For 2.6.32, the first backport looks wrong:

> --- a/fs/dcache.c
> +++ b/fs/dcache.c
> @@ -1910,7 +1910,7 @@ char *__d_path(const struct path *path, struct path *root,
>         struct dentry *dentry = path->dentry;
>         struct vfsmount *vfsmnt = path->mnt;
>         char *end = buffer + buflen;
> -       char *retval;
> +       char *retval, *tail;
>  
>         spin_lock(&vfsmount_lock);
>         prepend(&end, &buflen, "\0", 1);
> @@ -1923,6 +1923,7 @@ char *__d_path(const struct path *path, struct path *root,
>         /* Get '/' right */
>         retval = end-1;
>         *retval = '/';
> +       tail = end;

So tail points to the null terminator.

>         for (;;) {
>                 struct dentry * parent;
> @@ -1930,6 +1931,12 @@ char *__d_path(const struct path *path, struct path *root,
>                 if (dentry == root->dentry && vfsmnt == root->mnt)
>                         break;
>                 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
> +                       /* Escaped? */
> +                       if (dentry != vfsmnt->mnt_root) {
> +                               retval = tail;
> +                               *retval = '/';

Now we overwrite the null terminator.

> +                               goto out;
> +                       }
>                         /* Global root? */
>                         if (vfsmnt->mnt_parent == vfsmnt) {
>                                 goto global_root;

Also, nothing inserts the "(unreachable)" string.  I've attached my
version, which deals with both of these.

Ben.

-- 
Ben Hutchings
Once a job is fouled up, anything done to improve it makes it worse.
From: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
Date: Sat, 15 Aug 2015 13:36:12 -0500
Subject: dcache: Handle escaped paths in prepend_path
Origin: https://git.kernel.org/linus/cde93be45a8a90d8c264c776fab63487b5038a65

A rename can result in a dentry that by walking up d_parent
will never reach it's mnt_root.  For lack of a better term
I call this an escaped path.

prepend_path is called by four different functions __d_path,
d_absolute_path, d_path, and getcwd.

__d_path only wants to see paths are connected to the root it passes
in.  So __d_path needs prepend_path to return an error.

d_absolute_path similarly wants to see paths that are connected to
some root.  Escaped paths are not connected to any mnt_root so
d_absolute_path needs prepend_path to return an error greater
than 1.  So escaped paths will be treated like paths on lazily
unmounted mounts.

getcwd needs to prepend "(unreachable)" so getcwd also needs
prepend_path to return an error.

d_path is the interesting hold out.  d_path just wants to print
something, and does not care about the weird cases.  Which raises
the question what should be printed?

Given that <escaped_path>/<anything> should result in -ENOENT I
believe it is desirable for escaped paths to be printed as empty
paths.  As there are not really any meaninful path components when
considered from the perspective of a mount tree.

So tweak prepend_path to return an empty path with an new error
code of 3 when it encounters an escaped path.

Signed-off-by: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
Signed-off-by: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
[bwh: For 2.6.32, implement the "(unreachable)" string in __d_path()]
Signed-off-by: Ben Hutchings <ben@xxxxxxxxxxxxxxx>
---
 fs/dcache.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/fs/dcache.c b/fs/dcache.c
index 44c0aeafcbc9..e1accce92f68 100644
--- a/fs/dcache.c
+++ b/fs/dcache.c
@@ -1910,7 +1910,7 @@ char *__d_path(const struct path *path, struct path *root,
 	struct dentry *dentry = path->dentry;
 	struct vfsmount *vfsmnt = path->mnt;
 	char *end = buffer + buflen;
-	char *retval;
+	char *retval, *tail;
 
 	spin_lock(&vfsmount_lock);
 	prepend(&end, &buflen, "\0", 1);
@@ -1923,6 +1923,7 @@ char *__d_path(const struct path *path, struct path *root,
 	/* Get '/' right */
 	retval = end-1;
 	*retval = '/';
+	tail = end;
 
 	for (;;) {
 		struct dentry * parent;
@@ -1930,6 +1931,14 @@ char *__d_path(const struct path *path, struct path *root,
 		if (dentry == root->dentry && vfsmnt == root->mnt)
 			break;
 		if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
+			/* Escaped? */
+			if (dentry != vfsmnt->mnt_root) {
+				buflen += (tail - end);
+				end = tail;
+				prepend(&end, &buflen, "(unreachable)/", 14);
+				retval = end;
+				goto out;
+			}
 			/* Global root? */
 			if (vfsmnt->mnt_parent == vfsmnt) {
 				goto global_root;

Attachment: signature.asc
Description: This is a digitally signed message part


[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]