Re: [PATCH] utimensat() non-conformances and fixes -- version 2
From: Miklos Szeredi
Date: Mon May 19 2008 - 09:21:59 EST
> > Also could you do the patch against the
> >
> > git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs.git vfs-cleanups
> >
> > tree, which does big structural cleanups to do_utimes?
>
> You keep moving the goalposts here... First, it was provide an
> obvious correct fix (for the non-conformances); then: can you cleanup
> the owner checks;
Sorry, that was just an idea, but since it's not as simple as it
should be, I guess we should leave that till later. My main objection
was against introducing more is_owner_or_cap() checks. Just doing the
times == NULL case with ATTR_OWNER_CHECK should be fine.
That reminds me, one more comment about the patch: if you are
reindenting the ATTR_* definitions anyway, why not also change them to
the cleaner (1 << N) format?
> then: can you rewrite against my git tree... My
> time at the moment is fairly limited, and I have a problem: currently,
> I'm not a git user. That'll change soon, but I don't have the time to
> change it now. Can I just download a snapshot tarball of your git
> changes somehwere? Alternatively, when do you expect your changes to
> make it into an rc?
Here's the relevant part (dfe9b50d..aeb1fe4b) of that tree as a single
patch. I hope it compiles.
Thanks,
Miklos
diff --git a/fs/attr.c b/fs/attr.c
index 966b73e..e8bd11b 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -108,6 +108,12 @@ int notify_change(struct dentry * dentry, struct iattr * attr)
struct timespec now;
unsigned int ia_valid = attr->ia_valid;
+ if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID |
+ ATTR_ATIME_SET | ATTR_MTIME_SET)) {
+ if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+ return -EPERM;
+ }
+
now = current_fs_time(inode->i_sb);
attr->ia_ctime = now;
diff --git a/fs/exec.c b/fs/exec.c
index 1f8a24a..b68682a 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1763,7 +1763,7 @@ int do_coredump(long signr, int exit_code, struct pt_regs * regs)
goto close_fail;
if (!file->f_op->write)
goto close_fail;
- if (!ispipe && do_truncate(file->f_path.dentry, 0, 0, file) != 0)
+ if (!ispipe && file_truncate(file, 0, 0) != 0)
goto close_fail;
retval = binfmt->core_dump(signr, regs, file, core_limit);
diff --git a/fs/open.c b/fs/open.c
index a145008..bb604a6 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -195,6 +195,13 @@ out:
return error;
}
+/*
+ * do_truncate - truncate (or extend) an inode
+ * @dentry: the dentry to truncate
+ * @length: the new length
+ * @time_attrs: file times to be updated (e.g. ATTR_MTIME|ATTR_CTIME)
+ * @filp: an open file or NULL (see file_truncate() as well)
+ */
int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
struct file *filp)
{
@@ -221,6 +228,17 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs,
return err;
}
+/*
+ * file_truncate - truncate (or extend) an open file
+ * @filp: the open file
+ * @length: the new length
+ * @time_attrs: file times to be updated (e.g. ATTR_MTIME|ATTR_CTIME)
+ */
+int file_truncate(struct file *filp, loff_t length, unsigned int time_attrs)
+{
+ return do_truncate(filp->f_path.dentry, length, time_attrs, filp);
+}
+
static long do_sys_truncate(const char __user * path, loff_t length)
{
struct nameidata nd;
@@ -254,7 +272,7 @@ static long do_sys_truncate(const char __user * path, loff_t length)
goto mnt_drop_write_and_out;
error = -EPERM;
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
+ if (IS_APPEND(inode))
goto mnt_drop_write_and_out;
error = get_write_access(inode);
@@ -294,7 +312,6 @@ asmlinkage long sys_truncate(const char __user * path, unsigned long length)
static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
{
struct inode * inode;
- struct dentry *dentry;
struct file * file;
int error;
@@ -310,8 +327,7 @@ static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
if (file->f_flags & O_LARGEFILE)
small = 0;
- dentry = file->f_path.dentry;
- inode = dentry->d_inode;
+ inode = file->f_path.dentry->d_inode;
error = -EINVAL;
if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))
goto out_putf;
@@ -327,7 +343,7 @@ static long do_sys_ftruncate(unsigned int fd, loff_t length, int small)
error = locks_verify_truncate(inode, file, length);
if (!error)
- error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file);
+ error = file_truncate(file, length, ATTR_MTIME|ATTR_CTIME);
out_putf:
fput(file);
out:
@@ -582,9 +598,6 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
err = mnt_want_write(file->f_path.mnt);
if (err)
goto out_putf;
- err = -EPERM;
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
- goto out_drop_write;
mutex_lock(&inode->i_mutex);
if (mode == (mode_t) -1)
mode = inode->i_mode;
@@ -592,8 +605,6 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode)
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
err = notify_change(dentry, &newattrs);
mutex_unlock(&inode->i_mutex);
-
-out_drop_write:
mnt_drop_write(file->f_path.mnt);
out_putf:
fput(file);
@@ -617,11 +628,6 @@ asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
error = mnt_want_write(nd.path.mnt);
if (error)
goto dput_and_out;
-
- error = -EPERM;
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
- goto out_drop_write;
-
mutex_lock(&inode->i_mutex);
if (mode == (mode_t) -1)
mode = inode->i_mode;
@@ -629,8 +635,6 @@ asmlinkage long sys_fchmodat(int dfd, const char __user *filename,
newattrs.ia_valid = ATTR_MODE | ATTR_CTIME;
error = notify_change(nd.path.dentry, &newattrs);
mutex_unlock(&inode->i_mutex);
-
-out_drop_write:
mnt_drop_write(nd.path.mnt);
dput_and_out:
path_put(&nd.path);
@@ -645,18 +649,10 @@ asmlinkage long sys_chmod(const char __user *filename, mode_t mode)
static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
{
- struct inode * inode;
+ struct inode *inode = dentry->d_inode;
int error;
struct iattr newattrs;
- error = -ENOENT;
- if (!(inode = dentry->d_inode)) {
- printk(KERN_ERR "chown_common: NULL inode\n");
- goto out;
- }
- error = -EPERM;
- if (IS_IMMUTABLE(inode) || IS_APPEND(inode))
- goto out;
newattrs.ia_valid = ATTR_CTIME;
if (user != (uid_t) -1) {
newattrs.ia_valid |= ATTR_UID;
@@ -672,7 +668,7 @@ static int chown_common(struct dentry * dentry, uid_t user, gid_t group)
mutex_lock(&inode->i_mutex);
error = notify_change(dentry, &newattrs);
mutex_unlock(&inode->i_mutex);
-out:
+
return error;
}
diff --git a/fs/utimes.c b/fs/utimes.c
index af059d5..cccefe4 100644
--- a/fs/utimes.c
+++ b/fs/utimes.c
@@ -53,62 +53,14 @@ static bool nsec_valid(long nsec)
return nsec >= 0 && nsec <= 999999999;
}
-/* If times==NULL, set access and modification to current time,
- * must be owner or have write permission.
- * Else, update from *times, must be owner or super user.
- */
-long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags)
+static int utimes_common(struct path *path, struct timespec *times)
{
int error;
- struct nameidata nd;
- struct dentry *dentry;
- struct inode *inode;
struct iattr newattrs;
- struct file *f = NULL;
- struct vfsmount *mnt;
-
- error = -EINVAL;
- if (times && (!nsec_valid(times[0].tv_nsec) ||
- !nsec_valid(times[1].tv_nsec))) {
- goto out;
- }
-
- if (flags & ~AT_SYMLINK_NOFOLLOW)
- goto out;
-
- if (filename == NULL && dfd != AT_FDCWD) {
- error = -EINVAL;
- if (flags & AT_SYMLINK_NOFOLLOW)
- goto out;
-
- error = -EBADF;
- f = fget(dfd);
- if (!f)
- goto out;
- dentry = f->f_path.dentry;
- mnt = f->f_path.mnt;
- } else {
- error = __user_walk_fd(dfd, filename, (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW, &nd);
- if (error)
- goto out;
-
- dentry = nd.path.dentry;
- mnt = nd.path.mnt;
- }
-
- inode = dentry->d_inode;
-
- error = mnt_want_write(mnt);
- if (error)
- goto dput_and_out;
/* Don't worry, the checks are done in inode_change_ok() */
newattrs.ia_valid = ATTR_CTIME | ATTR_MTIME | ATTR_ATIME;
if (times) {
- error = -EPERM;
- if (IS_APPEND(inode) || IS_IMMUTABLE(inode))
- goto mnt_drop_write_and_out;
-
if (times[0].tv_nsec == UTIME_OMIT)
newattrs.ia_valid &= ~ATTR_ATIME;
else if (times[0].tv_nsec != UTIME_NOW) {
@@ -125,41 +77,118 @@ long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags
newattrs.ia_valid |= ATTR_MTIME_SET;
}
}
+ mutex_lock(&path->dentry->d_inode->i_mutex);
+ error = mnt_want_write(path->mnt);
+ if (!error) {
+ error = notify_change(path->dentry, &newattrs);
+ mnt_drop_write(path->mnt);
+ }
+ mutex_unlock(&path->dentry->d_inode->i_mutex);
+
+ return error;
+}
+
+/*
+ * If times is NULL or both times are either UTIME_OMIT or UTIME_NOW, then
+ * need to check permissions, because inode_change_ok() won't do it.
+ */
+static bool utimes_need_permission(struct timespec *times)
+{
+ return !times || (nsec_special(times[0].tv_nsec) &&
+ nsec_special(times[1].tv_nsec));
+}
+
+static int do_futimes(int fd, struct timespec *times)
+{
+ int error;
+ struct file *file = fget(fd);
+
+ if (!file)
+ return -EBADF;
+
+ if (utimes_need_permission(times)) {
+ struct inode *inode = file->f_path.dentry->d_inode;
+
+ error = -EACCES;
+ if (!is_owner_or_cap(inode) && !(file->f_mode & FMODE_WRITE))
+ goto out_fput;
+ }
+ error = utimes_common(&file->f_path, times);
+
+ out_fput:
+ fput(file);
+
+ return error;
+}
+
+static int do_utimes_name(int dfd, char __user *filename,
+ struct timespec *times, int flags)
+{
+ int error;
+ struct nameidata nd;
+ int lookup_flags;
+
+ if (flags & ~AT_SYMLINK_NOFOLLOW)
+ return -EINVAL;
+
+ lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
+ error = __user_walk_fd(dfd, filename, lookup_flags, &nd);
+ if (error)
+ return error;
+
+
+ if (utimes_need_permission(times)) {
+ struct inode *inode = nd.path.dentry->d_inode;
- /*
- * If times is NULL or both times are either UTIME_OMIT or
- * UTIME_NOW, then need to check permissions, because
- * inode_change_ok() won't do it.
- */
- if (!times || (nsec_special(times[0].tv_nsec) &&
- nsec_special(times[1].tv_nsec))) {
error = -EACCES;
- if (IS_IMMUTABLE(inode))
- goto mnt_drop_write_and_out;
+ if (IS_IMMUTABLE(inode))
+ goto out_path_put;
if (!is_owner_or_cap(inode)) {
- if (f) {
- if (!(f->f_mode & FMODE_WRITE))
- goto mnt_drop_write_and_out;
- } else {
- error = vfs_permission(&nd, MAY_WRITE);
- if (error)
- goto mnt_drop_write_and_out;
- }
+ error = vfs_permission(&nd, MAY_WRITE);
+ if (error)
+ goto out_path_put;
}
}
- mutex_lock(&inode->i_mutex);
- error = notify_change(dentry, &newattrs);
- mutex_unlock(&inode->i_mutex);
-mnt_drop_write_and_out:
- mnt_drop_write(mnt);
-dput_and_out:
- if (f)
- fput(f);
- else
- path_put(&nd.path);
-out:
+ error = utimes_common(&nd.path, times);
+
+ out_path_put:
+ path_put(&nd.path);
+
return error;
+
+}
+
+/*
+ * do_utimes - change times on filename or file descriptor
+ * @dfd: open file descriptor, -1 or AT_FDCWD
+ * @filename: path name or NULL
+ * @times: new times or NULL
+ * @flags: zero or more flags (only AT_SYMLINK_NOFOLLOW for the moment)
+ *
+ * If filename is NULL and dfd refers to an open file, then operate on
+ * the file. Otherwise look up filename, possibly using dfd as a
+ * starting point.
+ *
+ * If times==NULL, set access and modification to current time,
+ * must be owner or have write permission.
+ * Else, update from *times, must be owner or super user.
+ */
+int do_utimes(int dfd, char __user *filename, struct timespec *times, int flags)
+{
+ if (times && (!nsec_valid(times[0].tv_nsec) ||
+ !nsec_valid(times[1].tv_nsec))) {
+ return -EINVAL;
+ }
+
+ if (filename == NULL && dfd != AT_FDCWD) {
+ if (flags)
+ return -EINVAL;
+
+ return do_futimes(dfd, times);
+ } else {
+ return do_utimes_name(dfd, filename, times, flags);
+ }
}
asmlinkage long sys_utimensat(int dfd, char __user *filename, struct timespec __user *utimes, int flags)
diff --git a/include/linux/fs.h b/include/linux/fs.h
index f413085..5b382ca 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1600,6 +1600,8 @@ static inline int break_lease(struct inode *inode, unsigned int mode)
extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs,
struct file *filp);
+extern int file_truncate(struct file *filp, loff_t start,
+ unsigned int time_attrs);
extern long do_sys_open(int dfd, const char __user *filename, int flags,
int mode);
extern struct file *filp_open(const char *, int, int);
diff --git a/include/linux/time.h b/include/linux/time.h
index d32ef0a..d757ffc 100644
--- a/include/linux/time.h
+++ b/include/linux/time.h
@@ -109,7 +109,8 @@ extern void do_gettimeofday(struct timeval *tv);
extern int do_settimeofday(struct timespec *tv);
extern int do_sys_settimeofday(struct timespec *tv, struct timezone *tz);
#define do_posix_clock_monotonic_gettime(ts) ktime_get_ts(ts)
-extern long do_utimes(int dfd, char __user *filename, struct timespec *times, int flags);
+extern int do_utimes(int dfd, char __user *filename, struct timespec *times,
+ int flags);
struct itimerval;
extern int do_setitimer(int which, struct itimerval *value,
struct itimerval *ovalue);
diff --git a/mm/tiny-shmem.c b/mm/tiny-shmem.c
index ae532f5..65226ce 100644
--- a/mm/tiny-shmem.c
+++ b/mm/tiny-shmem.c
@@ -80,7 +80,7 @@ struct file *shmem_file_setup(char *name, loff_t size, unsigned long flags)
inode->i_nlink = 0; /* It is unlinked */
/* notify everyone as to the change of file size */
- error = do_truncate(dentry, size, 0, file);
+ error = file_truncate(file, size, 0);
if (error < 0)
goto close_file;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/