192 Commits

Author SHA1 Message Date
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
Andrew Kelley
12191c8a22 std: promote tests to doctests
Now these show up as "example usage" in generated documentation.
2024-03-21 14:11:46 -07:00
Andrew Kelley
cd62005f19 extract std.posix from std.os
closes #5019
2024-03-19 11:45:09 -07:00
Stephen Gregoratto
69175ad62f Windows: Add wrappers for GetCurrent(Process|Thread) via NT_TIB
This is how they've been implemented in `kernel32` since NT 3.1.
2024-03-16 23:37:50 +11:00
Tristan Ross
6067d39522
std.builtin: make atomic order fields lowercase 2024-03-11 07:09:10 -07:00
Ryan Liptak
ae7f3fc360 Eliminate error.InvalidHandle from OpenError and RealPathError
InvalidHandle in OpenError is no longer a possible error on any platform. In the past it was able to be returned in `openOptionsFromFlagsWasi`, but the implementation was changed in 7680c5330cbc9141b9a5444e30c512b6068ab50d to make it no longer possible.

InvalidHandle in RealPathError was a holdover from before d5312d53a066092ba9efd687e25b29a87eb6290c, which made realpath a compile error on WASI. However, InvalidHandle was also a possible error in the FreeBSD fallback implementation added in 537624734c4db9e0cdbdc0ebce57375d17172a70. This commit changes the FreeBSD fallback implementation to return FileNotFound instead of InvalidHandle which matches how EBADF is handled in all the other `realpath` implementations (including the FreeBSD non-fallback implementation).

Closes #19084
2024-02-27 15:59:50 -08:00
Ryan Liptak
68b87918df Fix handling of Windows (WTF-16) and WASI (UTF-8) paths
Windows paths now use WTF-16 <-> WTF-8 conversion everywhere, which is lossless. Previously, conversion of ill-formed UTF-16 paths would either fail or invoke illegal behavior.

WASI paths must be valid UTF-8, and the relevant function calls have been updated to handle the possibility of failure due to paths not being encoded/encodable as valid UTF-8.

Closes #18694
Closes #1774
Closes #2565
2024-02-24 14:05:24 -08:00
Ryan Liptak
80508b98c2 Update deprecated std.unicode function usages 2024-02-24 14:04:59 -08:00
Alex Kladov
ab6317d32b std: fix copy-paste typo in spawnWindows 2024-02-17 21:49:00 -08:00
Andrew Kelley
07c1dd3d1d std.os.windows.OpenFile: add missing error
Encountered in a recent CI run on an aarch64-windows dev kit.

Pretty sure I disabled the virus scanner but it looks like it turned
itself back on with a Windows Update.

Rather than marking the new error code as unreachable in the places
where it is unexpected, this commit makes it return `error.Unexpected`.
2024-02-14 11:14:43 -08:00
Andrew Kelley
7680c5330c some API work on std.c, std.os, std.os.wasi
* std.c: consolidate some definitions, making them share code. For
  example, freebsd, dragonfly, and openbsd can all share the same
  `pthread_mutex_t` definition.
* add type safety to std.c.O
  - this caught a bug where mode flags were incorrectly passed as the
    open flags.
* 3 fewer uses of usingnamespace keyword
* as per convention, remove purposeless field prefixes from struct field
  names even if they have those prefixes in the corresponding C code.
* fix incorrect wasi libc Stat definition
* remove C definitions from incorrectly being in std.os.wasi
* make std.os.wasi definitions type safe
* go through wasi native APIs even when linking libc because the libc
  APIs are problematic and wasteful
* don't expose WASI definitions in std.posix
* remove std.os.wasi.rights_t.ALL: this is a footgun. should it be all
  future rights too? or only all current rights known? both are
  the wrong answer.
2024-02-11 13:38:55 -07:00
Andrew Kelley
54bbc73f85
Merge pull request #18712 from Vexu/std.options
std: make options a struct instance instead of a namespace
2024-02-09 13:38:42 -08:00
Andrew Kelley
434a6a4f63 std.process.Child: use unreachable instead of @panic
This is how assertions work in zig.
2024-02-02 20:43:01 -07:00
Veikka Tuominen
a4f27e8987 remove std.io.Mode 2024-02-01 15:22:36 +02:00
xEgoist
194ed30825 child_process: Use security attributes while creating handle.
As suggested by @matu3ba, it can be better to use Security Attributes
directly while creating the handle instead of creating the handle then
setting the handle to inherit. Doing so can prevent potentially leaking
to other parallel spawned processes which would inherit the opened `\Device\Null`
handle.

This change also allows windows.OpenFile to handle when bInheritHandle
is set.

Note that we are using the same `saAttr`, but since it's taken as a
pointer to a const in all calls, it's never mutated, and OpenFile never alters it.

This also saves 1 kernel call for setting the handle to inherit.
2024-01-16 01:25:14 -08:00
xEgoist
41bf816fa6 child_process: Add write access to the null handle
This commit allows write access to the `\\Device\\Null` Handle.
Without a write access, it's not possible for the child process to write
SdOut to Null. As a requirement `SetHandleInformation` was also changed
to mark the handle as iheritable (by adding it to Flags) by the spawned process.
This allows the child to access the NUL device that was opened.

This also makes the Windows part to behave similarly to `spawnPosix`.
2024-01-16 01:25:14 -08:00
Carl Ã…stholm
4d9c4ab82c More accurate argv-to-command-line serialization when spawning child processes on Windows
The old implementation had a bug in it in that it didn't quote empty strings, but it also didn't properly follow the special quoting rules required for the first argument (the executable name). This new implementation serializes the argv correctly such that it can be parsed by the `CommandLineToArgvW` algorithm.
2023-12-18 23:58:26 +01:00
Andrew Kelley
2bffd81015
Merge pull request #18085 from ziglang/std-atomics
rework std.atomic
2023-11-23 04:55:28 -05:00
Andrew Kelley
70931dbdea rework std.atomic
* move std.atomic.Atomic to std.atomic.Value
* fix incorrect argument order passed to testing.expectEqual
* make the functions be a thin wrapper over the atomic builtins and
  stick to the naming conventions.
* remove pointless functions loadUnchecked and storeUnchecked. Instead,
  name the field `raw` instead of `value` (which is redundant with the
  type name).
* simplify the tests by not passing every possible combination. Many
  cases were iterating over every possible combinations but then not
  even using the for loop element value!
* remove the redundant compile errors which are already implemented by
  the language itself.
* remove dead x86 inline assembly. this should be implemented in the
  language if at all.
2023-11-22 19:08:55 -07:00
Andrew Kelley
e357550610 update for the std.fs.Dir changes 2023-11-22 15:24:57 -07:00
mlugg
51595d6b75
lib: correct unnecessary uses of 'var' 2023-11-19 09:55:07 +00:00
Andrew Kelley
3fc6fc6812 std.builtin.Endian: make the tags lower case
Let's take this breaking change opportunity to fix the style of this
enum.
2023-10-31 21:37:35 -04:00
Jacob Young
d890e81761 mem: fix ub in writeInt
Use inline to vastly simplify the exposed API.  This allows a
comptime-known endian parameter to be propogated, making extra functions
for a specific endianness completely unnecessary.
2023-10-31 21:37:35 -04:00
Jan Philipp Hafer
fd2239bde9 child_process + Build: rename exec to run + all related code
Justification: exec, execv etc are unix concepts and portable version
should be called differently.

Do no touch non-Zig code. Adjust error names as well, if associated.
Closes #5853.
2023-10-22 14:47:20 -04:00
Krzysztof Wolicki
bffe65c18f std.ChildProcess: fix error in killWindows 2023-10-18 19:11:28 -04:00
Ben Crist
fb9376bd04 Double check that child processes have really exited when
TerminateProcess reports ACCESS_DENIED
2023-10-17 16:56:39 -07:00
Ben Crist
6d47198303 return error.AlreadyTerminated from std.ChildProcess.kill when necessary 2023-10-17 16:56:15 -07:00
Veikka Tuominen
63bd2bff12 Sema: add @errorCast which works for both error sets and error unions
Closes #17343
2023-10-01 17:00:01 +03:00
Ryan Liptak
3f9294c735 Windows: Fix TooManyParentDirs handling for paths that shouldn't be cwd-relative
Previously, a relative path like `..` would:
- Attempt to be normalized (i.e. remove . and .. without any path resolution), but would error with TooManyParentDirs
- This would make wToPrefixedFileW run it through `RtlGetFullPathName_U` to do the necessary path resolution, but `RtlGetFullPathName_U` always resolves relative paths relative to the CWD

