Commit Graph

287 Commits

Author SHA1 Message Date
Andrew Kelley d3d6761e43 std: depend on NtDll rather than advapi32.dll for entropy 2026-01-05 12:16:54 -08:00
Andrew Kelley 60447ea97c std: fix windows compilation errors 2026-01-04 00:27:08 -08:00
Matthew Lugg e57c557ad4 std.Io.Threaded: hugely improve Windows and NetBSD support
The most interesting thing here is the replacement of the pthread futex
implementation with an implementation based on thread park/unpark APIs.
Thread parking tends to be the primitive provided by systems which do
not have a futex primitive, such as NetBSD, so this implementation is
far more efficient than the pthread one. It is also useful on Windows,
where `RtlWaitOnAddress` is itself a userland implementation based on
thread park/unpark; we can implement it ourselves including support for
features which Windows' implementation lacks, such as cancelation and
waking a number of waiters with 1<n<infinity.

Compared to the pthread implementation, this thread-parking-based one
also supports full robust cancelation. Thread parking also turns out to
be useful for implementing `sleep`, so is now used for that on Windows
and NetBSD.

This commit also introduces proper cancelation support for most Windows
operations. The most notable omission right now is DNS lookups through
`GetAddrInfoEx`, just because they're a little more work due to having
a unique cancelation mechanism---but the machinery is all there, so I'll
finish gluing it together soon.

As of this commit, there are very few parts of `Io.Threaded` which do
not support full robust cancelation. The only ones which actually really
matter (because they could block for a prolonged period of time) are DNS
lookups on Windows (as discussed above) and futex waits on WASM.
2026-01-03 15:45:10 +00:00
Jacob Young c13857e504 windows: type safety improvements and more ntdll functions 2025-12-12 01:58:21 -05:00
Ryan Liptak 53e615b920 Merge pull request #25993 from squeek502/windows-paths
Teach `std.fs.path` about the wonderful world of Windows paths
2025-11-24 15:27:24 -08:00
Ryan Liptak bf25816067 Move Windows rename implementation from std.posix to windows.RenameFile
This also unifies the rename implementations, since previously `posix.renameW` used `MoveFileEx` while `posix.renameatW` used `NtOpenFile`/`NtSetInformationFile`. This, in turn, allows the `MoveFileEx` bindings to be deleted as `posix.renameW` was the only usage.
2025-11-23 23:38:01 -08:00
Ryan Liptak 17ecc77fc4 os.windows: Delete unused functions and kernel32 bindings 2025-11-23 23:38:01 -08:00
Ryan Liptak 59b8bed222 Teach fs.path about the wonderful world of Windows paths
Previously, fs.path handled a few of the Windows path types, but not all of them, and only a few of them correctly/consistently. This commit aims to make `std.fs.path` correct and consistent in handling all possible Win32 path types.

This commit also slightly nudges the codebase towards a separation of Win32 paths and NT paths, as NT paths are not actually distinguishable from Win32 paths from looking at their contents alone (i.e. `\Device\Foo` could be an NT path or a Win32 rooted path, no way to tell without external context). This commit formalizes `std.fs.path` being fully concerned with Win32 paths, and having no special detection/handling of NT paths.

Resources on Windows path types, and Win32 vs NT paths:

- https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
- https://chrisdenton.github.io/omnipath/Overview.html
- https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file

API additions/changes/deprecations

