X-Git-Url: http://ftp.safe.ca/?a=blobdiff_plain;f=fs%2Fopen.c;h=74e5cd9f718e8b43eb17e8dad2f28074591fbaaf;hb=2a8e5e3637e2fc058798f5d3626f525729ffaaaf;hp=f53a5b9ffb7dce308f6e0e05d4982d343261564c;hpb=8c744fb83da0771afa04695028e3550b798dad90;p=safe%2Fjmp%2Flinux-2.6 diff --git a/fs/open.c b/fs/open.c index f53a5b9..74e5cd9 100644 --- a/fs/open.c +++ b/fs/open.c @@ -6,40 +6,45 @@ #include #include -#include #include -#include -#include +#include #include #include -#include #include #include #include +#include +#include #include #include #include +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include -#include +#include "internal.h" -int vfs_statfs(struct super_block *sb, struct kstatfs *buf) +int vfs_statfs(struct dentry *dentry, struct kstatfs *buf) { int retval = -ENODEV; - if (sb) { + if (dentry) { retval = -ENOSYS; - if (sb->s_op->statfs) { + if (dentry->d_sb->s_op->statfs) { memset(buf, 0, sizeof(*buf)); - retval = security_sb_statfs(sb); + retval = security_sb_statfs(dentry); if (retval) return retval; - retval = sb->s_op->statfs(sb, buf); + retval = dentry->d_sb->s_op->statfs(dentry, buf); if (retval == 0 && buf->f_frsize == 0) buf->f_frsize = buf->f_bsize; } @@ -49,12 +54,12 @@ int vfs_statfs(struct super_block *sb, struct kstatfs *buf) EXPORT_SYMBOL(vfs_statfs); -static int vfs_statfs_native(struct super_block *sb, struct statfs *buf) +static int vfs_statfs_native(struct dentry *dentry, struct statfs *buf) { struct kstatfs st; int retval; - retval = vfs_statfs(sb, &st); + retval = vfs_statfs(dentry, &st); if (retval) return retval; @@ -62,7 +67,8 @@ static int vfs_statfs_native(struct super_block *sb, struct statfs *buf) memcpy(buf, &st, sizeof(st)); else { if (sizeof buf->f_blocks == 4) { - if ((st.f_blocks | st.f_bfree | st.f_bavail) & + if ((st.f_blocks | st.f_bfree | st.f_bavail | + st.f_bsize | st.f_frsize) & 0xffffffff00000000ULL) return -EOVERFLOW; /* @@ -92,12 +98,12 @@ static int vfs_statfs_native(struct super_block *sb, struct statfs *buf) return 0; } -static int vfs_statfs64(struct super_block *sb, struct statfs64 *buf) +static int vfs_statfs64(struct dentry *dentry, struct statfs64 *buf) { struct kstatfs st; int retval; - retval = vfs_statfs(sb, &st); + retval = vfs_statfs(dentry, &st); if (retval) return retval; @@ -119,43 +125,41 @@ static int vfs_statfs64(struct super_block *sb, struct statfs64 *buf) return 0; } -asmlinkage long sys_statfs(const char __user * path, struct statfs __user * buf) +SYSCALL_DEFINE2(statfs, const char __user *, pathname, struct statfs __user *, buf) { - struct nameidata nd; + struct path path; int error; - error = user_path_walk(path, &nd); + error = user_path(pathname, &path); if (!error) { struct statfs tmp; - error = vfs_statfs_native(nd.dentry->d_inode->i_sb, &tmp); + error = vfs_statfs_native(path.dentry, &tmp); if (!error && copy_to_user(buf, &tmp, sizeof(tmp))) error = -EFAULT; - path_release(&nd); + path_put(&path); } return error; } - -asmlinkage long sys_statfs64(const char __user *path, size_t sz, struct statfs64 __user *buf) +SYSCALL_DEFINE3(statfs64, const char __user *, pathname, size_t, sz, struct statfs64 __user *, buf) { - struct nameidata nd; + struct path path; long error; if (sz != sizeof(*buf)) return -EINVAL; - error = user_path_walk(path, &nd); + error = user_path(pathname, &path); if (!error) { struct statfs64 tmp; - error = vfs_statfs64(nd.dentry->d_inode->i_sb, &tmp); + error = vfs_statfs64(path.dentry, &tmp); if (!error && copy_to_user(buf, &tmp, sizeof(tmp))) error = -EFAULT; - path_release(&nd); + path_put(&path); } return error; } - -asmlinkage long sys_fstatfs(unsigned int fd, struct statfs __user * buf) +SYSCALL_DEFINE2(fstatfs, unsigned int, fd, struct statfs __user *, buf) { struct file * file; struct statfs tmp; @@ -165,7 +169,7 @@ asmlinkage long sys_fstatfs(unsigned int fd, struct statfs __user * buf) file = fget(fd); if (!file) goto out; - error = vfs_statfs_native(file->f_dentry->d_inode->i_sb, &tmp); + error = vfs_statfs_native(file->f_path.dentry, &tmp); if (!error && copy_to_user(buf, &tmp, sizeof(tmp))) error = -EFAULT; fput(file); @@ -173,7 +177,7 @@ out: return error; } -asmlinkage long sys_fstatfs64(unsigned int fd, size_t sz, struct statfs64 __user *buf) +SYSCALL_DEFINE3(fstatfs64, unsigned int, fd, size_t, sz, struct statfs64 __user *, buf) { struct file * file; struct statfs64 tmp; @@ -186,7 +190,7 @@ asmlinkage long sys_fstatfs64(unsigned int fd, size_t sz, struct statfs64 __user file = fget(fd); if (!file) goto out; - error = vfs_statfs64(file->f_dentry->d_inode->i_sb, &tmp); + error = vfs_statfs64(file->f_path.dentry, &tmp); if (!error && copy_to_user(buf, &tmp, sizeof(tmp))) error = -EFAULT; fput(file); @@ -194,9 +198,10 @@ out: return error; } -int do_truncate(struct dentry *dentry, loff_t length, struct file *filp) +int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, + struct file *filp) { - int err; + int ret; struct iattr newattrs; /* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */ @@ -204,32 +209,37 @@ int do_truncate(struct dentry *dentry, loff_t length, struct file *filp) return -EINVAL; newattrs.ia_size = length; - newattrs.ia_valid = ATTR_SIZE | ATTR_CTIME; + newattrs.ia_valid = ATTR_SIZE | time_attrs; if (filp) { newattrs.ia_file = filp; newattrs.ia_valid |= ATTR_FILE; } - down(&dentry->d_inode->i_sem); - err = notify_change(dentry, &newattrs); - up(&dentry->d_inode->i_sem); - return err; + /* Remove suid/sgid on truncate too */ + ret = should_remove_suid(dentry); + if (ret) + newattrs.ia_valid |= ret | ATTR_FORCE; + + mutex_lock(&dentry->d_inode->i_mutex); + ret = notify_change(dentry, &newattrs); + mutex_unlock(&dentry->d_inode->i_mutex); + return ret; } -static inline long do_sys_truncate(const char __user * path, loff_t length) +static long do_sys_truncate(const char __user *pathname, loff_t length) { - struct nameidata nd; - struct inode * inode; + struct path path; + struct inode *inode; int error; error = -EINVAL; if (length < 0) /* sorry, but loff_t says... */ goto out; - error = user_path_walk(path, &nd); + error = user_path(pathname, &path); if (error) goto out; - inode = nd.dentry->d_inode; + inode = path.dentry->d_inode; /* For directories it's -EISDIR, for other non-regulars - -EINVAL */ error = -EISDIR; @@ -240,49 +250,52 @@ static inline long do_sys_truncate(const char __user * path, loff_t length) if (!S_ISREG(inode->i_mode)) goto dput_and_out; - error = vfs_permission(&nd, MAY_WRITE); + error = mnt_want_write(path.mnt); if (error) goto dput_and_out; - error = -EROFS; - if (IS_RDONLY(inode)) - goto dput_and_out; + error = inode_permission(inode, MAY_WRITE); + if (error) + goto mnt_drop_write_and_out; error = -EPERM; - if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) - goto dput_and_out; + if (IS_APPEND(inode)) + goto mnt_drop_write_and_out; - /* - * Make sure that there are no leases. - */ - error = break_lease(inode, FMODE_WRITE); + error = get_write_access(inode); if (error) - goto dput_and_out; + goto mnt_drop_write_and_out; - error = get_write_access(inode); + /* + * Make sure that there are no leases. get_write_access() protects + * against the truncate racing with a lease-granting setlease(). + */ + error = break_lease(inode, O_WRONLY); if (error) - goto dput_and_out; + goto put_write_and_out; error = locks_verify_truncate(inode, NULL, length); - if (!error) { - DQUOT_INIT(inode); - error = do_truncate(nd.dentry, length, NULL); - } - put_write_access(inode); + if (!error) + error = security_path_truncate(&path, length, 0); + if (!error) + error = do_truncate(path.dentry, length, 0, NULL); +put_write_and_out: + put_write_access(inode); +mnt_drop_write_and_out: + mnt_drop_write(path.mnt); dput_and_out: - path_release(&nd); + path_put(&path); out: return error; } -asmlinkage long sys_truncate(const char __user * path, unsigned long length) +SYSCALL_DEFINE2(truncate, const char __user *, path, long, length) { - /* on 32-bit boxen it will cut the range 2^31--2^32-1 off */ - return do_sys_truncate(path, (long)length); + return do_sys_truncate(path, length); } -static inline long do_sys_ftruncate(unsigned int fd, loff_t length, int small) +static long do_sys_ftruncate(unsigned int fd, loff_t length, int small) { struct inode * inode; struct dentry *dentry; @@ -301,7 +314,7 @@ static inline long do_sys_ftruncate(unsigned int fd, loff_t length, int small) if (file->f_flags & O_LARGEFILE) small = 0; - dentry = file->f_dentry; + dentry = file->f_path.dentry; inode = dentry->d_inode; error = -EINVAL; if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE)) @@ -318,236 +331,224 @@ static inline 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, file); + error = security_path_truncate(&file->f_path, length, + ATTR_MTIME|ATTR_CTIME); + if (!error) + error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, file); out_putf: fput(file); out: return error; } -asmlinkage long sys_ftruncate(unsigned int fd, unsigned long length) +SYSCALL_DEFINE2(ftruncate, unsigned int, fd, unsigned long, length) { - return do_sys_ftruncate(fd, length, 1); + long ret = do_sys_ftruncate(fd, length, 1); + /* avoid REGPARM breakage on x86: */ + asmlinkage_protect(2, ret, fd, length); + return ret; } /* LFS versions of truncate are only needed on 32 bit machines */ #if BITS_PER_LONG == 32 -asmlinkage long sys_truncate64(const char __user * path, loff_t length) +SYSCALL_DEFINE(truncate64)(const char __user * path, loff_t length) { return do_sys_truncate(path, length); } - -asmlinkage long sys_ftruncate64(unsigned int fd, loff_t length) +#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS +asmlinkage long SyS_truncate64(long path, loff_t length) { - return do_sys_ftruncate(fd, length, 0); + return SYSC_truncate64((const char __user *) path, length); } +SYSCALL_ALIAS(sys_truncate64, SyS_truncate64); #endif -#ifdef __ARCH_WANT_SYS_UTIME +SYSCALL_DEFINE(ftruncate64)(unsigned int fd, loff_t length) +{ + long ret = do_sys_ftruncate(fd, length, 0); + /* avoid REGPARM breakage on x86: */ + asmlinkage_protect(2, ret, fd, length); + return ret; +} +#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS +asmlinkage long SyS_ftruncate64(long fd, loff_t length) +{ + return SYSC_ftruncate64((unsigned int) fd, length); +} +SYSCALL_ALIAS(sys_ftruncate64, SyS_ftruncate64); +#endif +#endif /* BITS_PER_LONG == 32 */ -/* - * sys_utime() can be implemented in user-level using sys_utimes(). - * Is this for backwards compatibility? If so, why not move it - * into the appropriate arch directory (for those architectures that - * need it). - */ -/* 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. - */ -asmlinkage long sys_utime(char __user * filename, struct utimbuf __user * times) +int do_fallocate(struct file *file, int mode, loff_t offset, loff_t len) { - int error; - struct nameidata nd; - struct inode * inode; - struct iattr newattrs; + struct inode *inode = file->f_path.dentry->d_inode; + long ret; - error = user_path_walk(filename, &nd); - if (error) - goto out; - inode = nd.dentry->d_inode; - - error = -EROFS; - if (IS_RDONLY(inode)) - goto dput_and_out; + if (offset < 0 || len <= 0) + return -EINVAL; - /* 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 dput_and_out; - - error = get_user(newattrs.ia_atime.tv_sec, ×->actime); - newattrs.ia_atime.tv_nsec = 0; - if (!error) - error = get_user(newattrs.ia_mtime.tv_sec, ×->modtime); - newattrs.ia_mtime.tv_nsec = 0; - if (error) - goto dput_and_out; + /* Return error if mode is not supported */ + if (mode && !(mode & FALLOC_FL_KEEP_SIZE)) + return -EOPNOTSUPP; - newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; - } else { - error = -EACCES; - if (IS_IMMUTABLE(inode)) - goto dput_and_out; + if (!(file->f_mode & FMODE_WRITE)) + return -EBADF; + /* + * Revalidate the write permissions, in case security policy has + * changed since the files were opened. + */ + ret = security_file_permission(file, MAY_WRITE); + if (ret) + return ret; - if (current->fsuid != inode->i_uid && - (error = vfs_permission(&nd, MAY_WRITE)) != 0) - goto dput_and_out; - } - down(&inode->i_sem); - error = notify_change(nd.dentry, &newattrs); - up(&inode->i_sem); -dput_and_out: - path_release(&nd); -out: - return error; -} + if (S_ISFIFO(inode->i_mode)) + return -ESPIPE; -#endif + /* + * Let individual file system decide if it supports preallocation + * for directories or not. + */ + if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) + return -ENODEV; -/* 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(char __user * filename, struct timeval * times) -{ - int error; - struct nameidata nd; - struct inode * inode; - struct iattr newattrs; + /* Check for wrap through zero too */ + if (((offset + len) > inode->i_sb->s_maxbytes) || ((offset + len) < 0)) + return -EFBIG; - error = user_path_walk(filename, &nd); + if (!inode->i_op->fallocate) + return -EOPNOTSUPP; - if (error) - goto out; - inode = nd.dentry->d_inode; + return inode->i_op->fallocate(inode, mode, offset, len); +} - error = -EROFS; - if (IS_RDONLY(inode)) - goto dput_and_out; +SYSCALL_DEFINE(fallocate)(int fd, int mode, loff_t offset, loff_t len) +{ + struct file *file; + int error = -EBADF; - /* 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 dput_and_out; - - newattrs.ia_atime.tv_sec = times[0].tv_sec; - newattrs.ia_atime.tv_nsec = times[0].tv_usec * 1000; - newattrs.ia_mtime.tv_sec = times[1].tv_sec; - newattrs.ia_mtime.tv_nsec = times[1].tv_usec * 1000; - newattrs.ia_valid |= ATTR_ATIME_SET | ATTR_MTIME_SET; - } else { - error = -EACCES; - if (IS_IMMUTABLE(inode)) - goto dput_and_out; - - if (current->fsuid != inode->i_uid && - (error = vfs_permission(&nd, MAY_WRITE)) != 0) - goto dput_and_out; + file = fget(fd); + if (file) { + error = do_fallocate(file, mode, offset, len); + fput(file); } - down(&inode->i_sem); - error = notify_change(nd.dentry, &newattrs); - up(&inode->i_sem); -dput_and_out: - path_release(&nd); -out: + return error; } -asmlinkage long sys_utimes(char __user * filename, struct timeval __user * utimes) +#ifdef CONFIG_HAVE_SYSCALL_WRAPPERS +asmlinkage long SyS_fallocate(long fd, long mode, loff_t offset, loff_t len) { - struct timeval times[2]; - - if (utimes && copy_from_user(×, utimes, sizeof(times))) - return -EFAULT; - return do_utimes(filename, utimes ? times : NULL); + return SYSC_fallocate((int)fd, (int)mode, offset, len); } - +SYSCALL_ALIAS(sys_fallocate, SyS_fallocate); +#endif /* * access() needs to use the real uid/gid, not the effective uid/gid. * We do this by temporarily clearing all FS-related capabilities and * switching the fsuid/fsgid around to the real ones. */ -asmlinkage long sys_access(const char __user * filename, int mode) +SYSCALL_DEFINE3(faccessat, int, dfd, const char __user *, filename, int, mode) { - struct nameidata nd; - int old_fsuid, old_fsgid; - kernel_cap_t old_cap; + const struct cred *old_cred; + struct cred *override_cred; + struct path path; + struct inode *inode; int res; if (mode & ~S_IRWXO) /* where's F_OK, X_OK, W_OK, R_OK? */ return -EINVAL; - old_fsuid = current->fsuid; - old_fsgid = current->fsgid; - old_cap = current->cap_effective; + override_cred = prepare_creds(); + if (!override_cred) + return -ENOMEM; + + override_cred->fsuid = override_cred->uid; + override_cred->fsgid = override_cred->gid; - current->fsuid = current->uid; - current->fsgid = current->gid; + if (!issecure(SECURE_NO_SETUID_FIXUP)) { + /* Clear the capabilities if we switch to a non-root user */ + if (override_cred->uid) + cap_clear(override_cred->cap_effective); + else + override_cred->cap_effective = + override_cred->cap_permitted; + } + + old_cred = override_creds(override_cred); + + res = user_path_at(dfd, filename, LOOKUP_FOLLOW, &path); + if (res) + goto out; + inode = path.dentry->d_inode; + + if ((mode & MAY_EXEC) && S_ISREG(inode->i_mode)) { + /* + * MAY_EXEC on regular files is denied if the fs is mounted + * with the "noexec" flag. + */ + res = -EACCES; + if (path.mnt->mnt_flags & MNT_NOEXEC) + goto out_path_release; + } + + res = inode_permission(inode, mode | MAY_ACCESS); + /* SuS v2 requires we report a read only fs too */ + if (res || !(mode & S_IWOTH) || special_file(inode->i_mode)) + goto out_path_release; /* - * Clear the capabilities if we switch to a non-root user + * This is a rare case where using __mnt_is_readonly() + * is OK without a mnt_want/drop_write() pair. Since + * no actual write to the fs is performed here, we do + * not need to telegraph to that to anyone. * - * FIXME: There is a race here against sys_capset. The - * capabilities can change yet we will restore the old - * value below. We should hold task_capabilities_lock, - * but we cannot because user_path_walk can sleep. + * By doing this, we accept that this access is + * inherently racy and know that the fs may change + * state before we even see this result. */ - if (current->uid) - cap_clear(current->cap_effective); - else - current->cap_effective = current->cap_permitted; - - res = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_ACCESS, &nd); - if (!res) { - res = vfs_permission(&nd, mode); - /* SuS v2 requires we report a read only fs too */ - if(!res && (mode & S_IWOTH) && IS_RDONLY(nd.dentry->d_inode) - && !special_file(nd.dentry->d_inode->i_mode)) - res = -EROFS; - path_release(&nd); - } - - current->fsuid = old_fsuid; - current->fsgid = old_fsgid; - current->cap_effective = old_cap; + if (__mnt_is_readonly(path.mnt)) + res = -EROFS; +out_path_release: + path_put(&path); +out: + revert_creds(old_cred); + put_cred(override_cred); return res; } -asmlinkage long sys_chdir(const char __user * filename) +SYSCALL_DEFINE2(access, const char __user *, filename, int, mode) +{ + return sys_faccessat(AT_FDCWD, filename, mode); +} + +SYSCALL_DEFINE1(chdir, const char __user *, filename) { - struct nameidata nd; + struct path path; int error; - error = __user_walk(filename, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, &nd); + error = user_path_dir(filename, &path); if (error) goto out; - error = vfs_permission(&nd, MAY_EXEC); + error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_ACCESS); if (error) goto dput_and_out; - set_fs_pwd(current->fs, nd.mnt, nd.dentry); + set_fs_pwd(current->fs, &path); dput_and_out: - path_release(&nd); + path_put(&path); out: return error; } -asmlinkage long sys_fchdir(unsigned int fd) +SYSCALL_DEFINE1(fchdir, unsigned int, fd) { struct file *file; - struct dentry *dentry; struct inode *inode; - struct vfsmount *mnt; int error; error = -EBADF; @@ -555,50 +556,50 @@ asmlinkage long sys_fchdir(unsigned int fd) if (!file) goto out; - dentry = file->f_dentry; - mnt = file->f_vfsmnt; - inode = dentry->d_inode; + inode = file->f_path.dentry->d_inode; error = -ENOTDIR; if (!S_ISDIR(inode->i_mode)) goto out_putf; - error = file_permission(file, MAY_EXEC); + error = inode_permission(inode, MAY_EXEC | MAY_ACCESS); if (!error) - set_fs_pwd(current->fs, mnt, dentry); + set_fs_pwd(current->fs, &file->f_path); out_putf: fput(file); out: return error; } -asmlinkage long sys_chroot(const char __user * filename) +SYSCALL_DEFINE1(chroot, const char __user *, filename) { - struct nameidata nd; + struct path path; int error; - error = __user_walk(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY | LOOKUP_NOALT, &nd); + error = user_path_dir(filename, &path); if (error) goto out; - error = vfs_permission(&nd, MAY_EXEC); + error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_ACCESS); if (error) goto dput_and_out; error = -EPERM; if (!capable(CAP_SYS_CHROOT)) goto dput_and_out; + error = security_path_chroot(&path); + if (error) + goto dput_and_out; - set_fs_root(current->fs, nd.mnt, nd.dentry); - set_fs_altroot(); + set_fs_root(current->fs, &path); error = 0; dput_and_out: - path_release(&nd); + path_put(&path); out: return error; } -asmlinkage long sys_fchmod(unsigned int fd, mode_t mode) +SYSCALL_DEFINE2(fchmod, unsigned int, fd, mode_t, mode) { struct inode * inode; struct dentry * dentry; @@ -610,80 +611,76 @@ asmlinkage long sys_fchmod(unsigned int fd, mode_t mode) if (!file) goto out; - dentry = file->f_dentry; + dentry = file->f_path.dentry; inode = dentry->d_inode; - err = -EROFS; - if (IS_RDONLY(inode)) - goto out_putf; - err = -EPERM; - if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) + audit_inode(NULL, dentry); + + err = mnt_want_write_file(file); + if (err) goto out_putf; - down(&inode->i_sem); + mutex_lock(&inode->i_mutex); + err = security_path_chmod(dentry, file->f_vfsmnt, mode); + if (err) + goto out_unlock; if (mode == (mode_t) -1) mode = inode->i_mode; newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; err = notify_change(dentry, &newattrs); - up(&inode->i_sem); - +out_unlock: + mutex_unlock(&inode->i_mutex); + mnt_drop_write(file->f_path.mnt); out_putf: fput(file); out: return err; } -asmlinkage long sys_chmod(const char __user * filename, mode_t mode) +SYSCALL_DEFINE3(fchmodat, int, dfd, const char __user *, filename, mode_t, mode) { - struct nameidata nd; - struct inode * inode; + struct path path; + struct inode *inode; int error; struct iattr newattrs; - error = user_path_walk(filename, &nd); + error = user_path_at(dfd, filename, LOOKUP_FOLLOW, &path); if (error) goto out; - inode = nd.dentry->d_inode; - - error = -EROFS; - if (IS_RDONLY(inode)) - goto dput_and_out; + inode = path.dentry->d_inode; - error = -EPERM; - if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) + error = mnt_want_write(path.mnt); + if (error) goto dput_and_out; - - down(&inode->i_sem); + mutex_lock(&inode->i_mutex); + error = security_path_chmod(path.dentry, path.mnt, mode); + if (error) + goto out_unlock; if (mode == (mode_t) -1) mode = inode->i_mode; newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; - error = notify_change(nd.dentry, &newattrs); - up(&inode->i_sem); - + error = notify_change(path.dentry, &newattrs); +out_unlock: + mutex_unlock(&inode->i_mutex); + mnt_drop_write(path.mnt); dput_and_out: - path_release(&nd); + path_put(&path); out: return error; } -static int chown_common(struct dentry * dentry, uid_t user, gid_t group) +SYSCALL_DEFINE2(chmod, const char __user *, filename, mode_t, mode) { - struct inode * inode; + return sys_fchmodat(AT_FDCWD, filename, mode); +} + +static int chown_common(struct path *path, uid_t user, gid_t group) +{ + struct inode *inode = path->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 = -EROFS; - if (IS_RDONLY(inode)) - 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; @@ -694,78 +691,162 @@ static int chown_common(struct dentry * dentry, uid_t user, gid_t group) newattrs.ia_gid = group; } if (!S_ISDIR(inode->i_mode)) - newattrs.ia_valid |= ATTR_KILL_SUID|ATTR_KILL_SGID; - down(&inode->i_sem); - error = notify_change(dentry, &newattrs); - up(&inode->i_sem); -out: + newattrs.ia_valid |= + ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV; + mutex_lock(&inode->i_mutex); + error = security_path_chown(path, user, group); + if (!error) + error = notify_change(path->dentry, &newattrs); + mutex_unlock(&inode->i_mutex); + return error; } -asmlinkage long sys_chown(const char __user * filename, uid_t user, gid_t group) +SYSCALL_DEFINE3(chown, const char __user *, filename, uid_t, user, gid_t, group) { - struct nameidata nd; + struct path path; int error; - error = user_path_walk(filename, &nd); - if (!error) { - error = chown_common(nd.dentry, user, group); - path_release(&nd); - } + error = user_path(filename, &path); + if (error) + goto out; + error = mnt_want_write(path.mnt); + if (error) + goto out_release; + error = chown_common(&path, user, group); + mnt_drop_write(path.mnt); +out_release: + path_put(&path); +out: return error; } -asmlinkage long sys_lchown(const char __user * filename, uid_t user, gid_t group) +SYSCALL_DEFINE5(fchownat, int, dfd, const char __user *, filename, uid_t, user, + gid_t, group, int, flag) { - struct nameidata nd; - int error; + struct path path; + int error = -EINVAL; + int follow; - error = user_path_walk_link(filename, &nd); - if (!error) { - error = chown_common(nd.dentry, user, group); - path_release(&nd); - } + if ((flag & ~AT_SYMLINK_NOFOLLOW) != 0) + goto out; + + follow = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; + error = user_path_at(dfd, filename, follow, &path); + if (error) + goto out; + error = mnt_want_write(path.mnt); + if (error) + goto out_release; + error = chown_common(&path, user, group); + mnt_drop_write(path.mnt); +out_release: + path_put(&path); +out: return error; } +SYSCALL_DEFINE3(lchown, const char __user *, filename, uid_t, user, gid_t, group) +{ + struct path path; + int error; + + error = user_lpath(filename, &path); + if (error) + goto out; + error = mnt_want_write(path.mnt); + if (error) + goto out_release; + error = chown_common(&path, user, group); + mnt_drop_write(path.mnt); +out_release: + path_put(&path); +out: + return error; +} -asmlinkage long sys_fchown(unsigned int fd, uid_t user, gid_t group) +SYSCALL_DEFINE3(fchown, unsigned int, fd, uid_t, user, gid_t, group) { struct file * file; int error = -EBADF; + struct dentry * dentry; file = fget(fd); - if (file) { - error = chown_common(file->f_dentry, user, group); - fput(file); + if (!file) + goto out; + + error = mnt_want_write_file(file); + if (error) + goto out_fput; + dentry = file->f_path.dentry; + audit_inode(NULL, dentry); + error = chown_common(&file->f_path, user, group); + mnt_drop_write(file->f_path.mnt); +out_fput: + fput(file); +out: + return error; +} + +/* + * You have to be very careful that these write + * counts get cleaned up in error cases and + * upon __fput(). This should probably never + * be called outside of __dentry_open(). + */ +static inline int __get_file_write_access(struct inode *inode, + struct vfsmount *mnt) +{ + int error; + error = get_write_access(inode); + if (error) + return error; + /* + * Do not take mount writer counts on + * special files since no writes to + * the mount itself will occur. + */ + if (!special_file(inode->i_mode)) { + /* + * Balanced in __fput() + */ + error = mnt_want_write(mnt); + if (error) + put_write_access(inode); } return error; } static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, - int flags, struct file *f, - int (*open)(struct inode *, struct file *)) + struct file *f, + int (*open)(struct inode *, struct file *), + const struct cred *cred) { struct inode *inode; int error; - f->f_flags = flags; - f->f_mode = ((flags+1) & O_ACCMODE) | FMODE_LSEEK | + f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE; inode = dentry->d_inode; if (f->f_mode & FMODE_WRITE) { - error = get_write_access(inode); + error = __get_file_write_access(inode, mnt); if (error) goto cleanup_file; + if (!special_file(inode->i_mode)) + file_take_write(f); } f->f_mapping = inode->i_mapping; - f->f_dentry = dentry; - f->f_vfsmnt = mnt; + f->f_path.dentry = dentry; + f->f_path.mnt = mnt; f->f_pos = 0; f->f_op = fops_get(inode->i_fop); file_move(f, &inode->i_sb->s_files); + error = security_dentry_open(f, cred); + if (error) + goto cleanup_all; + if (!open && f->f_op) open = f->f_op->open; if (open) { @@ -773,6 +854,7 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, if (error) goto cleanup_all; } + ima_counts_get(f); f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); @@ -782,7 +864,7 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, if (f->f_flags & O_DIRECT) { if (!f->f_mapping->a_ops || ((!f->f_mapping->a_ops->direct_IO) && - (!f->f_mapping->a_ops->get_xip_page))) { + (!f->f_mapping->a_ops->get_xip_mem))) { fput(f); f = ERR_PTR(-EINVAL); } @@ -792,11 +874,22 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt, cleanup_all: fops_put(f->f_op); - if (f->f_mode & FMODE_WRITE) + if (f->f_mode & FMODE_WRITE) { put_write_access(inode); + if (!special_file(inode->i_mode)) { + /* + * We don't consider this a real + * mnt_want/drop_write() pair + * because it all happenend right + * here, so just reset the state. + */ + file_reset_write(f); + mnt_drop_write(mnt); + } + } file_kill(f); - f->f_dentry = NULL; - f->f_vfsmnt = NULL; + f->f_path.dentry = NULL; + f->f_path.mnt = NULL; cleanup_file: put_filp(f); dput(dentry); @@ -804,37 +897,6 @@ cleanup_file: return ERR_PTR(error); } -/* - * Note that while the flag value (low two bits) for sys_open means: - * 00 - read-only - * 01 - write-only - * 10 - read-write - * 11 - special - * it is changed into - * 00 - no permissions needed - * 01 - read-permission - * 10 - write-permission - * 11 - read-write - * for the internal routines (ie open_namei()/follow_link() etc). 00 is - * used by symlinks. - */ -struct file *filp_open(const char * filename, int flags, int mode) -{ - int namei_flags, error; - struct nameidata nd; - - namei_flags = flags; - if ((namei_flags+1) & O_ACCMODE) - namei_flags++; - - error = open_namei(filename, namei_flags, mode, &nd); - if (!error) - return nameidata_to_filp(&nd, flags); - - return ERR_PTR(error); -} -EXPORT_SYMBOL(filp_open); - /** * lookup_instantiate_filp - instantiates the open intent filp * @nd: pointer to nameidata @@ -845,6 +907,10 @@ EXPORT_SYMBOL(filp_open); * a fully instantiated struct file to the caller. * This function is meant to be called from within a filesystem's * lookup method. + * Beware of calling it for non-regular files! Those ->open methods might block + * (e.g. in fifo_open), leaving you with parent locked (and in case of fifo, + * leading to a deadlock, as nobody can open that fifo anymore, because + * another process to open fifo will block on locked parent when doing lookup). * Note that in case of error, nd->intent.open.file is destroyed, but the * path information remains valid. * If the open callback is set to NULL, then the standard f_op->open() @@ -853,14 +919,15 @@ EXPORT_SYMBOL(filp_open); struct file *lookup_instantiate_filp(struct nameidata *nd, struct dentry *dentry, int (*open)(struct inode *, struct file *)) { + const struct cred *cred = current_cred(); + if (IS_ERR(nd->intent.open.file)) goto out; if (IS_ERR(dentry)) goto out_err; - nd->intent.open.file = __dentry_open(dget(dentry), mntget(nd->mnt), - nd->intent.open.flags - 1, + nd->intent.open.file = __dentry_open(dget(dentry), mntget(nd->path.mnt), nd->intent.open.file, - open); + open, cred); out: return nd->intent.open.file; out_err: @@ -877,17 +944,19 @@ EXPORT_SYMBOL_GPL(lookup_instantiate_filp); * * Note that this function destroys the original nameidata */ -struct file *nameidata_to_filp(struct nameidata *nd, int flags) +struct file *nameidata_to_filp(struct nameidata *nd) { + const struct cred *cred = current_cred(); struct file *filp; /* Pick up the filp from the open intent */ filp = nd->intent.open.file; /* Has the filesystem initialised the file for us? */ - if (filp->f_dentry == NULL) - filp = __dentry_open(nd->dentry, nd->mnt, flags, filp, NULL); + if (filp->f_path.dentry == NULL) + filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp, + NULL, cred); else - path_release(nd); + path_put(&nd->path); return filp; } @@ -895,11 +964,26 @@ struct file *nameidata_to_filp(struct nameidata *nd, int flags) * dentry_open() will have done dput(dentry) and mntput(mnt) if it returns an * error. */ -struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags) +struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags, + const struct cred *cred) { int error; struct file *f; + validate_creds(cred); + + /* + * We must always pass in a valid mount pointer. Historically + * callers got away with not passing it, but we must enforce this at + * the earliest possible point now to avoid strange problems deep in the + * filesystem stack. + */ + if (!mnt) { + printk(KERN_WARNING "%s called with NULL vfsmount\n", __func__); + dump_stack(); + return ERR_PTR(-EINVAL); + } + error = -ENFILE; f = get_empty_filp(); if (f == NULL) { @@ -908,77 +992,20 @@ struct file *dentry_open(struct dentry *dentry, struct vfsmount *mnt, int flags) return ERR_PTR(error); } - return __dentry_open(dentry, mnt, flags, f, NULL); + f->f_flags = flags; + return __dentry_open(dentry, mnt, f, NULL, cred); } EXPORT_SYMBOL(dentry_open); -/* - * Find an empty file descriptor entry, and mark it busy. - */ -int get_unused_fd(void) -{ - struct files_struct * files = current->files; - int fd, error; - struct fdtable *fdt; - - error = -EMFILE; - spin_lock(&files->file_lock); - -repeat: - fdt = files_fdtable(files); - fd = find_next_zero_bit(fdt->open_fds->fds_bits, - fdt->max_fdset, - fdt->next_fd); - - /* - * N.B. For clone tasks sharing a files structure, this test - * will limit the total number of files that can be opened. - */ - if (fd >= current->signal->rlim[RLIMIT_NOFILE].rlim_cur) - goto out; - - /* Do we need to expand the fd array or fd set? */ - error = expand_files(files, fd); - if (error < 0) - goto out; - - if (error) { - /* - * If we needed to expand the fs array we - * might have blocked - try again. - */ - error = -EMFILE; - goto repeat; - } - - FD_SET(fd, fdt->open_fds); - FD_CLR(fd, fdt->close_on_exec); - fdt->next_fd = fd + 1; -#if 1 - /* Sanity check */ - if (fdt->fd[fd] != NULL) { - printk(KERN_WARNING "get_unused_fd: slot %d not NULL!\n", fd); - fdt->fd[fd] = NULL; - } -#endif - error = fd; - -out: - spin_unlock(&files->file_lock); - return error; -} - -EXPORT_SYMBOL(get_unused_fd); - -static inline void __put_unused_fd(struct files_struct *files, unsigned int fd) +static void __put_unused_fd(struct files_struct *files, unsigned int fd) { struct fdtable *fdt = files_fdtable(files); __FD_CLR(fd, fdt->open_fds); - if (fd < fdt->next_fd) - fdt->next_fd = fd; + if (fd < files->next_fd) + files->next_fd = fd; } -void fastcall put_unused_fd(unsigned int fd) +void put_unused_fd(unsigned int fd) { struct files_struct *files = current->files; spin_lock(&files->file_lock); @@ -989,7 +1016,7 @@ void fastcall put_unused_fd(unsigned int fd) EXPORT_SYMBOL(put_unused_fd); /* - * Install a file pointer in the fd array. + * Install a file pointer in the fd array. * * The VFS is full of places where we drop the files lock between * setting the open_fds bitmap and installing the file in the file @@ -1001,7 +1028,7 @@ EXPORT_SYMBOL(put_unused_fd); * will follow. */ -void fastcall fd_install(unsigned int fd, struct file * file) +void fd_install(unsigned int fd, struct file *file) { struct files_struct *files = current->files; struct fdtable *fdt; @@ -1014,20 +1041,20 @@ void fastcall fd_install(unsigned int fd, struct file * file) EXPORT_SYMBOL(fd_install); -long do_sys_open(const char __user *filename, int flags, int mode) +long do_sys_open(int dfd, const char __user *filename, int flags, int mode) { char *tmp = getname(filename); int fd = PTR_ERR(tmp); if (!IS_ERR(tmp)) { - fd = get_unused_fd(); + fd = get_unused_fd_flags(flags); if (fd >= 0) { - struct file *f = filp_open(tmp, flags, mode); + struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); if (IS_ERR(f)) { put_unused_fd(fd); fd = PTR_ERR(f); } else { - fsnotify_open(f->f_dentry); + fsnotify_open(f->f_path.dentry); fd_install(fd, f); } } @@ -1036,14 +1063,32 @@ long do_sys_open(const char __user *filename, int flags, int mode) return fd; } -asmlinkage long sys_open(const char __user *filename, int flags, int mode) +SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode) { + long ret; + if (force_o_largefile()) flags |= O_LARGEFILE; - return do_sys_open(filename, flags, mode); + ret = do_sys_open(AT_FDCWD, filename, flags, mode); + /* avoid REGPARM breakage on x86: */ + asmlinkage_protect(3, ret, filename, flags, mode); + return ret; +} + +SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, + int, mode) +{ + long ret; + + if (force_o_largefile()) + flags |= O_LARGEFILE; + + ret = do_sys_open(dfd, filename, flags, mode); + /* avoid REGPARM breakage on x86: */ + asmlinkage_protect(4, ret, dfd, filename, flags, mode); + return ret; } -EXPORT_SYMBOL_GPL(sys_open); #ifndef __alpha__ @@ -1051,7 +1096,7 @@ EXPORT_SYMBOL_GPL(sys_open); * For backward compatibility? Maybe this should be moved * into arch/i386 instead? */ -asmlinkage long sys_creat(const char __user * pathname, int mode) +SYSCALL_DEFINE2(creat, const char __user *, pathname, int, mode) { return sys_open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode); } @@ -1072,7 +1117,7 @@ int filp_close(struct file *filp, fl_owner_t id) } if (filp->f_op && filp->f_op->flush) - retval = filp->f_op->flush(filp); + retval = filp->f_op->flush(filp, id); dnotify_flush(filp, id); locks_remove_posix(filp, id); @@ -1087,11 +1132,12 @@ EXPORT_SYMBOL(filp_close); * releasing the fd. This ensures that one clone task can't release * an fd while another clone is opening it. */ -asmlinkage long sys_close(unsigned int fd) +SYSCALL_DEFINE1(close, unsigned int, fd) { struct file * filp; struct files_struct *files = current->files; struct fdtable *fdt; + int retval; spin_lock(&files->file_lock); fdt = files_fdtable(files); @@ -1104,23 +1150,31 @@ asmlinkage long sys_close(unsigned int fd) FD_CLR(fd, fdt->close_on_exec); __put_unused_fd(files, fd); spin_unlock(&files->file_lock); - return filp_close(filp, files); + retval = filp_close(filp, files); + + /* can't restart close syscall because file table entry was cleared */ + if (unlikely(retval == -ERESTARTSYS || + retval == -ERESTARTNOINTR || + retval == -ERESTARTNOHAND || + retval == -ERESTART_RESTARTBLOCK)) + retval = -EINTR; + + return retval; out_unlock: spin_unlock(&files->file_lock); return -EBADF; } - EXPORT_SYMBOL(sys_close); /* * This routine simulates a hangup on the tty, to arrange that users * are given clean terminals at login time. */ -asmlinkage long sys_vhangup(void) +SYSCALL_DEFINE0(vhangup) { if (capable(CAP_SYS_TTY_CONFIG)) { - tty_vhangup(current->signal->tty); + tty_vhangup_self(); return 0; } return -EPERM; @@ -1135,7 +1189,7 @@ asmlinkage long sys_vhangup(void) int generic_file_open(struct inode * inode, struct file * filp) { if (!(filp->f_flags & O_LARGEFILE) && i_size_read(inode) > MAX_NON_LFS) - return -EFBIG; + return -EOVERFLOW; return 0; }