Instead, when TooManyParentDirs occurs, we now look up the path of the passed in `dir` (if it's non-null) and append the relative path to it before giving it to `RtlGetFullPathName_U`. If `dir` is null, then we just give it RtlGetFullPathName_U directly and let it resolve it relative to the CWD.

Closes #16779
2023-08-11 18:58:40 -07:00
Zachary Raineri
49244dc0ca
std: remove some unused imports (#16710) 2023-08-06 15:18:50 -04:00
Ryan Liptak
9a3adeea6e windows.OpenFile/DeleteFile: Add NetworkNotFound as a possible error
When calling NtCreateFile with a UNC path, if either `\\server` or `\\server\share` are not found, then the statuses `BAD_NETWORK_PATH` or `BAD_NETWORK_NAME` are returned (respectively).

These statuses are not translated into `error.FileNotFound` because they convey more information than the typical FileNotFound error. For example, if you were trying to call `Dir.makePath` with an absolute UNC path like `\\MyServer\MyShare\a\b\c\d`, then knowing that `\\MyServer\MyShare` was not found allows for returning after trying to create the first directory instead of then trying to create `a\b\c`, `a\b`, etc. when it's already known that they will all fail in the same way.
2023-07-27 09:35:29 -07:00
Ryan Liptak
3f7166eb8b child_process: Fix regression on Windows for FAT filesystems
This fixes a regression caused by https://github.com/ziglang/zig/pull/13993

As an optimization, the first call to `NtQueryDirectoryFile` would only ask for a single result and assume that if the result returned did not match the app_name exactly, then the unappended app_name did not exist. However, this relied on the assumption that the unappended app_name would always be returned first, but that only seems to be the case on NTFS. On FAT filesystems, the order of returned files can be different, which meant that it could assume the unappended file doesn't exist when it actually does.

This commit fixes that by fully iterating the wildcard matches via `NtQueryDirectoryFile` and taking note of any unappended/PATHEXT-appended filenames it finds. In practice, this strategy does not introduce a speed regression compared to the previous (buggy) implementation.

Benchmark 1 (10 runs): winpathbench-master.exe
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           508ms ± 4.08ms     502ms …  517ms          1 (10%)        0%
  peak_rss           3.62MB ± 2.76KB    3.62MB … 3.63MB          0 ( 0%)        0%
Benchmark 2 (10 runs): winpathbench-fat32-fix.exe
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           500ms ± 21.4ms     480ms …  535ms          0 ( 0%)          -  1.5% ±  2.8%
  peak_rss           3.62MB ± 2.76KB    3.62MB … 3.63MB          0 ( 0%)          -  0.0% ±  0.1%

---

Partially addresses #16374 (it fixes `zig build` on FAT32 when no `zig-cache` is present)
2023-07-23 19:09:20 -07:00
Eric Joldasov
0a868dacdd std.cstr: deprecate namespace
Signed-off-by: Eric Joldasov <bratishkaerik@getgoogleoff.me>
2023-06-25 14:51:03 -07:00
mlugg
f26dda2117 all: migrate code to new cast builtin syntax
Most of this migration was performed automatically with `zig fmt`. There
were a few exceptions which I had to manually fix:

* `@alignCast` and `@addrSpaceCast` cannot be automatically rewritten
* `@truncate`'s fixup is incorrect for vectors
* Test cases are not formatted, and their error locations change
2023-06-24 16:56:39 -07:00
Eric Joldasov
50339f595a all: zig fmt and rename "@XToY" to "@YFromX"
Signed-off-by: Eric Joldasov <bratishkaerik@getgoogleoff.me>
2023-06-19 12:34:42 -07:00
Jacob Young
04e66e6b4d InternPool: add optional coercion 2023-06-10 20:47:59 -07:00
Jacob Young
cab79b0877 lib: add const to avoid regression
Not sure if this was meant to be legal or not, but either way this code
should have been using const anyway.
2023-06-10 20:47:58 -07:00
Andrew Kelley
629f0d23b5
Merge pull request #15579 from squeek502/mem-delimiters
Split `std.mem.split` and `tokenize` into `sequence`, `any`, and `scalar` versions
2023-06-03 13:51:02 -07:00
Jacob Young
28df1d09dc Revert "Revert "Windows: Support UNC, rooted, drive relative, and namespaced/device paths""
This reverts commit b5fad3a40a86eb379903d6a803bdbe66dcaa5487.

The latent x86_64 backend bug has been fixed.
2023-05-29 22:09:13 -04:00
Andrew Kelley
b5fad3a40a Revert "Windows: Support UNC, rooted, drive relative, and namespaced/device paths"
This reverts commit 1697d44809151e4759f6b5f9447a908c30ac1e84.

Master branch started failing after this commit landed.
2023-05-29 15:01:24 -07:00
Ryan Liptak
1697d44809 Windows: Support UNC, rooted, drive relative, and namespaced/device paths
There are many different types of Windows paths, and there are a few different possible namespaces on top of that. Before this commit, NT namespaced paths were somewhat supported, and for Win32 paths (those without a namespace prefix), only relative and drive absolute paths were supported. After this commit, all of the following are supported:

- Device namespaced paths (`\\.\`)
- Verbatim paths (`\\?\`)
- NT-namespaced paths (`\??\`)
- Relative paths (`foo`)
- Drive-absolute paths (`C:\foo`)
- Drive-relative paths (`C:foo`)
- Rooted paths (`\foo`)
- UNC absolute paths (`\\server\share\foo`)
- Root local device paths (`\\.` or `\\?` exactly)

Plus:

- Any of the path types and namespace types can be mixed and matched together as appropriate.
- All of the `std.os.windows.*ToPrefixedFileW` functions will accept any path type, prefixed or not, and do the appropriate thing to convert them to an NT-prefixed path if necessary.

This is achieved by making the `std.os.windows.*ToPrefixedFileW` functions behave like `ntdll.RtlDosPathNameToNtPathName_U`, but with a few differences:

- Does not allocate on the heap (this is why we can't use `ntdll.RtlDosPathNameToNtPathName_U` directly, it does internal heap allocation).
- Relative paths are kept as relative unless they contain too many .. components, in which case they are treated as 'drive relative' and resolved against the CWD (this is how it behaved before this commit as well).
- Special case device names like COM1, NUL, etc are not handled specially (TODO)
- `.` and space are not stripped from the end of relative paths (potential TODO)

Most of the non-trivial conversion of non-relative paths is done via `ntdll.RtlGetFullPathName_U`, which AFAIK is used internally by `ntdll.RtlDosPathNameToNtPathName_U`.

Some relevant reading on Windows paths:

- https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
- https://chrisdenton.github.io/omnipath/Overview.html

Closes #8205
Might close (untested) #12729

Note:
- This removes checking for illegal characters in `std.os.windows.sliceToPrefixedFileW`, since the previous solution (iterate the whole string and error if any illegal characters were found) was naive and won't work for all path types. This is further complicated by things like file streams (where `:` is used as a delimiter, e.g. `file.ext:stream_name:$DATA`) and things in the device namespace (where a path like `\\.\GLOBALROOT\??\UNC\localhost\C$\foo` is valid despite the `?`s in the path and is effectively equivalent to `C:\foo`). Truly validating paths is complicated and would need to be tailored to each path type. The illegal character checking being removed may open up users to more instances of hitting `OBJECT_NAME_INVALID => unreachable` when using `fs` APIs.
  + This is related to https://github.com/ziglang/zig/issues/15607
2023-05-29 13:08:51 +03:00
Ryan Liptak
815e53b147 Update all std.mem.tokenize calls to their appropriate function
Everywhere that can now use `tokenizeScalar` should get a nice little performance boost.
2023-05-13 13:45:04 -07:00
Linus Groh
94e30a756e std: fix a bunch of typos
The majority of these are in comments, some in doc comments which might
affect the generated documentation, and a few in parameter names -
nothing that should be breaking, however.
2023-04-30 18:16:04 -07:00
Andrew Kelley
6261c13731 update codebase to use @memset and @memcpy 2023-04-28 13:24:43 -07:00
tjog
f6a2b72ba8 std.process.Child: implement maxrss on Darwin
Notably the Darwin (XNU) kernel the maxrss field is number of bytes
and not kilobytes (kibibytes) like other platforms (e.g. Linux, BSD).

watchOS and tvOS are not supported because they do not have the ability
to spawn a child process. iOS is enabled but due to OS sandboxing it
should fail with a permission error.
2023-03-25 03:20:50 +01:00
xEgoist
2f5af6c972 Refactored GetProcessMemoryInfo to return VM_COUNTERS
This change allows the function to return the process memory info
directly instead of copying the result of the underlying Nt function.
2023-03-23 06:13:26 -05:00
xEgoist
70469d428d Implemented Zig wrapper for GetProcessMemoryInfo
`GetProcessMemoryInfo` is implemented using `NtQueryInformationProcess`
with `ProcessVmCounters` to obtain `VM_COUNTERS`. The structs, enum
definitions are found in `winternl.h` or `ntddk.h` in the latest WDK.
This should give the same results as using `K32GetProcessMemoryInfo`
2023-03-22 06:13:11 -05:00
xEgoist
cc44183787 Implemented getMaxRss for Windows
In Windows, the equivalent to maxrss is PeakWorkingSetSize which is
found in PROCESS_MEMORY_COUNTERS in bytes.

Currently, this is done by calling `GetProcessMemoryInfo` in kernel32.
2023-03-22 06:13:11 -05:00
Andrew Kelley
f4428e5804 fix wasm bootstrapping compilation errors 2023-03-15 10:48:14 -07:00
Andrew Kelley
41a5ad28c9 std: child process API supports rusage data 2023-03-15 10:48:13 -07:00