Add test to ensure the BatBadBut mitigation handles trailing . and space safely

Context:
- https://blog.rust-lang.org/2024/09/04/cve-2024-43402.html
- https://github.com/rust-lang/rust/pull/129962

Note that the Rust test case for this checks that it executes the batch file successfully with the proper mitigation in place, while the Zig test case expects a FileNotFound error. This is because of a PATHEXT optimization that Zig does, and that Rust doesn't do because Rust doesn't do PATHEXT appending (it only appends .exe specifically). See the added comment for more details.
This commit is contained in:
Ryan Liptak 2025-03-25 19:41:28 -07:00 committed by Alex Rønne Petersen
parent 2210c4c360
commit 63014d3819

View file

@ -73,6 +73,31 @@ pub fn main() anyerror!void {
try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); try testExec(allocator, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null);
try testExec(allocator, &.{"&whoami.exe"}, null); try testExec(allocator, &.{"&whoami.exe"}, null);
// Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution.
// In many Windows APIs (including CreateProcess), trailing space and . characters are stripped
// from paths, so if a path with trailing . and space character(s) is passed directly to
// CreateProcess, then it could end up executing a batch/cmd script that naive extension detection
// would not flag as .bat/.cmd.
//
// Note that we expect an error here, though, which *is* a valid mitigation, but also an implementation detail.
// This error is caused by the use of a wildcard with NtQueryDirectoryFile to optimize PATHEXT searching. That is,
// the trailing characters in the app name will lead to a FileNotFound error as the wildcard-appended path will not
// match any real paths on the filesystem (e.g. `foo.bat .. *` will not match `foo.bat`; only `foo.bat*` will).
//
// This being an error matches the behavior of running a command via the command line of cmd.exe, too:
//
// > "args1.bat .. "
// '"args1.bat .. "' is not recognized as an internal or external command,
// operable program or batch file.
try std.testing.expectError(error.FileNotFound, testExecBat(allocator, "args1.bat .. ", &.{"abc"}, null));
const absolute_with_trailing = blk: {
const absolute_path = try std.fs.realpathAlloc(allocator, "args1.bat");
defer allocator.free(absolute_path);
break :blk try std.mem.concat(allocator, u8, &.{ absolute_path, " .. " });
};
defer allocator.free(absolute_with_trailing);
try std.testing.expectError(error.FileNotFound, testExecBat(allocator, absolute_with_trailing, &.{"abc"}, null));
var env = env: { var env = env: {
var env = try std.process.getEnvMap(allocator); var env = try std.process.getEnvMap(allocator);
errdefer env.deinit(); errdefer env.deinit();