Bind mount escape fixes (CVE-2015-2925)
Subsystems (2)
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
Willy Tarreau
Applied[ 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
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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_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
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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_rootFor 3.16 I started with:
70291aecc6aa228c1b3bb36a5f3efdb0af636042namei: lift (open-coded)terminate_walk()infollow_dotdot_rcu()into callerswhich 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:
a03e283bf5c3d4851b4998122196ce9f849e6dfbdcache: Reduce the scope ofi_lockind_splice_aliasbut 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
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
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
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
Jiri Slaby
AppliedQuoted 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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_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
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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_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
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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_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
-0for (;;) {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.
--- fs/dcache.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
fs/dcache.c +10 -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 );
@@ -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;
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
-0for (;;) {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
Greg Kroah-Hartman
maintainerQuoted 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:
cde93be45a8a90d8c264c776fab63487b5038a65dcache: Handle escaped paths inprepend_path397d425dc26da728396e66d392d5dcb8dac30c37vfs: Test for and handle paths that are unreachable from theirmnt_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