authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 20:56:25+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-03-30 21:08:32+02:00
logee0c4457657523e218c1e211c447d3e196575ddc
tree51c14da38f52ac7a0170485747d8b4f7bab37d95
parent349349fa01f77fe3bf2b57dc821f889e2e869004

coff: due to ASLR we need to dupe the code for relocating

In addition, we need to be careful not to mark the relocations as resolved prematurely as then we are risking malforming the binary as we need to resolve the relocs twice: once for in-memory writes, and once for in-file updates.

2 files changed, 48 insertions(+), 25 deletions(-)

src/link/Coff.zig+40-19
...@@ -781,24 +781,47 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {...@@ -781,24 +781,47 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
781 const sym = atom.getSymbol(self);781 const sym = atom.getSymbol(self);
782 const section = self.sections.get(@enumToInt(sym.section_number) - 1);782 const section = self.sections.get(@enumToInt(sym.section_number) - 1);
783 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;783 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
784
784 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{785 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{
785 atom.getName(self),786 atom.getName(self),
786 file_offset,787 file_offset,
787 file_offset + code.len,788 file_offset + code.len,
788 });789 });
789790
791 const gpa = self.base.allocator;
792
793 // Gather relocs which can be resolved.
794 // We need to do this as we will be applying different slide values depending
795 // if we are running in hot-code swapping mode or not.
796 // TODO: how crazy would it be to try and apply the actual image base of the loaded
797 // process for the in-file values rather than the Windows defaults?
798 var relocs = std.ArrayList(*Relocation).init(gpa);
799 defer relocs.deinit();
800
801 if (self.relocs.getPtr(atom_index)) |rels| {
802 try relocs.ensureTotalCapacityPrecise(rels.items.len);
803 for (rels.items) |*reloc| {
804 if (reloc.isResolvable(self)) relocs.appendAssumeCapacity(reloc);
805 }
806 }
807
790 if (self.base.child_pid) |handle| {808 if (self.base.child_pid) |handle| {
791 const slide = @ptrToInt(self.hot_state.loaded_base_address.?);809 const slide = @ptrToInt(self.hot_state.loaded_base_address.?);
792810
793 const mem_code = try self.base.allocator.dupe(u8, code);811 const mem_code = try gpa.dupe(u8, code);
794 defer self.base.allocator.free(mem_code);812 defer gpa.free(mem_code);
795 self.resolveRelocs(atom_index, mem_code, slide);813 self.resolveRelocs(atom_index, relocs.items, mem_code, slide);
796814
797 const vaddr = sym.value + slide;815 const vaddr = sym.value + slide;
798 const pvaddr = @intToPtr(*anyopaque, vaddr);816 const pvaddr = @intToPtr(*anyopaque, vaddr);
817
799 log.debug("writing to memory at address {x}", .{vaddr});818 log.debug("writing to memory at address {x}", .{vaddr});
819
820 if (build_options.enable_logging) {
821 try debugMem(gpa, handle, pvaddr, mem_code);
822 }
823
800 if (section.header.flags.MEM_WRITE == 0) {824 if (section.header.flags.MEM_WRITE == 0) {
801 log.debug("page not mapped for write access; re-mapping...", .{});
802 writeMemProtected(handle, pvaddr, mem_code) catch |err| {825 writeMemProtected(handle, pvaddr, mem_code) catch |err| {
803 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});826 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
804 };827 };
...@@ -809,25 +832,29 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {...@@ -809,25 +832,29 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
809 }832 }
810 }833 }
811834
812 self.resolveRelocs(atom_index, code, self.getImageBase());835 self.resolveRelocs(atom_index, relocs.items, code, self.getImageBase());
813 try self.base.file.?.pwriteAll(code, file_offset);836 try self.base.file.?.pwriteAll(code, file_offset);
837
838 // Now we can mark the relocs as resolved.
839 while (relocs.popOrNull()) |reloc| {
840 reloc.dirty = false;
841 }
814}842}
815843
816fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {844fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
817 var buffer = try allocator.alloc(u8, code.len);845 var buffer = try allocator.alloc(u8, code.len);
818 defer allocator.free(buffer);846 defer allocator.free(buffer);
819 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);847 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);
820 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
821 log.debug("to write: {x}", .{std.fmt.fmtSliceHexLower(code)});848 log.debug("to write: {x}", .{std.fmt.fmtSliceHexLower(code)});
849 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
822}850}
823851
824fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {852fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
825 var old_prot: std.os.windows.DWORD = undefined;853 const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);
826 try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY, &old_prot);
827 try writeMem(handle, pvaddr, code);854 try writeMem(handle, pvaddr, code);
828 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.855 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
829 // For that though, we want to track which page has already been modified.856 // For that though, we want to track which page has already been modified.
830 try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot, null);857 _ = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, old_prot);
831}858}
832859
833fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {860fn writeMem(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
...@@ -868,16 +895,10 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {...@@ -868,16 +895,10 @@ fn markRelocsDirtyByAddress(self: *Coff, addr: u32) void {
868 }895 }
869}896}
870897
871fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8, image_base: u64) void {898fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void {
872 const relocs = self.relocs.getPtr(atom_index) orelse return;
873
874 log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});899 log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});
875900 for (relocs) |reloc| {
876 for (relocs.items) |*reloc| {901 reloc.resolve(atom_index, code, image_base, self);
877 if (!reloc.dirty) continue;
878 if (reloc.resolve(atom_index, code, image_base, self)) {
879 reloc.dirty = false;
880 }
881 }902 }
882}903}
883904
...@@ -1488,7 +1509,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod...@@ -1488,7 +1509,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
14881509
1489 for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {1510 for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {
1490 const needs_update = for (relocs.items) |reloc| {1511 const needs_update = for (relocs.items) |reloc| {
1491 if (reloc.dirty) break true;1512 if (reloc.isResolvable(self)) break true;
1492 } else false;1513 } else false;
14931514
1494 if (!needs_update) continue;1515 if (!needs_update) continue;
src/link/Coff/Relocation.zig+8-6
...@@ -72,14 +72,18 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 {...@@ -72,14 +72,18 @@ pub fn getTargetAddress(self: Relocation, coff_file: *const Coff) ?u32 {
72 }72 }
73}73}
7474
75/// Returns `false` if obtaining the target address has been deferred until `flushModule`.75/// Returns true if and only if the reloc is dirty AND the target address is available.
76/// This can happen when trying to resolve address of an import table entry ahead of time.76pub fn isResolvable(self: Relocation, coff_file: *Coff) bool {
77pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) bool {77 _ = self.getTargetAddress(coff_file) orelse return false;
78 return self.dirty;
79}
80
81pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) void {
78 const atom = coff_file.getAtom(atom_index);82 const atom = coff_file.getAtom(atom_index);
79 const source_sym = atom.getSymbol(coff_file);83 const source_sym = atom.getSymbol(coff_file);
80 const source_vaddr = source_sym.value + self.offset;84 const source_vaddr = source_sym.value + self.offset;
8185
82 const target_vaddr = self.getTargetAddress(coff_file) orelse return false;86 const target_vaddr = self.getTargetAddress(coff_file).?; // Oops, you didn't check if the relocation can be resolved with isResolvable().
83 const target_vaddr_with_addend = target_vaddr + self.addend;87 const target_vaddr_with_addend = target_vaddr + self.addend;
8488
85 log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{89 log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{
...@@ -102,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base:...@@ -102,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base:
102 .x86, .x86_64 => self.resolveX86(ctx),106 .x86, .x86_64 => self.resolveX86(ctx),
103 else => unreachable, // unhandled target architecture107 else => unreachable, // unhandled target architecture
104 }108 }
105
106 return true;
107}109}
108110
109const Context = struct {111const Context = struct {