- `std.os.windows.getWin32PathType` was added (it is analogous to `RtlDetermineDosPathNameType_U`), while `std.os.windows.getNamespacePrefix` and `std.os.windows.getUnprefixedPathType` were deleted. `getWin32PathType` forms the basis on which the updated `std.fs.path` functions operate.
- `std.fs.path.parsePath`, `std.fs.path.parsePathPosix`, and `std.fs.path.parsePathWindows` were added, while `std.fs.path.windowsParsePath` was deprecated. The new `parsePath` functions provide the "root" and the "kind" of a path, which is platform-specific. The now-deprecated `windowsParsePath` did not handle all possible path types, while the new `parsePathWindows` does.
- `std.fs.path.diskDesignator` has been deprecated in favor of `std.fs.path.parsePath`, and same deal with `diskDesignatorWindows` -> `parsePathWindows`
- `relativeWindows` is now a compile error when *not* targeting Windows, while `relativePosix` is now a compile error when targeting Windows. This is because those functions read/use the CWD path which will behave improperly when used from a system with different path semantics (e.g. calling `relativePosix` from a Windows system with a CWD like `C:\foo\bar` will give you a bogus result since that'd be treated as a single relative component when using POSIX semantics). This also allows `relativeWindows` to use Windows-specific APIs for getting the CWD and environment variables to cut down on allocations.
- `componentIterator`/`ComponentIterator.init` have been made infallible. These functions used to be able to error on UNC paths with an empty server component, and on paths that were assumed to be NT paths, but now:
  + We follow the lead of `RtlDetermineDosPathNameType_U`/`RtlGetFullPathName_U` in how it treats a UNC path with an empty server name (e.g. `\\\share`) and allow it, even if it'll be invalid at the time of usage
  + Now that `std.fs.path` assumes paths are Win32 paths and not NT paths, we don't have to worry about NT paths

Behavior changes

- `std.fs.path` generally: any combinations of mixed path separators for UNC paths are universally supported, e.g. `\/server/share`, `/\server\share`, `/\server/\\//share` are all seen as equivalent UNC paths
- `resolveWindows` handles all path types more appropriately/consistently.
  + `//` and `//foo` used to be treated as a relative path, but are now seen as UNC paths
  + If a rooted/drive-relative path cannot be resolved against anything more definite, the result will remain a rooted/drive-relative path.
  + I've created [a script to generate the results of a huge number of permutations of different path types](https://gist.github.com/squeek502/9eba7f19cad0d0d970ccafbc30f463bf) (the result of running the script is also included for anyone that'd like to vet the behavior).
- `dirnameWindows` now treats the drive-relative root as the dirname of a drive-relative path with a component, e.g. `dirname("C:foo")` is now `C:`, whereas before it would return null. `dirnameWindows` also handles local device paths appropriately now.
- `basenameWindows` now handles all path types more appropriately. The most notable change here is `//a` being treated as a partial UNC path now and therefore `basename` will return `""` for it, whereas before it would return `"a"`
- `relativeWindows` will now do its best to resolve against the most appropriate CWD for each path, e.g. relative for `D:foo` will look at the CWD to check if the drive letter matches, and if not, look at the special environment variable `=D:` to get the shell-defined CWD for that drive, and if that doesn't exist, then it'll resolve against `D:\`.

Implementation details

- `resolveWindows` previously looped through the paths twice to build up the relevant info before doing the actual resolution. Now, `resolveWindows` iterates backwards once and keeps track of which paths are actually relevant using a bit set, which also allows it to break from the loop when it's no longer possible for earlier paths to matter.
- A standalone test was added to test parts of `relativeWindows` since the CWD resolution logic depends on CWD information from the PEB and environment variables

Edge cases worth noting

- A strange piece of trivia that I found out while working on this is that it's technically possible to have a drive letter that it outside the intended A-Z range, or even outside the ASCII range entirely. Since we deal with both WTF-8 and WTF-16 paths, `path[0]`/`path[1]`/`path[2]` will not always refer to the same bits of information, so to get consistent behavior, some decision about how to deal with this edge case had to be made. I've made the choice to conform with how `RtlDetermineDosPathNameType_U` works, i.e. treat the first WTF-16 code unit as the drive letter. This means that when working with WTF-8, checking for drive-relative/drive-absolute paths is a bit more complicated. For more details, see the lengthy comment in `std.os.windows.getWin32PathType`
- `relativeWindows` will now almost always be able to return either a fully-qualified absolute path or a relative path, but there's one scenario where it may return a rooted path: when the CWD gotten from the PEB is not a drive-absolute or UNC path (if that's actually feasible/possible?). An alternative approach to this scenario might be to resolve against the `HOMEDRIVE` env var if available, and/or default to `C:\` as a last resort in order to guarantee the result of `relative` is never a rooted path.
- Partial UNC paths (e.g. `\\server` instead of `\\server\share`) are a bit awkward to handle, generally. Not entirely sure how best to handle them, so there may need to be another pass in the future to iron out any issues that arise. As of now the behavior is:
  + For `relative`, any part of a UNC disk designator is treated as the "root" and therefore isn't applicable for relative paths, e.g. calling `relative` with `\\server` and `\\server\share` will result in `\\server\share` rather than just `share` and if `relative` is called with `\\server\foo` and `\\server\bar` the result will be `\\server\bar` rather than `..\bar`
  + For `resolve`, any part of a UNC disk designator is also treated as the "root", but relative and rooted paths are still elligable for filling in missing portions of the disk designator, e.g. `resolve` with `\\server` and `foo` or `\foo` will result in `\\server\foo`

Fixes #25703
Closes #25702
2025-11-21 00:03:44 -08:00
qilme 8347791ce3 std.os.windows: eliminate forwarder function in kernel32 (#25766)
#1840

kernel32.AddVectoredExceptionHandler -> ntdll.RtlAddVectoredExceptionHandler
kernel32.RemoveVectoredExceptionHandler -> ntdll.RtlRemoveVectoredExceptionHandler
kernel32.ExitProcess -> ntdll.RtlExitUserProcess
kernel32.InitializeCriticalSection -> ntdll.RtlInitializeCriticalSection
kernel32.EnterCriticalSection -> ntdll.RtlEnterCriticalSection
kernel32.LeaveCriticalSection -> ntdll.RtlLeaveCriticalSection
kernel32.DeleteCriticalSection -> ntdll.RtlDeleteCriticalSection
kernel32.TryAcquireSRWLockExclusive -> ntdll.RtlTryAcquireSRWLockExclusive
kernel32.AcquireSRWLockExclusive -> ntdll.RtlAcquireSRWLockExclusive
kernel32.ReleaseSRWLockExclusive -> ntdll.RtlReleaseSRWLockExclusive
kernel32.WakeConditionVariable -> ntdll.RtlWakeConditionVariable
kernel32.WakeAllConditionVariable -> ntdll.RtlWakeAllConditionVariable
kernel32.HeapReAlloc -> ntdll.RtlReAllocateHeap
kernel32.HeapAlloc -> ntdll.RtlAllocateHeap
2025-10-31 13:54:50 +00:00
John Benediktsson 74c23a237e Merge pull request #25763 from mrjbq7/cancelled
rename Cancelled to Canceled
2025-10-30 04:40:13 +00:00
Andrew Kelley 4114392369 std: fix definition of ws2_32.GetAddrInfoExW
There was a missing parameter.
2025-10-29 06:20:52 -07:00
Andrew Kelley 6ccb53bff1 std.Io.Threaded: fix openSelfExe for Windows
missing a call to wToPrefixedFileW
2025-10-29 06:20:51 -07:00
Andrew Kelley a3ddca3657 std.Io.Threaded: delete Windows implementation of if_nametoindex
Microsoft documentation says "The if_nametoindex function is implemented
for portability of applications with Unix environments, but the
ConvertInterface functions are preferred."

This was also the only dependency on iphlpapi.
2025-10-29 06:20:51 -07:00
Andrew Kelley ab003cd054 std.Io.Threaded: implement netLookup for Windows 2025-10-29 06:20:51 -07:00
Andrew Kelley dab8dd5e03 std.os.windows.ws2_32: remove 'A' variants 2025-10-29 06:20:51 -07:00
Andrew Kelley 34891b528e std.Io.Threaded: implement netListen for Windows 2025-10-29 06:20:50 -07:00
Andrew Kelley fc335a3a48 update format strings in os/windows/test.zig 2025-07-08 08:46:31 -07:00
Andrew Kelley 0e37ff0d59 std.fmt: breaking API changes
added adapter to AnyWriter and GenericWriter to help bridge the gap
between old and new API

make std.testing.expectFmt work at compile-time

std.fmt no longer has a dependency on std.unicode. Formatted printing
was never properly unicode-aware. Now it no longer pretends to be.

Breakage/deprecations:
* std.fs.File.reader -> std.fs.File.deprecatedReader
* std.fs.File.writer -> std.fs.File.deprecatedWriter
* std.io.GenericReader -> std.io.Reader
* std.io.GenericWriter -> std.io.Writer
* std.io.AnyReader -> std.io.Reader
* std.io.AnyWriter -> std.io.Writer
* std.fmt.format -> std.fmt.deprecatedFormat
* std.fmt.fmtSliceEscapeLower -> std.ascii.hexEscape
* std.fmt.fmtSliceEscapeUpper -> std.ascii.hexEscape
* std.fmt.fmtSliceHexLower -> {x}
* std.fmt.fmtSliceHexUpper -> {X}
* std.fmt.fmtIntSizeDec -> {B}
* std.fmt.fmtIntSizeBin -> {Bi}
* std.fmt.fmtDuration -> {D}
* std.fmt.fmtDurationSigned -> {D}
* {} -> {f} when there is a format method
* format method signature
  - anytype -> *std.io.Writer
  - inferred error set -> error{WriteFailed}
  - options -> (deleted)
* std.fmt.Formatted
  - now takes context type explicitly
  - no fmt string
2025-07-07 22:43:51 -07:00
Ryan Liptak 8709326088 windows: Delete obsolete environment variable kernel32 wrappers and bindings
These functions have been unused for a long time (since cfffb9c5e96eeeae43cd724e2d02ec8c2b7714e0; the PEB is used for this stuff now), and the GetEnvironmentVariableW wrapper's parameter types don't make much sense to boot.

Contributes towards:
- https://github.com/ziglang/zig/issues/4426
- https://github.com/ziglang/zig/issues/1840
2025-06-02 10:34:37 +02:00
Jonathan Marler 1408288b95 support more process creation options on Windows
Adds a CreateProcessFlags packed struct for all the possible flags to
CreateProcessW on windows.  In addition, propagates the existing
`start_suspended` option in std.process.Child which was previously only
used on Darwin.  Also adds a `create_no_window` option to std.process.Child
which is a commonly used flag for launching console executables on
windows without causing a new console window to "pop up".
2025-03-25 23:48:27 +01:00
ziggoon 5b03e248b7 add FFI & wrappers for NtAllocateVirtualMemory & NtFreeVirtualMemory + add missing alloction constants MEM_RESERVE_PLACEHOLDER / MEM_PRESERVE_PLACEHOLDER 2025-03-04 22:10:49 -06:00
Ali Cheraghi bffbc918ee std.time: more precise nanoTimestamp in windows 2025-02-13 16:55:58 +01:00
Andrew Kelley 5e9b8c38d3 std.heap: remove HeapAllocator
Windows-only, depends on kernel32 in violation of zig std lib policy,
and redundant with other cross-platform APIs that perform the same
functionality.
2025-02-06 14:23:23 -08:00
Archbirdplus 439667be04 runtime page size detection
heap.zig: define new default page sizes
heap.zig: add min/max_page_size and their options
lib/std/c: add miscellaneous declarations
heap.zig: add pageSize() and its options
switch to new page sizes, especially in GPA/stdlib
mem.zig: remove page_size
2025-02-06 14:23:23 -08:00
Alex Rønne Petersen be8a527eb2 std.os.windows: Deprecate WINAPI in favor of CallingConvention.winapi. 2024-11-02 10:44:18 +01:00
Alex Rønne Petersen 537a873b17 Initial port work for *-windows-itanium support.
https://llvm.org/docs/HowToBuildWindowsItaniumPrograms.html

This is a weird middle ground between `*-windows-gnu` and `*-windows-msvc`. It
uses the C++ ABI of the former while using the system libraries of the latter.
2024-09-28 21:43:52 +02:00
Alex Rønne Petersen cb1fffb29e std.os.windows.tls: Set AddressOfCallBacks to &__xl_a + 1.
`__xl_a` is just a global variable containing a null function pointer. There's
nothing magical about it or its name at all.

The section names used on `__xl_a` and `__xl_b` (`.CRT$XLA` and `.CRT$XLZ`) are
the real magic here. The compiler emits TLS variables into `.CRT$XL<x>`
sections, where `x` is an uppercase letter between A and Z (exclusive). The
linker then sorts those sections alphabetically (due to the `$`), and the result
is a neat array of TLS initialization callbacks between `__xl_a` and `__xl_z`.

That array is null-terminated, though! Normally, `__xl_z` serves as the null
terminator; however, by pointing `AddressesOfCallBacks` to `__xl_a`, which just
contains a null function pointer, we've effectively made it so that the PE
loader will just immediately stop invoking TLS callbacks. Fix that by pointing
to the first actual TLS callback instead (or `__xl_z` if there are none).
2024-08-03 20:55:00 +02:00
Alex Rønne Petersen 1d8fca0060 std.os.windows.tls: Only define _tls_array when targeting MSVC.
LLVM does not use it when emitting code for the MinGW ABI.
2024-08-03 20:48:48 +02:00
Alex Rønne Petersen 0f1db90198 std.os.windows.tls: Slightly improve type safety. 2024-08-03 20:48:48 +02:00
Alex Rønne Petersen c2fcdc21c2 std.os.windows.tls: Change type of _tls_start/_tls_end to *anyopaque.
If they're typed as `u8`, they can be aligned to anything. We want at least
pointer size alignment.
2024-08-03 20:47:08 +02:00
Alex Rønne Petersen 8056a85151 std: Move start_windows_tls.zig to os/windows/tls.zig.
Just to be consistent with Linux.
2024-08-03 20:35:08 +02:00
Ryan Liptak efde3ed04a Fix compile error due to GetModuleFileNameW binding change
In https://github.com/ziglang/zig/pull/19641, this binding changed from `[*]u16` to `LPWSTR` which made it a sentinel-terminated pointer. This introduced a compiler error in the `std.os.windows.GetModuleFileNameW` wrapper since it takes a `[*]u16` pointer. This commit changes the binding back to what it was before instead of introducing a breaking change to `std.os.windows.GetModuleFileNameW`

Related: https://github.com/ziglang/zig/issues/20858
2024-07-29 16:06:36 -07:00
Jarrod Meyer 2b8f444dde windows: reintroduce ReadDirectoryChangesW
- additionally, introduces FileNotifyChangeFilter to improve use/readability
2024-07-27 11:32:43 -04:00
Andrew Kelley e8c4e79499 std.c reorganization
It is now composed of these main sections:
* Declarations that are shared among all operating systems.
* Declarations that have the same name, but different type signatures
  depending on the operating system. Often multiple operating systems
  share the same type signatures however.
* Declarations that are specific to a single operating system.
  - These are imported one per line so you can see where they come from,
    protected by a comptime block to prevent accessing the wrong one.

Closes #19352 by changing the convention to making types `void` and
functions `{}`, so that it becomes possible to update `@hasDecl` sites
to use `@TypeOf(f) != void` or `T != void`. Happily, this ended up
removing some duplicate logic and update some bitrotted feature
detection checks.

A handful of types have been modified to gain namespacing and type
safety. This is a breaking change.

Oh, and the last usage of `usingnamespace` site is eliminated.
2024-07-19 00:30:32 -07:00
Stephen Gregoratto 3095e83d11 Windows: Rework kernel32 apis
To facilitate #1840, this commit slims `std.windows.kernel32` to only
have the functions needed by the standard library. Since this will break
projects that relied on these, I offer two solutions:

- Make an argument as to why certain functions should be added back in.
  Note that they may just be wrappers around `ntdll` APIs, which would
  go against #1840.
  If necessary I'll add them back in *and* make wrappers in
  `std.windows` for it.
- Maintain your own list of APIs. This is the option taken by bun[1],
  where they wrap functions with tracing.
- Use `zigwin32`.

I've also added TODO comments that specify which functions can be
reimplemented using `ntdll` APIs in the future.

Other changes:
- Group functions into groups (I/O, process management etc.).
- Synchronize definitions against Microsoft documentation to use the
  proper parameter types/names.
- Break all functions with parameters over multiple lines.
2024-07-17 23:11:23 +10:00
Ryan Liptak 1a62cfffa7 Replace GetCommandLineW with PEB access, delete GetCommandLine bindings 2024-07-13 18:19:19 -07:00
Ryan Liptak 76fb2b685b std: Convert deprecated aliases to compile errors and fix usages
Deprecated aliases that are now compile errors:

- `std.fs.MAX_PATH_BYTES` (renamed to `std.fs.max_path_bytes`)
- `std.mem.tokenize` (split into `tokenizeAny`, `tokenizeSequence`, `tokenizeScalar`)
- `std.mem.split` (split into `splitSequence`, `splitAny`, `splitScalar`)
- `std.mem.splitBackwards` (split into `splitBackwardsSequence`, `splitBackwardsAny`, `splitBackwardsScalar`)
- `std.unicode`
  + `utf16leToUtf8Alloc`, `utf16leToUtf8AllocZ`, `utf16leToUtf8`, `fmtUtf16le` (all renamed to have capitalized `Le`)
  + `utf8ToUtf16LeWithNull` (renamed to `utf8ToUtf16LeAllocZ`)
- `std.zig.CrossTarget` (moved to `std.Target.Query`)

Deprecated `lib/std/std.zig` decls were deleted instead of made a `@compileError` because the `refAllDecls` in the test block would trigger the `@compileError`. The deleted top-level `std` namespaces are:

- `std.rand` (renamed to `std.Random`)
- `std.TailQueue` (renamed to `std.DoublyLinkedList`)
- `std.ChildProcess` (renamed/moved to `std.process.Child`)

This is not exhaustive. Deprecated aliases that I didn't touch:
  + `std.io.*`
  + `std.Build.*`
  + `std.builtin.Mode`
  + `std.zig.c_translation.CIntLiteralRadix`
  + anything in `src/`
2024-06-13 10:18:59 -04:00
Ryan Liptak 40afac40b8 std.Progress: Use Windows console API calls when ANSI escape codes are not supported 2024-05-28 10:41:07 -07:00
Ronald Chen c77afca957 [std] Fixed bug missing optional for lpName param on CreateEventExW. fixes #19946
https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventexw
2024-05-12 07:45:40 +02:00
Garfield Lee e69caaa39f lib/std/os/windows/kernel32: add signature for SetConsoleMode (#18715)
- From lib/libc/include/any-windows-any/wincon.h#L235
- See also https://learn.microsoft.com/en-us/windows/console/setconsolemode
- Also add DISABLE_NEWLINE_AUTO_RETURN constant which will be used by SetConsoleMode in lib/std/os/windows.

Co-authored-by: Kexy Biscuit <kexybiscuit@biscuitt.in>
2024-05-09 16:38:39 -07:00
Ryan Liptak 422464d540 std.process.Child: Mitigate arbitrary command execution vulnerability on Windows (BatBadBut)
> Note: This first part is mostly a rephrasing of https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
> See that article for more details

On Windows, it is possible to execute `.bat`/`.cmd` scripts via CreateProcessW. When this happens, `CreateProcessW` will (under-the-hood) spawn a `cmd.exe` process with the path to the script and the args like so:

    cmd.exe /c script.bat arg1 arg2

This is a problem because:

- `cmd.exe` has its own, separate, parsing/escaping rules for arguments
- Environment variables in arguments will be expanded before the `cmd.exe` parsing rules are applied

Together, this means that (1) maliciously constructed arguments can lead to arbitrary command execution via the APIs in `std.process.Child` and (2) escaping according to the rules of `cmd.exe` is not enough on its own.

A basic example argv field that reproduces the vulnerability (this will erroneously spawn `calc.exe`):

    .argv = &.{ "test.bat", "\"&calc.exe" },

And one that takes advantage of environment variable expansion to still spawn calc.exe even if the args are properly escaped for `cmd.exe`:

    .argv = &.{ "test.bat", "%CMDCMDLINE:~-1%&calc.exe" },

(note: if these spawned e.g. `test.exe` instead of `test.bat`, they wouldn't be vulnerable; it's only `.bat`/`.cmd` scripts that are vulnerable since they go through `cmd.exe`)

Zig allows passing `.bat`/`.cmd` scripts as `argv[0]` via `std.process.Child`, so the Zig API is affected by this vulnerability. Note also that Zig will search `PATH` for `.bat`/`.cmd` scripts, so spawning something like `foo` may end up executing `foo.bat` somewhere in the PATH (the PATH searching of Zig matches the behavior of cmd.exe).

> Side note to keep in mind: On Windows, the extension is significant in terms of how Windows will try to execute the command. If the extension is not `.bat`/`.cmd`, we know that it will not attempt to be executed as a `.bat`/`.cmd` script (and vice versa). This means that we can just look at the extension to know if we are trying to execute a `.bat`/`.cmd` script.

---

This general class of problem has been documented before in 2011 here:

https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way

and the course of action it suggests for escaping when executing .bat/.cmd files is:

- Escape first using the non-cmd.exe rules
- Then escape all cmd.exe 'metacharacters' (`(`, `)`, `%`, `!`, `^`, `"`, `<`, `>`, `&`, and `|`) with `^`

