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>