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 }