From bd8d6a8342914974ca163fa75db0562d181f6d27 Mon Sep 17 00:00:00 2001 From: m Date: Fri, 11 Feb 2022 15:28:36 +0100 Subject: [PATCH 1/2] std: validate frame-pointer address in stack walking --- lib/std/c.zig | 1 + lib/std/c/linux.zig | 1 + lib/std/debug.zig | 24 +++++++++++++++++++++++- lib/std/os.zig | 14 ++++++++++++++ lib/std/os/linux.zig | 10 ++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 6d904c8b98..396a6f6702 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -123,6 +123,7 @@ pub extern "c" fn write(fd: c.fd_t, buf: [*]const u8, nbyte: usize) isize; pub extern "c" fn pwrite(fd: c.fd_t, buf: [*]const u8, nbyte: usize, offset: c.off_t) isize; pub extern "c" fn mmap(addr: ?*align(page_size) anyopaque, len: usize, prot: c_uint, flags: c_uint, fd: c.fd_t, offset: c.off_t) *anyopaque; pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_int; +pub extern "c" fn msync(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int; pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int; pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8, flags: c_int) c_int; pub extern "c" fn linkat(oldfd: c.fd_t, oldpath: [*:0]const u8, newfd: c.fd_t, newpath: [*:0]const u8, flags: c_int) c_int; diff --git a/lib/std/c/linux.zig b/lib/std/c/linux.zig index db55b5d850..be136bcbea 100644 --- a/lib/std/c/linux.zig +++ b/lib/std/c/linux.zig @@ -30,6 +30,7 @@ pub const MAP = struct { /// Only used by libc to communicate failure. pub const FAILED = @intToPtr(*anyopaque, maxInt(usize)); }; +pub const MSF = linux.MSF; pub const MMAP2_UNIT = linux.MMAP2_UNIT; pub const MSG = linux.MSG; pub const NAME_MAX = linux.NAME_MAX; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 69a68faad6..be47985041 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -424,6 +424,28 @@ pub const StackIterator = struct { return address; } + fn isValidMemory(address: u64) bool { + if (native_os != .windows) { + var res = true; + const length = 2 * mem.page_size; + const aligned_address = address & ~@intCast(u64, (mem.page_size - 1)); + const aligned_memory = @intToPtr([*]align(mem.page_size) u8, aligned_address)[0..length]; + + os.msync(aligned_memory, os.MSF.ASYNC) catch |err| { + switch (err) { + os.MSyncError.UnmappedMemory => { + res = false; + }, + else => unreachable, + } + }; + return res; + } else { + // TODO: Using windows memory API check if a page is mapped + return true; + } + } + fn next_internal(self: *StackIterator) ?usize { const fp = if (comptime native_arch.isSPARC()) // On SPARC the offset is positive. (!) @@ -432,7 +454,7 @@ pub const StackIterator = struct { math.sub(usize, self.fp, fp_offset) catch return null; // Sanity check. - if (fp == 0 or !mem.isAligned(fp, @alignOf(usize))) + if (fp == 0 or !mem.isAligned(fp, @alignOf(usize)) or !isValidMemory(fp)) return null; const new_fp = math.add(usize, @intToPtr(*const usize, fp).*, fp_bias) catch return null; diff --git a/lib/std/os.zig b/lib/std/os.zig index a935cfd8c0..16a32766dc 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -88,6 +88,7 @@ pub const Kevent = system.Kevent; pub const LOCK = system.LOCK; pub const MADV = system.MADV; pub const MAP = system.MAP; +pub const MSF = system.MSF; pub const MAX_ADDR_LEN = system.MAX_ADDR_LEN; pub const MMAP2_UNIT = system.MMAP2_UNIT; pub const MSG = system.MSG; @@ -4016,6 +4017,19 @@ pub fn munmap(memory: []align(mem.page_size) const u8) void { } } +pub const MSyncError = error{ + UnmappedMemory, +} || UnexpectedError; + +pub fn msync(memory: []align(mem.page_size) u8, flags: i32) MSyncError!void { + switch (errno(system.msync(memory.ptr, memory.len, flags))) { + .SUCCESS => return, + .NOMEM => return error.UnmappedMemory, // Unsuccessful, provided pointer does not point mapped memory + .INVAL => unreachable, // Invalid parameters. + else => unreachable, + } +} + pub const AccessError = error{ PermissionDenied, FileNotFound, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index c1591f7ea1..608db08a9c 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -406,6 +406,16 @@ pub fn mprotect(address: [*]const u8, length: usize, protection: usize) usize { return syscall3(.mprotect, @ptrToInt(address), length, protection); } +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + +pub fn msync(address: [*]const u8, length: usize, flags: u32) usize { + return syscall3(.msync, @ptrToInt(address), length, flags); +} + pub fn munmap(address: [*]const u8, length: usize) usize { return syscall2(.munmap, @ptrToInt(address), length); } From 65299c37d1b4b4395616d6f86b5f064000951cf6 Mon Sep 17 00:00:00 2001 From: m Date: Fri, 11 Feb 2022 20:11:23 +0100 Subject: [PATCH 2/2] validate in Windows using VirtualQuery --- lib/std/c/darwin.zig | 6 ++++ lib/std/c/dragonfly.zig | 6 ++++ lib/std/c/freebsd.zig | 6 ++++ lib/std/c/netbsd.zig | 6 ++++ lib/std/c/openbsd.zig | 6 ++++ lib/std/c/solaris.zig | 6 ++++ lib/std/debug.zig | 56 +++++++++++++++++++++++---------- lib/std/os/linux.zig | 4 +-- lib/std/os/windows.zig | 32 +++++++++++++++++++ lib/std/os/windows/kernel32.zig | 2 ++ 10 files changed, 112 insertions(+), 18 deletions(-) diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index f4ca9cd6dd..091f77e937 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -636,6 +636,12 @@ pub const MAP = struct { pub const FAILED = @intToPtr(*anyopaque, maxInt(usize)); }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const SA = struct { /// take signal on signal stack pub const ONSTACK = 0x0001; diff --git a/lib/std/c/dragonfly.zig b/lib/std/c/dragonfly.zig index d43c8dd239..75e84f637d 100644 --- a/lib/std/c/dragonfly.zig +++ b/lib/std/c/dragonfly.zig @@ -185,6 +185,12 @@ pub const MAP = struct { pub const SIZEALIGN = 262144; }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const W = struct { pub const NOHANG = 0x0001; pub const UNTRACED = 0x0002; diff --git a/lib/std/c/freebsd.zig b/lib/std/c/freebsd.zig index a19ecd3bac..7b74c237ce 100644 --- a/lib/std/c/freebsd.zig +++ b/lib/std/c/freebsd.zig @@ -410,6 +410,12 @@ pub const MAP = struct { pub const @"32BIT" = 0x00080000; }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const W = struct { pub const NOHANG = 1; pub const UNTRACED = 2; diff --git a/lib/std/c/netbsd.zig b/lib/std/c/netbsd.zig index 4fbc7594b0..6507134d13 100644 --- a/lib/std/c/netbsd.zig +++ b/lib/std/c/netbsd.zig @@ -575,6 +575,12 @@ pub const MAP = struct { pub const STACK = 0x2000; }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const W = struct { pub const NOHANG = 0x00000001; pub const UNTRACED = 0x00000002; diff --git a/lib/std/c/openbsd.zig b/lib/std/c/openbsd.zig index 6ba11e8e5a..17187cf203 100644 --- a/lib/std/c/openbsd.zig +++ b/lib/std/c/openbsd.zig @@ -363,6 +363,12 @@ pub const MAP = struct { pub const CONCEAL = 0x8000; }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const W = struct { pub const NOHANG = 1; pub const UNTRACED = 2; diff --git a/lib/std/c/solaris.zig b/lib/std/c/solaris.zig index 5abb34d2ff..5f44dc5350 100644 --- a/lib/std/c/solaris.zig +++ b/lib/std/c/solaris.zig @@ -534,6 +534,12 @@ pub const MAP = struct { pub const INITDATA = 0x0800; }; +pub const MSF = struct { + pub const ASYNC = 1; + pub const INVALIDATE = 2; + pub const SYNC = 4; +}; + pub const MADV = struct { /// no further special treatment pub const NORMAL = 0; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index be47985041..c1571a70c6 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -424,24 +424,48 @@ pub const StackIterator = struct { return address; } - fn isValidMemory(address: u64) bool { - if (native_os != .windows) { - var res = true; - const length = 2 * mem.page_size; - const aligned_address = address & ~@intCast(u64, (mem.page_size - 1)); - const aligned_memory = @intToPtr([*]align(mem.page_size) u8, aligned_address)[0..length]; + fn isValidMemory(address: usize) bool { + const aligned_address = address & ~@intCast(usize, (mem.page_size - 1)); - os.msync(aligned_memory, os.MSF.ASYNC) catch |err| { - switch (err) { - os.MSyncError.UnmappedMemory => { - res = false; - }, - else => unreachable, - } - }; - return res; + // If the address does not span 2 pages, query only the first one + const length: usize = if (aligned_address == address) mem.page_size else 2 * mem.page_size; + + const aligned_memory = @intToPtr([*]align(mem.page_size) u8, aligned_address)[0..length]; + + if (native_os != .windows) { + if (native_os != .wasi) { + os.msync(aligned_memory, os.MSF.ASYNC) catch |err| { + switch (err) { + os.MSyncError.UnmappedMemory => { + return false; + }, + else => unreachable, + } + }; + } + + return true; } else { - // TODO: Using windows memory API check if a page is mapped + const w = os.windows; + var memory_info: w.MEMORY_BASIC_INFORMATION = undefined; + //const memory_info_ptr = @ptrCast(w.PMEMORY_BASIC_INFORMATION, buffer); + + // The only error this function can throw is ERROR_INVALID_PARAMETER. + // supply an address that invalid i'll be thrown. + const rc = w.VirtualQuery(aligned_memory.ptr, &memory_info, aligned_memory.len) catch { + return false; + }; + + // Result code has to be bigger than zero (number of bytes written) + if (rc == 0) { + return false; + } + + // Free pages cannot be read, they are unmapped + if (memory_info.State == w.MEM_FREE) { + return false; + } + return true; } } diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 608db08a9c..b6c8285b52 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -412,8 +412,8 @@ pub const MSF = struct { pub const SYNC = 4; }; -pub fn msync(address: [*]const u8, length: usize, flags: u32) usize { - return syscall3(.msync, @ptrToInt(address), length, flags); +pub fn msync(address: [*]const u8, length: usize, flags: i32) usize { + return syscall3(.msync, @ptrToInt(address), length, @bitCast(u32, flags)); } pub fn munmap(address: [*]const u8, length: usize) usize { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c832149577..31745cfe82 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1495,6 +1495,19 @@ pub fn VirtualFree(lpAddress: ?LPVOID, dwSize: usize, dwFreeType: DWORD) void { assert(kernel32.VirtualFree(lpAddress, dwSize, dwFreeType) != 0); } +pub const VirtualQuerryError = error{Unexpected}; + +pub fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) VirtualQuerryError!SIZE_T { + const rc = kernel32.VirtualQuery(lpAddress, lpBuffer, dwLength); + if (rc == 0) { + switch (kernel32.GetLastError()) { + else => |err| return unexpectedError(err), + } + } + + return rc; +} + pub const SetConsoleTextAttributeError = error{Unexpected}; pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void { @@ -2586,6 +2599,11 @@ pub const CREATE_EVENT_MANUAL_RESET = 0x00000001; pub const EVENT_ALL_ACCESS = 0x1F0003; pub const EVENT_MODIFY_STATE = 0x0002; +// MEMORY_BASIC_INFORMATION.Type flags for VirtualQuery +pub const MEM_IMAGE = 0x1000000; +pub const MEM_MAPPED = 0x40000; +pub const MEM_PRIVATE = 0x20000; + pub const PROCESS_INFORMATION = extern struct { hProcess: HANDLE, hThread: HANDLE, @@ -2661,6 +2679,7 @@ pub const HEAP_NO_SERIALIZE = 0x00000001; // AllocationType values pub const MEM_COMMIT = 0x1000; pub const MEM_RESERVE = 0x2000; +pub const MEM_FREE = 0x10000; pub const MEM_RESET = 0x80000; pub const MEM_RESET_UNDO = 0x1000000; pub const MEM_LARGE_PAGES = 0x20000000; @@ -2960,6 +2979,19 @@ pub const COINIT = enum(c_int) { COINIT_SPEED_OVER_MEMORY = 8, }; +pub const MEMORY_BASIC_INFORMATION = extern struct { + BaseAddress: PVOID, + AllocationBase: PVOID, + AllocationProtect: DWORD, + PartitionId: WORD, + RegionSize: SIZE_T, + State: DWORD, + Protect: DWORD, + Type: DWORD, +}; + +pub const PMEMORY_BASIC_INFORMATION = *MEMORY_BASIC_INFORMATION; + /// > The maximum path of 32,767 characters is approximate, because the "\\?\" /// > prefix may be expanded to a longer string by the system at run time, and /// > this expansion applies to the total length. diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index b602921648..e947d1e505 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -56,6 +56,7 @@ const LPOVERLAPPED_COMPLETION_ROUTINE = windows.LPOVERLAPPED_COMPLETION_ROUTINE; const UCHAR = windows.UCHAR; const FARPROC = windows.FARPROC; const INIT_ONCE_FN = windows.INIT_ONCE_FN; +const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION; pub extern "kernel32" fn AddVectoredExceptionHandler(First: c_ulong, Handler: ?VECTORED_EXCEPTION_HANDLER) callconv(WINAPI) ?*anyopaque; pub extern "kernel32" fn RemoveVectoredExceptionHandler(Handle: HANDLE) callconv(WINAPI) c_ulong; @@ -245,6 +246,7 @@ pub extern "kernel32" fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*co pub extern "kernel32" fn VirtualAlloc(lpAddress: ?LPVOID, dwSize: SIZE_T, flAllocationType: DWORD, flProtect: DWORD) callconv(WINAPI) ?LPVOID; pub extern "kernel32" fn VirtualFree(lpAddress: ?LPVOID, dwSize: SIZE_T, dwFreeType: DWORD) callconv(WINAPI) BOOL; +pub extern "kernel32" fn VirtualQuery(lpAddress: ?LPVOID, lpBuffer: PMEMORY_BASIC_INFORMATION, dwLength: SIZE_T) callconv(WINAPI) SIZE_T; pub extern "kernel32" fn LocalFree(hMem: HLOCAL) callconv(WINAPI) ?HLOCAL;