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 {
781781 const sym = atom.getSymbol(self);
782782 const section = self.sections.get(@enumToInt(sym.section_number) - 1);
783783 const file_offset = section.header.pointer_to_raw_data + sym.value - section.header.virtual_address;
784
784785 log.debug("writing atom for symbol {s} at file offset 0x{x} to 0x{x}", .{
785786 atom.getName(self),
786787 file_offset,
787788 file_offset + code.len,
788789 });
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
790808 if (self.base.child_pid) |handle| {
791809 const slide = @ptrToInt(self.hot_state.loaded_base_address.?);
792810
793 const mem_code = try self.base.allocator.dupe(u8, code);
794 defer self.base.allocator.free(mem_code);
795 self.resolveRelocs(atom_index, mem_code, slide);
811 const mem_code = try gpa.dupe(u8, code);
812 defer gpa.free(mem_code);
813 self.resolveRelocs(atom_index, relocs.items, mem_code, slide);
796814
797815 const vaddr = sym.value + slide;
798816 const pvaddr = @intToPtr(*anyopaque, vaddr);
817
799818 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
800824 if (section.header.flags.MEM_WRITE == 0) {
801 log.debug("page not mapped for write access; re-mapping...", .{});
802825 writeMemProtected(handle, pvaddr, mem_code) catch |err| {
803826 log.warn("writing to protected memory failed with error: {s}", .{@errorName(err)});
804827 };
......@@ -809,25 +832,29 @@ fn writeAtom(self: *Coff, atom_index: Atom.Index, code: []u8) !void {
809832 }
810833 }
811834
812 self.resolveRelocs(atom_index, code, self.getImageBase());
835 self.resolveRelocs(atom_index, relocs.items, code, self.getImageBase());
813836 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 }
814842}
815843
816844fn debugMem(allocator: Allocator, handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
817845 var buffer = try allocator.alloc(u8, code.len);
818846 defer allocator.free(buffer);
819847 const memread = try std.os.windows.ReadProcessMemory(handle, pvaddr, buffer);
820 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
821848 log.debug("to write: {x}", .{std.fmt.fmtSliceHexLower(code)});
849 log.debug("in memory: {x}", .{std.fmt.fmtSliceHexLower(memread)});
822850}
823851
824852fn writeMemProtected(handle: std.ChildProcess.Id, pvaddr: std.os.windows.LPVOID, code: []const u8) !void {
825 var old_prot: std.os.windows.DWORD = undefined;
826 try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY, &old_prot);
853 const old_prot = try std.os.windows.VirtualProtectEx(handle, pvaddr, code.len, std.os.windows.PAGE_EXECUTE_WRITECOPY);
827854 try writeMem(handle, pvaddr, code);
828855 // TODO: We can probably just set the pages writeable and leave it at that without having to restore the attributes.
829856 // 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);
831858}
832859
833860fn 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 {
868895 }
869896}
870897
871fn resolveRelocs(self: *Coff, atom_index: Atom.Index, code: []u8, image_base: u64) void {
872 const relocs = self.relocs.getPtr(atom_index) orelse return;
873
898fn resolveRelocs(self: *Coff, atom_index: Atom.Index, relocs: []*const Relocation, code: []u8, image_base: u64) void {
874899 log.debug("relocating '{s}'", .{self.getAtom(atom_index).getName(self)});
875
876 for (relocs.items) |*reloc| {
877 if (!reloc.dirty) continue;
878 if (reloc.resolve(atom_index, code, image_base, self)) {
879 reloc.dirty = false;
880 }
900 for (relocs) |reloc| {
901 reloc.resolve(atom_index, code, image_base, self);
881902 }
882903}
883904
......@@ -1488,7 +1509,7 @@ pub fn flushModule(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
14881509
14891510 for (self.relocs.keys(), self.relocs.values()) |atom_index, relocs| {
14901511 const needs_update = for (relocs.items) |reloc| {
1491 if (reloc.dirty) break true;
1512 if (reloc.isResolvable(self)) break true;
14921513 } else false;
14931514
14941515 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 {
7272 }
7373}
7474
75/// Returns `false` if obtaining the target address has been deferred until `flushModule`.
76/// This can happen when trying to resolve address of an import table entry ahead of time.
77pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base: u64, coff_file: *Coff) bool {
75/// Returns true if and only if the reloc is dirty AND the target address is available.
76pub fn isResolvable(self: Relocation, 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 {
7882 const atom = coff_file.getAtom(atom_index);
7983 const source_sym = atom.getSymbol(coff_file);
8084 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().
8387 const target_vaddr_with_addend = target_vaddr + self.addend;
8488
8589 log.debug(" ({x}: [() => 0x{x} ({s})) ({s}) ", .{
......@@ -102,8 +106,6 @@ pub fn resolve(self: Relocation, atom_index: Atom.Index, code: []u8, image_base:
102106 .x86, .x86_64 => self.resolveX86(ctx),
103107 else => unreachable, // unhandled target architecture
104108 }
105
106 return true;
107109}
108110
109111const Context = struct {