However, escaping with ^ on its own is insufficient because it does not stop cmd.exe from expanding environment variables. For example:

```
args.bat %PATH%
```

escaped with ^ (and wrapped in quotes that are also escaped), it *will* stop cmd.exe from expanding `%PATH%`:

```
> args.bat ^"^%PATH^%^"
"%PATH%"
```

but it will still try to expand `%PATH^%`:

```
set PATH^^=123
> args.bat ^"^%PATH^%^"
"123"
```

The goal is to stop *all* environment variable expansion, so this won't work.

Another problem with the ^ approach is that it does not seem to allow all possible command lines to round trip through cmd.exe (as far as I can tell at least).

One known example:

```
args.bat ^"\^"key^=value\^"^"
```

where args.bat is:

```
@echo %1 %2 %3 %4 %5 %6 %7 %8 %9
```

will print

```
"\"key value\""
```

(it will turn the `=` into a space for an unknown reason; other minor variations do roundtrip, e.g. `\^"key^=value\^"`, `^"key^=value^"`, so it's unclear what's going on)

It may actually be possible to escape with ^ such that every possible command line round trips correctly, but it's probably not worth the effort to figure it out, since the suggested mitigation for BatBadBut has better roundtripping and leads to less garbled command lines overall.

---

Ultimately, the mitigation used here is the same as the one suggested in:

