Skip to content

Commit affd8f8

Browse files
committed
macho: fix incorrect segment/section growth calculation
Otherwise, for last sections in segments it could happen we would not expand the segment when actually required thus exceeding the segment's size and causing data clobbering and dyld runtime errors.
1 parent a2dd0c3 commit affd8f8

File tree

7 files changed

+9065
-62
lines changed

7 files changed

+9065
-62
lines changed

src/link/MachO.zig

Lines changed: 73 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4085,6 +4085,70 @@ fn findFreeSpace(self: MachO, segment_id: u16, alignment: u64, start: ?u64) u64
40854085
return mem.alignForwardGeneric(u64, final_off, alignment);
40864086
}
40874087

4088+
fn growSegment(self: *MachO, seg_id: u16, new_size: u64) !void {
4089+
const seg = &self.load_commands.items[seg_id].Segment;
4090+
const new_seg_size = mem.alignForwardGeneric(u64, new_size, self.page_size);
4091+
assert(new_seg_size > seg.inner.filesize);
4092+
const offset_amt = new_seg_size - seg.inner.filesize;
4093+
log.debug("growing segment {s} from 0x{x} to 0x{x}", .{ seg.inner.segname, seg.inner.filesize, new_seg_size });
4094+
seg.inner.filesize = new_seg_size;
4095+
seg.inner.vmsize = new_seg_size;
4096+
4097+
log.debug(" (new segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4098+
seg.inner.fileoff,
4099+
seg.inner.fileoff + seg.inner.filesize,
4100+
seg.inner.vmaddr,
4101+
seg.inner.vmaddr + seg.inner.vmsize,
4102+
});
4103+
4104+
// TODO We should probably nop the expanded by distance, or put 0s.
4105+
4106+
// TODO copyRangeAll doesn't automatically extend the file on macOS.
4107+
const ledit_seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
4108+
const new_filesize = offset_amt + ledit_seg.inner.fileoff + ledit_seg.inner.filesize;
4109+
try self.base.file.?.pwriteAll(&[_]u8{0}, new_filesize - 1);
4110+
4111+
var next: usize = seg_id + 1;
4112+
while (next < self.linkedit_segment_cmd_index.? + 1) : (next += 1) {
4113+
const next_seg = &self.load_commands.items[next].Segment;
4114+
_ = try self.base.file.?.copyRangeAll(
4115+
next_seg.inner.fileoff,
4116+
self.base.file.?,
4117+
next_seg.inner.fileoff + offset_amt,
4118+
next_seg.inner.filesize,
4119+
);
4120+
next_seg.inner.fileoff += offset_amt;
4121+
next_seg.inner.vmaddr += offset_amt;
4122+
4123+
log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4124+
next_seg.inner.segname,
4125+
next_seg.inner.fileoff,
4126+
next_seg.inner.fileoff + next_seg.inner.filesize,
4127+
next_seg.inner.vmaddr,
4128+
next_seg.inner.vmaddr + next_seg.inner.vmsize,
4129+
});
4130+
4131+
for (next_seg.sections.items) |*moved_sect, moved_sect_id| {
4132+
moved_sect.offset += @intCast(u32, offset_amt);
4133+
moved_sect.addr += offset_amt;
4134+
4135+
log.debug(" (new {s},{s} file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4136+
commands.segmentName(moved_sect.*),
4137+
commands.sectionName(moved_sect.*),
4138+
moved_sect.offset,
4139+
moved_sect.offset + moved_sect.size,
4140+
moved_sect.addr,
4141+
moved_sect.addr + moved_sect.size,
4142+
});
4143+
4144+
try self.allocateLocalSymbols(.{
4145+
.seg = @intCast(u16, next),
4146+
.sect = @intCast(u16, moved_sect_id),
4147+
}, @intCast(i64, offset_amt));
4148+
}
4149+
}
4150+
}
4151+
40884152
fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void {
40894153
const tracy = trace(@src());
40904154
defer tracy.end();
@@ -4098,7 +4162,14 @@ fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void {
40984162
const needed_size = mem.alignForwardGeneric(u32, ideal_size, alignment);
40994163

41004164
if (needed_size > max_size) blk: {
4101-
log.debug(" (need to grow!)", .{});
4165+
log.debug(" (need to grow! needed 0x{x}, max 0x{x})", .{ needed_size, max_size });
4166+
4167+
if (match.sect == seg.sections.items.len - 1) {
4168+
// Last section, just grow segments
4169+
try self.growSegment(match.seg, seg.inner.filesize + needed_size - max_size);
4170+
break :blk;
4171+
}
4172+
41024173
// Need to move all sections below in file and address spaces.
41034174
const offset_amt = offset: {
41044175
const max_alignment = try self.getSectionMaxAlignment(match.seg, match.sect + 1);
@@ -4114,70 +4185,10 @@ fn growSection(self: *MachO, match: MatchingSection, new_size: u32) !void {
41144185

41154186
if (last_sect_off + offset_amt > seg_off) {
41164187
// Need to grow segment first.
4117-
log.debug(" (need to grow segment first)", .{});
41184188
const spill_size = (last_sect_off + offset_amt) - seg_off;
4119-
const seg_offset_amt = mem.alignForwardGeneric(u64, spill_size, self.page_size);
4120-
seg.inner.filesize += seg_offset_amt;
4121-
seg.inner.vmsize += seg_offset_amt;
4122-
4123-
log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4124-
seg.inner.segname,
4125-
seg.inner.fileoff,
4126-
seg.inner.fileoff + seg.inner.filesize,
4127-
seg.inner.vmaddr,
4128-
seg.inner.vmaddr + seg.inner.vmsize,
4129-
});
4130-
4131-
// TODO We should probably nop the expanded by distance, or put 0s.
4132-
4133-
// TODO copyRangeAll doesn't automatically extend the file on macOS.
4134-
const ledit_seg = &self.load_commands.items[self.linkedit_segment_cmd_index.?].Segment;
4135-
const new_filesize = seg_offset_amt + ledit_seg.inner.fileoff + ledit_seg.inner.filesize;
4136-
try self.base.file.?.pwriteAll(&[_]u8{0}, new_filesize - 1);
4137-
4138-
var next: usize = match.seg + 1;
4139-
while (next < self.linkedit_segment_cmd_index.? + 1) : (next += 1) {
4140-
const next_seg = &self.load_commands.items[next].Segment;
4141-
_ = try self.base.file.?.copyRangeAll(
4142-
next_seg.inner.fileoff,
4143-
self.base.file.?,
4144-
next_seg.inner.fileoff + seg_offset_amt,
4145-
next_seg.inner.filesize,
4146-
);
4147-
next_seg.inner.fileoff += seg_offset_amt;
4148-
next_seg.inner.vmaddr += seg_offset_amt;
4149-
4150-
log.debug(" (new {s} segment file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4151-
next_seg.inner.segname,
4152-
next_seg.inner.fileoff,
4153-
next_seg.inner.fileoff + next_seg.inner.filesize,
4154-
next_seg.inner.vmaddr,
4155-
next_seg.inner.vmaddr + next_seg.inner.vmsize,
4156-
});
4157-
4158-
for (next_seg.sections.items) |*moved_sect, moved_sect_id| {
4159-
moved_sect.offset += @intCast(u32, seg_offset_amt);
4160-
moved_sect.addr += seg_offset_amt;
4161-
4162-
log.debug(" (new {s},{s} file offsets from 0x{x} to 0x{x} (in memory 0x{x} to 0x{x}))", .{
4163-
commands.segmentName(moved_sect.*),
4164-
commands.sectionName(moved_sect.*),
4165-
moved_sect.offset,
4166-
moved_sect.offset + moved_sect.size,
4167-
moved_sect.addr,
4168-
moved_sect.addr + moved_sect.size,
4169-
});
4170-
4171-
try self.allocateLocalSymbols(.{
4172-
.seg = @intCast(u16, next),
4173-
.sect = @intCast(u16, moved_sect_id),
4174-
}, @intCast(i64, seg_offset_amt));
4175-
}
4176-
}
4189+
try self.growSegment(match.seg, seg.inner.filesize + spill_size);
41774190
}
41784191

4179-
if (match.sect + 1 >= seg.sections.items.len) break :blk;
4180-
41814192
// We have enough space to expand within the segment, so move all sections by
41824193
// the required amount and update their header offsets.
41834194
const next_sect = seg.sections.items[match.sect + 1];

test/standalone.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub fn addCases(cases: *tests.StandaloneContext) void {
2828
cases.addBuildFile("test/standalone/empty_env/build.zig", .{});
2929
cases.addBuildFile("test/standalone/issue_7030/build.zig", .{});
3030
cases.addBuildFile("test/standalone/install_raw_hex/build.zig", .{});
31+
cases.addBuildFile("test/standalone/issue_9812/build.zig", .{});
3132
if (std.Target.current.os.tag != .wasi) {
3233
cases.addBuildFile("test/standalone/load_dynamic_library/build.zig", .{});
3334
}

test/standalone/issue_9812/build.zig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.build.Builder) !void {
4+
const mode = b.standardReleaseOptions();
5+
const zip_add = b.addTest("main.zig");
6+
zip_add.setBuildMode(mode);
7+
zip_add.addCSourceFile("vendor/kuba-zip/zip.c", &[_][]const u8{
8+
"-std=c99",
9+
"-fno-sanitize=undefined",
10+
});
11+
zip_add.addIncludeDir("vendor/kuba-zip");
12+
zip_add.linkLibC();
13+
14+
const test_step = b.step("test", "Test it");
15+
test_step.dependOn(&zip_add.step);
16+
}

test/standalone/issue_9812/main.zig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const std = @import("std");
2+
const testing = std.testing;
3+
4+
const c = @cImport({
5+
@cInclude("zip.h");
6+
});
7+
8+
const Error = error{
9+
FailedToWriteEntry,
10+
FileNotFound,
11+
FailedToCreateEntry,
12+
Overflow,
13+
OutOfMemory,
14+
InvalidCmdLine,
15+
};
16+
17+
test "" {
18+
const allocator = std.heap.c_allocator;
19+
20+
const args = try std.process.argsAlloc(allocator);
21+
if (args.len != 4) {
22+
return;
23+
}
24+
25+
const zip_file = args[1];
26+
const src_file_name = args[2];
27+
const dst_file_name = args[3];
28+
29+
errdefer |e| switch (@as(Error, e)) {
30+
error.FailedToWriteEntry => std.log.err("could not find {s}", .{src_file_name}),
31+
error.FileNotFound => std.log.err("could not open {s}", .{zip_file}),
32+
error.FailedToCreateEntry => std.log.err("could not create {s}", .{dst_file_name}),
33+
else => {},
34+
};
35+
36+
const zip = c.zip_open(zip_file, c.ZIP_DEFAULT_COMPRESSION_LEVEL, 'a') orelse return error.FileNotFound;
37+
defer c.zip_close(zip);
38+
39+
if (c.zip_entry_open(zip, dst_file_name) < 0)
40+
return error.FailedToCreateEntry;
41+
defer _ = c.zip_entry_close(zip);
42+
43+
if (c.zip_entry_fwrite(zip, src_file_name) < 0)
44+
return error.FailedToWriteEntry;
45+
}

0 commit comments

Comments
 (0)