diff mbox series

[X,CVE-2015-1350] fs: Avoid premature clearing of capabilities

Message ID 1534419711-17876-1-git-send-email-jose.pekkarinen@canonical.com
State New
Headers show
Series [X,CVE-2015-1350] fs: Avoid premature clearing of capabilities | expand

Commit Message

Jose Pekkarinen Aug. 16, 2018, 11:41 a.m. UTC
CVE-2015-1350

Buglink: https://launchpad.net/bugs/1415636

Currently, notify_change() clears capabilities or IMA attributes by
calling security_inode_killpriv() before calling into ->setattr. Thus it
happens before any other permission checks in inode_change_ok() and user
is thus allowed to trigger clearing of capabilities or IMA attributes
for any file he can look up e.g. by calling chown for that file. This is
unexpected and can lead to user DoSing a system.

Fix the problem by calling security_inode_killpriv() at the end of
inode_change_ok() instead of from notify_change(). At that moment we are
sure user has permissions to do the requested change.

Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: José Pekkarinen <jose.pekkarinen@canonical.com>

---
 fs/attr.c | 23 +++++++++++++++++------
 1 file changed, 17 insertions(+), 6 deletions(-)

Comments

Kleber Sacilotto de Souza Aug. 20, 2018, 6:58 a.m. UTC | #1
On 08/16/18 13:41, José Pekkarinen wrote:
> CVE-2015-1350
> 
> Buglink: https://launchpad.net/bugs/1415636
> 
> Currently, notify_change() clears capabilities or IMA attributes by
> calling security_inode_killpriv() before calling into ->setattr. Thus it
> happens before any other permission checks in inode_change_ok() and user
> is thus allowed to trigger clearing of capabilities or IMA attributes
> for any file he can look up e.g. by calling chown for that file. This is
> unexpected and can lead to user DoSing a system.
> 
> Fix the problem by calling security_inode_killpriv() at the end of
> inode_change_ok() instead of from notify_change(). At that moment we are
> sure user has permissions to do the requested change.
> 

As Stefan mentioned, the complete commit message should be kept. So the
line below shouldn't be removed.

References: CVE-2015-1350
> Reviewed-by: Christoph Hellwig <hch@lst.de>
> Signed-off-by: Jan Kara <jack@suse.cz>

As Stefan also mentioned, please add the original SHA1 of the patch
mentioning if it's a clean cherry pick "(cherry picked from ...)" or a
backport "(backported from ...)" as

(backported from 030b533c4fd4d2ec3402363323de4bb2983c9cee)
> Signed-off-by: José Pekkarinen <jose.pekkarinen@canonical.com>

I'm not NAK'ing or ACK'ing the patch, if the patch is fine we can
include those fixes when applying it if others agree as well.


Thanks,
Kleber

> 
> ---
>  fs/attr.c | 23 +++++++++++++++++------
>  1 file changed, 17 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/attr.c b/fs/attr.c
> index df05bc1..870e4b6 100644
> --- a/fs/attr.c
> +++ b/fs/attr.c
> @@ -68,7 +68,7 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
>  
>  	/* If force is set do it anyway. */
>  	if (ia_valid & ATTR_FORCE)
> -		return 0;
> +		goto kill_priv;
>  
>  	/* Make sure a caller can chown. */
>  	if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
> @@ -95,6 +95,19 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
>  			return -EPERM;
>  	}
>  
> +kill_priv:
> +	/* User has permission for the change */
> +	if (ia_valid & ATTR_KILL_PRIV) {
> +		int error;
> +		struct dentry * dentry = d_find_alias(inode);
> +
> +		if (dentry) {
> +			error = security_inode_killpriv(dentry);
> +			if (error)
> +				return error;
> +		}
> +	}
> +
>  	return 0;
>  }
>  EXPORT_SYMBOL(inode_change_ok);
> @@ -250,13 +263,11 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
>  	if (!(ia_valid & ATTR_MTIME_SET))
>  		attr->ia_mtime = now;
>  	if (ia_valid & ATTR_KILL_PRIV) {
> -		attr->ia_valid &= ~ATTR_KILL_PRIV;
> -		ia_valid &= ~ATTR_KILL_PRIV;
>  		error = security_inode_need_killpriv(dentry);
> -		if (error > 0)
> -			error = security_inode_killpriv(dentry);
> -		if (error)
> +		if (error < 0)
>  			return error;
> +		if (error == 0)
> +			ia_valid = attr->ia_valid &= ~ATTR_KILL_PRIV;
>  	}
>  
>  	/*
>
Stefan Bader Aug. 20, 2018, 1:13 p.m. UTC | #2
On 20.08.2018 08:58, Kleber Souza wrote:
> On 08/16/18 13:41, José Pekkarinen wrote:
>> CVE-2015-1350
>>
>> Buglink: https://launchpad.net/bugs/1415636
>>
>> Currently, notify_change() clears capabilities or IMA attributes by
>> calling security_inode_killpriv() before calling into ->setattr. Thus it
>> happens before any other permission checks in inode_change_ok() and user
>> is thus allowed to trigger clearing of capabilities or IMA attributes
>> for any file he can look up e.g. by calling chown for that file. This is
>> unexpected and can lead to user DoSing a system.
>>
>> Fix the problem by calling security_inode_killpriv() at the end of
>> inode_change_ok() instead of from notify_change(). At that moment we are
>> sure user has permissions to do the requested change.
>>
> 
> As Stefan mentioned, the complete commit message should be kept. So the
> line below shouldn't be removed.
> 
> References: CVE-2015-1350
>> Reviewed-by: Christoph Hellwig <hch@lst.de>
>> Signed-off-by: Jan Kara <jack@suse.cz>
> 
> As Stefan also mentioned, please add the original SHA1 of the patch
> mentioning if it's a clean cherry pick "(cherry picked from ...)" or a
> backport "(backported from ...)" as
> 
> (backported from 030b533c4fd4d2ec3402363323de4bb2983c9cee)
>> Signed-off-by: José Pekkarinen <jose.pekkarinen@canonical.com>
> 
> I'm not NAK'ing or ACK'ing the patch, if the patch is fine we can
> include those fixes when applying it if others agree as well.
> 
> 
> Thanks,
> Kleber
> 
>>
>> ---
>>  fs/attr.c | 23 +++++++++++++++++------
>>  1 file changed, 17 insertions(+), 6 deletions(-)
>>
>> diff --git a/fs/attr.c b/fs/attr.c
>> index df05bc1..870e4b6 100644
>> --- a/fs/attr.c
>> +++ b/fs/attr.c
>> @@ -68,7 +68,7 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
>>  
>>  	/* If force is set do it anyway. */
>>  	if (ia_valid & ATTR_FORCE)
>> -		return 0;
>> +		goto kill_priv;
>>  
>>  	/* Make sure a caller can chown. */
>>  	if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
>> @@ -95,6 +95,19 @@ int inode_change_ok(const struct inode *inode, struct iattr *attr)
>>  			return -EPERM;
>>  	}
>>  
>> +kill_priv:
>> +	/* User has permission for the change */
>> +	if (ia_valid & ATTR_KILL_PRIV) {
>> +		int error;
>> +		struct dentry * dentry = d_find_alias(inode);
>> +
>> +		if (dentry) {
>> +			error = security_inode_killpriv(dentry);
>> +			if (error)
>> +				return error;
>> +		}
>> +	}
>> +
>>  	return 0;

