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 }