From db51cc5423ef65b61572b5211d5426e0d53b0954 Mon Sep 17 00:00:00 2001 From: jotpalch Date: Fri, 16 Oct 2026 15:12:50 +0800 Subject: [PATCH] Fail a '..' after a missing /dev/bus/usb name /dev/bus/usb/099/../001/001 answered as the usbfs node where Linux answers ENOENT: the walk fails at 099, which does not exist, before the '..' is applied. The layer folded the '..' without checking whether the name before it exists, so stat and open served the node, and 099/../../../null from a /dev/bus/usb cwd reached /dev/null. On a sysroot that carries a dev/bus/usb/099 directory, /dev/bus/usb/099/../../other/f went to the backing, which walked through that directory although the layer answers ENOENT for /dev/bus/usb/099 itself. Every name under /dev/bus/usb is the layer's, so usb_sysfs_resolve_dev_climb, which already checks each directory it pops, fails such a walk with ENOENT in path_translate_at. path_rebuild_under stops its fold at a missing name as it does at a node, so a name relative to a /dev/bus cwd or descriptor reaches the same check. Its stop test, usb_sysfs_stops_fold, takes usb_lock to look a bus up, and cwd_lock holds only fuse_lock beneath it, so resolve_proc_cwd_path copies the cwd out of its view before rebuilding. A '..' after a name shaped like a node, such as 001/099, is left to the intercepts, which answer ENOENT when no such device exists; calls that skip the intercepts, such as chmod, still reach the backing for such a name. A '..' after a name outside /dev/bus/usb is still applied unchecked, and in an absolute name so is every later '..' except one after a name shaped like a node, as docs/internals.md says. The shadow-dotdot column was recorded on Linux 7.0; recording the table again left every other cell unchanged. With the ENOENT return in usb_sysfs_resolve_dev_climb taken out, the column fails 19 of its 20 cells. test-usb-sysfs checks the absolute, cwd, fchdir, dirfd and openat-retry spellings. --- docs/internals.md | 22 +++++-- src/runtime/usb-sysfs.c | 50 ++++++++++----- src/runtime/usb-sysfs.h | 23 ++++--- src/syscall/path.c | 43 ++++++++----- src/syscall/path.h | 2 +- tests/test-usb-sysfs-matrix.c | 10 ++- tests/test-usb-sysfs.c | 103 ++++++++++++++++++++++++++++--- tests/usb-sysfs-matrix-vectors.h | 45 +++++++------- 8 files changed, 217 insertions(+), 81 deletions(-) diff --git a/docs/internals.md b/docs/internals.md index e73de956..0f86ef9d 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -1683,12 +1683,8 @@ path is built by `usb_sys_resolve_suffix`, which resolves symlinks and applies each `..` to what the previous component resolved to, as Linux does, so `/subsystem/..` names `/sys/bus`. On `/dev/bus` a `..` after a usbfs node is not applied, since Linux applies `..` only to a directory, and the name -answers `ENOTDIR` once the node exists. A `..` after any other name is applied -without checking that the name exists, so `/dev/bus/usb/099/../001/001` is -served where Linux answers `ENOENT`. A relative name is folded the same way up -to a usbfs node, so `099/../../../null` from a `/dev/bus/usb` cwd skips the -check the climb rewrite makes for the absolute spelling. Deciding ownership on -the unfolded spelling splits the two halves apart in both directions: +answers `ENOTDIR` once the node exists. Deciding ownership on the unfolded +spelling splits the two halves apart in both directions: `/dev/bus/usb/../other/f` reads as a malformed device number and is claimed, and `/dev/bus/other/../usb/001/002` reads as a foreign bus and is disowned. A suffix that folds away above its own root leaves as `USB_PATH_NONE`. @@ -1703,6 +1699,20 @@ in `.`, as in `/dev/.`, so `rmdir` and `rename` fail rather than act on the directory reached, with `EINVAL` on a sysroot where Linux gives `ENOTEMPTY` and `EBUSY` for a final `..`. +Every name under `/dev/bus/usb` is the layer's, so a `..` after a bus no device +has, or after a malformed bus or device number, fails the walk with `ENOENT`, as +Linux fails it at the missing name. The path layer refuses +`/dev/bus/usb/099/../001/001` in the same step that rewrites a climb, before any +intercept sees it, and a relative name is folded only up to a usbfs node or such +a name, so `099/../001/001` from a `/dev/bus/usb` cwd is refused the same way. A +`..` after a name shaped like a node for a device that does not exist is left to +the intercepts, which answer `ENOENT`. A `..` after a `USB_PATH_DEV_FOREIGN` +name is applied without checking that the backing has it, so +`/dev/bus/nosuch/../usb/001/001` is served where Linux answers `ENOENT`. In an +absolute name the rest is then folded unchecked as well, so +`/dev/bus/other/../usb/099/../001/001` is served too, while the same name +relative to a `/dev/bus` cwd is `ENOENT`. + ### Filesystem Identity Of A Descriptor Two questions have one answer here: what `statfs` reports for a name, and what diff --git a/src/runtime/usb-sysfs.c b/src/runtime/usb-sysfs.c index 2a992219..300092db 100644 --- a/src/runtime/usb-sysfs.c +++ b/src/runtime/usb-sysfs.c @@ -1295,8 +1295,9 @@ static bool usb_dev_names_dir(const char *folded) * Returns 1 with @out filled, 0 when the walk climbs above the root, -1 when * the result does not fit. With @rest, under usb_lock after ensure_usb_tree(), * a '..' pops only a directory usb_dev_names_dir accepts, and *@rest is left on - * what follows a climbing '..', or NULL when a '..' meets any other name. - * Without @rest no lock is held, and any other name is popped unchecked. + * what follows a climbing '..'; a '..' that meets any other name returns 0 with + * *@rest NULL and that name in @out. Without @rest no lock is held, and any + * other name is popped unchecked. */ static int usb_suffix_normalize(const char *suffix, bool dev, @@ -2327,12 +2328,21 @@ int usb_sysfs_resolve_guest_path(const char *guest_path, return rc; } -bool usb_sysfs_names_node(const char *guest_path) +bool usb_sysfs_stops_fold(const char *guest_path) { + if (!path_prefix_match(guest_path, "/dev/bus/usb", 12)) + return false; int bus = 0, dev = 0; const char *sfx = ""; - return path_prefix_match(guest_path, "/dev/bus/usb", 12) && - classify_path(guest_path, &bus, &dev, &sfx) == USB_PATH_DEV_NODE; + usb_path_kind_t kind = classify_path(guest_path, &bus, &dev, &sfx); + if (kind == USB_PATH_DEV_USB) + return false; + if (kind != USB_PATH_DEV_BUSNUM) + return true; + pthread_mutex_lock(&usb_lock); + bool served = ensure_usb_tree() == 0 && bus_exists(bus); + pthread_mutex_unlock(&usb_lock); + return !served; } bool usb_sysfs_node_path(const char *guest_path, char *out, size_t outsz) @@ -2345,23 +2355,31 @@ bool usb_sysfs_node_path(const char *guest_path, char *out, size_t outsz) &cerr) == USB_PATH_DEV_NODE; } -bool usb_sysfs_resolve_dev_climb(const char *guest_path, - char *out, - size_t outsz) +int usb_sysfs_resolve_dev_climb(const char *guest_path, char *out, size_t outsz) { if (!path_prefix_match(guest_path, "/dev/bus", 8) || !strstr(guest_path, "..")) - return false; + return 0; char folded[LINUX_PATH_MAX]; const char *rest = NULL; + int frc = -1; pthread_mutex_lock(&usb_lock); - bool climbs = ensure_usb_tree() == 0 && - usb_suffix_normalize(skip_slashes(guest_path + 8), true, - folded, sizeof(folded), &rest) == 0 && - rest; + if (ensure_usb_tree() == 0) + frc = usb_suffix_normalize(skip_slashes(guest_path + 8), true, folded, + sizeof(folded), &rest); pthread_mutex_unlock(&usb_lock); - if (!climbs) - return false; + if (frc != 0) + return 0; + + /* The '..' met a name that is not a directory the layer serves. */ + if (!rest) { + int bus = 0; + usb_path_kind_t kind = usb_dev_kind(folded, &bus); + if (kind != USB_PATH_DEV_BUSNUM && kind != USB_PATH_DEV_ABSENT) + return 0; + errno = ENOENT; + return -1; + } /* A further '..' climbs /dev, and one past it stays at the root as on * Linux; left in the name, it reaches the backing as /dev/.., where no @@ -2379,7 +2397,7 @@ bool usb_sysfs_resolve_dev_climb(const char *guest_path, * sysroot where Linux gives ENOTEMPTY and EBUSY for a final '..'. */ int n = snprintf(out, outsz, "%s/%s", dir, *rest ? rest : "."); - return n > 0 && (size_t) n < outsz; + return n > 0 && (size_t) n < outsz ? 1 : 0; } bool usb_sysfs_dir_unions_backing(const char *guest_path) diff --git a/src/runtime/usb-sysfs.h b/src/runtime/usb-sysfs.h index 0827b1b5..d2df85d0 100644 --- a/src/runtime/usb-sysfs.h +++ b/src/runtime/usb-sysfs.h @@ -75,10 +75,12 @@ int usb_sysfs_resolve_guest_path(const char *guest_path, char *out, size_t outsz); -/* Whether the folded guest name @guest_path is a usbfs node, by its shape - * alone; it takes no lock. +/* Whether a fold of a relative name stops at the folded guest name @guest_path: + * every name under /dev/bus/usb except /dev/bus/usb itself and a bus directory + * the layer has, since Linux applies '..' only to a directory that exists. It + * takes usb_lock to look a bus directory up. */ -bool usb_sysfs_names_node(const char *guest_path); +bool usb_sysfs_stops_fold(const char *guest_path); /* The canonical /dev/bus/usb/BBB/DDD spelling of @guest_path when it names a * usbfs node once its '.' and '..' are folded as the stat intercept folds them. @@ -95,14 +97,17 @@ bool usb_sysfs_node_path(const char *guest_path, char *out, size_t outsz); * directories the layer serves, to /dev followed by what comes after the '..' * that climbed, or to /dev/. when nothing does. Those directories are the * layer's, and the macOS host has no /dev/bus at all, so the '..' cannot be - * left to the backing; a walk through any other name is. A further '..' is - * applied to /dev and stops at the root. + * left to the backing. A further '..' is applied to /dev and stops at the root. + * A '..' after a bus no device has, or after a name under /dev/bus/usb that is + * not a bus or device number, fails the walk, as on Linux; one after a node, a + * name below a node or a name outside /dev/bus/usb is left as written. * - * Returns true with @out filled, or false when nothing is rewritten. + * Returns 1 with @out filled, 0 when nothing is rewritten, or -1 with errno set + * to ENOENT for a '..' that fails the walk. */ -bool usb_sysfs_resolve_dev_climb(const char *guest_path, - char *out, - size_t outsz); +int usb_sysfs_resolve_dev_climb(const char *guest_path, + char *out, + size_t outsz); /* Malloc'd copy of the usbfs descriptors blob (18-byte little-endian device * descriptor followed by every raw configuration descriptor in index order) for diff --git a/src/syscall/path.c b/src/syscall/path.c index e619555a..88573782 100644 --- a/src/syscall/path.c +++ b/src/syscall/path.c @@ -647,14 +647,19 @@ int path_translate_at(guest_fd_t dirfd, } /* usb_sysfs_resolve_dev_climb rewrites a /dev/bus name whose '..' climbs - * out of it. What follows can climb out again, as in - * /dev/bus/../bus/../null, and each pass shortens the name. A climb can - * land in /sys, so this runs before the /sys rewrite below. + * out of it, and fails one whose '..' follows a bus no device has or a name + * under /dev/bus/usb that is not a bus or device number. What follows a + * climb can climb out again, as in /dev/bus/../bus/../null, and each pass + * shortens the name. A climb can land in /sys, so this runs before the /sys + * rewrite below. */ while (path_prefix_match(tx->guest_path, "/dev/bus", 8)) { char resolved[LINUX_PATH_MAX]; - if (!usb_sysfs_resolve_dev_climb(tx->guest_path, resolved, - sizeof(resolved))) + int crc = usb_sysfs_resolve_dev_climb(tx->guest_path, resolved, + sizeof(resolved)); + if (crc < 0) + return -1; + if (crc == 0) break; str_copy_trunc(tx->guest_buf, resolved, sizeof(tx->guest_buf)); tx->guest_path = tx->guest_buf; @@ -1368,10 +1373,11 @@ static bool proc_path_fd_is_dir(const fd_entry_t *snap) } /* Rebuild @path against the guest directory @base as an absolute guest path, - * folding its components. Once the walk reaches a usbfs node the rest is joined - * as written from the slash after it: Linux applies '..' only to a directory, - * and the USB layer answers ENOTDIR for a '..', a name or a trailing slash - * after a node. + * folding its components. Once the walk reaches a usbfs node, or a name under + * /dev/bus/usb that does not exist, the rest is joined as written from the + * slash after it: Linux applies '..' only to a directory that exists, and the + * USB layer answers ENOTDIR for a '..', a name or a trailing slash after a + * node, and ENOENT for a '..' after a missing name. * * Returns 0, or -1 with errno set to ENAMETOOLONG. */ @@ -1388,7 +1394,7 @@ static int path_rebuild_under(const char *base, if (proc_seed_absolute_path(base, out, outsz, marks, ARRAY_SIZE(marks), &depth) < 0) goto toolong; - while (*p && !usb_sysfs_names_node(out)) { + while (*p && !usb_sysfs_stops_fold(out)) { p += strspn(p, "/"); size_t n = strcspn(p, "/"); if (n == 0) @@ -1461,9 +1467,15 @@ static int resolve_proc_cwd_path(const char *path, char *out, size_t outsz) if (!path || path[0] == '\0' || path[0] == '/') return 0; + /* The cwd is copied out of the view: path_rebuild_under can take usb_lock, + * and cwd_lock holds only fuse_lock beneath it. + */ proc_cwd_view_t view; if (proc_acquire_cwd_view(&view) < 0) return 0; + char base[LINUX_PATH_MAX]; + str_copy_trunc(base, view.path, sizeof(base)); + proc_release_cwd_view(&view); /* /dev/pts and the synthetic USB trees join /proc here: all are served from * host directories whose contents are not what the guest names, so a @@ -1472,13 +1484,10 @@ static int resolve_proc_cwd_path(const char *path, char *out, size_t outsz) * set by fchdir() onto a synthetic USB directory would resolve relative * names straight against the scratch tree. */ - int rc = 0; - if (!strncmp(view.path, "/proc", 5) || !strncmp(view.path, "/dev/pts", 8) || - !strncmp(view.path, "/sys", 4) || !strncmp(view.path, "/dev/bus", 8)) - rc = path_rebuild_under(view.path, path, out, outsz) < 0 ? -1 : 1; - - proc_release_cwd_view(&view); - return rc; + if (!strncmp(base, "/proc", 5) || !strncmp(base, "/dev/pts", 8) || + !strncmp(base, "/sys", 4) || !strncmp(base, "/dev/bus", 8)) + return path_rebuild_under(base, path, out, outsz) < 0 ? -1 : 1; + return 0; } int resolve_proc_at_path(guest_fd_t dirfd, diff --git a/src/syscall/path.h b/src/syscall/path.h index 1a95f1fd..3c3af1a7 100644 --- a/src/syscall/path.h +++ b/src/syscall/path.h @@ -276,7 +276,7 @@ int path_translate_dirent_name(bool dir_holds_escapes, /* Rebase a relative path against a host directory fd into the guest-visible * absolute spelling (F_GETPATH and sysroot strip, with the components folded up - * to a usbfs node). + * to a usbfs node or a /dev/bus/usb name that does not exist). * * Returns 1 with out filled, 0 when no mapping exists. See path.c for the * chase() rationale. diff --git a/tests/test-usb-sysfs-matrix.c b/tests/test-usb-sysfs-matrix.c index 3db37c3a..12739a45 100644 --- a/tests/test-usb-sysfs-matrix.c +++ b/tests/test-usb-sysfs-matrix.c @@ -100,6 +100,7 @@ enum { COL_CLIMB, /* a '..' out of /dev/bus over its own directories */ COL_CLIMB_FILE, /* a '..' out of /dev/bus after the backing's file */ COL_NODE_DOTDOT, /* the usb node followed by '..' */ + COL_SHADOW_DOTDOT, /* a '..' out of /dev/bus/usb after the backing's bus */ COL_COUNT, }; @@ -107,7 +108,7 @@ static const char *col_name[COL_COUNT] = { "synth-dir", "back-sys", "back-dev", "subsys", "escape", "escape-syn", "usb-node", "absent", "long-sys", "sys-root", "dev-bus", "shadow", "subsys-out", "dev-fold-out", "dev-fold-in", - "sys-fold-in", "dev-climb", "dev-climb-f", "node-dotdot", + "sys-fold-in", "dev-climb", "dev-climb-f", "node-dotdot", "shadow-dotdot", }; /* COL_SUBSYS is the one spelling that cannot be shared: the recording host's @@ -181,6 +182,8 @@ static const char *col_path(int c) return "/dev/bus/other/f/../../../null"; case COL_NODE_DOTDOT: return "/dev/bus/usb/001/001/.."; + case COL_SHADOW_DOTDOT: + return "/dev/bus/usb/099/../../other/f"; default: return "/dev/bus"; } @@ -234,6 +237,11 @@ static const char *col_path(int c) * answers ENOTDIR for and a lexical fold would turn into the bus directory. */ +/* COL_SHADOW_DOTDOT walks through COL_SHADOW's bus directory and out to + * COL_BACK_DEV's file. Linux fails the walk at 099, a bus no device has; the + * directory the sysroot fixture plants there must not let it through. + */ + /* Names that must be listed by the union directories, one comma-free name per * entry, NULL-terminated. Empty for the columns where enumeration is not the * property under test. diff --git a/tests/test-usb-sysfs.c b/tests/test-usb-sysfs.c index e0d00a34..6da69f98 100644 --- a/tests/test-usb-sysfs.c +++ b/tests/test-usb-sysfs.c @@ -1185,8 +1185,8 @@ static void check_dev_bus_names(void) close(fd); /* path_rebuild_under folds a relative name against a /dev/bus cwd or dirfd - * up to a usbfs node, so every other '..' is applied before any entry point - * or stamp sees the name. + * up to a usbfs node or a missing /dev/bus/usb name, so every other '..' is + * applied before any entry point or stamp sees the name. */ TEST("getcwd after chdir(\"..\") from /dev/bus/usb/001 is /dev/bus/usb"); EXPECT_TRUE(chdir("/dev/bus/usb/001") == 0 && chdir("..") == 0 && @@ -1312,14 +1312,17 @@ static void check_dotdot_leaves_bus(void) } /* openat of @rel from the host directory @dir, which the host refuses and - * openat retries through the guest spelling: ENOTDIR when @enotdir, else a + * openat retries through the guest spelling: @err when it is not 0, else a * descriptor. */ -static void check_hostdir_openat(const char *dir, const char *rel, bool enotdir) +static void check_hostdir_openat(const char *dir, const char *rel, int err) { char what[96]; snprintf(what, sizeof(what), "openat(%s, \"%s\") %s", dir, rel, - enotdir ? "is ENOTDIR" : "opens"); + !err ? "opens" + : err == ENOTDIR ? "is ENOTDIR" + : err == ENOENT ? "is ENOENT" + : "fails"); TEST(what); int hfd = open(dir, O_RDONLY | O_DIRECTORY); if (hfd < 0) { @@ -1328,7 +1331,7 @@ static void check_hostdir_openat(const char *dir, const char *rel, bool enotdir) } errno = 0; int fd = openat(hfd, rel, O_RDONLY | O_DIRECTORY); - EXPECT_TRUE(enotdir ? fd < 0 && errno == ENOTDIR : fd >= 0, what); + EXPECT_TRUE(err ? fd < 0 && errno == err : fd >= 0, what); if (fd >= 0) close(fd); close(hfd); @@ -1419,10 +1422,89 @@ static void check_node_dotdot_relative(void) /* The retry folds the name up to the node, so the node is not folded away * and a walk from / or out of /dev still reaches the layer. */ - check_hostdir_openat("/dev", "bus/usb/001/001/..", true); - check_hostdir_openat("/", "dev/bus/usb/001/001/..", true); - check_hostdir_openat("/dev", "../dev/bus/usb/001/001/..", true); - check_hostdir_openat("/", "dev/bus/usb/001", false); + check_hostdir_openat("/dev", "bus/usb/001/001/..", ENOTDIR); + check_hostdir_openat("/", "dev/bus/usb/001/001/..", ENOTDIR); + check_hostdir_openat("/dev", "../dev/bus/usb/001/001/..", ENOTDIR); + check_hostdir_openat("/", "dev/bus/usb/001", 0); +} + +/* A '..' after a missing /dev/bus/usb name is ENOENT, absolute or relative to a + * cwd, a dirfd or a host directory, as Linux fails the walk at the missing + * name. + */ +static void check_dotdot_after_missing(void) +{ + static const char *const names[] = { + "/dev/bus/usb/099/../001/001", + "/dev/bus/usb/foo/../001/001", + "/dev/bus/usb/001/foo/../001", + "/dev/bus/usb/../usb/099/../001/001", + "/dev/bus/../bus/usb/099/../001/001", + }; + static const char *const relative[] = { + "099/../001/001", + "099/../../../null", + "foo/..", + }; + struct stat st; + char what[96]; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + snprintf(what, sizeof(what), "%s is ENOENT", names[i]); + TEST(what); + EXPECT_ERRNO(stat(names[i], &st), ENOENT, names[i]); + } + + TEST("a read-write open of /dev/bus/usb/099/../001/001 is ENOENT"); + EXPECT_ERRNO(open("/dev/bus/usb/099/../001/001", O_RDWR), ENOENT, + "open(\"/dev/bus/usb/099/../001/001\", O_RDWR)"); + + for (size_t i = 0; i < sizeof(relative) / sizeof(relative[0]); i++) { + snprintf(what, sizeof(what), "\"%s\" in a /dev/bus/usb cwd is ENOENT", + relative[i]); + TEST(what); + if (chdir("/dev/bus/usb") != 0) + FAIL("chdir /dev/bus/usb"); + else + EXPECT_ERRNO(stat(relative[i], &st), ENOENT, relative[i]); + if (chdir("/") != 0) + FAIL("chdir back to /"); + } + + TEST("chdir(\"099/..\") in a /dev/bus/usb cwd is ENOENT"); + if (chdir("/dev/bus/usb") != 0) + FAIL("chdir /dev/bus/usb"); + else + EXPECT_ERRNO(chdir("099/.."), ENOENT, "chdir(\"099/..\")"); + if (chdir("/") != 0) + FAIL("chdir back to /"); + + check_hostdir_openat("/", "dev/bus/usb/099/../001", ENOENT); + + int dfd = open("/dev/bus/usb/001", O_RDONLY | O_DIRECTORY); + TEST("\"../099/../001/001\" against a /dev/bus/usb/001 dirfd is ENOENT"); + if (dfd < 0) { + FAIL("open /dev/bus/usb/001"); + return; + } + EXPECT_ERRNO(fstatat(dfd, "../099/../001/001", &st, 0), ENOENT, + "fstatat(/dev/bus/usb/001, \"../099/../001/001\")"); + + TEST("\"../001/001\" against a /dev/bus/usb/001 dirfd is the node"); + EXPECT_TRUE(fstatat(dfd, "../001/001", &st, 0) == 0 && S_ISCHR(st.st_mode), + "fstatat(/dev/bus/usb/001, \"../001/001\")"); + + TEST( + "\"../099/../001/001\" in a /dev/bus/usb/001 cwd from fchdir is " + "ENOENT"); + if (fchdir(dfd) != 0) + FAIL("fchdir /dev/bus/usb/001"); + else + EXPECT_ERRNO(stat("../099/../001/001", &st), ENOENT, + "stat(\"../099/../001/001\")"); + if (chdir("/") != 0) + FAIL("chdir back to /"); + close(dfd); } int main(void) @@ -1435,6 +1517,7 @@ int main(void) check_dev_bus_names(); check_dotdot_leaves_bus(); check_node_dotdot_relative(); + check_dotdot_after_missing(); /* Stated, not implied: the second half is only as strong as the bus it ran * against, and a zero here means those assertions did not execute. diff --git a/tests/usb-sysfs-matrix-vectors.h b/tests/usb-sysfs-matrix-vectors.h index 252e0707..c8b966a6 100644 --- a/tests/usb-sysfs-matrix-vectors.h +++ b/tests/usb-sysfs-matrix-vectors.h @@ -52,6 +52,9 @@ * the same, after back-dev's file * node-dotdot /dev/bus/usb/001/001/.. * usb-node's node followed by '..' + * shadow-dotdot /dev/bus/usb/099/../../other/f + * back-dev's file, spelled through shadow's bus directory, + * which only the backing has * * Two markers appear in the table. * @@ -97,25 +100,25 @@ */ /* clang-format off */ -/* synth-dir back-sys back-dev subsys escape escape-syn usb-node absent long-sys sys-root dev-bus shadow subsys-out dev-fold-out dev-fold-in sys-fold-in dev-climb dev-climb-f node-dotdot */ -/* open */ {"ok", "ok", "ok", "ok", "ok", "?ok", "-", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "-", "?ok", "ok", "E20", "E20"}, -/* open_nofollow */ {"ok", "ok", "ok", "E40", "ok", "?ok", "-", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "-", "?ok", "ok", "E20", "E20"}, -/* openat_dirfd */ {"ok", "ok", "ok", "ok", "ok", "?ok", "-", "E2", "ok", "skip", "ok", "skip", "ok", "ok", "-", "?ok", "ok", "skip", "skip"}, -/* stat */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20"}, -/* lstat */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20"}, -/* fstatat_nofollow */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20"}, -/* fstatat_dirfd */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "skip", "ok:d", "skip", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "skip", "skip"}, -/* statx */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20"}, -/* access */ {"ok", "ok", "ok", "ok", "ok", "?ok", "ok", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "ok", "?ok", "ok", "E20", "E20"}, -/* faccessat_nofollow */ {"ok", "ok", "ok", "ok", "ok", "?ok", "ok", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "ok", "?ok", "ok", "E20", "E20"}, -/* readlink */ {"E22", "E22", "E22", "ok", "E22", "?E22", "E22", "E2", "E22", "E22", "E22", "E2", "E22", "E22", "E22", "?E22", "E22", "E20", "E20"}, -/* readlinkat_dirfd */ {"E22", "E22", "E22", "ok", "E22", "?E22", "E22", "E2", "E22", "skip", "E22", "skip", "E22", "E22", "E22", "?E22", "E22", "skip", "skip"}, -/* getdents64 */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20"}, -/* statfs */ {"sysfs", "sysfs", "other", "sysfs","other","?other", "other", "E2", "sysfs", "sysfs", "other", "E2", "sysfs", "other", "other", "sysfs", "other", "E20", "E20"}, -/* fstatfs */ {"sysfs", "sysfs", "other", "sysfs","other","?other", "other", "E2", "sysfs", "sysfs", "other", "E2", "sysfs", "other", "other", "?sysfs", "other", "E20", "E20"}, -/* fstat_type */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20"}, -/* chdir */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20"}, -/* fchdir */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20"}, -/* epoll_ctl */ {"E1", "E1", "E1", "E1", "E1", "?E1", "-", "E2", "ok", "E1", "E1", "E2", "E1", "E1", "-", "?E1", "E1", "E20", "E20"}, -/* union_listing */ {"n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "all", "all", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a"}, +/* synth-dir back-sys back-dev subsys escape escape-syn usb-node absent long-sys sys-root dev-bus shadow subsys-out dev-fold-out dev-fold-in sys-fold-in dev-climb dev-climb-f node-dotdot shadow-dotdot */ +/* open */ {"ok", "ok", "ok", "ok", "ok", "?ok", "-", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "-", "?ok", "ok", "E20", "E20", "E2"}, +/* open_nofollow */ {"ok", "ok", "ok", "E40", "ok", "?ok", "-", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "-", "?ok", "ok", "E20", "E20", "E2"}, +/* openat_dirfd */ {"ok", "ok", "ok", "ok", "ok", "?ok", "-", "E2", "ok", "skip", "ok", "skip", "ok", "ok", "-", "?ok", "ok", "skip", "skip", "skip"}, +/* stat */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20", "E2"}, +/* lstat */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20", "E2"}, +/* fstatat_nofollow */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20", "E2"}, +/* fstatat_dirfd */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "skip", "ok:d", "skip", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "skip", "skip", "skip"}, +/* statx */ {"ok:d", "ok:d", "ok:f", "ok:d", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20", "E2"}, +/* access */ {"ok", "ok", "ok", "ok", "ok", "?ok", "ok", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "ok", "?ok", "ok", "E20", "E20", "E2"}, +/* faccessat_nofollow */ {"ok", "ok", "ok", "ok", "ok", "?ok", "ok", "E2", "ok", "ok", "ok", "E2", "ok", "ok", "ok", "?ok", "ok", "E20", "E20", "E2"}, +/* readlink */ {"E22", "E22", "E22", "ok", "E22", "?E22", "E22", "E2", "E22", "E22", "E22", "E2", "E22", "E22", "E22", "?E22", "E22", "E20", "E20", "E2"}, +/* readlinkat_dirfd */ {"E22", "E22", "E22", "ok", "E22", "?E22", "E22", "E2", "E22", "skip", "E22", "skip", "E22", "E22", "E22", "?E22", "E22", "skip", "skip", "skip"}, +/* getdents64 */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20", "E2"}, +/* statfs */ {"sysfs", "sysfs", "other", "sysfs","other","?other", "other", "E2", "sysfs", "sysfs", "other", "E2", "sysfs", "other", "other", "sysfs", "other", "E20", "E20", "E2"}, +/* fstatfs */ {"sysfs", "sysfs", "other", "sysfs","other","?other", "other", "E2", "sysfs", "sysfs", "other", "E2", "sysfs", "other", "other", "?sysfs", "other", "E20", "E20", "E2"}, +/* fstat_type */ {"ok:d", "ok:d", "ok:f", "ok:l", "ok:f", "?ok:f", "ok:c", "E2", "ok:f", "ok:d", "ok:d", "E2", "ok:d", "ok:f", "ok:c", "?ok:d", "ok:d", "E20", "E20", "E2"}, +/* chdir */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20", "E2"}, +/* fchdir */ {"ok", "ok", "E20", "ok", "E20", "?E20", "E20", "E2", "E20", "ok", "ok", "E2", "ok", "E20", "E20", "?ok", "ok", "E20", "E20", "E2"}, +/* epoll_ctl */ {"E1", "E1", "E1", "E1", "E1", "?E1", "-", "E2", "ok", "E1", "E1", "E2", "E1", "E1", "-", "?E1", "E1", "E20", "E20", "E2"}, +/* union_listing */ {"n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "all", "all", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a", "n/a"}, /* clang-format on */