From fb7bb95209e7c835ce7f8f64684df695a4a43184 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Thu, 18 Jun 2026 01:13:04 +0800 Subject: [PATCH] Fix trailing-slash mkdir under sysroot proc_resolve_sysroot_create_path located the leaf separator with strrchr on the resolved sysroot path. A guest path ending in a slash made strrchr match that trailing slash, so the parent buffer collapsed to the target itself. The parent-existence access() then failed and the resolver wrongly fell back to the host literal path (or auto-created the target as if it were its own parent), so mkdir("/dir/") reported ENOENT or EEXIST instead of creating the directory in the sysroot. Drop trailing slashes before splitting off the leaf so the check sees the real parent. Guard all-slash guest paths ("/", "///") up front: they name the root, which always exists and has no parent, and trimming would otherwise walk strrchr into the sysroot prefix and trip the containment check (ELOOP) on a multi-component sysroot. Close #100 --- src/syscall/proc-state.c | 16 +++++++++++ tests/test-sysroot-create-paths.c | 44 +++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/syscall/proc-state.c b/src/syscall/proc-state.c index 81dc4e0..fdda07d 100644 --- a/src/syscall/proc-state.c +++ b/src/syscall/proc-state.c @@ -592,8 +592,24 @@ const char *proc_resolve_sysroot_create_path(const char *path, return NULL; } + /* An all-slash guest path ("/", "///") names the root, which always exists + * and has no parent to check; trimming it would walk strrchr into the + * sysroot prefix and trip the containment guard. + * + * Return buf as-is. + */ + if (path[strspn(path, "/")] == '\0') + return buf; + char parent[LINUX_PATH_MAX]; str_copy_trunc(parent, buf, sizeof(parent)); + /* Trailing slashes name the same directory (POSIX); drop them so the final + * separator splits off the leaf component rather than matching the trailing + * slash itself, which would leave the target as its own parent. + */ + size_t plen = strlen(parent); + while (plen > 1 && parent[plen - 1] == '/') + parent[--plen] = '\0'; char *slash = strrchr(parent, '/'); if (!slash || slash == parent) return buf; diff --git a/tests/test-sysroot-create-paths.c b/tests/test-sysroot-create-paths.c index 3ff1082..3eb1596 100644 --- a/tests/test-sysroot-create-paths.c +++ b/tests/test-sysroot-create-paths.c @@ -6,8 +6,10 @@ #include #include +#include #include #include +#include #include #include @@ -57,6 +59,23 @@ int main(int argc, char **argv) printf("test-sysroot-create-paths: create-path routing tests\n"); + TEST("root create path stays inside sysroot"); + { + const char *roots[] = {"/", "///"}; + bool ok = true; + for (size_t i = 0; i < sizeof(roots) / sizeof(roots[0]); i++) { + errno = 0; + if (mkdir(roots[i], 0777) == 0 || errno != EEXIST) { + ok = false; + break; + } + } + if (ok) + PASS(); + else + FAIL("mkdir root path returned wrong result"); + } + TEST("/tmp create is redirected into sysroot"); { if (write_file(guest_tmp_path, "tmp-redir\n") < 0) { @@ -75,6 +94,31 @@ int main(int argc, char **argv) } } + TEST("trailing-slash mkdir resolves against the real parent"); + { + /* Issue #100: a trailing slash made the parent-existence check split + * the target off itself, so the create wrongly fell back / returned + * EEXIST instead of creating the directory in the sysroot. + */ + const char *parent = "/tmp/elfuse-trailing-slash"; + const char *child = "/tmp/elfuse-trailing-slash/child/"; + struct stat st; + rmdir("/tmp/elfuse-trailing-slash/child"); + rmdir(parent); + if (mkdir(parent, 0777) < 0 && errno != EEXIST) { + FAIL("parent mkdir failed"); + } else if (mkdir(child, 0777) < 0) { + FAIL("trailing-slash mkdir failed"); + } else if (stat("/tmp/elfuse-trailing-slash/child", &st) < 0 || + !S_ISDIR(st.st_mode)) { + FAIL("trailing-slash target is not a directory"); + } else { + rmdir("/tmp/elfuse-trailing-slash/child"); + rmdir(parent); + PASS(); + } + } + TEST("non-sysroot absolute create falls back to host"); { if (write_file(host_fallback_path, "host-fallback\n") < 0) {