Skip to content

fix(windows): prefer PATHEXT variants over bare filename in resolver#5364

Merged
divens merged 2 commits into
zellij-org:mainfrom
divens:fix-windows-batch-file-spawn
Jul 21, 2026
Merged

fix(windows): prefer PATHEXT variants over bare filename in resolver#5364
divens merged 2 commits into
zellij-org:mainfrom
divens:fix-windows-batch-file-spawn

Conversation

@divens

@divens divens commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #5361

find_executable used to short-circuit on the first exists()-and-is_file match and only consult PATHEXT if no bare file was present. That returned the wrong file for tools whose Windows installer ships both a Unix-style launcher and a .bat alongside it — the reported case is Composer, whose setup places bare composer (a Unix launcher, invalid PE on Windows) next to composer.bat. The bare file was resolved and handed to CreateProcessW, which rejected it with ERROR_BAD_EXE_FORMAT.

On Windows, when the candidate has no user-supplied extension, walk PATHEXT first and only fall back to the bare match if nothing matches. This mirrors cmd.exe's resolution.

`find_executable` used to short-circuit on the first exists()-and-is_file
match and only consult PATHEXT if no bare file was present. That returned
the wrong file for tools whose Windows installer ships both a Unix-style
launcher and a `.bat` alongside it — the reported case is Composer, whose
setup places bare `composer` (a Unix launcher, invalid PE on Windows) next
to `composer.bat`. The bare file was resolved and handed to CreateProcessW,
which rejected it with ERROR_BAD_EXE_FORMAT.

On Windows, when the candidate has no user-supplied extension, walk PATHEXT
first and only fall back to the bare match if nothing matches. This mirrors
cmd.exe's resolution.
@divens
divens force-pushed the fix-windows-batch-file-spawn branch from 6a8efbc to a18a07f Compare July 13, 2026 13:27
@imsnif

imsnif commented Jul 17, 2026

Copy link
Copy Markdown
Member

Hum. I'm a little bit worried about this as it's the sort of thing that's easily exploitable. How is the behavior in windows shells? If I just execute "composer" and there's both a bare composer file and a composer.bat file, is the latter the one to be executed?

@divens

divens commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

How is the behavior in windows shells? If I just execute "composer" and there's both a bare composer file and a composer.bat file, is the latter the one to be executed?

Both cmd and powershell resolve to composer.bat. I verified this locally:

  • for cmd, I created a trivial test script (printing a message) both with and without extension, changing the message, ran it with cmd /c test_script. The message printed was the one from the .bat test script.
  • for powershell, I did the same empirical verification, same result. On top of that powershell has Get-Command -All which is documented to return matching commands in execution precedence order. The .bat script comes before the bare name.

A note on why it works this way : on Windows, the PATHEXT environment variable contains the ordered list of file extensions that Windows considers executable. This is documented here. Powershell refers to this when resolving a command and respects the order for precedence.

When we're resolving commands ourselves in zellij, we must first honor extensions present in PATHEXT before falling back to a bare name option. CreateProcessW which we use in spawn_child_process doesn't resolve the command that we pass to it.

I'm a little bit worried about this as it's the sort of thing that's easily exploitable.

The specific new behavior in this patch is that when a bare file and a same-name .bat (or other PATHEXT extension) sit in the same directory, we now pick the .bat. For that to be exploitable, an attacker needs write access to a directory holding a legitimate extensionless PE binary. At that point they can just replace that binary directly.

If the attacker doesnt have access to the directory containing the legitimate binary (or script) we're trying to run, another vector would be PATH-hijacking via an earlier-PATH directory (planting a similarly name malicious binary or script in a PATH directory that appears before the one we want to run from). This is not specific to this patch.

One thing to note : the pre-patch situation wasn't really a safe fallback, it just happened to hand a non-Windows script to CreateProcessW which failed with ERROR_BAD_EXE_FORMAT and caused the crash. It's also worth noting that extension-less executables are uncommon on native Windows; CLIs, scripts and tools rely on extension resolution to give users the shorter form (composer instead of composer.bat) while conforming to the platform's file-naming conventions. That's what PATHEXT exists for, and this patch just brings zellij's resolver in line with it.

@imsnif

imsnif commented Jul 20, 2026

Copy link
Copy Markdown
Member

If this is also the behavior of the windows shells, it's fine for me (as long of course as this is just a windows change, which it seems it is).

@divens
divens merged commit 812ad86 into zellij-org:main Jul 21, 2026
10 checks passed
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.

Crash: %1 is not a valid Win32 application

2 participants