lib/stabilizer/src/properties/code.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const hypothesis = @import("hypothesis");
3 const stabilizer = @import("stabilizer");
4
5 const Allocator = std.mem.Allocator;
6 const Alignment = std.mem.Alignment;
7
8 const default_shuffle_slots = stabilizer.default_shuffle_slots;
9 const default_region_size = stabilizer.default_region_size;
10 const code_alignment = stabilizer.code_alignment;
11 const stack_alignment = stabilizer.stack_alignment;
12 const rerandomize_interval_ms = stabilizer.rerandomize_interval_ms;
13 const max_stack_pad_unit = stabilizer.max_stack_pad_unit;
14
15 const Reference = stabilizer.Reference;
16 const Marsaglia = stabilizer.Marsaglia;
17 const StackPads = stabilizer.StackPads;
18 const ShuffleAllocator = stabilizer.ShuffleAllocator;
19 const sizeClass = stabilizer.sizeClass;
20 const FunctionId = stabilizer.FunctionId;
21 const LocationId = stabilizer.LocationId;
22 const FunctionOptions = stabilizer.FunctionOptions;
23 const FunctionEntryState = stabilizer.FunctionEntryState;
24 const FunctionLocation = stabilizer.FunctionLocation;
25 const CodeRandomizer = stabilizer.CodeRandomizer;
26 const Runtime = stabilizer.Runtime;
27
28 fn settings() hypothesis.Settings {
29 return hypothesis.Settings.quick()
30 .withSeed(0x57ab_11e5)
31 .withDatabase("zig-out/hypothesis-failures/stabilizer");
32 }
33
34 fn drawUsize(data: *hypothesis.ConjectureData, min: usize, max: usize, shrink_towards: usize) !usize {
35 return @intCast(try data.drawInteger(
36 @intCast(min),
37 @intCast(max),
38 @intCast(shrink_towards),
39 ));
40 }
41
42 fn drawAlignment(data: *hypothesis.ConjectureData) !Alignment {
43 const shift = try drawUsize(data, 0, 8, 0);
44 return .fromByteUnits(@as(usize, 1) << @as(u6, @intCast(shift)));
45 }
46
47 test "code randomizer preserves live defunct locations across marked sweep" {
48 var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
49 defer code.deinit();
50
51 const function = try code.registerFunction(.{ .code_size = 64, .table_size = 16, .table_adjacent = true });
52 const first = try code.relocate(function);
53 const root = first.base + 4;
54 _ = try code.relocate(function);
55 code.beginRerandomization();
56 _ = try code.trap(function, &.{root});
57
58 try std.testing.expect(code.locationCount() >= 2);
59 var saw_marked_survivor = false;
60 for (code.locations.items) |location| {
61 if (location.base == first.base) saw_marked_survivor = true;
62 }
63 try std.testing.expect(saw_marked_survivor);
64 }
65
66 test "code randomizer adjusts relocated addresses back to original code" {
67 var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
68 defer code.deinit();
69
70 const function = try code.registerFunction(.{ .code_size = 64, .table_size = 16, .table_adjacent = true });
71 const original = code.originalBase(function).?;
72 const first = try code.relocate(function);
73 const first_base = first.base;
74 const first_size = first.size;
75 try std.testing.expectEqual(original + 5, code.adjustAddress(first_base + 5));
76 try std.testing.expectEqual(original + 70, code.adjustAddress(first_base + 70));
77
78 const unrelated = first_base + first_size + 1024;
79 try std.testing.expectEqual(unrelated, code.adjustAddress(unrelated));
80
81 const second = try code.relocate(function);
82 const second_base = second.base;
83 try std.testing.expectEqual(original + 9, code.adjustAddress(first_base + 9));
84 try std.testing.expectEqual(original + 9, code.adjustAddress(second_base + 9));
85
86 code.sweep();
87 try std.testing.expectEqual(first_base + 9, code.adjustAddress(first_base + 9));
88 try std.testing.expectEqual(original + 9, code.adjustAddress(second_base + 9));
89 }
90
91 test "code randomizer forwards timer interrupts at live function entries" {
92 var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
93 defer code.deinit();
94
95 const active = try code.registerFunction(.{ .code_size = 64 });
96 const inactive = try code.registerFunction(.{ .code_size = 64 });
97 const active_location = try code.relocate(active);
98 const active_base = active_location.base;
99 const active_original = code.originalBase(active).?;
100 const inactive_original = code.originalBase(inactive).?;
101
102 try std.testing.expectEqual(active_base, code.beginRerandomizationAt(active_original));
103 try std.testing.expectEqual(FunctionEntryState.trap, code.entryState(active).?);
104 try std.testing.expectEqual(FunctionEntryState.trap, code.entryState(inactive).?);
105 try std.testing.expectEqual(@as(usize, 0), code.live.items.len);
106 try std.testing.expect(code.rerandomizing);
107
108 const offset_entry = active_original + 1;
109 try std.testing.expectEqual(offset_entry, code.beginRerandomizationAt(offset_entry));
110 try std.testing.expectEqual(inactive_original, code.beginRerandomizationAt(inactive_original));
111 }
112
113 test "code randomizer models trap and forwarding entry states" {
114 var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
115 defer code.deinit();
116
117 const function = try code.registerFunction(.{ .code_size = 64 });
118 try std.testing.expectEqual(FunctionEntryState.trap, code.entryState(function).?);
119
120 const first = (try code.enterFunction(function, &.{})).?;
121 try std.testing.expectEqual(FunctionEntryState.forwarding, code.entryState(function).?);
122 try std.testing.expectEqual(@as(usize, 1), code.locationCount());
123
124 const forwarded = (try code.enterFunction(function, &.{})).?;
125 try std.testing.expectEqual(first.id, forwarded.id);
126 try std.testing.expectEqual(@as(usize, 1), code.locationCount());
127
128 code.beginRerandomization();
129 try std.testing.expectEqual(FunctionEntryState.trap, code.entryState(function).?);
130 const second = (try code.enterFunction(function, &.{})).?;
131 try std.testing.expect(second.id != first.id);
132 try std.testing.expectEqual(FunctionEntryState.forwarding, code.entryState(function).?);
133 try std.testing.expectEqual(@as(usize, 2), code.locationCount());
134 }
135
136 test "disabled code randomizer is inert" {
137 var code = CodeRandomizer.init(std.testing.allocator, .{ .enabled = false }, 55);
138 defer code.deinit();
139
140 try std.testing.expectError(
141 error.CodeRandomizationDisabled,
142 code.registerFunction(.{ .code_size = 64 }),
143 );
144 try std.testing.expectError(
145 error.CodeRandomizationDisabled,
146 code.registerFunctionImage(.{ .code = &.{0xaa} }),
147 );
148 code.beginRerandomization();
149 try std.testing.expect(!code.rerandomizing);
150 try std.testing.expectEqual(@as(usize, 0), code.locationCount());
151 try std.testing.expectError(error.CodeRandomizationDisabled, code.relocate(1));
152 try std.testing.expectError(error.CodeRandomizationDisabled, code.trap(1, &.{}));
153 code.markAddress(0);
154 code.sweep();
155 try std.testing.expectEqual(@as(usize, 0), code.locationCount());
156 }
157
158 const CodeProperty = struct {
159 pub fn property(data: *hypothesis.ConjectureData, allocator: Allocator) !void {
160 var code = CodeRandomizer.init(allocator, .{
161 .shuffle_slots = 16,
162 .alignment = code_alignment,
163 }, 0x434f_4445);
164 defer code.deinit();
165
166 const functions = try drawUsize(data, 1, 8, 2);
167 var ids: [8]FunctionId = undefined;
168 var index: usize = 0;
169 while (index < functions) : (index += 1) {
170 ids[index] = try code.registerFunction(.{
171 .code_size = try drawUsize(data, 1, 512, 64),
172 .table_size = try drawUsize(data, 0, 128, 0),
173 .table_adjacent = try data.drawBoolean(),
174 });
175 }
176
177 const steps = try drawUsize(data, 1, 64, 8);
178 index = 0;
179 while (index < steps) : (index += 1) {
180 const id = ids[try drawUsize(data, 0, functions - 1, 0)];
181 if (try data.drawBoolean()) code.beginRerandomization();
182 const current = code.currentLocation(id);
183 var roots_buf: [1]usize = undefined;
184 const roots: []const usize = if (current) |location| blk: {
185 roots_buf[0] = location.base;
186 break :blk roots_buf[0..1];
187 } else &.{};
188 const location = try code.trap(id, roots);
189 try std.testing.expect(std.mem.isAligned(location.base, code_alignment));
190 try std.testing.expect(location.size == code.allocationSize(id).?);
191 try std.testing.expect(code.currentLocation(id).?.id == location.id);
192 }
193 }
194 };
195
196 test "pbt: code relocation model keeps current locations coherent" {
197 try hypothesis.checkNamed(CodeProperty, "stabilizer-code", settings());
198 }
199
200 const FunctionRangeProperty = struct {
201 pub fn property(data: *hypothesis.ConjectureData, allocator: Allocator) !void {
202 var code = CodeRandomizer.init(allocator, .{
203 .shuffle_slots = 16,
204 .alignment = code_alignment,
205 }, 0x5241_4e47);
206 defer code.deinit();
207
208 const code_len = try drawUsize(data, 1, 64, 4);
209 const image_code = try allocator.alloc(u8, code_len);
210 defer allocator.free(image_code);
211 for (image_code, 0..) |*byte, index| byte.* = @truncate(index);
212
213 const table_words = try drawUsize(data, 0, 8, 2);
214 const table_len = table_words * @sizeOf(usize);
215 const table = try allocator.alloc(u8, table_len);
216 defer allocator.free(table);
217
218 var stack_pad: u8 = @truncate(try drawUsize(data, 0, 255, 0));
219 const stack_pad_address = @intFromPtr(&stack_pad);
220 var expected_offsets: [8]usize = undefined;
221 var expected_values: [8]usize = undefined;
222 var expected_count: usize = 0;
223 var word: usize = 0;
224 while (word < table_words) : (word += 1) {
225 const offset = word * @sizeOf(usize);
226 const use_stack_pad = try data.drawBoolean();
227 const value = if (use_stack_pad) stack_pad_address else 0x1000 + word * 17;
228 std.mem.writeInt(usize, table[offset..][0..@sizeOf(usize)], value, .little);
229 expected_values[word] = value;
230 if (use_stack_pad) {
231 expected_offsets[expected_count] = offset;
232 expected_count += 1;
233 }
234 }
235
236 const adjacent = try data.drawBoolean();
237 const function = try code.registerFunctionRange(.{
238 .code_base = image_code.ptr,
239 .code_limit = image_code.ptr + image_code.len,
240 .table_base = if (table.len == 0) null else table.ptr,
241 .table_size = table.len,
242 .table_adjacent = adjacent,
243 .stack_pad = &stack_pad,
244 });
245 try std.testing.expectEqual(@as(usize, 1), code.functionCount());
246 try std.testing.expectEqual(stack_pad, code.stackPadUnit(function).?);
247
248 const location = try code.relocate(function);
249 const expected_len = if (adjacent) image_code.len + table.len else image_code.len;
250 try std.testing.expectEqual(expected_len, location.contents.len);
251 try std.testing.expectEqualSlices(u8, image_code, location.contents[0..image_code.len]);
252
253 if (expected_count > 0) {
254 const relocated_stack_pad_address = code.stackPadAddress(function).?;
255 var index: usize = 0;
256 while (index < expected_count) : (index += 1) {
257 const offset = expected_offsets[index];
258 if (adjacent) {
259 try std.testing.expectEqual(relocated_stack_pad_address, std.mem.readInt(
260 usize,
261 location.contents[image_code.len + offset ..][0..@sizeOf(usize)],
262 .little,
263 ));
264 }
265 }
266 } else {
267 try std.testing.expect(code.stackPadAddress(function) == null);
268 }
269
270 if (adjacent) {
271 word = 0;
272 while (word < table_words) : (word += 1) {
273 const offset = word * @sizeOf(usize);
274 const value = std.mem.readInt(
275 usize,
276 location.contents[image_code.len + offset ..][0..@sizeOf(usize)],
277 .little,
278 );
279 if (expected_values[word] != stack_pad_address) {
280 try std.testing.expectEqual(expected_values[word], value);
281 }
282 }
283 }
284 }
285 };
286
287 test "pbt: function range registration patches stack pad table entries" {
288 try hypothesis.checkNamed(FunctionRangeProperty, "stabilizer-function-range", settings());
289 }