Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 13 Oct 2025 11:54:09 GMT
From:      Dag-Erling =?utf-8?Q?Sm=C3=B8rgrav?= <des@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 1406de21e176 - main - realpath: Report correct path on failure
Message-ID:  <202510131154.59DBs9gN052160@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=1406de21e176d8700240ac9e473df007cd41eec1

commit 1406de21e176d8700240ac9e473df007cd41eec1
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-10-13 11:53:22 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-10-13 11:53:52 +0000

    realpath: Report correct path on failure
    
    If lstat() fails with EACCES or ENOTDIR, the path we need to return in
    the caller-provided buffer is that of the parent directory (which is
    either unreadable or not a directory; the latter can only happen in the
    case of a race) rather than that of the child we attempted to stat.
    
    Sponsored by:   Klara, Inc.
    Reviewed by:    markj
    Differential Revision:  https://reviews.freebsd.org/D53025
---
 lib/libc/stdlib/realpath.c          | 14 ++++++++++++--
 lib/libc/tests/gen/realpath2_test.c | 11 ++---------
 2 files changed, 14 insertions(+), 11 deletions(-)

diff --git a/lib/libc/stdlib/realpath.c b/lib/libc/stdlib/realpath.c
index 4c52b73319ab..18f29e95ee6b 100644
--- a/lib/libc/stdlib/realpath.c
+++ b/lib/libc/stdlib/realpath.c
@@ -49,7 +49,7 @@ realpath1(const char *path, char *resolved)
 {
 	struct stat sb;
 	char *p, *q;
-	size_t left_len, resolved_len, next_token_len;
+	size_t left_len, prev_len, resolved_len, next_token_len;
 	unsigned symlinks;
 	ssize_t slen;
 	char left[PATH_MAX], next_token[PATH_MAX], symlink[PATH_MAX];
@@ -98,6 +98,7 @@ realpath1(const char *path, char *resolved)
 			left_len = 0;
 		}
 
+		prev_len = resolved_len;
 		if (resolved[resolved_len - 1] != '/') {
 			if (resolved_len + 1 >= PATH_MAX) {
 				errno = ENAMETOOLONG;
@@ -133,8 +134,17 @@ realpath1(const char *path, char *resolved)
 			errno = ENAMETOOLONG;
 			return (NULL);
 		}
-		if (lstat(resolved, &sb) != 0)
+		if (lstat(resolved, &sb) != 0) {
+			/*
+			 * EACCES means the parent directory is not
+			 * readable, while ENOTDIR means the parent
+			 * directory is not a directory.  Rewind the path
+			 * to correctly indicate where the error lies.
+			 */
+			if (errno == EACCES || errno == ENOTDIR)
+				resolved[prev_len] = '\0';
 			return (NULL);
+		}
 		if (S_ISLNK(sb.st_mode)) {
 			if (symlinks++ > MAXSYMLINKS) {
 				errno = ELOOP;
diff --git a/lib/libc/tests/gen/realpath2_test.c b/lib/libc/tests/gen/realpath2_test.c
index b8f951d9b10f..431df8721ae0 100644
--- a/lib/libc/tests/gen/realpath2_test.c
+++ b/lib/libc/tests/gen/realpath2_test.c
@@ -158,15 +158,8 @@ ATF_TC_BODY(realpath_partial, tc)
 	ATF_REQUIRE_EQ(0, chmod("foo", 000));
 	ATF_REQUIRE_ERRNO(EACCES, realpath("foo/bar/baz", resb) == NULL);
 	len = strnlen(resb, sizeof(resb));
-	ATF_REQUIRE(len > 8 && len < sizeof(resb));
-	/*
-	 * This is arguably wrong.  The problem is not with bar, but with
-	 * foo.  However, since foo exists and is a directory and the only
-	 * reliable way to detect whether a directory is readable is to
-	 * attempt to read it, we do not detect the problem until we try
-	 * to access bar.
-	 */
-	ATF_REQUIRE_STREQ("/foo/bar", resb + len - 8);
+	ATF_REQUIRE(len > 4 && len < sizeof(resb));
+	ATF_REQUIRE_STREQ("/foo", resb + len - 4);
 
 	/* scenario 6: not a directory */
 	ATF_REQUIRE_EQ(0, close(creat("bar", 0644)));



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202510131154.59DBs9gN052160>