vfs: move permission checking into notify_change() for utimes(NULL)
authorMiklos Szeredi <mszeredi@redhat.com>
Fri, 16 Sep 2016 10:44:20 +0000 (12:44 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Fri, 16 Sep 2016 10:44:20 +0000 (12:44 +0200)
This fixes a bug where the permission was not properly checked in
overlayfs.  The testcase is ltp/utimensat01.

It is also cleaner and safer to do the permission checking in the vfs
helper instead of the caller.

This patch introduces an additional ia_valid flag ATTR_TOUCH (since
touch(1) is the most obvious user of utimes(NULL)) that is passed into
notify_change whenever the conditions for this special permission checking
mode are met.

Reported-by: Aihua Zhang <zhangaihua1@huawei.com>
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Tested-by: Aihua Zhang <zhangaihua1@huawei.com>
Cc: <stable@vger.kernel.org> # v3.18+
fs/attr.c
fs/utimes.c
include/linux/fs.h

index 42bb42bb3c72c206923294a3f08f0020df6c5a43..3c42cab06b5d58141d93a5f0cc7088d2edaa4d08 100644 (file)
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -202,6 +202,21 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de
                        return -EPERM;
        }
 
+       /*
+        * If utimes(2) and friends are called with times == NULL (or both
+        * times are UTIME_NOW), then we need to check for write permission
+        */
+       if (ia_valid & ATTR_TOUCH) {
+               if (IS_IMMUTABLE(inode))
+                       return -EPERM;
+
+               if (!inode_owner_or_capable(inode)) {
+                       error = inode_permission(inode, MAY_WRITE);
+                       if (error)
+                               return error;
+               }
+       }
+
        if ((ia_valid & ATTR_MODE)) {
                umode_t amode = attr->ia_mode;
                /* Flag setting protected by i_mutex */
index 794f5f5b1fb5cfd636725bfd65a7d21df916c36e..ba54b9e648c9688692dbb34a86ba966c296a91bc 100644 (file)
@@ -87,21 +87,7 @@ static int utimes_common(struct path *path, struct timespec *times)
                 */
                newattrs.ia_valid |= ATTR_TIMES_SET;
        } else {
-               /*
-                * If times is NULL (or both times are UTIME_NOW),
-                * then we need to check permissions, because
-                * inode_change_ok() won't do it.
-                */
-               error = -EPERM;
-                if (IS_IMMUTABLE(inode))
-                       goto mnt_drop_write_and_out;
-
-               error = -EACCES;
-               if (!inode_owner_or_capable(inode)) {
-                       error = inode_permission(inode, MAY_WRITE);
-                       if (error)
-                               goto mnt_drop_write_and_out;
-               }
+               newattrs.ia_valid |= ATTR_TOUCH;
        }
 retry_deleg:
        inode_lock(inode);
@@ -113,7 +99,6 @@ retry_deleg:
                        goto retry_deleg;
        }
 
-mnt_drop_write_and_out:
        mnt_drop_write(path->mnt);
 out:
        return error;
index 901e25d495ccfb71d8fc91b158a19cfe4dd9fccf..7c391366fb43ed55b2ace45d1573f2f64fb49c70 100644 (file)
@@ -224,6 +224,7 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
 #define ATTR_KILL_PRIV (1 << 14)
 #define ATTR_OPEN      (1 << 15) /* Truncating from open(O_TRUNC) */
 #define ATTR_TIMES_SET (1 << 16)
+#define ATTR_TOUCH     (1 << 17)
 
 /*
  * Whiteout is represented by a char device.  The following constants define the