Index: lib/libc/sys/Makefile.inc =================================================================== --- lib/libc/sys/Makefile.inc +++ lib/libc/sys/Makefile.inc @@ -184,7 +184,9 @@ extattr_get_file.2 \ fcntl.2 \ ffclock.2 \ + fhlink.2 \ fhopen.2 \ + fhreadlink.2 \ flock.2 \ fork.2 \ fsync.2 \ @@ -395,7 +397,8 @@ MLINKS+=fhopen.2 fhstat.2 fhopen.2 fhstatfs.2 MLINKS+=fsync.2 fdatasync.2 MLINKS+=getdirentries.2 getdents.2 -MLINKS+=getfh.2 lgetfh.2 +MLINKS+=getfh.2 lgetfh.2 \ + getfh.2 getfhat.2 MLINKS+=getgid.2 getegid.2 MLINKS+=getitimer.2 setitimer.2 MLINKS+=getlogin.2 getlogin_r.3 Index: lib/libc/sys/Symbol.map =================================================================== --- lib/libc/sys/Symbol.map +++ lib/libc/sys/Symbol.map @@ -382,6 +382,9 @@ FBSD_1.5 { clock_nanosleep; fdatasync; + fhlink; + fhlinkat; + fhreadlink; fhstat; fhstatfs; fstat; @@ -389,6 +392,7 @@ fstatfs; getdents; getdirentries; + getfhat; getfsstat; getrandom; kevent; Index: lib/libc/sys/getfh.2 =================================================================== --- lib/libc/sys/getfh.2 +++ lib/libc/sys/getfh.2 @@ -28,12 +28,13 @@ .\" @(#)getfh.2 8.1 (Berkeley) 6/9/93 .\" $FreeBSD$ .\" -.Dd April 14, 2011 +.Dd November 26, 2018 .Dt GETFH 2 .Os .Sh NAME .Nm getfh , -.Nm lgetfh +.Nm lgetfh , +.Nm getfhat .Nd get file handle .Sh LIBRARY .Lb libc @@ -44,6 +45,8 @@ .Fn getfh "const char *path" "fhandle_t *fhp" .Ft int .Fn lgetfh "const char *path" "fhandle_t *fhp" +.Ft int +.Fn getfhat "int fd" "const char *path" "fhandle_t *fhp" "int flag" .Sh DESCRIPTION The .Fn getfh @@ -51,6 +54,7 @@ returns a file handle for the specified file or directory in the file handle pointed to by .Fa fhp . +.Pp The .Fn lgetfh system call is like @@ -62,6 +66,85 @@ while .Fn getfh returns information about the file the link references. +.Pp +The +.Fn getfhat +system call is equivalent to +.Fn getfh +and +.Fn lgetfh +except when the +.Fa path +specifies a relative path, or the +.Dv AT_BENEATH +flag is provided. +For +.Fn getfhat +and relative +.Fa path , +the status is retrieved from a file relative to +the directory associated with the file descriptor +.Fa fd +instead of the current working directory. +For +.Dv AT_BENEATH +and absolute +.Fa path , +the status is retrieved from a file specified by the +.Fa path , +but additional permission checks are performed, see below. +.Pp +The values for the +.Fa flag +are constructed by a bitwise-inclusive OR of flags from this list, +defined in +.In fcntl.h : +.Bl -tag -width indent +.It Dv AT_SYMLINK_NOFOLLOW +If +.Fa path +names a symbolic link, the status of the symbolic link is returned. +.It Dv AT_BENEATH +Only stat files and directories below the topping directory. +See the description of the +.Dv O_BENEATH +flag in the +.Xr open 2 +manual page. +.El +.Pp +If +.Fn getfhat +is passed the special value +.Dv AT_FDCWD +in the +.Fa fd +parameter, the current working directory is used and the behavior is +identical to a call to +.Fn getfth +or +.Fn lgetfh +respectively, depending on whether or not the +.Dv AT_SYMLINK_NOFOLLOW +bit is set in +.Fa flag . +.Pp +When +.Fn getfhat +is called with an absolute +.Fa path +without the +.Dv AT_BENEATH +flag, it ignores the +.Fa fd +argument. +When +.Dv AT_BENEATH +is specified with an absolute +.Fa path , +a directory passed by the +.Fa fd +argument is used as the topping point for the resolution. These system calls are restricted to the superuser. .Sh RETURN VALUES .Rv -std @@ -104,6 +187,35 @@ .Tn I/O error occurred while reading from or writing to the file system. .El +.Pp +In addition to the errors returned by +.Fn getfh , +and +.Fn lgetfh , +the +.Fn getfhat +system call may fail if: +.Bl -tag -width Er +.It Bq Er EBADF +The +.Fa path +argument does not specify an absolute path and the +.Fa fd +argument, is neither +.Dv AT_FDCWD +nor a valid file descriptor open for searching. +.It Bq Er EINVAL +The value of the +.Fa flag +argument is not valid. +.It Bq Er ENOTDIR +The +.Fa path +argument is not an absolute path and +.Fa fd +is neither +.Dv AT_FDCWD +nor a file descriptor associated with a directory. .Sh SEE ALSO .Xr fhopen 2 , .Xr open 2 , Index: sys/compat/freebsd32/syscalls.master =================================================================== --- sys/compat/freebsd32/syscalls.master +++ sys/compat/freebsd32/syscalls.master @@ -1138,5 +1138,12 @@ int policy); } 563 AUE_NULL NOPROTO { int getrandom(void *buf, size_t buflen, \ unsigned int flags); } +564 AUE_NULL NOPROTO { int getfhat( int fd, char *path, \ + struct fhandle *fhp, int flags); } +565 AUE_NULL NOPROTO { int fhlink( struct fhandle *fhp, const char *to ); } +566 AUE_NULL NOPROTO { int fhlinkat( struct fhandle *fhp, int tofd, \ + const char *to, int flags,); } +567 AUE_NULL NOPROTO { int fhreadlink( struct fhandle *fhp, char *buf, \ + size_t bufsize); } ; vim: syntax=off Index: sys/kern/capabilities.conf =================================================================== --- sys/kern/capabilities.conf +++ sys/kern/capabilities.conf @@ -469,6 +469,7 @@ symlinkat unlinkat utimensat +getfhat ## ## Process descriptor-related system calls are allowed. Index: sys/kern/syscalls.master =================================================================== --- sys/kern/syscalls.master +++ sys/kern/syscalls.master @@ -3139,6 +3139,35 @@ unsigned int flags ); } +564 AUE_NULL STD { + int getfhat( + int fd, + _In_z_ char *path, + _Out_ struct fhandle *fhp, + int flags + ); + } +565 AUE_NULL STD { + int fhlink( + _In_ struct fhandle *fhp, + _In_z_ const char *to + ); + } +566 AUE_NULL STD { + int fhlinkat( + _In_ struct fhandle *fhp, + int tofd, + _In_z_ const char *to, + int flags, + ); + } +567 AUE_NULL STD { + int fhreadlink( + _In_ struct fhandle *fhp, + _Out_writes_(bufsize) char *buf, + size_t bufsize + ); + } ; Please copy any additions and changes to the following compatability tables: ; sys/compat/freebsd32/syscalls.master Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c +++ sys/kern/vfs_syscalls.c @@ -105,6 +105,10 @@ const struct timespec *, int, int); static int vn_access(struct vnode *vp, int user_flags, struct ucred *cred, struct thread *td); +static int kern_fhlinkat(struct thread *td, int fd, const char *path, + enum uio_seg pathseg, fhandle_t *fhp, int flags); +static int kern_getfhat(struct thread *td, int flags, int fd, + const char *path, enum uio_seg pathseg, fhandle_t *fhp); /* * Sync each mounted filesystem. @@ -4121,67 +4125,278 @@ */ #ifndef _SYS_SYSPROTO_H_ struct lgetfh_args { - char *fname; + char *fname; fhandle_t *fhp; }; #endif int sys_lgetfh(struct thread *td, struct lgetfh_args *uap) { + return (kern_getfhat(td, AT_SYMLINK_NOFOLLOW, AT_FDCWD, uap->fname, + UIO_USERSPACE, uap->fhp)); +} + +#ifndef _SYS_SYSPROTO_H_ +struct getfh_args { + char *fname; + fhandle_t *fhp; +}; +#endif +int +sys_getfh(struct thread *td, struct getfh_args *uap) +{ + return (kern_getfhat(td, 0, AT_FDCWD, uap->fname, UIO_USERSPACE, + uap->fhp)); +} + +/* + * syscall for the rpc.lockd to use to translate an open descriptor into + * a NFS file handle. + * + * warning: do not remove the priv_check() call or this becomes one giant + * security hole. + */ +#ifndef _SYS_SYSPROTO_H_ +struct getfhat_args { + int fd; + char *path; + fhandle_t *fhp; + int flags; +}; +#endif +int +sys_getfhat(struct thread *td, struct getfhat_args *uap) +{ + + if ((uap->flags & ~(AT_SYMLINK_NOFOLLOW | AT_BENEATH)) != 0) + return (EINVAL); + return (kern_getfhat(td, uap->flags, uap->fd, uap->path, + UIO_USERSPACE, uap->fhp)); +} + +static int +kern_getfhat(struct thread *td, int flags, int fd, const char *path, + enum uio_seg pathseg, fhandle_t *fhp) +{ struct nameidata nd; fhandle_t fh; struct vnode *vp; + cap_rights_t rights; int error; error = priv_check(td, PRIV_VFS_GETFH); if (error != 0) return (error); - NDINIT(&nd, LOOKUP, NOFOLLOW | LOCKLEAF | AUDITVNODE1, UIO_USERSPACE, - uap->fname, td); - error = namei(&nd); - if (error != 0) - return (error); - NDFREE(&nd, NDF_ONLY_PNBUF); - vp = nd.ni_vp; + + if (path != NULL) { + NDINIT_AT(&nd, LOOKUP, (flags & AT_SYMLINK_NOFOLLOW ? NOFOLLOW : FOLLOW) + | (flags & AT_BENEATH ? BENEATH : 0) | LOCKLEAF | AUDITVNODE1, + pathseg, path, fd, td); + error = namei(&nd); + if (error != 0) + return (error); + NDFREE(&nd, NDF_ONLY_PNBUF); + vp = nd.ni_vp; + } else { + error = fgetvp(td, fd, cap_rights_init(&rights, CAP_PREAD), &vp); + if (error != 0) + return (error); + vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); + } + bzero(&fh, sizeof(fh)); fh.fh_fsid = vp->v_mount->mnt_stat.f_fsid; error = VOP_VPTOFH(vp, &fh.fh_fid); vput(vp); if (error == 0) - error = copyout(&fh, uap->fhp, sizeof (fh)); + error = copyout(&fh, fhp, sizeof (fh)); return (error); } #ifndef _SYS_SYSPROTO_H_ -struct getfh_args { - char *fname; +struct fhlink_args { fhandle_t *fhp; + int tofd; + const char *to; }; #endif int -sys_getfh(struct thread *td, struct getfh_args *uap) +sys_fhlink(struct thread *td, struct fhlink_args *uap) { + return (kern_fhlinkat(td, AT_FDCWD, uap->to, UIO_USERSPACE, uap->fhp, 0)); +} + +#ifndef _SYS_SYSPROTO_H_ +struct fhlinkat_args { + fhandle_t *fhp; + int tofd; + const char *to; + int flags; +}; +#endif +int +sys_fhlinkat(struct thread *td, struct fhlinkat_args *uap) +{ + + if ((uap->flags & ~(AT_SYMLINK_FOLLOW | AT_BENEATH)) != 0) + return (EINVAL); + return (kern_fhlinkat(td, uap->flags, uap->to, UIO_USERSPACE, uap->fhp, + uap->flags)); +} + +static int +kern_fhlinkat(struct thread *td, int fd, const char *path, + enum uio_seg pathseg, fhandle_t *fhp, int flags) +{ + fhandle_t fh; + struct mount *mp; + struct vnode *vp; struct nameidata nd; + int error; + + error = priv_check(td, PRIV_VFS_GETFH); + if (error != 0) + return (error); + + error = copyin(fhp, &fh, sizeof(fh)); + if (error != 0) + return (error); + +again: + bwillwrite(); + if ((mp = vfs_busyfs(&fh.fh_fsid)) == NULL) + return (ESTALE); + + error = VFS_FHTOVP(mp, &fh.fh_fid, LK_EXCLUSIVE, &vp); + vfs_unbusy(mp); + if (error != 0) + return (error); + + if (vp->v_type == VDIR) { + vput(vp); + return (EPERM); /* POSIX */ + } + + /* reuse the same target rights capability as linkat() */ + NDINIT_ATRIGHTS(&nd, CREATE, (flags & AT_SYMLINK_NOFOLLOW ? NOFOLLOW : FOLLOW) + | (flags & AT_BENEATH ? BENEATH : 0) + | LOCKPARENT | SAVENAME | AUDITVNODE2 | NOCACHE, + pathseg, path, fd, &cap_linkat_target_rights, td); + + if ((error = namei(&nd)) == 0) { + if (nd.ni_vp != NULL) { + NDFREE(&nd, NDF_ONLY_PNBUF); + if (nd.ni_dvp == nd.ni_vp) + vrele(nd.ni_dvp); + else + vput(nd.ni_dvp); + vrele(nd.ni_vp); + vput(vp); + return (EEXIST); + } else if (nd.ni_dvp->v_mount != vp->v_mount) { + /* + * Cross-device link. No need to recheck + * vp->v_type, since it cannot change, except + * to VBAD. + */ + NDFREE(&nd, NDF_ONLY_PNBUF); + vput(nd.ni_dvp); + vput(vp); + return (EXDEV); + } else { + error = can_hardlink(vp, td->td_ucred); +#ifdef MAC + if (error == 0) + error = mac_vnode_check_link(td->td_ucred, + nd.ni_dvp, vp, &nd.ni_cnd); +#endif + if (error != 0) { + vput(vp); + vput(nd.ni_dvp); + NDFREE(&nd, NDF_ONLY_PNBUF); + return (error); + } + error = vn_start_write(vp, &mp, V_NOWAIT); + if (error != 0) { + vput(vp); + vput(nd.ni_dvp); + NDFREE(&nd, NDF_ONLY_PNBUF); + error = vn_start_write(NULL, &mp, + V_XSLEEP | PCATCH); + if (error != 0) + return (error); + goto again; + } + error = VOP_LINK(nd.ni_dvp, vp, &nd.ni_cnd); + vput(nd.ni_dvp); + vn_finished_write(mp); + NDFREE(&nd, NDF_ONLY_PNBUF); + } + } + vput(vp); + return (error); +} + +#ifndef _SYS_SYSPROTO_H_ +struct fhreadlink_args { + fhandle_t *fhp; + char *buf; + size_t bufsize; +}; +#endif +int +sys_fhreadlink(struct thread *td, struct fhreadlink_args *uap) +{ fhandle_t fh; + struct iovec aiov; + struct uio auio; + struct mount *mp; struct vnode *vp; int error; error = priv_check(td, PRIV_VFS_GETFH); if (error != 0) return (error); - NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | AUDITVNODE1, UIO_USERSPACE, - uap->fname, td); - error = namei(&nd); + + if (uap->bufsize > IOSIZE_MAX) + return (EINVAL); + + error = copyin(uap->fhp, &fh, sizeof(fh)) if (error != 0) return (error); - NDFREE(&nd, NDF_ONLY_PNBUF); - vp = nd.ni_vp; - bzero(&fh, sizeof(fh)); - fh.fh_fsid = vp->v_mount->mnt_stat.f_fsid; - error = VOP_VPTOFH(vp, &fh.fh_fid); + + if ((mp = vfs_busyfs(&fh.fh_fsid)) == NULL) + return (ESTALE); + + error = VFS_FHTOVP(mp, &fh.fh_fid, LK_EXCLUSIVE, &vp); + vfs_unbusy(mp); + if (error != 0) + return (error); + +#ifdef MAC + error = mac_vnode_check_readlink(td->td_ucred, vp); + if (error != 0) { + vput(vp); + return (error); + } +#endif + + if (vp->v_type != VLNK && (vp->v_vflag & VV_READLINK) == 0) + error = EINVAL; + else { + aiov.iov_base = uap->buf; + aiov.iov_len = uap->bufsize; + auio.uio_iov = &aiov; + auio.uio_iovcnt = 1; + auio.uio_offset = 0; + auio.uio_rw = UIO_READ; + auio.uio_segflg = UIO_USERSPACE; + auio.uio_td = td; + auio.uio_resid = uap->bufsize; + error = VOP_READLINK(vp, &auio, td->td_ucred); + td->td_retval[0] = uap->bufsize - auio.uio_resid; + } vput(vp); - if (error == 0) - error = copyout(&fh, uap->fhp, sizeof (fh)); return (error); } Index: sys/sys/mount.h =================================================================== --- sys/sys/mount.h +++ sys/sys/mount.h @@ -932,11 +932,15 @@ struct stat; __BEGIN_DECLS +int fhlink(struct fhandle *, const char *); +int fhlinkat(struct fhandle *, int, const char *, int); int fhopen(const struct fhandle *, int); +int fhreadlink(struct fhandle *, char *, size_t); int fhstat(const struct fhandle *, struct stat *); int fhstatfs(const struct fhandle *, struct statfs *); int fstatfs(int, struct statfs *); int getfh(const char *, fhandle_t *); +int getfhat(int, char *, struct fhandle *, int); int getfsstat(struct statfs *, long, int); int getmntinfo(struct statfs **, int); int lgetfh(const char *, fhandle_t *);