From dbe0a4a978b81e0e747075bfb850832bcd3ff73f Mon Sep 17 00:00:00 2001 From: dave caruso Date: Wed, 31 Jan 2024 22:14:03 -0800 Subject: [PATCH] do more assertions (#8610) --- src/string_immutable.zig | 56 ++++++++++++++++++++++++++++++++-------- src/sys.zig | 13 +++++++++- src/sys_uv.zig | 18 +++++++++++++ 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/string_immutable.zig b/src/string_immutable.zig index c51436b00c..c73bcc83b8 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -657,6 +657,14 @@ pub fn startsWith(self: string, str: string) bool { return eqlLong(self[0..str.len], str, false); } +pub fn startsWithGeneric(comptime T: type, self: []const T, str: []const T) bool { + if (str.len > self.len) { + return false; + } + + return eqlLong(bun.reinterpretSlice(u8, self[0..str.len]), str, false); +} + pub inline fn endsWith(self: string, str: string) bool { return str.len == 0 or @call(.always_inline, std.mem.endsWith, .{ u8, self, str }); } @@ -848,8 +856,19 @@ pub fn hasPrefixComptimeUTF16(self: []const u16, comptime alt: []const u8) bool return self.len >= alt.len and eqlComptimeCheckLenWithType(u16, self[0..alt.len], comptime toUTF16Literal(alt), false); } -pub fn hasPrefixComptimeType(comptime T: type, self: []const T, comptime alt: []const T) bool { - return self.len >= alt.len and eqlComptimeCheckLenWithType(u16, self[0..alt.len], alt, false); +pub fn hasPrefixComptimeType(comptime T: type, self: []const T, comptime alt: anytype) bool { + const rhs = comptime switch (T) { + u8 => alt, + u16 => switch (std.meta.Child(@TypeOf(alt))) { + u8 => w(alt), + else => |t| switch (std.meta.Child(t)) { + u8 => w(alt), + else => alt, + }, + }, + else => unreachable, + }; + return self.len >= alt.len and eqlComptimeCheckLenWithType(T, self[0..rhs.len], rhs, false); } pub fn hasSuffixComptime(self: string, comptime alt: anytype) bool { @@ -1650,10 +1669,22 @@ pub fn utf16Codepoint(comptime Type: type, input: Type) UTF16Replacement { } } -fn windowsPathIsPosixAbsolute(utf8: []const u8) bool { - if (utf8.len == 0) return false; - if (!charIsAnySlash(utf8[0])) return false; - if (utf8.len > 1 and charIsAnySlash(utf8[1])) return false; +/// '/hello' -> true +/// '\hello' -> true +/// 'C:/hello' -> false +/// '\??\C:\hello' -> false +fn windowsPathIsPosixAbsolute(comptime T: type, chars: []const T) bool { + if (chars.len == 0) return false; + if (!(chars[0] == '/' or chars[0] == '\\')) return false; + if (chars.len > 1 and + (chars[1] == '/' or chars[1] == '\\')) return false; + if (chars.len > 2 and + chars[2] == ':') return false; + if (chars.len > 4 and + chars[1] == '?' and + chars[2] == '?' and + (chars[3] == '/' or chars[3] == '\\')) + return windowsPathIsPosixAbsolute(T, chars[4..]); return true; } @@ -1754,11 +1785,16 @@ pub fn toWDirPath(wbuf: []u16, utf8: []const u8) [:0]const u16 { return toWPathMaybeDir(wbuf, utf8, true); } -pub fn assertIsValidWindowsPath(utf8: []const u8) void { +pub fn assertIsValidWindowsPath(comptime T: type, path: []const T) void { if (Environment.allow_assert and Environment.isWindows) { - if (startsWith(utf8, ":/")) { + if (windowsPathIsPosixAbsolute(T, path)) { + std.debug.panic("Do not pass posix paths to windows APIs, was given '{s}' (missing a root like 'C:\\', see PosixToWinNormalizer for why this is an assertion)", .{ + if (T == u8) path else std.unicode.fmtUtf16le(path), + }); + } + if (hasPrefixComptimeType(T, path, ":/")) { std.debug.panic("Path passed to windows API '{s}' is almost certainly invalid. Where did the drive letter go?", .{ - utf8, + if (T == u8) path else std.unicode.fmtUtf16le(path), }); } } @@ -1767,8 +1803,6 @@ pub fn assertIsValidWindowsPath(utf8: []const u8) void { pub fn toWPathMaybeDir(wbuf: []u16, utf8: []const u8, comptime add_trailing_lash: bool) [:0]const u16 { std.debug.assert(wbuf.len > 0); - assertIsValidWindowsPath(utf8); - var result = bun.simdutf.convert.utf8.to.utf16.with_errors.le( utf8, wbuf[0..wbuf.len -| (1 + @as(usize, @intFromBool(add_trailing_lash)))], diff --git a/src/sys.zig b/src/sys.zig index 528909a172..537c126980 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -15,6 +15,7 @@ const C = @import("root").bun.C; const linux = os.linux; const Maybe = JSC.Maybe; const kernel32 = bun.windows; +const assertIsValidWindowsPath = bun.strings.assertIsValidWindowsPath; pub const sys_uv = if (Environment.isWindows) @import("./sys_uv.zig") else Syscall; @@ -169,6 +170,8 @@ pub fn fchmod(fd: bun.FileDescriptor, mode: bun.Mode) Maybe(void) { } pub fn chdirOSPath(destination: bun.OSPathSliceZ) Maybe(void) { + assertIsValidWindowsPath(bun.OSPathChar, destination); + if (comptime Environment.isPosix) { const rc = sys.chdir(destination); return Maybe(void).errnoSys(rc, .chdir) orelse Maybe(void).success; @@ -303,8 +306,10 @@ pub fn mkdirA(file_path: []const u8, flags: bun.Mode) Maybe(void) { if (comptime Environment.isWindows) { var wbuf: bun.WPathBuffer = undefined; + const wpath = bun.strings.toWPath(&wbuf, file_path); + assertIsValidWindowsPath(u16, wpath); return Maybe(void).errnoSysP( - kernel32.CreateDirectoryW(bun.strings.toWPath(&wbuf, file_path).ptr, null), + kernel32.CreateDirectoryW(wpath.ptr, null), .mkdir, file_path, ) orelse Maybe(void).success; @@ -315,6 +320,7 @@ pub fn mkdirOSPath(file_path: bun.OSPathSliceZ, flags: bun.Mode) Maybe(void) { return switch (Environment.os) { else => mkdir(file_path, flags), .windows => { + assertIsValidWindowsPath(bun.OSPathChar, file_path); return Maybe(void).errnoSys( kernel32.CreateDirectoryW(file_path, null), .mkdir, @@ -416,6 +422,7 @@ pub fn openDirAtWindows( iterable: bool, no_follow: bool, ) Maybe(bun.FileDescriptor) { + assertIsValidWindowsPath(u16, path); const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE; const flags: u32 = if (iterable) base_flags | w.FILE_LIST_DIRECTORY else base_flags; @@ -568,6 +575,7 @@ pub fn ntCreateFile( // this path is probably already backslash normalized so we're only going to check for '.\' const path = if (bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, ".\\")) path_maybe_leading_dot[2..] else path_maybe_leading_dot; std.debug.assert(!bun.strings.hasPrefixComptimeUTF16(path_maybe_leading_dot, "./")); + assertIsValidWindowsPath(u16, path); const path_len_bytes = std.math.cast(u16, path.len * 2) orelse return .{ .err = .{ @@ -1403,6 +1411,7 @@ pub fn mmap( } pub fn mmapFile(path: [:0]const u8, flags: u32, wanted_size: ?usize, offset: usize) Maybe([]align(mem.page_size) u8) { + assertIsValidWindowsPath(u8, path); const fd = switch (open(path, os.O.RDWR, 0)) { .result => |fd| fd, .err => |err| return .{ .err = err }, @@ -1658,6 +1667,7 @@ pub fn existsOSPath(path: bun.OSPathSliceZ) bool { } if (comptime Environment.isWindows) { + assertIsValidWindowsPath(bun.OSPathChar, path); const result = kernel32.GetFileAttributesW(path.ptr); if (Environment.isDebug) { log("GetFileAttributesW({}) = {d}", .{ bun.fmt.fmtUTF16(path), result }); @@ -1676,6 +1686,7 @@ pub fn exists(path: []const u8) bool { if (comptime Environment.isWindows) { var wbuf: bun.WPathBuffer = undefined; const path_to_use = bun.strings.toWPath(&wbuf, path); + assertIsValidWindowsPath(u16, path_to_use); return kernel32.GetFileAttributesW(path_to_use.ptr) != windows.INVALID_FILE_ATTRIBUTES; } diff --git a/src/sys_uv.zig b/src/sys_uv.zig index f1ef3de9b5..4e7c951213 100644 --- a/src/sys_uv.zig +++ b/src/sys_uv.zig @@ -15,6 +15,7 @@ const E = C.E; const linux = os.linux; const Maybe = JSC.Maybe; const kernel32 = bun.windows; +const assertIsValidWindowsPath = bun.strings.assertIsValidWindowsPath; const uv = bun.windows.libuv; @@ -38,6 +39,8 @@ pub const mkdirOSPath = bun.sys.mkdirOSPath; // Note: `req = undefined; req.deinit()` has a saftey-check in a debug build pub fn open(file_path: [:0]const u8, c_flags: bun.Mode, _perm: bun.Mode) Maybe(bun.FileDescriptor) { + assertIsValidWindowsPath(u8, file_path); + var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); @@ -58,6 +61,7 @@ pub fn open(file_path: [:0]const u8, c_flags: bun.Mode, _perm: bun.Mode) Maybe(b } pub fn mkdir(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_mkdir(uv.Loop.get(), &req, file_path.ptr, flags, null); @@ -70,6 +74,7 @@ pub fn mkdir(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { } pub fn chmod(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_chmod(uv.Loop.get(), &req, file_path.ptr, flags, null); @@ -95,6 +100,7 @@ pub fn fchmod(fd: FileDescriptor, flags: bun.Mode) Maybe(void) { } pub fn chown(file_path: [:0]const u8, uid: uv.uv_uid_t, gid: uv.uv_uid_t) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_chown(uv.Loop.get(), &req, file_path.ptr, uid, gid, null); @@ -121,6 +127,7 @@ pub fn fchown(fd: FileDescriptor, uid: uv.uv_uid_t, gid: uv.uv_uid_t) Maybe(void } pub fn access(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_access(uv.Loop.get(), &req, file_path.ptr, flags, null); @@ -133,6 +140,7 @@ pub fn access(file_path: [:0]const u8, flags: bun.Mode) Maybe(void) { } pub fn rmdir(file_path: [:0]const u8) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_rmdir(uv.Loop.get(), &req, file_path.ptr, null); @@ -145,6 +153,7 @@ pub fn rmdir(file_path: [:0]const u8) Maybe(void) { } pub fn unlink(file_path: [:0]const u8) Maybe(void) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_unlink(uv.Loop.get(), &req, file_path.ptr, null); @@ -157,6 +166,7 @@ pub fn unlink(file_path: [:0]const u8) Maybe(void) { } pub fn readlink(file_path: [:0]const u8, buf: []u8) Maybe(usize) { + assertIsValidWindowsPath(u8, file_path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); // Edge cases: http://docs.libuv.org/en/v1.x/fs.html#c.uv_fs_realpath @@ -180,6 +190,8 @@ pub fn readlink(file_path: [:0]const u8, buf: []u8) Maybe(usize) { } pub fn rename(from: [:0]const u8, to: [:0]const u8) Maybe(void) { + assertIsValidWindowsPath(u8, from); + assertIsValidWindowsPath(u8, to); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_rename(uv.Loop.get(), &req, from.ptr, to.ptr, null); @@ -192,6 +204,8 @@ pub fn rename(from: [:0]const u8, to: [:0]const u8) Maybe(void) { } pub fn link(from: [:0]const u8, to: [:0]const u8) Maybe(void) { + assertIsValidWindowsPath(u8, from); + assertIsValidWindowsPath(u8, to); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_link(uv.Loop.get(), &req, from.ptr, to.ptr, null); @@ -204,6 +218,8 @@ pub fn link(from: [:0]const u8, to: [:0]const u8) Maybe(void) { } pub fn symlinkUV(from: [:0]const u8, to: [:0]const u8, flags: c_int) Maybe(void) { + assertIsValidWindowsPath(u8, from); + assertIsValidWindowsPath(u8, to); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_symlink(uv.Loop.get(), &req, from.ptr, to.ptr, flags, null); @@ -268,6 +284,7 @@ pub fn fsync(fd: FileDescriptor) Maybe(void) { } pub fn stat(path: [:0]const u8) Maybe(bun.Stat) { + assertIsValidWindowsPath(u8, path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_stat(uv.Loop.get(), &req, path.ptr, null); @@ -280,6 +297,7 @@ pub fn stat(path: [:0]const u8) Maybe(bun.Stat) { } pub fn lstat(path: [:0]const u8) Maybe(bun.Stat) { + assertIsValidWindowsPath(u8, path); var req: uv.fs_t = uv.fs_t.uninitialized; defer req.deinit(); const rc = uv.uv_fs_lstat(uv.Loop.get(), &req, path.ptr, null);