Add AT_EMPTY_PATH for several *at(2) syscalls
It is currently allowed to fchownat(2), fchmodat(2), fchflagsat(2), utimensat(2), fstatat(2), and linkat(2). For linkat(2), PRIV_VFS_FHOPEN privilege is required to exercise the flag. It allows to link any open file. Requested by: trasz Tested by: pho, trasz Reviewed by: markj Sponsored by: The FreeBSD Foundation MFC after: 2 weeks Differential revision: https://reviews.freebsd.org/D29111
This commit is contained in:
parent
d51b4b0aac
commit
509124b626
@ -28,7 +28,7 @@
|
||||
.\" @(#)access.2 8.2 (Berkeley) 4/1/94
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt ACCESS 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -129,6 +129,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Pp
|
||||
Even if a process's real or effective user has appropriate privileges
|
||||
|
@ -28,7 +28,7 @@
|
||||
.\" @(#)chflags.2 8.3 (Berkeley) 5/2/95
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt CHFLAGS 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -103,6 +103,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Pp
|
||||
If
|
||||
|
@ -28,7 +28,7 @@
|
||||
.\" @(#)chmod.2 8.1 (Berkeley) 6/4/93
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt CHMOD 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -110,6 +110,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Pp
|
||||
If
|
||||
|
@ -28,7 +28,7 @@
|
||||
.\" @(#)chown.2 8.4 (Berkeley) 4/19/94
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt CHOWN 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -127,6 +127,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Pp
|
||||
If
|
||||
|
@ -28,7 +28,7 @@
|
||||
.\" @(#)link.2 8.3 (Berkeley) 1/12/94
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt LINK 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -124,6 +124,15 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path2
|
||||
argument is an empty string, link the file referenced by the descriptor
|
||||
.Fa fd2 .
|
||||
The operation requires that the calling process has the
|
||||
.Dv PRIV_VFS_FHOPEN
|
||||
privilege, effectively being executed with effective user
|
||||
.Dv root .
|
||||
.El
|
||||
.Pp
|
||||
If
|
||||
|
@ -28,7 +28,7 @@
|
||||
.\" @(#)stat.2 8.4 (Berkeley) 5/1/95
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt STAT 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -111,6 +111,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Pp
|
||||
If
|
||||
|
@ -31,7 +31,7 @@
|
||||
.\" @(#)utimes.2 8.1 (Berkeley) 6/4/93
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd February 23, 2021
|
||||
.Dd March 30, 2021
|
||||
.Dt UTIMENSAT 2
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -155,6 +155,17 @@ See the description of the
|
||||
flag in the
|
||||
.Xr open 2
|
||||
manual page.
|
||||
.It Dv AT_EMPTY_PATH
|
||||
If the
|
||||
.Fa path
|
||||
argument is an empty string, operate on the file or directory
|
||||
referenced by the descriptor
|
||||
.Fa fd .
|
||||
If
|
||||
.Fa fd
|
||||
is equal to
|
||||
.Dv AT_FDCWD ,
|
||||
operate on the current working directory.
|
||||
.El
|
||||
.Sh RETURN VALUES
|
||||
.Rv -std
|
||||
|
@ -401,7 +401,9 @@ namei_setup(struct nameidata *ndp, struct vnode **dpp, struct pwd **pwdp)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (error == 0 && (*dpp)->v_type != VDIR)
|
||||
if (error == 0 && (*dpp)->v_type != VDIR &&
|
||||
(cnp->cn_pnbuf[0] != '\0' ||
|
||||
(cnp->cn_flags & EMPTYPATH) == 0))
|
||||
error = ENOTDIR;
|
||||
}
|
||||
if (error == 0 && (cnp->cn_flags & RBENEATH) != 0) {
|
||||
@ -458,23 +460,62 @@ namei_getpath(struct nameidata *ndp)
|
||||
&ndp->ni_pathlen);
|
||||
}
|
||||
|
||||
if (__predict_false(error != 0)) {
|
||||
namei_cleanup_cnp(cnp);
|
||||
if (__predict_false(error != 0))
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Don't allow empty pathnames.
|
||||
* Don't allow empty pathnames unless EMPTYPATH is specified.
|
||||
* Caller checks for ENOENT as an indication for the empty path.
|
||||
*/
|
||||
if (__predict_false(*cnp->cn_pnbuf == '\0')) {
|
||||
namei_cleanup_cnp(cnp);
|
||||
if (__predict_false(*cnp->cn_pnbuf == '\0'))
|
||||
return (ENOENT);
|
||||
}
|
||||
|
||||
cnp->cn_nameptr = cnp->cn_pnbuf;
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
namei_emptypath(struct nameidata *ndp)
|
||||
{
|
||||
struct componentname *cnp;
|
||||
struct pwd *pwd;
|
||||
struct vnode *dp;
|
||||
int error;
|
||||
|
||||
cnp = &ndp->ni_cnd;
|
||||
MPASS(*cnp->cn_pnbuf == '\0');
|
||||
MPASS((cnp->cn_flags & EMPTYPATH) != 0);
|
||||
MPASS((cnp->cn_flags & (LOCKPARENT | WANTPARENT)) == 0);
|
||||
|
||||
error = namei_setup(ndp, &dp, &pwd);
|
||||
if (error != 0) {
|
||||
namei_cleanup_cnp(cnp);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
ndp->ni_vp = dp;
|
||||
vref(dp);
|
||||
namei_cleanup_cnp(cnp);
|
||||
pwd_drop(pwd);
|
||||
ndp->ni_resflags |= NIRES_EMPTYPATH;
|
||||
NDVALIDATE(ndp);
|
||||
if ((cnp->cn_flags & LOCKLEAF) != 0) {
|
||||
VOP_LOCK(dp, (cnp->cn_flags & LOCKSHARED) != 0 ?
|
||||
LK_SHARED : LK_EXCLUSIVE);
|
||||
if (VN_IS_DOOMED(dp)) {
|
||||
vput(dp);
|
||||
error = ENOENT;
|
||||
goto errout;
|
||||
}
|
||||
}
|
||||
SDT_PROBE4(vfs, namei, lookup, return, 0, ndp->ni_vp, false, ndp);
|
||||
return (0);
|
||||
|
||||
errout:
|
||||
SDT_PROBE4(vfs, namei, lookup, return, error, NULL, false, ndp);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a pathname into a pointer to a locked vnode.
|
||||
*
|
||||
@ -555,6 +596,11 @@ namei(struct nameidata *ndp)
|
||||
|
||||
error = namei_getpath(ndp);
|
||||
if (__predict_false(error != 0)) {
|
||||
if (error == ENOENT && (cnp->cn_flags & EMPTYPATH) != 0)
|
||||
return (namei_emptypath(ndp));
|
||||
namei_cleanup_cnp(cnp);
|
||||
SDT_PROBE4(vfs, namei, lookup, return, error, NULL,
|
||||
false, ndp);
|
||||
return (error);
|
||||
}
|
||||
|
||||
@ -588,6 +634,7 @@ namei(struct nameidata *ndp)
|
||||
ndp->ni_loopcnt = 0;
|
||||
error = namei_getpath(ndp);
|
||||
if (__predict_false(error != 0)) {
|
||||
namei_cleanup_cnp(cnp);
|
||||
return (error);
|
||||
}
|
||||
/* FALLTHROUGH */
|
||||
|
@ -129,6 +129,8 @@ at2cnpflags(u_int at_flags, u_int mask)
|
||||
res |= (at_flags & AT_SYMLINK_NOFOLLOW) != 0 ? NOFOLLOW :
|
||||
FOLLOW;
|
||||
}
|
||||
if ((mask & AT_EMPTY_PATH) != 0 && (at_flags & AT_EMPTY_PATH) != 0)
|
||||
res |= EMPTYPATH;
|
||||
return (res);
|
||||
}
|
||||
|
||||
@ -1496,12 +1498,13 @@ sys_linkat(struct thread *td, struct linkat_args *uap)
|
||||
int flag;
|
||||
|
||||
flag = uap->flag;
|
||||
if ((flag & ~(AT_SYMLINK_FOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((flag & ~(AT_SYMLINK_FOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
return (kern_linkat(td, uap->fd1, uap->fd2, uap->path1, uap->path2,
|
||||
UIO_USERSPACE, at2cnpflags(flag, AT_SYMLINK_FOLLOW |
|
||||
AT_RESOLVE_BENEATH)));
|
||||
AT_RESOLVE_BENEATH | AT_EMPTY_PATH)));
|
||||
}
|
||||
|
||||
int hardlink_check_uid = 0;
|
||||
@ -1578,6 +1581,23 @@ kern_linkat_vp(struct thread *td, struct vnode *vp, int fd, const char *path,
|
||||
LOCKPARENT | SAVENAME | AUDITVNODE2 | NOCACHE, segflag, path, fd,
|
||||
&cap_linkat_target_rights, td);
|
||||
if ((error = namei(&nd)) == 0) {
|
||||
if ((nd.ni_resflags & NIRES_EMPTYPATH) != 0) {
|
||||
error = priv_check(td, PRIV_VFS_FHOPEN);
|
||||
if (error != 0) {
|
||||
NDFREE(&nd, NDF_ONLY_PNBUF);
|
||||
if (nd.ni_vp != NULL) {
|
||||
if (nd.ni_dvp == nd.ni_vp)
|
||||
vrele(nd.ni_dvp);
|
||||
else
|
||||
vput(nd.ni_dvp);
|
||||
vrele(nd.ni_vp);
|
||||
} else {
|
||||
vput(nd.ni_dvp);
|
||||
}
|
||||
vrele(vp);
|
||||
return (error);
|
||||
}
|
||||
}
|
||||
if (nd.ni_vp != NULL) {
|
||||
NDFREE(&nd, NDF_ONLY_PNBUF);
|
||||
if (nd.ni_dvp == nd.ni_vp)
|
||||
@ -2075,7 +2095,7 @@ kern_accessat(struct thread *td, int fd, const char *path,
|
||||
struct nameidata nd;
|
||||
int error;
|
||||
|
||||
if ((flag & ~(AT_EACCESS | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((flag & ~(AT_EACCESS | AT_RESOLVE_BENEATH | AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
if (amode != F_OK && (amode & ~(R_OK | W_OK | X_OK)) != 0)
|
||||
return (EINVAL);
|
||||
@ -2096,8 +2116,8 @@ kern_accessat(struct thread *td, int fd, const char *path,
|
||||
usecred = cred;
|
||||
AUDIT_ARG_VALUE(amode);
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, FOLLOW | LOCKSHARED | LOCKLEAF |
|
||||
AUDITVNODE1 | at2cnpflags(flag, AT_RESOLVE_BENEATH),
|
||||
pathseg, path, fd, &cap_fstat_rights, td);
|
||||
AUDITVNODE1 | at2cnpflags(flag, AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH), pathseg, path, fd, &cap_fstat_rights, td);
|
||||
if ((error = namei(&nd)) != 0)
|
||||
goto out;
|
||||
vp = nd.ni_vp;
|
||||
@ -2387,12 +2407,13 @@ kern_statat(struct thread *td, int flag, int fd, const char *path,
|
||||
struct nameidata nd;
|
||||
int error;
|
||||
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_RESOLVE_BENEATH |
|
||||
AT_SYMLINK_NOFOLLOW) | LOCKSHARED | LOCKLEAF | AUDITVNODE1,
|
||||
pathseg, path, fd, &cap_fstat_rights, td);
|
||||
AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH) | LOCKSHARED | LOCKLEAF |
|
||||
AUDITVNODE1, pathseg, path, fd, &cap_fstat_rights, td);
|
||||
|
||||
if ((error = namei(&nd)) != 0)
|
||||
return (error);
|
||||
@ -2710,7 +2731,8 @@ int
|
||||
sys_chflagsat(struct thread *td, struct chflagsat_args *uap)
|
||||
{
|
||||
|
||||
if ((uap->atflag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((uap->atflag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
return (kern_chflagsat(td, uap->fd, uap->path, UIO_USERSPACE,
|
||||
@ -2743,8 +2765,8 @@ kern_chflagsat(struct thread *td, int fd, const char *path,
|
||||
|
||||
AUDIT_ARG_FFLAGS(flags);
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(atflag, AT_SYMLINK_NOFOLLOW |
|
||||
AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd,
|
||||
&cap_fchflags_rights, td);
|
||||
AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path,
|
||||
fd, &cap_fchflags_rights, td);
|
||||
if ((error = namei(&nd)) != 0)
|
||||
return (error);
|
||||
NDFREE_NOTHING(&nd);
|
||||
@ -2838,7 +2860,8 @@ int
|
||||
sys_fchmodat(struct thread *td, struct fchmodat_args *uap)
|
||||
{
|
||||
|
||||
if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
return (kern_fchmodat(td, uap->fd, uap->path, UIO_USERSPACE,
|
||||
@ -2871,8 +2894,8 @@ kern_fchmodat(struct thread *td, int fd, const char *path,
|
||||
|
||||
AUDIT_ARG_MODE(mode);
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW |
|
||||
AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd,
|
||||
&cap_fchmod_rights, td);
|
||||
AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path,
|
||||
fd, &cap_fchmod_rights, td);
|
||||
if ((error = namei(&nd)) != 0)
|
||||
return (error);
|
||||
NDFREE_NOTHING(&nd);
|
||||
@ -2966,7 +2989,8 @@ int
|
||||
sys_fchownat(struct thread *td, struct fchownat_args *uap)
|
||||
{
|
||||
|
||||
if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((uap->flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
return (kern_fchownat(td, uap->fd, uap->path, UIO_USERSPACE, uap->uid,
|
||||
@ -2982,8 +3006,8 @@ kern_fchownat(struct thread *td, int fd, const char *path,
|
||||
|
||||
AUDIT_ARG_OWNER(uid, gid);
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW |
|
||||
AT_RESOLVE_BENEATH) | AUDITVNODE1, pathseg, path, fd,
|
||||
&cap_fchown_rights, td);
|
||||
AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1, pathseg, path,
|
||||
fd, &cap_fchown_rights, td);
|
||||
|
||||
if ((error = namei(&nd)) != 0)
|
||||
return (error);
|
||||
@ -3334,13 +3358,14 @@ kern_utimensat(struct thread *td, int fd, const char *path,
|
||||
struct timespec ts[2];
|
||||
int error, flags;
|
||||
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH)) != 0)
|
||||
if ((flag & ~(AT_SYMLINK_NOFOLLOW | AT_RESOLVE_BENEATH |
|
||||
AT_EMPTY_PATH)) != 0)
|
||||
return (EINVAL);
|
||||
|
||||
if ((error = getutimens(tptr, tptrseg, ts, &flags)) != 0)
|
||||
return (error);
|
||||
NDINIT_ATRIGHTS(&nd, LOOKUP, at2cnpflags(flag, AT_SYMLINK_NOFOLLOW |
|
||||
AT_RESOLVE_BENEATH) | AUDITVNODE1,
|
||||
AT_RESOLVE_BENEATH | AT_EMPTY_PATH) | AUDITVNODE1,
|
||||
pathseg, path, fd, &cap_futimes_rights, td);
|
||||
if ((error = namei(&nd)) != 0)
|
||||
return (error);
|
||||
|
@ -224,6 +224,7 @@ typedef __pid_t pid_t;
|
||||
/* #define AT_UNUSED1 0x1000 *//* Was AT_BENEATH */
|
||||
#define AT_RESOLVE_BENEATH 0x2000 /* Do not allow name resolution
|
||||
to walk out of dirfd */
|
||||
#define AT_EMPTY_PATH 0x4000 /* Operate on dirfd if path is empty */
|
||||
#endif /* __BSD_VISIBLE */
|
||||
|
||||
/*
|
||||
|
@ -144,10 +144,12 @@ int cache_fplookup(struct nameidata *ndp, enum cache_fpl_status *status,
|
||||
#define WANTPARENT 0x0010 /* want parent vnode returned unlocked */
|
||||
#define FAILIFEXISTS 0x0020 /* return EEXIST if found */
|
||||
#define FOLLOW 0x0040 /* follow symbolic links */
|
||||
#define EMPTYPATH 0x0080 /* Allow empty path for *at */
|
||||
#define LOCKSHARED 0x0100 /* Shared lock leaf */
|
||||
#define NOFOLLOW 0x0000 /* do not follow symbolic links (pseudo) */
|
||||
#define RBENEATH 0x100000000ULL /* No escape, even tmp, from start dir */
|
||||
#define MODMASK 0xf000001ffULL /* mask of operational modifiers */
|
||||
|
||||
/*
|
||||
* Namei parameter descriptors.
|
||||
*
|
||||
@ -198,6 +200,7 @@ int cache_fplookup(struct nameidata *ndp, enum cache_fpl_status *status,
|
||||
*/
|
||||
#define NIRES_ABS 0x00000001 /* Path was absolute */
|
||||
#define NIRES_STRICTREL 0x00000002 /* Restricted lookup result */
|
||||
#define NIRES_EMPTYPATH 0x00000004 /* EMPTYPATH used */
|
||||
|
||||
/*
|
||||
* Flags in ni_lcf, valid for the duration of the namei call.
|
||||
|
Loading…
Reference in New Issue
Block a user