Skip to contentSkip to conversation

Bind mount escape fixes (CVE-2015-2925)

PATCH Applied Eric W. Biederman 6 participants 12 messages Oct 1, 2015 – Oct 18, 2015

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Bind mount escape fixes (CVE-2015-2925)

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.

Eric

#1 lore raw

[ trimmed CC list ]

Quoted 8 lines · Eric W. Biederman

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.

(...)

queued for 2.6.32, much appreciated, thanks Eric!

Willy

#2 lore raw
In reply to Eric W. Biederman
Quoted 7 lines · Eric W. Biederman

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.

I'm not convinced that this is necessary for the 2.6.32, 3.2 or 3.4 stable branches. While it is possible for an administrator to screw this up, there is no possibility of a user being able to exploit this from a user namespace where they have namespaced-CAP_SYS_ADMIN.

Quoted 3 lines

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

For 3.16 I started with:

70291aecc6aa228c1b3bb36a5f3efdb0af636042 namei: lift (open-coded) terminate_walk() in follow_dotdot_rcu() into callers

which then made the other two trivial to apply. I think that would also work for 3.14 and 3.18.

Quoted 6 lines

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 4.2, I had the idea that this one was needed too:

a03e283bf5c3d4851b4998122196ce9f849e6dfb dcache: Reduce the scope of i_lock in d_splice_alias

but perhaps that is just cleanup/optimisation?

Ben.

Signature · 2 lines
Ben Hutchings
When in doubt, use brute force. - Ken Thompson
#3 lore raw
Quoted 9 lines · Ben Hutchings
Quoted 7 lines · Eric W. Biederman

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.

Quoted 4 lines

I'm not convinced that this is necessary for the 2.6.32, 3.2 or 3.4 stable branches. While it is possible for an administrator to screw this up, there is no possibility of a user being able to exploit this from a user namespace where they have namespaced-CAP_SYS_ADMIN.

It is cheap and easy to fix. I made and tested the changes. So why not.

Nothing about the bug or the exploit depends on user namespaces, user namespaces just make it 100% reliable to arrange the necessary conditions to be able to escape a bind mount. I don't think anyone even knew what to look for to allow or prevent this until just recently. So I hesitate to call it an administrator messed up if no one understood the issue existed.

Quoted 10 lines
Quoted 3 lines

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

For 3.16 I started with:

70291aecc6aa228c1b3bb36a5f3efdb0af636042 namei: lift (open-coded) terminate_walk() in follow_dotdot_rcu() into callers

which then made the other two trivial to apply. I think that would also work for 3.14 and 3.18.

It probably does fix the issue. Without applying 70291aecc6aa228c1b3bb36a5f3efdb0af636042 and reading the code of fs/namei.c I can not tell. That would probably take me an hour and I am not volunteering that time right now. That backport addresses the issues I can think of off the top of my head with in for 3.14 and 3.18, but there are a lot of subtle dependencies in fs/namei.c.

What I know is that there were a number of kernels where my patch that added a return and a return code to follow_dotdot applied cleanly but did not work correctly. mountpoint_last was a factor, as was the movement/consolidation of terminate_walk().

I found it easier to adapt my change to follow_dotdot to not need the movement of terminate_walk() than to figure out which part of the cleanup would be needed to remove the need.

For a backport it seemed the better part of valor to make the necessary changes as small and as locally correct as I could. As with less code it is harder to get it wrong.

Quoted 5 lines
Quoted 5 lines

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.

Quoted 7 lines
Quoted 1 line

Please queue these changes for the appropriate stable trees.

For 4.2, I had the idea that this one was needed too:

a03e283bf5c3d4851b4998122196ce9f849e6dfb dcache: Reduce the scope of i_lock in d_splice_alias

but perhaps that is just cleanup/optimisation?

Yes it has no immediate bearing on this issue as it was fixed.

It is halfway to fixing the locking craziness in d_splice_alias and a more ambitious fix would probably could take advantage of that. But the more ambitious fix was not

Yes that is just a cleanup. It is also half way to removing the locking craziness in d_splice_alias. Which is a related but different battle.

Eric

#4 lore raw
Quoted 11 lines · [email protected] (Eric W. Biederman) writes:
Quoted 9 lines · Ben Hutchings
Quoted 7 lines · Eric W. Biederman

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.

Quoted 8 lines
Quoted 4 lines

I'm not convinced that this is necessary for the 2.6.32, 3.2 or 3.4 stable branches. While it is possible for an administrator to screw this up, there is no possibility of a user being able to exploit this from a user namespace where they have namespaced-CAP_SYS_ADMIN.