https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/

The mitigation steps are reproduced here, noted with one deviation that Zig makes (following Rust's lead):

1. Replace percent sign (%) with %%cd:~,%.
2. Replace the backslash (\) in front of the double quote (") with two backslashes (\\).
3. Replace the double quote (") with two double quotes ("").
4. ~~Remove newline characters (\n).~~
  - Instead, `\n`, `\r`, and NUL are disallowed and will trigger `error.InvalidBatchScriptArg` if they are found in `argv`. These three characters do not roundtrip through a `.bat` file and therefore are of dubious/no use. It's unclear to me if `\n` in particular is relevant to the BatBadBut vulnerability (I wasn't able to find a reproduction with \n and the post doesn't mention anything about it except in the suggested mitigation steps); it just seems to act as a 'end of arguments' marker and therefore anything after the `\n` is lost (and same with NUL). `\r` seems to be stripped from the command line arguments when passed through a `.bat`/`.cmd`, so that is also disallowed to ensure that `argv` can always fully roundtrip through `.bat`/`.cmd`.
5. Enclose the argument with double quotes (").

The escaped command line is then run as something like:

    cmd.exe /d /e:ON /v:OFF /c "foo.bat arg1 arg2"

Note: Previously, we would pass `foo.bat arg1 arg2` as the command line and the path to `foo.bat` as the app name and let CreateProcessW handle the `cmd.exe` spawning for us, but because we need to pass `/e:ON` and `/v:OFF` to cmd.exe to ensure the mitigation is effective, that is no longer tenable. Instead, we now get the full path to `cmd.exe` and use that as the app name when executing `.bat`/`.cmd` files.

---

A standalone test has also been added that tests two things:

1. Known reproductions of the vulnerability are tested to ensure that they do not reproduce the vulnerability
2. Randomly generated command line arguments roundtrip when passed to a `.bat` file and then are passed from the `.bat` file to a `.exe`. This fuzz test is as thorough as possible--it tests that things like arbitrary Unicode codepoints and unpaired surrogates roundtrip successfully.

Note: In order for the `CreateProcessW` -> `.bat` -> `.exe` roundtripping to succeed, the .exe must split the arguments using the post-2008 C runtime argv splitting implementation, see https://github.com/ziglang/zig/pull/19655 for details on when that change was made in Zig.
2024-04-23 03:21:51 -07:00
Michael Ortmann afdc41cfd6 std.os.windows: add POLL.IN and POLL.OUT 2024-03-21 17:08:50 +02:00
Andrew Kelley cd62005f19 extract std.posix from std.os
closes #5019
2024-03-19 11:45:09 -07:00
Stephen Gregoratto 9532f72937 Windows: Replace CreatePipe with ntdll implementation
This implementation is now a direct replacement for the `kernel32` one.
New bitflags for named pipes and other generic ones were added based on
browsing the ReactOS sources.

`UNICODE_STRING.Buffer` has also been changed to be nullable, as
this is what makes the implementation work.
This required some changes to places accesssing the buffer after a
`SUCCESS`ful return, most notably `QueryObjectName` which even referred
to it being nullable.
2024-03-16 23:37:50 +11:00
Ryan Liptak 80508b98c2 Update deprecated std.unicode function usages 2024-02-24 14:04:59 -08:00
Krzysztof Wolicki eff58d6c18 os.windows: Fix error 258 name in Win32Error 2024-01-23 18:06:23 -08:00
Matthew Wozniak aaf1e0b25b add ability to open dlls with platform-specific flags (#18370) 2024-01-09 20:11:22 -05:00
Carl Åstholm 60982ea5bd Correct CreateProcessW parameter types 2023-12-22 12:23:27 +02:00
David Rubin 1e42a3de89 Remove all usages of std.mem.copy and remove std.mem.set (#18143) 2023-11-29 16:03:02 -05:00
Ryan Liptak 0bc4ee1792 Remove std.os.windows.ole32/shell32 2023-11-23 03:06:47 -08:00