diff options
Diffstat (limited to 'fs/open.c')
| -rw-r--r-- | fs/open.c | 146 | 
1 files changed, 112 insertions, 34 deletions
diff --git a/fs/open.c b/fs/open.c index b62f5c0923a8..0788b3715731 100644 --- a/fs/open.c +++ b/fs/open.c @@ -955,48 +955,83 @@ struct file *open_with_fake_path(const struct path *path, int flags,  }  EXPORT_SYMBOL(open_with_fake_path); -static inline int build_open_flags(int flags, umode_t mode, struct open_flags *op) +#define WILL_CREATE(flags)	(flags & (O_CREAT | __O_TMPFILE)) +#define O_PATH_FLAGS		(O_DIRECTORY | O_NOFOLLOW | O_PATH | O_CLOEXEC) + +inline struct open_how build_open_how(int flags, umode_t mode) +{ +	struct open_how how = { +		.flags = flags & VALID_OPEN_FLAGS, +		.mode = mode & S_IALLUGO, +	}; + +	/* O_PATH beats everything else. */ +	if (how.flags & O_PATH) +		how.flags &= O_PATH_FLAGS; +	/* Modes should only be set for create-like flags. */ +	if (!WILL_CREATE(how.flags)) +		how.mode = 0; +	return how; +} + +inline int build_open_flags(const struct open_how *how, struct open_flags *op)  { +	int flags = how->flags;  	int lookup_flags = 0;  	int acc_mode = ACC_MODE(flags); +	/* Must never be set by userspace */ +	flags &= ~(FMODE_NONOTIFY | O_CLOEXEC); +  	/* -	 * Clear out all open flags we don't know about so that we don't report -	 * them in fcntl(F_GETFD) or similar interfaces. +	 * Older syscalls implicitly clear all of the invalid flags or argument +	 * values before calling build_open_flags(), but openat2(2) checks all +	 * of its arguments.  	 */ -	flags &= VALID_OPEN_FLAGS; +	if (flags & ~VALID_OPEN_FLAGS) +		return -EINVAL; +	if (how->resolve & ~VALID_RESOLVE_FLAGS) +		return -EINVAL; -	if (flags & (O_CREAT | __O_TMPFILE)) -		op->mode = (mode & S_IALLUGO) | S_IFREG; -	else +	/* Deal with the mode. */ +	if (WILL_CREATE(flags)) { +		if (how->mode & ~S_IALLUGO) +			return -EINVAL; +		op->mode = how->mode | S_IFREG; +	} else { +		if (how->mode != 0) +			return -EINVAL;  		op->mode = 0; - -	/* Must never be set by userspace */ -	flags &= ~FMODE_NONOTIFY & ~O_CLOEXEC; +	}  	/* -	 * 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. +	 * In order to ensure programs get explicit errors when trying to use +	 * O_TMPFILE on old kernels, O_TMPFILE is implemented such that it +	 * looks like (O_DIRECTORY|O_RDWR & ~O_CREAT) to old kernels. But we +	 * have to require userspace to explicitly set it.  	 */ -	if (flags & __O_SYNC) -		flags |= O_DSYNC; -  	if (flags & __O_TMPFILE) {  		if ((flags & O_TMPFILE_MASK) != O_TMPFILE)  			return -EINVAL;  		if (!(acc_mode & MAY_WRITE))  			return -EINVAL; -	} else if (flags & O_PATH) { -		/* -		 * If we have O_PATH in the open flag. Then we -		 * cannot have anything other than the below set of flags -		 */ -		flags &= O_DIRECTORY | O_NOFOLLOW | O_PATH; +	} +	if (flags & O_PATH) { +		/* O_PATH only permits certain other flags to be set. */ +		if (flags & ~O_PATH_FLAGS) +			return -EINVAL;  		acc_mode = 0;  	} +	/* +	 * 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; +  	op->open_flag = flags;  	/* O_TRUNC implies we need access checks for write permissions */ @@ -1022,6 +1057,18 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o  		lookup_flags |= LOOKUP_DIRECTORY;  	if (!(flags & O_NOFOLLOW))  		lookup_flags |= LOOKUP_FOLLOW; + +	if (how->resolve & RESOLVE_NO_XDEV) +		lookup_flags |= LOOKUP_NO_XDEV; +	if (how->resolve & RESOLVE_NO_MAGICLINKS) +		lookup_flags |= LOOKUP_NO_MAGICLINKS; +	if (how->resolve & RESOLVE_NO_SYMLINKS) +		lookup_flags |= LOOKUP_NO_SYMLINKS; +	if (how->resolve & RESOLVE_BENEATH) +		lookup_flags |= LOOKUP_BENEATH; +	if (how->resolve & RESOLVE_IN_ROOT) +		lookup_flags |= LOOKUP_IN_ROOT; +  	op->lookup_flags = lookup_flags;  	return 0;  } @@ -1040,8 +1087,11 @@ static inline int build_open_flags(int flags, umode_t mode, struct open_flags *o  struct file *file_open_name(struct filename *name, int flags, umode_t mode)  {  	struct open_flags op; -	int err = build_open_flags(flags, mode, &op); -	return err ? ERR_PTR(err) : do_filp_open(AT_FDCWD, name, &op); +	struct open_how how = build_open_how(flags, mode); +	int err = build_open_flags(&how, &op); +	if (err) +		return ERR_PTR(err); +	return do_filp_open(AT_FDCWD, name, &op);  }  /** @@ -1072,17 +1122,19 @@ struct file *file_open_root(struct dentry *dentry, struct vfsmount *mnt,  			    const char *filename, int flags, umode_t mode)  {  	struct open_flags op; -	int err = build_open_flags(flags, mode, &op); +	struct open_how how = build_open_how(flags, mode); +	int err = build_open_flags(&how, &op);  	if (err)  		return ERR_PTR(err);  	return do_file_open_root(dentry, mnt, filename, &op);  }  EXPORT_SYMBOL(file_open_root); -long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode) +static long do_sys_openat2(int dfd, const char __user *filename, +			   struct open_how *how)  {  	struct open_flags op; -	int fd = build_open_flags(flags, mode, &op); +	int fd = build_open_flags(how, &op);  	struct filename *tmp;  	if (fd) @@ -1092,7 +1144,7 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)  	if (IS_ERR(tmp))  		return PTR_ERR(tmp); -	fd = get_unused_fd_flags(flags); +	fd = get_unused_fd_flags(how->flags);  	if (fd >= 0) {  		struct file *f = do_filp_open(dfd, tmp, &op);  		if (IS_ERR(f)) { @@ -1107,12 +1159,16 @@ long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)  	return fd;  } -SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) +long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)  { -	if (force_o_largefile()) -		flags |= O_LARGEFILE; +	struct open_how how = build_open_how(flags, mode); +	return do_sys_openat2(dfd, filename, &how); +} -	return do_sys_open(AT_FDCWD, filename, flags, mode); + +SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode) +{ +	return ksys_open(filename, flags, mode);  }  SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags, @@ -1120,10 +1176,32 @@ SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename, int, flags,  {  	if (force_o_largefile())  		flags |= O_LARGEFILE; -  	return do_sys_open(dfd, filename, flags, mode);  } +SYSCALL_DEFINE4(openat2, int, dfd, const char __user *, filename, +		struct open_how __user *, how, size_t, usize) +{ +	int err; +	struct open_how tmp; + +	BUILD_BUG_ON(sizeof(struct open_how) < OPEN_HOW_SIZE_VER0); +	BUILD_BUG_ON(sizeof(struct open_how) != OPEN_HOW_SIZE_LATEST); + +	if (unlikely(usize < OPEN_HOW_SIZE_VER0)) +		return -EINVAL; + +	err = copy_struct_from_user(&tmp, sizeof(tmp), how, usize); +	if (err) +		return err; + +	/* O_LARGEFILE is only allowed for non-O_PATH. */ +	if (!(tmp.flags & O_PATH) && force_o_largefile()) +		tmp.flags |= O_LARGEFILE; + +	return do_sys_openat2(dfd, filename, &tmp); +} +  #ifdef CONFIG_COMPAT  /*   * Exactly like sys_open(), except that it doesn't set the  |