Skip to content

Fail a '..' after a missing /dev/bus/usb name - #444

Open
jotpalch wants to merge 1 commit into
sysprog21:mainfrom
jotpalch:pr-dotdot-absent-bus
Open

jotpalch wants to merge 1 commit into
sysprog21:mainfrom
jotpalch:pr-dotdot-absent-bus

Conversation

@jotpalch

@jotpalch jotpalch commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

/dev/bus/usb/099/../001/001 answers as the usbfs node and 099/../../../null from a /dev/bus/usb cwd reaches /dev/null, where Linux answers ENOENT for both, because the layer applies a .. without checking that the name before it exists. On a sysroot with a dev/bus/usb/099 directory, /dev/bus/usb/099/../../other/f reaches the backing's file the same way. Review raised the sysroot case on #439, which left this change for a separate PR.

Reproduce with the fixture devices and a static aarch64 busybox:

$ ELFUSE_USB_FIXTURE=1 build/elfuse busybox stat -c %F /dev/bus/usb/099/../001/001
$ ELFUSE_USB_FIXTURE=1 build/elfuse busybox sh -c 'cd /dev/bus/usb && test -c 099/../../../null && echo reached || echo refused'

On main these print character special file and reached. With this change the first fails with No such file or directory and the second prints refused, as Linux does.

Tested on macOS 15.6.1 (24G90) with the 15.5 SDK on an Apple M1 (MacBookAir10,1). make check passes 100 of 101 and make test-matrix-elfuse-aarch64 gives 299 passed, 1 failed and 10 skipped. On main and on this branch alike, the one failure in each is test-thread-churn timing out, and it passes when run alone.


Summary by cubic

Fails .. after a missing /dev/bus/usb name with ENOENT, matching Linux. Previously /dev/bus/usb/099/../001/001 was served as the usbfs node and 099/../../../null from a /dev/bus/usb cwd reached /dev/null; on a sysroot with a dev/bus/usb/099 directory, 099/../../other/f reached the backing.

  • The climb rewrite in path_translate_at now returns ENOENT when .. follows a name under /dev/bus/usb that is not a served directory.
  • Relative names fold only up to a node or a missing name, so cwd, fchdir, dirfd, and openat-retry spellings reach the same check.
  • A .. after a name outside /dev/bus/usb is still applied unchecked, so /dev/bus/nosuch/../usb/001/001 remains served.
  • Adds a shadow-dotdot matrix column and test-usb-sysfs coverage for absolute, cwd, fchdir, dirfd, and openat-retry spellings.

Written for commit db51cc5. Summary will update on new commits.

View guided diff

/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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/runtime/usb-sysfs.c">

<violation number="1" location="src/runtime/usb-sysfs.c:2378">
P2: `/dev/bus/other/../usb/099/../001/001` hits this `FOREIGN` early return, so `099/..` is later folded unchecked and a valid `001/001` node is served instead of `ENOENT`. Continue validating the walk after the foreign climb so missing names reached under `/dev/bus/usb` still fail.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread src/runtime/usb-sysfs.c
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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: /dev/bus/other/../usb/099/../001/001 hits this FOREIGN early return, so 099/.. is later folded unchecked and a valid 001/001 node is served instead of ENOENT. Continue validating the walk after the foreign climb so missing names reached under /dev/bus/usb still fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At src/runtime/usb-sysfs.c, line 2378:

<comment>`/dev/bus/other/../usb/099/../001/001` hits this `FOREIGN` early return, so `099/..` is later folded unchecked and a valid `001/001` node is served instead of `ENOENT`. Continue validating the walk after the foreign climb so missing names reached under `/dev/bus/usb` still fail.</comment>

<file context>
@@ -2345,23 +2355,31 @@ bool usb_sysfs_node_path(const char *guest_path, char *out, size_t outsz)
+    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;
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant