aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Belousov <kib@FreeBSD.org>2021-03-07 14:29:09 +0000
committerKonstantin Belousov <kib@FreeBSD.org>2021-04-15 09:48:11 +0000
commit509124b62616f73dcdc42263ee109392dafafd99 (patch)
tree955762ad6dab0613574d56c408e93eb63de36ac3
parentd51b4b0aac43d9d25f7eb3f17b2d3034a5c851d8 (diff)
downloadsrc-509124b62616f73dcdc42263ee109392dafafd99.tar.gz
src-509124b62616f73dcdc42263ee109392dafafd99.zip
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
-rw-r--r--lib/libc/sys/access.213
-rw-r--r--lib/libc/sys/chflags.213
-rw-r--r--lib/libc/sys/chmod.213
-rw-r--r--lib/libc/sys/chown.213
-rw-r--r--lib/libc/sys/link.211
-rw-r--r--lib/libc/sys/stat.213
-rw-r--r--lib/libc/sys/utimensat.213
-rw-r--r--sys/kern/vfs_lookup.c63
-rw-r--r--sys/kern/vfs_syscalls.c63
-rw-r--r--sys/sys/fcntl.h1
-rw-r--r--sys/sys/namei.h3
11 files changed, 185 insertions, 34 deletions
diff --git a/lib/libc/sys/access.2 b/lib/libc/sys/access.2
index 13bfd7e5a88a..12af63385780 100644
--- a/lib/libc/sys/access.2
+++ b/lib/libc/sys/access.2
@@ -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
diff --git a/lib/libc/sys/chflags.2 b/lib/libc/sys/chflags.2
index a44713904599..f8dfd59c39d3 100644
--- a/lib/libc/sys/chflags.2
+++ b/lib/libc/sys/chflags.2
@@ -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
diff --git a/lib/libc/sys/chmod.2 b/lib/libc/sys/chmod.2
index 0127a5b629e4..44a1b18718f1 100644
--- a/lib/libc/sys/chmod.2
+++ b/lib/libc/sys/chmod.2
@@ -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
diff --git a/lib/libc/sys/chown.2 b/lib/libc/sys/chown.2
index 4c45ce9174bb..467ff8a87e55 100644
--- a/lib/libc/sys/chown.2
+++ b/lib/libc/sys/chown.2
@@ -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
diff --git a/lib/libc/sys/link.2 b/lib/libc/sys/link.2
index bcf03f17f3bb..37225f9571d0 100644
--- a/lib/libc/sys/link.2
+++ b/lib/libc/sys/link.2
@@ -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
diff --git a/lib/libc/sys/stat.2 b/lib/libc/sys/stat.2
index 0ed70620af63..55221d05a60e 100644
--- a/lib/libc/sys/stat.2
+++ b/lib/libc/sys/stat.2
@@ -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
diff --git a/lib/libc/sys/utimensat.2 b/lib/libc/sys/utimensat.2
index d31ee1f1515a..2af452898c9d 100644
--- a/lib/libc/sys/utimensat.2
+++ b/lib/libc/sys/utimensat.2
@@ -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
diff --git a/sys/kern/vfs_lookup.c b/sys/kern/vfs_lookup.c
index 07c89e634de4..f4ec3cea9fff 100644
--- a/sys/kern/vfs_lookup.c
+++ b/sys/kern/vfs_lookup.c
@@ -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 */
diff --git a/sys/kern/vfs_syscalls.c b/sys/kern/vfs_syscalls.c
index 48df8a3e9051..45f155ebff3d 100644
--- a/sys/kern/vfs_syscalls.c
+++ b/sys/kern/vfs_syscalls.c
@@ -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);
diff --git a/sys/sys/fcntl.h b/sys/sys/fcntl.h
index bc2011c31e88..0fa4e7758c9d 100644
--- a/sys/sys/fcntl.h
+++ b/sys/sys/fcntl.h
@@ -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 */
/*
diff --git a/sys/sys/namei.h b/sys/sys/namei.h
index b6985f1fa6ff..5f3d917083a5 100644
--- a/sys/sys/namei.h
+++ b/sys/sys/namei.h
@@ -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.