git: 73a642c47955 - stable/13 - Add AT_EMPTY_PATH for several *at(2) syscalls

Konstantin Belousov kib at FreeBSD.org
Fri Apr 23 11:15:33 UTC 2021


The branch stable/13 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=73a642c479550ea46b56d1f25cc32a99f917b891

commit 73a642c479550ea46b56d1f25cc32a99f917b891
Author:     Konstantin Belousov <kib at FreeBSD.org>
AuthorDate: 2021-03-07 14:29:09 +0000
Commit:     Konstantin Belousov <kib at FreeBSD.org>
CommitDate: 2021-04-23 11:14:10 +0000

    Add AT_EMPTY_PATH for several *at(2) syscalls
    
    (cherry picked from commit 509124b62616f73dcdc42263ee109392dafafd99)
---
 lib/libc/sys/access.2    | 13 +++++++++-
 lib/libc/sys/chflags.2   | 13 +++++++++-
 lib/libc/sys/chmod.2     | 13 +++++++++-
 lib/libc/sys/chown.2     | 13 +++++++++-
 lib/libc/sys/link.2      | 11 ++++++++-
 lib/libc/sys/stat.2      | 13 +++++++++-
 lib/libc/sys/utimensat.2 | 13 +++++++++-
 sys/kern/vfs_lookup.c    | 63 ++++++++++++++++++++++++++++++++++++++++++------
 sys/kern/vfs_syscalls.c  | 63 +++++++++++++++++++++++++++++++++---------------
 sys/sys/fcntl.h          |  1 +
 sys/sys/namei.h          |  3 +++
 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.


More information about the dev-commits-src-all mailing list