From 82b70e322306816515627d4ef65225c21a59c7da Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 10 Dec 2025 12:26:34 -0800 Subject: [PATCH] Fix a bug in `linkat` This commit fixes a longstanding issue in wasi-libc where the `linkat` function, when both parameters are `AT_FDCWD` or absolute paths, would relativize the two input paths in such a way that the previous stomped over the first, meaning that the wrong actual syscall was issued. This fixes the problem in the same manner as other two-path-taking functions which is to use `find_relpath` for one path and `find_relpath_alt` for the other path. --- libc-bottom-half/sources/posix.c | 2 +- test/src/link.c | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/libc-bottom-half/sources/posix.c b/libc-bottom-half/sources/posix.c index 7da6a3df6..499def65c 100644 --- a/libc-bottom-half/sources/posix.c +++ b/libc-bottom-half/sources/posix.c @@ -408,7 +408,7 @@ __wasilibc_link(const char *oldpath, const char *newpath, int flags) char *old_relative_path; char *new_relative_path; int old_dirfd = find_relpath(oldpath, &old_relative_path); - int new_dirfd = find_relpath(newpath, &new_relative_path); + int new_dirfd = find_relpath_alt(newpath, &new_relative_path); // If we can't find a preopen for it, fail as if we can't find the path. if (old_dirfd == -1 || new_dirfd == -1) { diff --git a/test/src/link.c b/test/src/link.c index b425ab880..3eb2122d8 100644 --- a/test/src/link.c +++ b/test/src/link.c @@ -25,9 +25,34 @@ int main(void) close(fd); TEST(link(tmp, tmp1)==0); + TEST(unlink(tmp1) != -1); + TEST(linkat(AT_FDCWD, tmp, AT_FDCWD, tmp1, 0)==0); TEST(unlink(tmp1) != -1); + TEST(unlink(tmp) != -1); + char src[] = "/dir/a"; + char dst[] = "/dir/b"; + + TEST(mkdir("dir", 0700) == 0); + TEST((fd = open(src, flags | O_WRONLY | O_CREAT | O_EXCL, 0600)) > 2); + close(fd); + + TEST(link(src, dst)==0); + TEST(unlink(dst) != -1); + + TEST(linkat(AT_FDCWD, src, AT_FDCWD, dst, 0) == 0); + TEST(unlink(dst) != -1); + + TEST(chdir("/dir") == 0); + + TEST(linkat(AT_FDCWD, "a", AT_FDCWD, "b", 0) == 0); + TEST(unlink("b") != -1); + + TEST(chdir("/") == 0); + + TEST(unlink(src) != -1); + return t_status; }