Re: [PATCH v3] fs: introduce getfsxattrat and setfsxattrat syscalls

Jan Kara <[email protected]>
Newsgroups gmane.linux.ports.sh.devel,gmane.linux.ports.alpha,gmane.linux.kernel,gmane.linux.ports.arm.kernel,gmane.linux.ports.mips,gmane.linux.ports.parisc,gmane.linux.ports.ppc64.devel,gmane.linux.ports.sparc,gmane.linux.file-systems,gmane.linux.kernel.lsm,gmane.linux.kernel.api,gmane.linux.kernel.cross-arch
Message-ID <fyp7gcbeo3xlrh7zi7k6m5aa6h5otbufxq3kh5zvgr3sjdbxl3@4nkuwx46yajk>
On Tue 11-02-25 18:22:47, Andrey Albershteyn wrote:
> From: Andrey Albershteyn <[email protected]>
> 
> Introduce getfsxattrat and setfsxattrat syscalls to manipulate inode
> extended attributes/flags. The syscalls take parent directory fd and
> path to the child together with struct fsxattr.
> 
> This is an alternative to FS_IOC_FSSETXATTR ioctl with a difference
> that file don't need to be open as we can reference it with a path
> instead of fd. By having this we can manipulated inode extended
> attributes not only on regular files but also on special ones. This
> is not possible with FS_IOC_FSSETXATTR ioctl as with special files
> we can not call ioctl() directly on the filesystem inode using fd.
> 
> This patch adds two new syscalls which allows userspace to get/set
> extended inode attributes on special files by using parent directory
> and a path - *at() like syscall.
> 
> Also, as vfs_fileattr_set() is now will be called on special files
> too, let's forbid any other attributes except projid and nextents
> (symlink can have an extent).
> 
> CC: [email protected]
> CC: [email protected]
> CC: [email protected]
> Signed-off-by: Andrey Albershteyn <[email protected]>

Some comments below:

> +SYSCALL_DEFINE4(getfsxattrat, int, dfd, const char __user *, filename,
> +		struct fsxattr __user *, fsx, unsigned int, at_flags)
> +{
> +	CLASS(fd, dir)(dfd);
> +	struct fileattr fa;
> +	struct path filepath;
> +	int error;
> +	unsigned int lookup_flags = 0;
> +
> +	if ((at_flags & ~(AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH)) != 0)
> +		return -EINVAL;
> +
> +	if (at_flags & AT_SYMLINK_FOLLOW)
	    ^^ This should be !(at_flags & AT_SYMLINK_NOFOLLOW)?

In the check above you verify for AT_SYMLINK_NOFOLLOW and that also matches
what setxattrat() does...


> +		lookup_flags |= LOOKUP_FOLLOW;
> +
> +	if (at_flags & AT_EMPTY_PATH)
> +		lookup_flags |= LOOKUP_EMPTY;
> +
> +	if (fd_empty(dir))
> +		return -EBADF;

This check is wrong and in fact the whole dfd handling looks buggy.
openat(2) manpage describes the expected behavior:

       The dirfd argument is used in conjunction with the pathname argument as
       follows:

       •  If the pathname given in pathname is absolute,  then  dirfd  is  ig-
          nored.
	  ^^^^ This is what you break. If the pathname is absolute, you're
not expected to touch dirfd.

       •  If  the pathname given in pathname is relative and dirfd is the spe-
          cial value AT_FDCWD, then pathname is interpreted  relative  to  the
          current working directory of the calling process (like open()).
          ^^^ Also AT_FDCWD handling would be broken by the above check.

       •  If  the  pathname  given  in pathname is relative, then it is inter-
          preted relative to the directory referred to by the file  descriptor
          dirfd  (rather than relative to the current working directory of the
          calling process, as is done by open() for a relative pathname).   In
          this  case,  dirfd  must  be a directory that was opened for reading
          (O_RDONLY) or using the O_PATH flag.

       If the pathname given in pathname is relative, and dirfd is not a valid
       file descriptor, an error (EBADF) results.  (Specifying an invalid file
       descriptor number in dirfd can be used as a means to ensure that  path-
       name is absolute.)

> +
> +	error = user_path_at(dfd, filename, lookup_flags, &filepath);
		^^^ And user_path_at() isn't quite what you need either
because with AT_EMPTY_PATH we also want to allow for filename to be NULL
(not just empty string) and user_path_at() does not support that. That's
why I in my previous replies suggested you should follow what setxattrat()
does and that sadly it is more painful than it should be. You need
something like:

	name = getname_maybe_null(filename, at_flags);
	if (!name) {
		CLASS(fd, f)(dfd);

		if (fd_empty(f))
			return -EBADF;
		error = vfs_fileattr_get(file_dentry(fd_file(f)), &fa);
	} else {
		error = filename_lookup(dfd, filename, lookup_flags, &filepath,
					NULL);
		if (error)
			goto out;
		error = vfs_fileattr_get(filepath.dentry, &fa);
		path_put(&filepath);
	}
	if (!error)
		error = copy_fsxattr_to_user(&fa, fsx);
out:
	putname(name);
	return error;

Longer term, we need to provide user_path_maybe_null_at() for this but I
don't want to drag you into this cleanup :)

> +	if (error)
> +		return error;
> +
> +	error = vfs_fileattr_get(filepath.dentry, &fa);
> +	if (!error)
> +		error = copy_fsxattr_to_user(&fa, fsx);
> +
> +	path_put(&filepath);
> +	return error;
> +}
> +
> +SYSCALL_DEFINE4(setfsxattrat, int, dfd, const char __user *, filename,
> +		struct fsxattr __user *, fsx, unsigned int, at_flags)
> +{
> +	CLASS(fd, dir)(dfd);
> +	struct fileattr fa;
> +	struct path filepath;
> +	int error;
> +	unsigned int lookup_flags = 0;
> +
> +	if ((at_flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) != 0)
> +		return -EINVAL;
> +
> +	if (at_flags & AT_SYMLINK_FOLLOW)
> +		lookup_flags |= LOOKUP_FOLLOW;

I think using AT_SYMLINK_NOFOLLOW is actually more traditional and thus
less surprising to users so I'd prefer that. Definitely this needs to be
consistent with getfsxattrat().

> +
> +	if (at_flags & AT_EMPTY_PATH)
> +		lookup_flags |= LOOKUP_EMPTY;
> +
> +	if (fd_empty(dir))
> +		return -EBADF;

Same comment regarding dfd handling as above.

> +
> +	if (copy_fsxattr_from_user(&fa, fsx))
> +		return -EFAULT;
> +
> +	error = user_path_at(dfd, filename, lookup_flags, &filepath);
> +	if (error)
> +		return error;
> +
> +	error = mnt_want_write(filepath.mnt);
> +	if (!error) {
> +		error = vfs_fileattr_set(file_mnt_idmap(fd_file(dir)),
> +					 filepath.dentry, &fa);
> +		mnt_drop_write(filepath.mnt);
> +	}
> +
> +	path_put(&filepath);
> +	return error;
> +}

Otherwise the patch looks good to me.

								Honza
-- 
Jan Kara <[email protected]>
SUSE Labs, CR
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.