lib/stabilizer/src/properties/stack.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 "stack pad draw stays within byte aligned range" {
 48     var runtime = try Runtime.init(std.testing.allocator, .{ .seed = 1 });
 49     defer runtime.deinit();
 50 
 51     var index: usize = 0;
 52     while (index < 1024) : (index += 1) {
 53         const pad = runtime.nextStackPad();
 54         try std.testing.expect(pad.unit <= max_stack_pad_unit);
 55         try std.testing.expectEqual(@as(usize, pad.unit) * stack_alignment, pad.bytes);
 56     }
 57 }
 58 
 59 test "stack config preserves upstream sixteen byte multiplier" {
 60     var rng = Marsaglia.init(10);
 61     var pads = try StackPads.init(std.testing.allocator, .{
 62         .entries = 4,
 63         .alignment = 3,
 64     }, &rng);
 65     defer pads.deinit();
 66 
 67     var index: usize = 0;
 68     while (index < 16) : (index += 1) {
 69         const pad = pads.next(&rng);
 70         try std.testing.expectEqual(@as(usize, pad.unit) * stack_alignment, pad.bytes);
 71     }
 72 }
 73 
 74 test "code randomizer refreshes function stack pad on relocation" {
 75     var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
 76     defer code.deinit();
 77 
 78     const function = try code.registerFunction(.{
 79         .code_size = 64,
 80         .stack_pad_unit = 0,
 81     });
 82     try std.testing.expectEqual(@as(u8, 0), code.stackPadUnit(function).?);
 83 
 84     _ = try code.relocate(function);
 85     const first = code.stackPad(function).?;
 86     try std.testing.expectEqual(@as(usize, first.unit) * stack_alignment, first.bytes);
 87 
 88     _ = try code.relocate(function);
 89     const second = code.stackPad(function).?;
 90     try std.testing.expectEqual(@as(usize, second.unit) * stack_alignment, second.bytes);
 91 }
 92 
 93 test "runtime refills stack pads only before code functions register" {
 94     var expected_rng = Marsaglia.init(9);
 95     _ = expected_rng.nextByte();
 96     const expected_refill = expected_rng.nextByte();
 97 
 98     var runtime = try Runtime.init(std.testing.allocator, .{
 99         .seed = 9,
100         .stack = .{ .entries = 1 },
101         .code = .{ .enabled = true },
102     });
103     defer runtime.deinit();
104 
105     runtime.stack.entries[0] = 0;
106     runtime.rerandomize();
107     try std.testing.expectEqual(expected_refill, runtime.stack.entries[0]);
108     try std.testing.expect(runtime.code.rerandomizing);
109 
110     runtime.stack.entries[0] = 0xaa;
111     const function = try runtime.code.registerFunction(.{
112         .code_size = 64,
113         .stack_pad_unit = 0,
114     });
115     _ = try runtime.enterFunction(function, &.{});
116     runtime.rerandomize();
117     try std.testing.expectEqual(@as(u8, 0xaa), runtime.stack.entries[0]);
118     try std.testing.expect(runtime.code.rerandomizing);
119     try std.testing.expectEqual(FunctionEntryState.trap, runtime.code.entryState(function).?);
120 }
121 
122 test "code randomizer patches stack pad table slots on first image relocation" {
123     var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
124     defer code.deinit();
125 
126     var table = @as([(@sizeOf(usize) * 2)]u8, @splat(0));
127     const function = try code.registerFunctionImage(.{
128         .code = &.{ 0xaa, 0xbb, 0xcc },
129         .table = &table,
130         .table_adjacent = true,
131         .stack_pad_unit = 0,
132         .stack_pad_table_offsets = &.{@sizeOf(usize)},
133     });
134     try std.testing.expect(code.stackPadAddress(function) == null);
135 
136     const first = try code.relocate(function);
137     const address = code.stackPadAddress(function).?;
138     try std.testing.expectEqual(
139         address,
140         std.mem.readInt(usize, first.contents[3 + @sizeOf(usize) ..][0..@sizeOf(usize)], .little),
141     );
142 
143     const second = try code.relocate(function);
144     try std.testing.expectEqual(address, code.stackPadAddress(function).?);
145     try std.testing.expectEqual(
146         address,
147         std.mem.readInt(usize, second.contents[3 + @sizeOf(usize) ..][0..@sizeOf(usize)], .little),
148     );
149 }
150 
151 test "code randomizer rejects invalid stack pad table slots" {
152     var code = CodeRandomizer.init(std.testing.allocator, .{ .shuffle_slots = 4 }, 55);
153     defer code.deinit();
154 
155     var table = @as([@sizeOf(usize)]u8, @splat(0));
156     try std.testing.expectError(
157         error.InvalidStackPadTableOffset,
158         code.registerFunctionImage(.{
159             .code = &.{0xaa},
160             .table = &table,
161             .stack_pad_unit = 0,
162             .stack_pad_table_offsets = &.{1},
163         }),
164     );
165     try std.testing.expectError(
166         error.InvalidStackPadTableOffset,
167         code.registerFunctionImage(.{
168             .code = &.{0xaa},
169             .table = table[0 .. table.len - 1],
170             .stack_pad_unit = 0,
171             .stack_pad_table_offsets = &.{0},
172         }),
173     );
174 }
175 
176 const StackProperty = struct {
177     pub fn property(data: *hypothesis.ConjectureData, allocator: Allocator) !void {
178         const entries = try drawUsize(data, 1, 32, 8);
179         const configured_alignment = try drawUsize(data, 1, 64, stack_alignment);
180         var rng = Marsaglia.init(try data.drawInteger(1, std.math.maxInt(u32), 1));
181         var pads = try StackPads.init(allocator, .{
182             .entries = entries,
183             .alignment = configured_alignment,
184         }, &rng);
185         defer pads.deinit();
186 
187         var index: usize = 0;
188         while (index < entries * 4) : (index += 1) {
189             const pad = pads.next(&rng);
190             try std.testing.expectEqual(@as(usize, pad.unit) * stack_alignment, pad.bytes);
191         }
192     }
193 };
194 
195 test "pbt: stack pads multiply byte draws by alignment" {
196     try hypothesis.checkNamed(StackProperty, "stabilizer-stack", settings());
197 }