authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-08 18:44:40-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-09 13:52:00-08:00
log4d6d2922b814d9f6885cfeec7603e73e9a852417
tree6bced54da3c8843b2906b960923d7593b5e6b2ac
parent9aaecde63f4d507b11de236c9eb6fc547f256b0e

std: move memory locking and memory protection to process

and introduce type safety for posix.PROT (mmap, mprotect) progress towards #6600

23 files changed, 311 insertions(+), 268 deletions(-)

lib/fuzzer.zig+1-1
......@@ -1320,7 +1320,7 @@ pub const MemoryMappedList = struct {
13201320 const ptr = try std.posix.mmap(
13211321 null,
13221322 capacity,
1323 std.posix.PROT.READ | std.posix.PROT.WRITE,
1323 .{ .READ = true, .WRITE = true },
13241324 .{ .TYPE = .SHARED },
13251325 file.handle,
13261326 0,
lib/std/Build/Fuzz.zig+1-1
......@@ -422,7 +422,7 @@ fn prepareTables(fuzz: *Fuzz, run_step: *Step.Run, coverage_id: u64) error{ OutO
422422 const mapped_memory = std.posix.mmap(
423423 null,
424424 file_size,
425 std.posix.PROT.READ,
425 .{ .READ = true },
426426 .{ .TYPE = .SHARED },
427427 coverage_file.handle,
428428 0,
lib/std/Thread.zig+9-9
......@@ -1535,7 +1535,7 @@ const LinuxThreadImpl = struct {
15351535 const mapped = posix.mmap(
15361536 null,
15371537 map_bytes,
1538 posix.PROT.NONE,
1538 .{},
15391539 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
15401540 -1,
15411541 0,
......@@ -1551,14 +1551,14 @@ const LinuxThreadImpl = struct {
15511551 assert(mapped.len >= map_bytes);
15521552 errdefer posix.munmap(mapped);
15531553
1554 // map everything but the guard page as read/write
1555 posix.mprotect(
1556 @alignCast(mapped[guard_offset..]),
1557 posix.PROT.READ | posix.PROT.WRITE,
1558 ) catch |err| switch (err) {
1559 error.AccessDenied => unreachable,
1560 else => |e| return e,
1561 };
1554 // Map everything but the guard page as read/write.
1555 const guarded: []align(std.heap.page_size_min) u8 = @alignCast(mapped[guard_offset..]);
1556 const protection: posix.PROT = .{ .READ = true, .WRITE = true };
1557 switch (posix.errno(posix.system.mprotect(guarded.ptr, guarded.len, protection))) {
1558 .SUCCESS => {},
1559 .NOMEM => return error.OutOfMemory,
1560 else => |err| return posix.unexpectedErrno(err),
1561 }
15621562
15631563 // Prepare the TLS segment and prepare a user_desc struct when needed on x86
15641564 var tls_ptr = linux.tls.prepareArea(mapped[tls_offset..]);
lib/std/c.zig+14-33
......@@ -1672,10 +1672,10 @@ pub const MCL = switch (native_os) {
16721672 // https://github.com/NetBSD/src/blob/fd2741deca927c18e3ba15acdf78b8b14b2abe36/sys/sys/mman.h#L179
16731673 // https://github.com/openbsd/src/blob/39404228f6d36c0ca4be5f04ab5385568ebd6aa3/sys/sys/mman.h#L129
16741674 // https://github.com/illumos/illumos-gate/blob/5280477614f83fea20fc938729df6adb3e44340d/usr/src/uts/common/sys/mman.h#L343
1675 .freebsd, .dragonfly, .netbsd, .openbsd, .illumos => packed struct(c_int) {
1676 CURRENT: bool = 0,
1677 FUTURE: bool = 0,
1678 _: std.meta.Int(.unsigned, @bitSizeOf(c_int) - 2) = 0,
1675 .freebsd, .dragonfly, .netbsd, .openbsd, .illumos => packed struct(u32) {
1676 CURRENT: bool = false,
1677 FUTURE: bool = false,
1678 _: u30 = 0,
16791679 },
16801680 else => void,
16811681};
......@@ -1887,32 +1887,13 @@ pub const PROT = switch (native_os) {
18871887 .linux => linux.PROT,
18881888 .emscripten => emscripten.PROT,
18891889 // https://github.com/SerenityOS/serenity/blob/6d59d4d3d9e76e39112842ec487840828f1c9bfe/Kernel/API/POSIX/sys/mman.h#L28-L31
1890 .openbsd, .haiku, .dragonfly, .netbsd, .illumos, .freebsd, .windows, .serenity => struct {
1891 /// page can not be accessed
1892 pub const NONE = 0x0;
1893 /// page can be read
1894 pub const READ = 0x1;
1895 /// page can be written
1896 pub const WRITE = 0x2;
1897 /// page can be executed
1898 pub const EXEC = 0x4;
1899 },
1900 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => struct {
1901 /// [MC2] no permissions
1902 pub const NONE: vm_prot_t = 0x00;
1903 /// [MC2] pages can be read
1904 pub const READ: vm_prot_t = 0x01;
1905 /// [MC2] pages can be written
1906 pub const WRITE: vm_prot_t = 0x02;
1907 /// [MC2] pages can be executed
1908 pub const EXEC: vm_prot_t = 0x04;
1909 /// When a caller finds that they cannot obtain write permission on a
1910 /// mapped entry, the following flag can be used. The entry will be
1911 /// made "needs copy" effectively copying the object (using COW),
1912 /// and write permission will be added to the maximum protections for
1913 /// the associated entry.
1914 pub const COPY: vm_prot_t = 0x10;
1890 .openbsd, .haiku, .dragonfly, .netbsd, .illumos, .freebsd, .windows, .serenity => packed struct(u32) {
1891 READ: bool = false,
1892 WRITE: bool = false,
1893 EXEC: bool = false,
1894 _: u29 = 0,
19151895 },
1896 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => vm_prot_t,
19161897 else => void,
19171898};
19181899
......@@ -10349,7 +10330,7 @@ pub extern "c" fn getgrgid(gid: gid_t) ?*group;
1034910330pub extern "c" fn getgrgid_r(gid: gid_t, grp: *group, buf: [*]u8, buflen: usize, result: *?*group) c_int;
1035010331pub extern "c" fn getrlimit64(resource: rlimit_resource, rlim: *rlimit) c_int;
1035110332pub extern "c" fn lseek64(fd: fd_t, offset: i64, whence: c_int) i64;
10352pub extern "c" fn mmap64(addr: ?*align(page_size) anyopaque, len: usize, prot: c_uint, flags: c_uint, fd: fd_t, offset: i64) *anyopaque;
10333pub extern "c" fn mmap64(addr: ?*align(page_size) anyopaque, len: usize, prot: PROT, flags: c_uint, fd: fd_t, offset: i64) *anyopaque;
1035310334pub extern "c" fn open64(path: [*:0]const u8, oflag: O, ...) c_int;
1035410335pub extern "c" fn openat64(fd: c_int, path: [*:0]const u8, oflag: O, ...) c_int;
1035510336pub extern "c" fn pread64(fd: fd_t, buf: [*]u8, nbyte: usize, offset: i64) isize;
......@@ -10478,7 +10459,7 @@ pub const mlock = switch (native_os) {
1047810459};
1047910460
1048010461pub const mlock2 = switch (native_os) {
10481 linux => private.mlock2,
10462 .linux => private.mlock2,
1048210463 else => {},
1048310464};
1048410465
......@@ -10667,10 +10648,10 @@ pub extern "c" fn writev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint) i
1066710648pub extern "c" fn pwritev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint, offset: off_t) isize;
1066810649pub extern "c" fn write(fd: fd_t, buf: [*]const u8, nbyte: usize) isize;
1066910650pub extern "c" fn pwrite(fd: fd_t, buf: [*]const u8, nbyte: usize, offset: off_t) isize;
10670pub extern "c" fn mmap(addr: ?*align(page_size) anyopaque, len: usize, prot: c_uint, flags: MAP, fd: fd_t, offset: off_t) *anyopaque;
10651pub extern "c" fn mmap(addr: ?*align(page_size) anyopaque, len: usize, prot: PROT, flags: MAP, fd: fd_t, offset: off_t) *anyopaque;
1067110652pub extern "c" fn munmap(addr: *align(page_size) const anyopaque, len: usize) c_int;
1067210653pub extern "c" fn mremap(addr: ?*align(page_size) const anyopaque, old_len: usize, new_len: usize, flags: MREMAP, ...) *anyopaque;
10673pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: c_uint) c_int;
10654pub extern "c" fn mprotect(addr: *align(page_size) anyopaque, len: usize, prot: PROT) c_int;
1067410655pub extern "c" fn link(oldpath: [*:0]const u8, newpath: [*:0]const u8) c_int;
1067510656pub extern "c" fn linkat(oldfd: fd_t, oldpath: [*:0]const u8, newfd: fd_t, newpath: [*:0]const u8, flags: c_uint) c_int;
1067610657pub extern "c" fn unlink(path: [*:0]const u8) c_int;
lib/std/c/darwin.zig+1-1
......@@ -808,7 +808,7 @@ pub const task_vm_info = extern struct {
808808
809809pub const task_vm_info_data_t = task_vm_info;
810810
811pub const vm_prot_t = c_int;
811pub const vm_prot_t = std.macho.vm_prot_t;
812812pub const boolean_t = c_int;
813813
814814pub extern "c" fn mach_vm_protect(
lib/std/debug/ElfFile.zig+1-1
......@@ -442,7 +442,7 @@ fn loadInner(
442442 break :mapped std.posix.mmap(
443443 null,
444444 file_len,
445 std.posix.PROT.READ,
445 .{ .READ = true },
446446 .{ .TYPE = .SHARED },
447447 elf_file.handle,
448448 0,
lib/std/debug/MachOFile.zig+1-1
......@@ -526,7 +526,7 @@ fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) c
526526 return posix.mmap(
527527 null,
528528 file_len,
529 posix.PROT.READ,
529 .{ .READ = true },
530530 .{ .TYPE = .SHARED },
531531 file.handle,
532532 0,
lib/std/debug/SelfInfo/MachO.zig+1-1
......@@ -631,7 +631,7 @@ fn mapDebugInfoFile(io: Io, path: []const u8) ![]align(std.heap.page_size_min) c
631631 return posix.mmap(
632632 null,
633633 file_len,
634 posix.PROT.READ,
634 .{ .READ = true },
635635 .{ .TYPE = .SHARED },
636636 file.handle,
637637 0,
lib/std/dynamic_library.zig+9-9
......@@ -238,7 +238,7 @@ pub const ElfDynLib = struct {
238238 const file_bytes = try posix.mmap(
239239 null,
240240 mem.alignForward(usize, size, page_size),
241 posix.PROT.READ,
241 .{ .READ = true },
242242 .{ .TYPE = .PRIVATE },
243243 file.handle,
244244 0,
......@@ -276,7 +276,7 @@ pub const ElfDynLib = struct {
276276 const all_loaded_mem = try posix.mmap(
277277 null,
278278 virt_addr_end,
279 posix.PROT.NONE,
279 .{},
280280 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
281281 -1,
282282 0,
......@@ -302,7 +302,7 @@ pub const ElfDynLib = struct {
302302 const extra_bytes = (base + ph.p_vaddr) - aligned_addr;
303303 const extended_memsz = mem.alignForward(usize, ph.p_memsz + extra_bytes, page_size);
304304 const ptr = @as([*]align(std.heap.page_size_min) u8, @ptrFromInt(aligned_addr));
305 const prot = elfToMmapProt(ph.p_flags);
305 const prot = elfToProt(ph.p_flags);
306306 if ((ph.p_flags & elf.PF_W) == 0) {
307307 // If it does not need write access, it can be mapped from the fd.
308308 _ = try posix.mmap(
......@@ -531,12 +531,12 @@ pub const ElfDynLib = struct {
531531 return null;
532532 }
533533
534 fn elfToMmapProt(elf_prot: u64) u32 {
535 var result: u32 = posix.PROT.NONE;
536 if ((elf_prot & elf.PF_R) != 0) result |= posix.PROT.READ;
537 if ((elf_prot & elf.PF_W) != 0) result |= posix.PROT.WRITE;
538 if ((elf_prot & elf.PF_X) != 0) result |= posix.PROT.EXEC;
539 return result;
534 fn elfToProt(elf_prot: u64) posix.PROT {
535 return .{
536 .READ = (elf_prot & elf.PF_R) != 0,
537 .WRITE = (elf_prot & elf.PF_W) != 0,
538 .EXEC = (elf_prot & elf.PF_X) != 0,
539 };
540540 }
541541};
542542
lib/std/heap/PageAllocator.zig+1-1
......@@ -96,7 +96,7 @@ pub fn map(n: usize, alignment: mem.Alignment) ?[*]u8 {
9696 const slice = posix.mmap(
9797 hint,
9898 overalloc_len,
99 posix.PROT.READ | posix.PROT.WRITE,
99 .{ .READ = true, .WRITE = true },
100100 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
101101 -1,
102102 0,
lib/std/macho.zig+16-4
......@@ -9,7 +9,19 @@ const Allocator = mem.Allocator;
99
1010pub const cpu_type_t = c_int;
1111pub const cpu_subtype_t = c_int;
12pub const vm_prot_t = c_int;
12pub const vm_prot_t = packed struct(u32) {
13 READ: bool = false,
14 WRITE: bool = false,
15 EXEC: bool = false,
16 _: u1 = 0,
17 /// When a caller finds that they cannot obtain write permission on a
18 /// mapped entry, the following flag can be used. The entry will be
19 /// made "needs copy" effectively copying the object (using COW),
20 /// and write permission will be added to the maximum protections for
21 /// the associated entry.
22 COPY: bool = false,
23 __: u27 = 0,
24};
1325
1426pub const mach_header = extern struct {
1527 magic: u32,
......@@ -648,10 +660,10 @@ pub const segment_command_64 = extern struct {
648660 filesize: u64 = 0,
649661
650662 /// maximum VM protection
651 maxprot: vm_prot_t = PROT.NONE,
663 maxprot: vm_prot_t = .{},
652664
653665 /// initial VM protection
654 initprot: vm_prot_t = PROT.NONE,
666 initprot: vm_prot_t = .{},
655667
656668 /// number of sections in segment
657669 nsects: u32 = 0,
......@@ -662,7 +674,7 @@ pub const segment_command_64 = extern struct {
662674 }
663675
664676 pub fn isWriteable(seg: segment_command_64) bool {
665 return seg.initprot & PROT.WRITE != 0;
677 return seg.initprot.write;
666678 }
667679};
668680
lib/std/os/emscripten.zig+8-7
......@@ -331,13 +331,14 @@ pub const POLL = struct {
331331 pub const RDBAND = 0x080;
332332};
333333
334pub const PROT = struct {
335 pub const NONE = 0x0;
336 pub const READ = 0x1;
337 pub const WRITE = 0x2;
338 pub const EXEC = 0x4;
339 pub const GROWSDOWN = 0x01000000;
340 pub const GROWSUP = 0x02000000;
334pub const PROT = packed struct(u32) {
335 READ: bool = false,
336 WRITE: bool = false,
337 EXEC: bool = false,
338 _: u21 = 0,
339 GROWSDOWN: bool = false,
340 GROWSUP: bool = false,
341 __: u6 = 0,
341342};
342343
343344pub const rlim_t = u64;
lib/std/os/linux.zig+30-24
......@@ -986,13 +986,13 @@ pub fn pivot_root(new_root: [*:0]const u8, put_old: [*:0]const u8) usize {
986986 return syscall2(.pivot_root, @intFromPtr(new_root), @intFromPtr(put_old));
987987}
988988
989pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: MAP, fd: i32, offset: i64) usize {
989pub fn mmap(address: ?[*]u8, length: usize, prot: PROT, flags: MAP, fd: i32, offset: i64) usize {
990990 if (@hasField(SYS, "mmap2")) {
991991 return syscall6(
992992 .mmap2,
993993 @intFromPtr(address),
994994 length,
995 prot,
995 @as(u32, @bitCast(prot)),
996996 @as(u32, @bitCast(flags)),
997997 @bitCast(@as(isize, fd)),
998998 @truncate(@as(u64, @bitCast(offset)) / std.heap.pageSize()),
......@@ -1005,7 +1005,7 @@ pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: MAP, fd: i32, of
10051005 @intFromPtr(&[_]usize{
10061006 @intFromPtr(address),
10071007 length,
1008 prot,
1008 @as(u32, @bitCast(prot)),
10091009 @as(u32, @bitCast(flags)),
10101010 @bitCast(@as(isize, fd)),
10111011 @as(u64, @bitCast(offset)),
......@@ -1014,7 +1014,7 @@ pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: MAP, fd: i32, of
10141014 .mmap,
10151015 @intFromPtr(address),
10161016 length,
1017 prot,
1017 @as(u32, @bitCast(prot)),
10181018 @as(u32, @bitCast(flags)),
10191019 @bitCast(@as(isize, fd)),
10201020 @as(u64, @bitCast(offset)),
......@@ -1022,8 +1022,8 @@ pub fn mmap(address: ?[*]u8, length: usize, prot: usize, flags: MAP, fd: i32, of
10221022 }
10231023}
10241024
1025pub fn mprotect(address: [*]const u8, length: usize, protection: usize) usize {
1026 return syscall3(.mprotect, @intFromPtr(address), length, protection);
1025pub fn mprotect(address: [*]const u8, length: usize, protection: PROT) usize {
1026 return syscall3(.mprotect, @intFromPtr(address), length, @as(u32, @bitCast(protection)));
10271027}
10281028
10291029pub fn mremap(old_addr: ?[*]const u8, old_len: usize, new_len: usize, flags: MREMAP, new_addr: ?[*]const u8) usize {
......@@ -3616,24 +3616,30 @@ pub const FUTEX2_FLAGS = packed struct(u32) {
36163616 _undefined: u24 = 0,
36173617};
36183618
3619pub const PROT = struct {
3620 /// page can not be accessed
3621 pub const NONE = 0x0;
3622 /// page can be read
3623 pub const READ = 0x1;
3624 /// page can be written
3625 pub const WRITE = 0x2;
3626 /// page can be executed
3627 pub const EXEC = 0x4;
3628 /// page may be used for atomic ops
3629 pub const SEM = switch (native_arch) {
3630 .mips, .mipsel, .mips64, .mips64el, .xtensa, .xtensaeb => 0x10,
3631 else => 0x8,
3632 };
3633 /// mprotect flag: extend change to start of growsdown vma
3634 pub const GROWSDOWN = 0x01000000;
3635 /// mprotect flag: extend change to end of growsup vma
3636 pub const GROWSUP = 0x02000000;
3619pub const PROT = switch (native_arch) {
3620 .mips, .mipsel, .mips64, .mips64el, .xtensa, .xtensaeb => packed struct(u32) {
3621 READ: bool = false,
3622 WRITE: bool = false,
3623 EXEC: bool = false,
3624 _: u1 = 0,
3625 /// Page may be used for atomic ops.
3626 SEM: bool = false,
3627 __: u19 = 0,
3628 GROWSDOWN: bool = false,
3629 GROWSUP: bool = false,
3630 ___: u6 = 0,
3631 },
3632 else => packed struct(u32) {
3633 READ: bool = false,
3634 WRITE: bool = false,
3635 EXEC: bool = false,
3636 /// Page may be used for atomic ops.
3637 SEM: bool = false,
3638 __: u20 = 0,
3639 GROWSDOWN: bool = false,
3640 GROWSUP: bool = false,
3641 ___: u6 = 0,
3642 },
36373643};
36383644
36393645pub const FD_CLOEXEC = 1;
lib/std/os/linux/IoUring.zig+3-3
......@@ -1526,7 +1526,7 @@ pub const SubmissionQueue = struct {
15261526 const mmap = try posix.mmap(
15271527 null,
15281528 size,
1529 posix.PROT.READ | posix.PROT.WRITE,
1529 .{ .READ = true, .WRITE = true },
15301530 .{ .TYPE = .SHARED, .POPULATE = true },
15311531 fd,
15321532 linux.IORING_OFF_SQ_RING,
......@@ -1540,7 +1540,7 @@ pub const SubmissionQueue = struct {
15401540 const mmap_sqes = try posix.mmap(
15411541 null,
15421542 size_sqes,
1543 posix.PROT.READ | posix.PROT.WRITE,
1543 .{ .READ = true, .WRITE = true },
15441544 .{ .TYPE = .SHARED, .POPULATE = true },
15451545 fd,
15461546 linux.IORING_OFF_SQES,
......@@ -1747,7 +1747,7 @@ pub fn setup_buf_ring(
17471747 const mmap = try posix.mmap(
17481748 null,
17491749 mmap_size,
1750 posix.PROT.READ | posix.PROT.WRITE,
1750 .{ .READ = true, .WRITE = true },
17511751 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
17521752 -1,
17531753 0,
lib/std/os/linux/tls.zig+4-4
......@@ -568,7 +568,7 @@ pub fn initStatic(phdrs: []elf.Phdr) void {
568568}
569569
570570inline fn mmap_tls(length: usize) usize {
571 const prot = linux.PROT.READ | linux.PROT.WRITE;
571 const prot: linux.PROT = .{ .READ = true, .WRITE = true };
572572 const flags: linux.MAP = .{ .TYPE = .PRIVATE, .ANONYMOUS = true };
573573
574574 if (@hasField(linux.SYS, "mmap2")) {
......@@ -576,7 +576,7 @@ inline fn mmap_tls(length: usize) usize {
576576 .mmap2,
577577 0,
578578 length,
579 prot,
579 @as(u32, @bitCast(prot)),
580580 @as(u32, @bitCast(flags)),
581581 @as(usize, @bitCast(@as(isize, -1))),
582582 0,
......@@ -589,7 +589,7 @@ inline fn mmap_tls(length: usize) usize {
589589 @intFromPtr(&[_]usize{
590590 0,
591591 length,
592 prot,
592 @as(u32, @bitCast(prot)),
593593 @as(u32, @bitCast(flags)),
594594 @as(usize, @bitCast(@as(isize, -1))),
595595 0,
......@@ -598,7 +598,7 @@ inline fn mmap_tls(length: usize) usize {
598598 .mmap,
599599 0,
600600 length,
601 prot,
601 @as(u32, @bitCast(prot)),
602602 @as(u32, @bitCast(flags)),
603603 @as(usize, @bitCast(@as(isize, -1))),
604604 0,
lib/std/os/windows.zig-34
......@@ -3765,40 +3765,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_
37653765 };
37663766}
37673767
3768pub const VirtualProtectError = error{
3769 InvalidAddress,
3770 Unexpected,
3771};
3772
3773pub fn VirtualProtect(lpAddress: ?LPVOID, dwSize: SIZE_T, flNewProtect: DWORD, lpflOldProtect: *DWORD) VirtualProtectError!void {
3774 // ntdll takes an extra level of indirection here
3775 var addr = lpAddress;
3776 var size = dwSize;
3777 switch (ntdll.NtProtectVirtualMemory(GetCurrentProcess(), &addr, &size, flNewProtect, lpflOldProtect)) {
3778 .SUCCESS => {},
3779 .INVALID_ADDRESS => return error.InvalidAddress,
3780 else => |st| return unexpectedStatus(st),
3781 }
3782}
3783
3784pub fn VirtualProtectEx(handle: HANDLE, addr: ?LPVOID, size: SIZE_T, new_prot: DWORD) VirtualProtectError!DWORD {
3785 var old_prot: DWORD = undefined;
3786 var out_addr = addr;
3787 var out_size = size;
3788 switch (ntdll.NtProtectVirtualMemory(
3789 handle,
3790 &out_addr,
3791 &out_size,
3792 new_prot,
3793 &old_prot,
3794 )) {
3795 .SUCCESS => return old_prot,
3796 .INVALID_ADDRESS => return error.InvalidAddress,
3797 // TODO: map errors
3798 else => |rc| return unexpectedStatus(rc),
3799 }
3800}
3801
38023768pub const SetConsoleTextAttributeError = error{Unexpected};
38033769
38043770pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void {
lib/std/posix.zig+2-117
......@@ -934,128 +934,14 @@ pub fn fanotify_markZ(
934934 }
935935}
936936
937pub const MlockError = error{
938 PermissionDenied,
939 LockedMemoryLimitExceeded,
940 SystemResources,
941} || UnexpectedError;
942
943pub fn mlock(memory: []align(page_size_min) const u8) MlockError!void {
944 if (@TypeOf(system.mlock) == void)
945 @compileError("mlock not supported on this OS");
946 return switch (errno(system.mlock(memory.ptr, memory.len))) {
947 .SUCCESS => {},
948 .INVAL => unreachable, // unaligned, negative, runs off end of addrspace
949 .PERM => error.PermissionDenied,
950 .NOMEM => error.LockedMemoryLimitExceeded,
951 .AGAIN => error.SystemResources,
952 else => |err| unexpectedErrno(err),
953 };
954}
955
956pub fn mlock2(memory: []align(page_size_min) const u8, flags: MLOCK) MlockError!void {
957 if (@TypeOf(system.mlock2) == void)
958 @compileError("mlock2 not supported on this OS");
959 return switch (errno(system.mlock2(memory.ptr, memory.len, flags))) {
960 .SUCCESS => {},
961 .INVAL => unreachable, // bad memory or bad flags
962 .PERM => error.PermissionDenied,
963 .NOMEM => error.LockedMemoryLimitExceeded,
964 .AGAIN => error.SystemResources,
965 else => |err| unexpectedErrno(err),
966 };
967}
968
969pub fn munlock(memory: []align(page_size_min) const u8) MlockError!void {
970 if (@TypeOf(system.munlock) == void)
971 @compileError("munlock not supported on this OS");
972 return switch (errno(system.munlock(memory.ptr, memory.len))) {
973 .SUCCESS => {},
974 .INVAL => unreachable, // unaligned or runs off end of addr space
975 .PERM => return error.PermissionDenied,
976 .NOMEM => return error.LockedMemoryLimitExceeded,
977 .AGAIN => return error.SystemResources,
978 else => |err| unexpectedErrno(err),
979 };
980}
981
982pub fn mlockall(flags: MCL) MlockError!void {
983 if (@TypeOf(system.mlockall) == void)
984 @compileError("mlockall not supported on this OS");
985 return switch (errno(system.mlockall(flags))) {
986 .SUCCESS => {},
987 .INVAL => unreachable, // bad flags
988 .PERM => error.PermissionDenied,
989 .NOMEM => error.LockedMemoryLimitExceeded,
990 .AGAIN => error.SystemResources,
991 else => |err| unexpectedErrno(err),
992 };
993}
994
995pub fn munlockall() MlockError!void {
996 if (@TypeOf(system.munlockall) == void)
997 @compileError("munlockall not supported on this OS");
998 return switch (errno(system.munlockall())) {
999 .SUCCESS => {},
1000 .PERM => error.PermissionDenied,
1001 .NOMEM => error.LockedMemoryLimitExceeded,
1002 .AGAIN => error.SystemResources,
1003 else => |err| unexpectedErrno(err),
1004 };
1005}
1006
1007pub const MProtectError = error{
1008 /// The memory cannot be given the specified access. This can happen, for example, if you
1009 /// mmap(2) a file to which you have read-only access, then ask mprotect() to mark it
1010 /// PROT_WRITE.
1011 AccessDenied,
1012
1013 /// Changing the protection of a memory region would result in the total number of map‐
1014 /// pings with distinct attributes (e.g., read versus read/write protection) exceeding the
1015 /// allowed maximum. (For example, making the protection of a range PROT_READ in the mid‐
1016 /// dle of a region currently protected as PROT_READ|PROT_WRITE would result in three map‐
1017 /// pings: two read/write mappings at each end and a read-only mapping in the middle.)
1018 OutOfMemory,
1019} || UnexpectedError;
1020
1021pub fn mprotect(memory: []align(page_size_min) u8, protection: u32) MProtectError!void {
1022 if (native_os == .windows) {
1023 const win_prot: windows.DWORD = switch (@as(u3, @truncate(protection))) {
1024 0b000 => windows.PAGE_NOACCESS,
1025 0b001 => windows.PAGE_READONLY,
1026 0b010 => unreachable, // +w -r not allowed
1027 0b011 => windows.PAGE_READWRITE,
1028 0b100 => windows.PAGE_EXECUTE,
1029 0b101 => windows.PAGE_EXECUTE_READ,
1030 0b110 => unreachable, // +w -r not allowed
1031 0b111 => windows.PAGE_EXECUTE_READWRITE,
1032 };
1033 var old: windows.DWORD = undefined;
1034 windows.VirtualProtect(memory.ptr, memory.len, win_prot, &old) catch |err| switch (err) {
1035 error.InvalidAddress => return error.AccessDenied,
1036 error.Unexpected => return error.Unexpected,
1037 };
1038 } else {
1039 switch (errno(system.mprotect(memory.ptr, memory.len, protection))) {
1040 .SUCCESS => return,
1041 .INVAL => unreachable,
1042 .ACCES => return error.AccessDenied,
1043 .NOMEM => return error.OutOfMemory,
1044 else => |err| return unexpectedErrno(err),
1045 }
1046 }
1047}
1048
1049937pub const MMapError = error{
1050938 /// The underlying filesystem of the specified file does not support memory mapping.
1051939 MemoryMappingNotSupported,
1052
1053940 /// A file descriptor refers to a non-regular file. Or a file mapping was requested,
1054941 /// but the file descriptor is not open for reading. Or `MAP.SHARED` was requested
1055942 /// and `PROT_WRITE` is set, but the file descriptor is not open in `RDWR` mode.
1056943 /// Or `PROT_WRITE` is set, but the file is append-only.
1057944 AccessDenied,
1058
1059945 /// The `prot` argument asks for `PROT_EXEC` but the mapped area belongs to a file on
1060946 /// a filesystem that was mounted no-exec.
1061947 PermissionDenied,
......@@ -1063,7 +949,6 @@ pub const MMapError = error{
1063949 ProcessFdQuotaExceeded,
1064950 SystemFdQuotaExceeded,
1065951 OutOfMemory,
1066
1067952 /// Using FIXED_NOREPLACE flag and the process has already mapped memory at the given address
1068953 MappingAlreadyExists,
1069954} || UnexpectedError;
......@@ -1076,8 +961,8 @@ pub const MMapError = error{
1076961pub fn mmap(
1077962 ptr: ?[*]align(page_size_min) u8,
1078963 length: usize,
1079 prot: u32,
1080 flags: system.MAP,
964 prot: PROT,
965 flags: MAP,
1081966 fd: fd_t,
1082967 offset: u64,
1083968) MMapError![]align(page_size_min) u8 {
lib/std/posix/test.zig+3-3
......@@ -172,7 +172,7 @@ test "mmap" {
172172 const data = try posix.mmap(
173173 null,
174174 1234,
175 posix.PROT.READ | posix.PROT.WRITE,
175 .{ .READ = true, .WRITE = true },
176176 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
177177 -1,
178178 0,
......@@ -214,7 +214,7 @@ test "mmap" {
214214 const data = try posix.mmap(
215215 null,
216216 alloc_size,
217 posix.PROT.READ,
217 .{ .READ = true },
218218 .{ .TYPE = .PRIVATE },
219219 file.handle,
220220 0,
......@@ -239,7 +239,7 @@ test "mmap" {
239239 const data = try posix.mmap(
240240 null,
241241 alloc_size / 2,
242 posix.PROT.READ,
242 .{ .READ = true },
243243 .{ .TYPE = .PRIVATE },
244244 file.handle,
245245 alloc_size / 2,
lib/std/process.zig+192
......@@ -886,3 +886,195 @@ pub const SetCurrentDirError = error{
886886pub fn setCurrentDir(io: Io, dir: Io.Dir) !void {
887887 return io.vtable.processSetCurrentDir(io.userdata, dir);
888888}
889
890pub const LockMemoryError = error{
891 UnsupportedOperation,
892 PermissionDenied,
893 LockedMemoryLimitExceeded,
894 SystemResources,
895} || Io.UnexpectedError;
896
897pub const LockMemoryOptions = struct {
898 /// Lock pages that are currently resident and mark the entire range so
899 /// that the remaining nonresident pages are locked when they are populated
900 /// by a page fault.
901 on_fault: bool = false,
902};
903
904/// Request part of the calling process's virtual address space to be in RAM,
905/// preventing that memory from being paged to the swap area.
906///
907/// Corresponds to "mlock" or "mlock2" in libc.
908///
909/// See also:
910/// * unlockMemory
911pub fn lockMemory(memory: []align(std.heap.page_size_min) const u8, options: LockMemoryOptions) LockMemoryError!void {
912 if (native_os == .windows) {
913 // TODO call VirtualLock
914 }
915 if (!options.on_fault and @TypeOf(posix.system.mlock) != void) {
916 switch (posix.errno(posix.system.mlock(memory.ptr, memory.len))) {
917 .SUCCESS => return,
918 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned, negative, runs off end of addrspace
919 .PERM => return error.PermissionDenied,
920 .NOMEM => return error.LockedMemoryLimitExceeded,
921 .AGAIN => return error.SystemResources,
922 else => |err| return posix.unexpectedErrno(err),
923 }
924 }
925 if (@TypeOf(posix.system.mlock2) != void) {
926 const flags: posix.MLOCK = .{ .ONFAULT = options.on_fault };
927 switch (posix.errno(posix.system.mlock2(memory.ptr, memory.len, flags))) {
928 .SUCCESS => return,
929 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned, negative, runs off end of addrspace
930 .PERM => return error.PermissionDenied,
931 .NOMEM => return error.LockedMemoryLimitExceeded,
932 .AGAIN => return error.SystemResources,
933 else => |err| return posix.unexpectedErrno(err),
934 }
935 }
936 return error.UnsupportedOperation;
937}
938
939pub const UnlockMemoryError = error{
940 PermissionDenied,
941 OutOfMemory,
942 SystemResources,
943} || Io.UnexpectedError;
944
945/// Withdraw request for process's virtual address space to be in RAM.
946///
947/// Corresponds to "munlock" in libc.
948///
949/// See also:
950/// * `lockMemory`
951pub fn unlockMemory(memory: []align(std.heap.page_size_min) const u8) UnlockMemoryError!void {
952 if (@TypeOf(posix.system.munlock) == void) return;
953 switch (posix.errno(posix.system.munlock(memory.ptr, memory.len))) {
954 .SUCCESS => return,
955 .INVAL => |err| return std.Io.Threaded.errnoBug(err), // unaligned or runs off end of addr space
956 .PERM => return error.PermissionDenied,
957 .NOMEM => return error.OutOfMemory,
958 .AGAIN => return error.SystemResources,
959 else => |err| return posix.unexpectedErrno(err),
960 }
961}
962
963pub const LockMemoryAllOptions = struct {
964 current: bool = false,
965 future: bool = false,
966 /// Asserted to be used together with `current` or `future`, or both.
967 on_fault: bool = false,
968};
969
970pub fn lockMemoryAll(options: LockMemoryAllOptions) LockMemoryError!void {
971 if (@TypeOf(posix.system.mlockall) == void) return error.UnsupportedOperation;
972 var flags: posix.MCL = .{
973 .CURRENT = options.current,
974 .FUTURE = options.future,
975 };
976 if (options.on_fault) {
977 assert(options.current or options.future);
978 if (@hasField(posix.MCL, "ONFAULT")) {
979 flags.ONFAULT = true;
980 } else {
981 return error.UnsupportedOperation;
982 }
983 }
984 switch (posix.errno(posix.system.mlockall(flags))) {
985 .SUCCESS => return,
986 .INVAL => |err| return std.Io.Threaded.errnoBug(err),
987 .PERM => return error.PermissionDenied,
988 .NOMEM => return error.LockedMemoryLimitExceeded,
989 .AGAIN => return error.SystemResources,
990 else => |err| return posix.unexpectedErrno(err),
991 }
992}
993
994pub fn unlockMemoryAll() UnlockMemoryError!void {
995 if (@TypeOf(posix.system.munlockall) == void) return;
996 switch (posix.errno(posix.system.munlockall())) {
997 .SUCCESS => return,
998 .PERM => return error.PermissionDenied,
999 .NOMEM => return error.OutOfMemory,
1000 .AGAIN => return error.SystemResources,
1001 else => |err| return posix.unexpectedErrno(err),
1002 }
1003}
1004
1005pub const ProtectMemoryError = error{
1006 UnsupportedOperation,
1007 /// The memory cannot be given the specified access. This can happen, for
1008 /// example, if you memory map a file to which you have read-only access,
1009 /// then use `protectMemory` to mark it writable.
1010 AccessDenied,
1011 /// Changing the protection of a memory region would result in the total
1012 /// number of mappings with distinct attributes exceeding the allowed
1013 /// maximum.
1014 OutOfMemory,
1015} || Io.UnexpectedError;
1016
1017pub const ProtectMemoryOptions = packed struct(u3) {
1018 read: bool = false,
1019 write: bool = false,
1020 execute: bool = false,
1021};
1022
1023pub fn protectMemory(
1024 memory: []align(std.heap.page_size_min) u8,
1025 options: ProtectMemoryOptions,
1026) ProtectMemoryError!void {
1027 if (native_os == .windows) {
1028 var addr = memory.ptr; // ntdll takes an extra level of indirection here
1029 var size = memory.len; // ntdll takes an extra level of indirection here
1030 var old: windows.PAGE = undefined;
1031 const current_process: windows.HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))));
1032 const new: windows.PAGE = switch (@as(u3, @bitCast(options))) {
1033 0b000 => .{ .NOACCESS = true },
1034 0b001 => .{ .READONLY = true },
1035 0b010 => return error.AccessDenied, // +w -r not allowed
1036 0b011 => .{ .READWRITE = true },
1037 0b100 => .{ .EXECUTE = true },
1038 0b101 => .{ .EXECUTE_READ = true },
1039 0b110 => return error.AccessDenied, // +w -r not allowed
1040 0b111 => .{ .EXECUTE_READWRITE = true },
1041 };
1042 switch (windows.ntdll.NtProtectVirtualMemory(current_process, @ptrCast(&addr), &size, new, &old)) {
1043 .SUCCESS => return,
1044 .INVALID_ADDRESS => return error.AccessDenied,
1045 else => |st| return windows.unexpectedStatus(st),
1046 }
1047 } else if (posix.PROT != void) {
1048 const flags: posix.PROT = .{
1049 .READ = options.read,
1050 .WRITE = options.write,
1051 .EXEC = options.execute,
1052 };
1053 switch (posix.errno(posix.system.mprotect(memory.ptr, memory.len, flags))) {
1054 .SUCCESS => return,
1055 .INVAL => |err| return std.Io.Threaded.errnoBug(err),
1056 .ACCES => return error.AccessDenied,
1057 .NOMEM => return error.OutOfMemory,
1058 else => |err| return posix.unexpectedErrno(err),
1059 }
1060 }
1061 return error.UnsupportedOperation;
1062}
1063
1064test lockMemory {
1065 var page: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined;
1066 lockMemory(&page, .{}) catch return error.SkipZigTest;
1067 unlockMemory(&page) catch return error.SkipZigTest;
1068}
1069
1070test lockMemoryAll {
1071 lockMemoryAll(.{ .current = true }) catch return error.SkipZigTest;
1072 unlockMemoryAll() catch return error.SkipZigTest;
1073}
1074
1075test protectMemory {
1076 if (builtin.cpu.arch == .hexagon) return error.SkipZigTest; // TODO
1077 var page: [std.heap.page_size_min]u8 align(std.heap.page_size_min) = undefined;
1078 protectMemory(&page, .{}) catch return error.SkipZigTest;
1079 protectMemory(&page, .{ .read = true, .write = true }) catch return error.SkipZigTest;
1080}
src/link/MachO.zig+10-10
......@@ -1758,10 +1758,10 @@ fn initSyntheticSections(self: *MachO) !void {
17581758}
17591759
17601760fn getSegmentProt(segname: []const u8) macho.vm_prot_t {
1761 if (mem.eql(u8, segname, "__PAGEZERO")) return macho.PROT.NONE;
1762 if (mem.eql(u8, segname, "__TEXT")) return macho.PROT.READ | macho.PROT.EXEC;
1763 if (mem.eql(u8, segname, "__LINKEDIT")) return macho.PROT.READ;
1764 return macho.PROT.READ | macho.PROT.WRITE;
1761 if (mem.eql(u8, segname, "__PAGEZERO")) return .{};
1762 if (mem.eql(u8, segname, "__TEXT")) return .{ .READ = true, .EXEC = true };
1763 if (mem.eql(u8, segname, "__LINKEDIT")) return .{ .READ = true };
1764 return .{ .READ = true, .WRITE = true };
17651765}
17661766
17671767fn getSegmentRank(segname: []const u8) u8 {
......@@ -3348,7 +3348,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
33483348 .filesize = filesize,
33493349 .vmaddr = base_vmaddr + 0x4000000,
33503350 .vmsize = filesize,
3351 .prot = macho.PROT.READ | macho.PROT.EXEC,
3351 .prot = .{ .READ = true, .EXEC = true },
33523352 });
33533353 }
33543354
......@@ -3360,7 +3360,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
33603360 .filesize = filesize,
33613361 .vmaddr = base_vmaddr + 0xc000000,
33623362 .vmsize = filesize,
3363 .prot = macho.PROT.READ | macho.PROT.WRITE,
3363 .prot = .{ .READ = true, .WRITE = true },
33643364 });
33653365 }
33663366
......@@ -3372,7 +3372,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
33723372 .filesize = filesize,
33733373 .vmaddr = base_vmaddr + 0x10000000,
33743374 .vmsize = filesize,
3375 .prot = macho.PROT.READ | macho.PROT.WRITE,
3375 .prot = .{ .READ = true, .WRITE = true },
33763376 });
33773377 }
33783378
......@@ -3381,7 +3381,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
33813381 self.zig_bss_seg_index = try self.addSegment("__BSS_ZIG", .{
33823382 .vmaddr = base_vmaddr + 0x14000000,
33833383 .vmsize = memsize,
3384 .prot = macho.PROT.READ | macho.PROT.WRITE,
3384 .prot = .{ .READ = true, .WRITE = true },
33853385 });
33863386 }
33873387
......@@ -3711,7 +3711,7 @@ pub fn addSegment(self: *MachO, name: []const u8, opts: struct {
37113711 vmsize: u64 = 0,
37123712 fileoff: u64 = 0,
37133713 filesize: u64 = 0,
3714 prot: macho.vm_prot_t = macho.PROT.NONE,
3714 prot: macho.vm_prot_t = .{},
37153715}) error{OutOfMemory}!u8 {
37163716 const gpa = self.base.comp.gpa;
37173717 const index = @as(u8, @intCast(self.segments.items.len));
......@@ -4903,7 +4903,7 @@ pub const MachTask = extern struct {
49034903 try task.setCurrProtection(
49044904 address,
49054905 buf.len,
4906 std.c.PROT.READ | std.c.PROT.WRITE | std.c.PROT.COPY,
4906 .{ .READ = true, .WRITE = true, .COPY = true },
49074907 );
49084908 defer {
49094909 task.setCurrProtection(address, buf.len, curr_prot) catch {};
src/link/MachO/DebugSymbols.zig+2-2
......@@ -94,8 +94,8 @@ pub fn initMetadata(self: *DebugSymbols, macho_file: *MachO) !void {
9494 self.linkedit_segment_cmd_index = @intCast(self.segments.items.len);
9595 try self.segments.append(self.allocator, .{
9696 .segname = makeStaticString("__LINKEDIT"),
97 .maxprot = macho.PROT.READ,
98 .initprot = macho.PROT.READ,
97 .maxprot = .{ .READ = true },
98 .initprot = .{ .READ = true },
9999 .cmdsize = @sizeOf(macho.segment_command_64),
100100 });
101101}
src/link/MachO/relocatable.zig+1-1
......@@ -539,7 +539,7 @@ fn createSegment(macho_file: *MachO) !void {
539539 const gpa = macho_file.base.comp.gpa;
540540
541541 // For relocatable, we only ever need a single segment so create it now.
542 const prot: macho.vm_prot_t = macho.PROT.READ | macho.PROT.WRITE | macho.PROT.EXEC;
542 const prot: macho.vm_prot_t = .{ .READ = true, .WRITE = true, .EXEC = true };
543543 try macho_file.segments.append(gpa, .{
544544 .cmdsize = @sizeOf(macho.segment_command_64),
545545 .segname = MachO.makeStaticString(""),
src/link/MappedFile.zig+1-1
......@@ -1049,7 +1049,7 @@ pub fn ensureTotalCapacityPrecise(mf: *MappedFile, new_capacity: usize) !void {
10491049 } else mf.contents = try std.posix.mmap(
10501050 null,
10511051 aligned_capacity,
1052 std.posix.PROT.READ | std.posix.PROT.WRITE,
1052 .{ .READ = true, .WRITE = true },
10531053 .{ .TYPE = if (is_linux) .SHARED_VALIDATE else .SHARED },
10541054 mf.file.handle,
10551055 0,