> 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.
A general-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
Documentation
If you are looking at this README file in a source tree, please refer to the Release Notes, Language Reference, or Standard Library Documentation corresponding to the version of Zig that you are using by following the appropriate link on the download page.
Otherwise, you're looking at a release of Zig, and you can find documentation here:
- doc/langref.html
- doc/std/index.html
Installation
A Zig installation is composed of two things:
- The Zig executable
- The lib/ directory
At runtime, the executable searches up the file system for the lib/ directory, relative to itself:
- lib/
- lib/zig/
- ../lib/
- ../lib/zig/
- (and so on)
In other words, you can unpack a release of Zig anywhere, and then begin
using it immediately. There is no need to install it globally, although this
mechanism supports that use case too (i.e. /usr/bin/zig and /usr/lib/zig/).
Building from Source
Ensure you have the required dependencies:
- CMake >= 3.5
- System C/C++ Toolchain
- LLVM, Clang, LLD development libraries == 17.x
Then it is the standard CMake build process:
mkdir build
cd build
cmake ..
make install
For more options, tips, and troubleshooting, please see the Building Zig From Source page on the wiki.
Building from Source without LLVM
In this case, the only system dependency is a C compiler.
cc -o bootstrap bootstrap.c
./bootstrap
This produces a zig2 executable in the current working directory. This is a
"stage2" build of the compiler,
without LLVM extensions, and is
therefore lacking these features:
- Release mode optimizations
- aarch64 machine code backend
@cImport/zig translate-c- Ability to compile C files
- Ability to compile assembly files
- Some ELF linking features
- Most COFF/PE linking features
- Some WebAssembly linking features
- Ability to output LLVM bitcode
- Windows resource file compilation
- Ability to create import libs from def files
- Automatic importlib file generation for Windows DLLs
- Ability to create static archives from object files
- Ability to compile C++, Objective-C, and Objective-C++ files
However, a compiler built this way does provide a C backend, which may be useful for creating system packages of Zig projects using the system C toolchain. In such case, LLVM is not needed!
Contributing
Zig is Free and Open Source Software. We welcome bug reports and patches from everyone. However, keep in mind that Zig governance is BDFN (Benevolent Dictator For Now) which means that Andrew Kelley has final say on the design and implementation of everything.
One of the best ways you can contribute to Zig is to start using it for an open-source personal project.
This leads to discovering bugs and helps flesh out use cases, which lead to further design iterations of Zig. Importantly, each issue found this way comes with real world motivations, making it straightforward to explain the reasoning behind proposals and feature requests.
You will be taken much more seriously on the issue tracker if you have a personal project that uses Zig.
The issue label Contributor Friendly exists to help you find issues that are limited in scope and/or knowledge of Zig internals.
Please note that issues labeled Proposal but do not also have the Accepted label are still under consideration, and efforts to implement such a proposal have a high risk of being wasted. If you are interested in a proposal which is still under consideration, please express your interest in the issue tracker, providing extra insights and considerations that others have not yet expressed. The most highly regarded argument in such a discussion is a real world use case.
For more tips, please see the Contributing page on the wiki.
Community
The Zig community is decentralized. Anyone is free to start and maintain their own space for Zig users to gather. There is no concept of "official" or "unofficial". Each gathering place has its own moderators and rules. Users are encouraged to be aware of the social structures of the spaces they inhabit, and work purposefully to facilitate spaces that align with their values.
Please see the Community wiki page for a public listing of social spaces.