It is cheap and easy to fix. I made and tested the changes. So why not.

Having thought about this I definitely think we need this on older kernels. I am aware of at least one piece of software that predates 2.6.32 is vulnerable to this escape.

The software in all innocence bind mounted a users /home directory into a root filesystem that was stored in the users /home directory. That is enough to allow the escape with a simple unprivileged rename.

So since this is actually exploitable on real userspace software that predates 2.6.32 I think this fix needs to be backported, as it is not a theoretical issue.

Eric

#5 lore raw

On Fri, 2015-10-02 at 11:01 -0500, Eric W. Biederman wrote:
[...]

Quoted 11 lines

Having thought about this I definitely think we need this on older kernels. I am aware of at least one piece of software that predates 2.6.32 is vulnerable to this escape.

The software in all innocence bind mounted a users /home directory into a root filesystem that was stored in the users /home directory. That is enough to allow the escape with a simple unprivileged rename.

So since this is actually exploitable on real userspace software that predates 2.6.32 I think this fix needs to be backported, as it is not a theoretical issue.

Thanks for the explanation. I'll review and test the patches for 2.6.32 and 3.2 in a while.

Ben.

Signature · 2 lines
Ben Hutchings
compatible: Gracefully accepts erroneous data from any source
#6 lore raw
Quoted 10 lines · Ben Hutchings

On Fri, 2015-10-02 at 11:01 -0500, Eric W. Biederman wrote:
[...]

Quoted 8 lines

Having thought about this I definitely think we need this on older kernels. I am aware of at least one piece of software that predates 2.6.32 is vulnerable to this escape.

The software in all innocence bind mounted a users /home directory into a root filesystem that was stored in the users /home directory. That is enough to allow the escape with a simple unprivileged rename.

Quoted 6 lines
Quoted 3 lines

So since this is actually exploitable on real userspace software that predates 2.6.32 I think this fix needs to be backported, as it is not a theoretical issue.

Thanks for the explanation. I'll review and test the patches for 2.6.32 and 3.2 in a while.

Thanks as well.

Willy

Applied
#7 lore raw
In reply to Eric W. Biederman
Quoted 13 lines · Eric W. Biederman

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

Quoted 6 lines

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.

Applied to 3.12. Thanks!

Signature · 2 lines
js
suse labs
#8 lore raw
In reply to Eric W. Biederman
Quoted 13 lines · Eric W. Biederman

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

Quoted 7 lines

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.

Thank you, Eric. I'm queuing these for the 3.16 kernel as well (picking the 3.18 backports).

Cheers,
--
Lu�s

#9 lore raw
In reply to Eric W. Biederman
Quoted 12 lines · Eric W. Biederman

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

Quoted 6 lines

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:

Quoted 12 lines
fs/dcache.c +1 -1
@@ -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);

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1926

Quoted 4 lines
fs/dcache.c +1 -0
@@ -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.

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1937

Quoted 10 lines
fs/dcache.c +4 -0
        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.

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1942

Quoted 5 lines
fs/dcache.c +2 -0
+                               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.

Signature · 2 lines
Ben Hutchings
Once a job is fouled up, anything done to improve it makes it worse.

From: "Eric W. Biederman" <[email protected]>
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.

[bwh: For 2.6.32, implement the "(unreachable)" string in __d_path()]
---
 fs/dcache.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
fs/dcache.c +10 -1
index 44c0aeafcbc9..e1accce92f68 100644
@@ -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;
#10 lore raw

Hi Ben,

Quoted 21 lines · Ben Hutchings

For 2.6.32, the first backport looks wrong:

Quoted 12 lines
fs/dcache.c +1 -1
@@ -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);

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1926

Quoted 4 lines
fs/dcache.c +1 -0
@@ -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.

Quoted 12 lines

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1937

Quoted 10 lines
fs/dcache.c +4 -0
        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.

Good catch!

Quoted 8 lines

fs/dcache.c char *__d_path(const struct path *path, struct path *root, · line 1944

Quoted 5 lines
fs/dcache.c +2 -0
+                               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.

Thanks, I've queued this one instead!

Willy

#11 lore raw
In reply to Eric W. Biederman
Quoted 13 lines · Eric W. Biederman

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

Quoted 7 lines

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.

Thanks for these, now applied to 4.2, 4.1, 3.14, and 3.10 stable trees.

greg k-h

Quoted 2 lines

Eric