Additionally to above reasons, this is basically a submission Khaled has made in
the past and Theadeu had proposed the use of d_obtain_alias, but _also_ said a
matching dput() would be needed. Plus, if one really reads the code, it is
obvious that the return value of d_obtain_alias() is nevel NULL, hence that part
should look like in Khaled's v3 submission:

+kill_priv:
+	/* User has permission for the change */
+	if (ia_valid & ATTR_KILL_PRIV) {
+		int error;
+		struct dentry *dentry;
+
+		dentry = d_obtain_alias(inode);
+		if (!IS_ERR(dentry)) {
+			error = security_inode_killpriv(dentry);
+			dput(dentry);
+			return error;
+		}
+	}
+

-Stefan

PS: Same reply for the Trusty patch which could be made a single patch
submission with the note that the first hunk needs to accept a fuzz 1.

>>  }
>>  EXPORT_SYMBOL(inode_change_ok);
>> @@ -250,13 +263,11 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
>>  	if (!(ia_valid & ATTR_MTIME_SET))
>>  		attr->ia_mtime = now;
>>  	if (ia_valid & ATTR_KILL_PRIV) {
>> -		attr->ia_valid &= ~ATTR_KILL_PRIV;
>> -		ia_valid &= ~ATTR_KILL_PRIV;
>>  		error = security_inode_need_killpriv(dentry);
>> -		if (error > 0)
>> -			error = security_inode_killpriv(dentry);
>> -		if (error)
>> +		if (error < 0)
>>  			return error;
>> +		if (error == 0)
>> +			ia_valid = attr->ia_valid &= ~ATTR_KILL_PRIV;
>>  	}
>>  
>>  	/*
>>
> 
>
diff mbox series

Patch

diff --git a/fs/attr.c b/fs/attr.c
index df05bc1..870e4b6 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -68,7 +68,7 @@  int inode_change_ok(const struct inode *inode, struct iattr *attr)
 
 	/* If force is set do it anyway. */
 	if (ia_valid & ATTR_FORCE)
-		return 0;
+		goto kill_priv;
 
 	/* Make sure a caller can chown. */
 	if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid))
@@ -95,6 +95,19 @@  int inode_change_ok(const struct inode *inode, struct iattr *attr)
 			return -EPERM;
 	}
 
+kill_priv:
+	/* User has permission for the change */
+	if (ia_valid & ATTR_KILL_PRIV) {
+		int error;
+		struct dentry * dentry = d_find_alias(inode);
+
+		if (dentry) {
+			error = security_inode_killpriv(dentry);
+			if (error)
+				return error;
+		}
+	}
+
 	return 0;
 }
 EXPORT_SYMBOL(inode_change_ok);
@@ -250,13 +263,11 @@  int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
 	if (!(ia_valid & ATTR_MTIME_SET))
 		attr->ia_mtime = now;
 	if (ia_valid & ATTR_KILL_PRIV) {
-		attr->ia_valid &= ~ATTR_KILL_PRIV;
-		ia_valid &= ~ATTR_KILL_PRIV;
 		error = security_inode_need_killpriv(dentry);
-		if (error > 0)
-			error = security_inode_killpriv(dentry);
-		if (error)
+		if (error < 0)
 			return error;
+		if (error == 0)
+			ia_valid = attr->ia_valid &= ~ATTR_KILL_PRIV;
 	}
 
 	/*