1585 lines · 39.2 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* *  linux/fs/open.c * *  Copyright (C) 1991, 1992  Linus Torvalds */ #include <linux/string.h>#include <linux/mm.h>#include <linux/file.h>#include <linux/fdtable.h>#include <linux/fsnotify.h>#include <linux/module.h>#include <linux/tty.h>#include <linux/namei.h>#include <linux/backing-dev.h>#include <linux/capability.h>#include <linux/securebits.h>#include <linux/security.h>#include <linux/mount.h>#include <linux/fcntl.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/fs.h>#include <linux/personality.h>#include <linux/pagemap.h>#include <linux/syscalls.h>#include <linux/rcupdate.h>#include <linux/audit.h>#include <linux/falloc.h>#include <linux/fs_struct.h>#include <linux/dnotify.h>#include <linux/compat.h>#include <linux/mnt_idmapping.h>#include <linux/filelock.h> #include "internal.h" int do_truncate(struct mnt_idmap *idmap, struct dentry *dentry,		loff_t length, unsigned int time_attrs, struct file *filp){	int ret;	struct iattr newattrs; 	/* Not pretty: "inode->i_size" shouldn't really be signed. But it is. */	if (length < 0)		return -EINVAL; 	newattrs.ia_size = length;	newattrs.ia_valid = ATTR_SIZE | time_attrs;	if (filp) {		newattrs.ia_file = filp;		newattrs.ia_valid |= ATTR_FILE;	} 	/* Remove suid, sgid, and file capabilities on truncate too */	ret = dentry_needs_remove_privs(idmap, dentry);	if (ret < 0)		return ret;	if (ret)		newattrs.ia_valid |= ret | ATTR_FORCE; 	ret = inode_lock_killable(dentry->d_inode);	if (ret)		return ret; 	/* Note any delegations or leases have already been broken: */	ret = notify_change(idmap, dentry, &newattrs, NULL);	inode_unlock(dentry->d_inode);	return ret;} int vfs_truncate(const struct path *path, loff_t length){	struct mnt_idmap *idmap;	struct inode *inode;	int error; 	inode = path->dentry->d_inode; 	/* For directories it's -EISDIR, for other non-regulars - -EINVAL */	if (S_ISDIR(inode->i_mode))		return -EISDIR;	if (!S_ISREG(inode->i_mode))		return -EINVAL; 	idmap = mnt_idmap(path->mnt);	error = inode_permission(idmap, inode, MAY_WRITE);	if (error)		return error; 	error = fsnotify_truncate_perm(path, length);	if (error)		return error; 	error = mnt_want_write(path->mnt);	if (error)		return error; 	error = -EPERM;	if (IS_APPEND(inode))		goto mnt_drop_write_and_out; 	error = get_write_access(inode);	if (error)		goto mnt_drop_write_and_out; 	/*	 * 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 put_write_and_out; 	error = security_path_truncate(path);	if (!error)		error = do_truncate(idmap, path->dentry, length, 0, NULL); put_write_and_out:	put_write_access(inode);mnt_drop_write_and_out:	mnt_drop_write(path->mnt); 	return error;}EXPORT_SYMBOL_GPL(vfs_truncate); int ksys_truncate(const char __user *pathname, loff_t length){	unsigned int lookup_flags = LOOKUP_FOLLOW;	struct path path;	int error; 	if (length < 0)	/* sorry, but loff_t says... */		return -EINVAL; 	CLASS(filename, name)(pathname);retry:	error = filename_lookup(AT_FDCWD, name, lookup_flags, &path, NULL);	if (!error) {		error = vfs_truncate(&path, length);		path_put(&path);		if (retry_estale(error, lookup_flags)) {			lookup_flags |= LOOKUP_REVAL;			goto retry;		}	}	return error;} SYSCALL_DEFINE2(truncate, const char __user *, path, long, length){	return ksys_truncate(path, length);} #ifdef CONFIG_COMPATCOMPAT_SYSCALL_DEFINE2(truncate, const char __user *, path, compat_off_t, length){	return ksys_truncate(path, length);}#endif int do_ftruncate(struct file *file, loff_t length, unsigned int flags){	struct dentry *dentry = file->f_path.dentry;	struct inode *inode = dentry->d_inode;	int error; 	if (!S_ISREG(inode->i_mode) || !(file->f_mode & FMODE_WRITE))		return -EINVAL; 	/*	 * Cannot ftruncate over 2^31 bytes without large file support, either	 * through opening with O_LARGEFILE or by using ftruncate64().	 */	if (length > MAX_NON_LFS &&	    !(file->f_flags & O_LARGEFILE) && !(flags & FTRUNCATE_LFS))		return -EINVAL; 	/* Check IS_APPEND on real upper inode */	if (IS_APPEND(file_inode(file)))		return -EPERM; 	error = security_file_truncate(file);	if (error)		return error; 	error = fsnotify_truncate_perm(&file->f_path, length);	if (error)		return error; 	scoped_guard(super_write, inode->i_sb)		return do_truncate(file_mnt_idmap(file), dentry, length,				   ATTR_MTIME | ATTR_CTIME, file);} int ksys_ftruncate(unsigned int fd, loff_t length, unsigned int flags){	if (length < 0)		return -EINVAL;	CLASS(fd, f)(fd);	if (fd_empty(f))		return -EBADF; 	return do_ftruncate(fd_file(f), length, flags);} SYSCALL_DEFINE2(ftruncate, unsigned int, fd, off_t, length){	return ksys_ftruncate(fd, length, 0);} #ifdef CONFIG_COMPATCOMPAT_SYSCALL_DEFINE2(ftruncate, unsigned int, fd, compat_off_t, length){	return ksys_ftruncate(fd, length, 0);}#endif /* LFS versions of truncate are only needed on 32 bit machines */#if BITS_PER_LONG == 32SYSCALL_DEFINE2(truncate64, const char __user *, path, loff_t, length){	return ksys_truncate(path, length);} SYSCALL_DEFINE2(ftruncate64, unsigned int, fd, loff_t, length){	return ksys_ftruncate(fd, length, FTRUNCATE_LFS);}#endif /* BITS_PER_LONG == 32 */ #if defined(CONFIG_COMPAT) && defined(__ARCH_WANT_COMPAT_TRUNCATE64)COMPAT_SYSCALL_DEFINE3(truncate64, const char __user *, pathname,		       compat_arg_u64_dual(length)){	return ksys_truncate(pathname, compat_arg_u64_glue(length));}#endif #if defined(CONFIG_COMPAT) && defined(__ARCH_WANT_COMPAT_FTRUNCATE64)COMPAT_SYSCALL_DEFINE3(ftruncate64, unsigned int, fd,		       compat_arg_u64_dual(length)){	return ksys_ftruncate(fd, compat_arg_u64_glue(length), FTRUNCATE_LFS);}#endif int vfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len){	struct inode *inode = file_inode(file);	int ret;	loff_t sum; 	if (offset < 0 || len <= 0)		return -EINVAL; 	if (mode & ~(FALLOC_FL_MODE_MASK | FALLOC_FL_KEEP_SIZE))		return -EOPNOTSUPP; 	/*	 * Modes are exclusive, even if that is not obvious from the encoding	 * as bit masks and the mix with the flag in the same namespace.	 *	 * To make things even more complicated, FALLOC_FL_ALLOCATE_RANGE is	 * encoded as no bit set.	 */	switch (mode & FALLOC_FL_MODE_MASK) {	case FALLOC_FL_ALLOCATE_RANGE:	case FALLOC_FL_UNSHARE_RANGE:	case FALLOC_FL_ZERO_RANGE:		break;	case FALLOC_FL_PUNCH_HOLE:		if (!(mode & FALLOC_FL_KEEP_SIZE))			return -EOPNOTSUPP;		break;	case FALLOC_FL_COLLAPSE_RANGE:	case FALLOC_FL_INSERT_RANGE:	case FALLOC_FL_WRITE_ZEROES:		if (mode & FALLOC_FL_KEEP_SIZE)			return -EOPNOTSUPP;		break;	default:		return -EOPNOTSUPP;	} 	if (!(file->f_mode & FMODE_WRITE))		return -EBADF; 	/*	 * On append-only files only space preallocation is supported.	 */	if ((mode & ~FALLOC_FL_KEEP_SIZE) && IS_APPEND(inode))		return -EPERM; 	if (IS_IMMUTABLE(inode))		return -EPERM; 	/*	 * We cannot allow any fallocate operation on an active swapfile	 */	if (IS_SWAPFILE(inode))		return -ETXTBSY; 	/*	 * 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; 	ret = fsnotify_file_area_perm(file, MAY_WRITE, &offset, len);	if (ret)		return ret; 	if (S_ISFIFO(inode->i_mode))		return -ESPIPE; 	if (S_ISDIR(inode->i_mode))		return -EISDIR; 	if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))		return -ENODEV; 	/* Check for wraparound */	if (check_add_overflow(offset, len, &sum))		return -EFBIG; 	if (sum > inode->i_sb->s_maxbytes)		return -EFBIG; 	if (!file->f_op->fallocate)		return -EOPNOTSUPP; 	file_start_write(file);	ret = file->f_op->fallocate(file, mode, offset, len); 	/*	 * Create inotify and fanotify events.	 *	 * To keep the logic simple always create events if fallocate succeeds.	 * This implies that events are even created if the file size remains	 * unchanged, e.g. when using flag FALLOC_FL_KEEP_SIZE.	 */	if (ret == 0)		fsnotify_modify(file); 	file_end_write(file);	return ret;}EXPORT_SYMBOL_GPL(vfs_fallocate); int ksys_fallocate(int fd, int mode, loff_t offset, loff_t len){	CLASS(fd, f)(fd); 	if (fd_empty(f))		return -EBADF; 	return vfs_fallocate(fd_file(f), mode, offset, len);} SYSCALL_DEFINE4(fallocate, int, fd, int, mode, loff_t, offset, loff_t, len){	return ksys_fallocate(fd, mode, offset, len);} #if defined(CONFIG_COMPAT) && defined(__ARCH_WANT_COMPAT_FALLOCATE)COMPAT_SYSCALL_DEFINE6(fallocate, int, fd, int, mode, compat_arg_u64_dual(offset),		       compat_arg_u64_dual(len)){	return ksys_fallocate(fd, mode, compat_arg_u64_glue(offset),			      compat_arg_u64_glue(len));}#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. * * Creating new credentials is expensive, so we try to skip doing it, * which we can if the result would match what we already got. */static bool access_need_override_creds(int flags){	const struct cred *cred; 	if (flags & AT_EACCESS)		return false; 	cred = current_cred();	if (!uid_eq(cred->fsuid, cred->uid) ||	    !gid_eq(cred->fsgid, cred->gid))		return true; 	if (!issecure(SECURE_NO_SETUID_FIXUP)) {		kuid_t root_uid = make_kuid(cred->user_ns, 0);		if (!uid_eq(cred->uid, root_uid)) {			if (!cap_isclear(cred->cap_effective))				return true;		} else {			if (!cap_isidentical(cred->cap_effective,			    cred->cap_permitted))				return true;		}	} 	return false;} static const struct cred *access_override_creds(void){	struct cred *override_cred; 	override_cred = prepare_creds();	if (!override_cred)		return NULL; 	/*	 * XXX access_need_override_creds performs checks in hopes of skipping	 * this work. Make sure it stays in sync if making any changes in this	 * routine.	 */ 	override_cred->fsuid = override_cred->uid;	override_cred->fsgid = override_cred->gid; 	if (!issecure(SECURE_NO_SETUID_FIXUP)) {		/* Clear the capabilities if we switch to a non-root user */		kuid_t root_uid = make_kuid(override_cred->user_ns, 0);		if (!uid_eq(override_cred->uid, root_uid))			cap_clear(override_cred->cap_effective);		else			override_cred->cap_effective =				override_cred->cap_permitted;	} 	/*	 * The new set of credentials can *only* be used in	 * task-synchronous circumstances, and does not need	 * RCU freeing, unless somebody then takes a separate	 * reference to it.	 *	 * NOTE! This is _only_ true because this credential	 * is used purely for override_creds() that installs	 * it as the subjective cred. Other threads will be	 * accessing ->real_cred, not the subjective cred.	 *	 * If somebody _does_ make a copy of this (using the	 * 'get_current_cred()' function), that will clear the	 * non_rcu field, because now that other user may be	 * expecting RCU freeing. But normal thread-synchronous	 * cred accesses will keep things non-racy to avoid RCU	 * freeing.	 */	override_cred->non_rcu = 1;	return override_creds(override_cred);} static int do_faccessat(int dfd, const char __user *filename, int mode, int flags){	struct path path;	struct inode *inode;	int res;	unsigned int lookup_flags = LOOKUP_FOLLOW;	const struct cred *old_cred = NULL; 	if (mode & ~S_IRWXO)	/* where's F_OK, X_OK, W_OK, R_OK? */		return -EINVAL; 	if (flags & ~(AT_EACCESS | AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH))		return -EINVAL; 	if (flags & AT_SYMLINK_NOFOLLOW)		lookup_flags &= ~LOOKUP_FOLLOW; 	if (access_need_override_creds(flags)) {		old_cred = access_override_creds();		if (!old_cred)			return -ENOMEM;	} 	CLASS(filename_uflags, name)(filename, flags);retry:	res = filename_lookup(dfd, name, lookup_flags, &path, NULL);	if (res)		goto out; 	inode = d_backing_inode(path.dentry); 	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_noexec(&path))

Lines 1–500 of 1585.