diff options
Diffstat (limited to 'fs/open.c')
| -rw-r--r-- | fs/open.c | 137 | 
1 files changed, 128 insertions, 9 deletions
diff --git a/fs/open.c b/fs/open.c index 5a2c6ebc22b5..f83ca80cc59a 100644 --- a/fs/open.c +++ b/fs/open.c @@ -233,6 +233,14 @@ int do_fallocate(struct file *file, int mode, loff_t offset, loff_t len)  	if (!(file->f_mode & FMODE_WRITE))  		return -EBADF; + +	/* It's not possible punch hole on append only file */ +	if (mode & FALLOC_FL_PUNCH_HOLE && IS_APPEND(inode)) +		return -EPERM; + +	if (IS_IMMUTABLE(inode)) +		return -EPERM; +  	/*  	 * Revalidate the write permissions, in case security policy has  	 * changed since the files were opened. @@ -565,13 +573,15 @@ SYSCALL_DEFINE5(fchownat, int, dfd, const char __user *, filename, uid_t, user,  {  	struct path path;  	int error = -EINVAL; -	int follow; +	int lookup_flags; -	if ((flag & ~AT_SYMLINK_NOFOLLOW) != 0) +	if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)  		goto out; -	follow = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; -	error = user_path_at(dfd, filename, follow, &path); +	lookup_flags = (flag & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW; +	if (flag & AT_EMPTY_PATH) +		lookup_flags |= LOOKUP_EMPTY; +	error = user_path_at(dfd, filename, lookup_flags, &path);  	if (error)  		goto out;  	error = mnt_want_write(path.mnt); @@ -661,11 +671,16 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,  					int (*open)(struct inode *, struct file *),  					const struct cred *cred)  { +	static const struct file_operations empty_fops = {};  	struct inode *inode;  	int error;  	f->f_mode = OPEN_FMODE(f->f_flags) | FMODE_LSEEK |  				FMODE_PREAD | FMODE_PWRITE; + +	if (unlikely(f->f_flags & O_PATH)) +		f->f_mode = FMODE_PATH; +  	inode = dentry->d_inode;  	if (f->f_mode & FMODE_WRITE) {  		error = __get_file_write_access(inode, mnt); @@ -679,9 +694,15 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,  	f->f_path.dentry = dentry;  	f->f_path.mnt = mnt;  	f->f_pos = 0; -	f->f_op = fops_get(inode->i_fop);  	file_sb_list_add(f, inode->i_sb); +	if (unlikely(f->f_mode & FMODE_PATH)) { +		f->f_op = &empty_fops; +		return f; +	} + +	f->f_op = fops_get(inode->i_fop); +  	error = security_dentry_open(f, cred);  	if (error)  		goto cleanup_all; @@ -693,7 +714,8 @@ static struct file *__dentry_open(struct dentry *dentry, struct vfsmount *mnt,  		if (error)  			goto cleanup_all;  	} -	ima_counts_get(f); +	if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) +		i_readcount_inc(inode);  	f->f_flags &= ~(O_CREAT | O_EXCL | O_NOCTTY | O_TRUNC); @@ -882,15 +904,110 @@ void fd_install(unsigned int fd, struct file *file)  EXPORT_SYMBOL(fd_install); +static inline int build_open_flags(int flags, int mode, struct open_flags *op) +{ +	int lookup_flags = 0; +	int acc_mode; + +	if (!(flags & O_CREAT)) +		mode = 0; +	op->mode = mode; + +	/* Must never be set by userspace */ +	flags &= ~FMODE_NONOTIFY; + +	/* +	 * O_SYNC is implemented as __O_SYNC|O_DSYNC.  As many places only +	 * check for O_DSYNC if the need any syncing at all we enforce it's +	 * always set instead of having to deal with possibly weird behaviour +	 * for malicious applications setting only __O_SYNC. +	 */ +	if (flags & __O_SYNC) +		flags |= O_DSYNC; + +	/* +	 * If we have O_PATH in the open flag. Then we +	 * cannot have anything other than the below set of flags +	 */ +	if (flags & O_PATH) { +		flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH; +		acc_mode = 0; +	} else { +		acc_mode = MAY_OPEN | ACC_MODE(flags); +	} + +	op->open_flag = flags; + +	/* O_TRUNC implies we need access checks for write permissions */ +	if (flags & O_TRUNC) +		acc_mode |= MAY_WRITE; + +	/* Allow the LSM permission hook to distinguish append +	   access from general write access. */ +	if (flags & O_APPEND) +		acc_mode |= MAY_APPEND; + +	op->acc_mode = acc_mode; + +	op->intent = flags & O_PATH ? 0 : LOOKUP_OPEN; + +	if (flags & O_CREAT) { +		op->intent |= LOOKUP_CREATE; +		if (flags & O_EXCL) +			op->intent |= LOOKUP_EXCL; +	} + +	if (flags & O_DIRECTORY) +		lookup_flags |= LOOKUP_DIRECTORY; +	if (!(flags & O_NOFOLLOW)) +		lookup_flags |= LOOKUP_FOLLOW; +	return lookup_flags; +} + +/** + * filp_open - open file and return file pointer + * + * @filename:	path to open + * @flags:	open flags as per the open(2) second argument + * @mode:	mode for the new file if O_CREAT is set, else ignored + * + * This is the helper to open a file from kernelspace if you really + * have to.  But in generally you should not do this, so please move + * along, nothing to see here.. + */ +struct file *filp_open(const char *filename, int flags, int mode) +{ +	struct open_flags op; +	int lookup = build_open_flags(flags, mode, &op); +	return do_filp_open(AT_FDCWD, filename, &op, lookup); +} +EXPORT_SYMBOL(filp_open); + +struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt, +			    const char *filename, int flags) +{ +	struct open_flags op; +	int lookup = build_open_flags(flags, 0, &op); +	if (flags & O_CREAT) +		return ERR_PTR(-EINVAL); +	if (!filename && (flags & O_DIRECTORY)) +		if (!dentry->d_inode->i_op->lookup) +			return ERR_PTR(-ENOTDIR); +	return do_file_open_root(dentry, mnt, filename, &op, lookup); +} +EXPORT_SYMBOL(file_open_root); +  long do_sys_open(int dfd, const char __user *filename, int flags, int mode)  { +	struct open_flags op; +	int lookup = build_open_flags(flags, mode, &op);  	char *tmp = getname(filename);  	int fd = PTR_ERR(tmp);  	if (!IS_ERR(tmp)) {  		fd = get_unused_fd_flags(flags);  		if (fd >= 0) { -			struct file *f = do_filp_open(dfd, tmp, flags, mode, 0); +			struct file *f = do_filp_open(dfd, tmp, &op, lookup);  			if (IS_ERR(f)) {  				put_unused_fd(fd);  				fd = PTR_ERR(f); @@ -960,8 +1077,10 @@ int filp_close(struct file *filp, fl_owner_t id)  	if (filp->f_op && filp->f_op->flush)  		retval = filp->f_op->flush(filp, id); -	dnotify_flush(filp, id); -	locks_remove_posix(filp, id); +	if (likely(!(filp->f_mode & FMODE_PATH))) { +		dnotify_flush(filp, id); +		locks_remove_posix(filp, id); +	}  	fput(filp);  	return retval;  }  | 
