From ed1b5317369adc0ec046f709074bf8b27b074c41 Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Fri, 8 May 2026 14:00:00 +0100 Subject: [PATCH 1/3] deserialize: validate offsets for structs, slices, lists (#843) Malformed peer-supplied SSZ could slice past the buffer and panic in struct second pass, variable []T decode, variable arrays, and List sszDecode. - Mirror array-deserializer guards: alignment, prefix in bounds, start/end ordering. - Struct: bounds-check fixed-field reads and variable-field slices; require first var offset to match fixed-prefix length. - Union: require non-empty payload before reading selector. - utils.List: fixed-size path rejects ragged length and count > N; dynamic path bounds-checks offset prefix. Adds regression tests for struct offsets past the buffer. Closes https://github.com/blockblaz/zeam/issues/843 when consumed downstream. --- build.zig.zon | 1 + src/lib.zig | 59 ++++++++++++++++++++++++++++++++++++++++----------- src/tests.zig | 24 +++++++++++++++++++++ src/utils.zig | 9 ++++++-- 4 files changed, 79 insertions(+), 14 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index f335c95..f52a830 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,5 +2,6 @@ .name = .ssz, .fingerprint = 0x1d34bd0ceb1dfc2d, .version = "0.0.9", + .minimum_zig_version = "0.16.0", .paths = .{""}, } diff --git a/src/lib.zig b/src/lib.zig index 7fd12fb..eaf4b08 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -458,15 +458,21 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat } else { // first variable index is also the size of the list // of indices. Recast that list as a []const u32. - const size = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little) / @sizeOf(u32); - const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]); + if (serialized.len < 4) return error.OffsetExceedsSize; + const offset_prefix = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little); + if (offset_prefix % @sizeOf(u32) != 0) return error.OffsetOrdering; + const size = offset_prefix / @sizeOf(u32); + if (size > serialized.len / @sizeOf(u32)) return error.OffsetExceedsSize; + if (offset_prefix > serialized.len) return error.OffsetExceedsSize; + const indices = std.mem.bytesAsSlice(u32, serialized[0..offset_prefix]); var i = @as(usize, 0); while (i < size) : (i += 1) { const end = if (i < size - 1) indices[i + 1] else serialized.len; const start = indices[i]; - if (start >= serialized.len or end > serialized.len) { + if (start > serialized.len or end > serialized.len) { return error.OffsetExceedsSize; } + if (start > end) return error.OffsetOrdering; if (i > 0 and start < indices[i - 1]) { return error.OffsetOrdering; } @@ -515,12 +521,23 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat } else { // read the first index, determine when the "variable size" list ends, // and determine the size of the item as a result. - var offset: usize = 0; - var first_offset: usize = 0; - offset = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little); - first_offset = offset; - const n_items = offset / @sizeOf(u32); - var next_offset: usize = if (n_items == 1) serialized.len else std.mem.readInt(u32, serialized[4..8], std.builtin.Endian.little); + if (serialized.len < 4) return error.OffsetExceedsSize; + const first_offset_u32 = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little); + const first_offset = @as(usize, first_offset_u32); + if (first_offset > serialized.len) return error.OffsetExceedsSize; + if (first_offset % @sizeOf(u32) != 0) return error.OffsetOrdering; + const n_items = first_offset / @sizeOf(u32); + if (n_items == 0) return error.OffsetOrdering; + + var offset: usize = first_offset; + var next_offset: usize = if (n_items == 1) serialized.len else blk: { + if (serialized.len < 8) return error.OffsetExceedsSize; + const n = std.mem.readInt(u32, serialized[4..8], std.builtin.Endian.little); + break :blk @as(usize, n); + }; + if (next_offset > serialized.len) return error.OffsetExceedsSize; + if (offset > next_offset) return error.OffsetOrdering; + if (allocator) |alloc| { out.* = try alloc.alloc(ptr.child, n_items); } @@ -529,7 +546,16 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat offset = next_offset; // next offset is either the next entry in the list of offsets, // or the end of the serialized payload. - next_offset = if ((i + 2) * 4 >= first_offset) serialized.len else std.mem.readInt(u32, serialized[(i + 2) * 4 ..][0..4], std.builtin.Endian.little); + next_offset = if ((i + 2) * 4 >= first_offset) + serialized.len + else blk: { + const rel = (i + 2) * 4; + if (rel + 4 > serialized.len) return error.OffsetExceedsSize; + const n = std.mem.readInt(u32, serialized[rel..][0..4], std.builtin.Endian.little); + break :blk @as(usize, n); + }; + if (next_offset > serialized.len) return error.OffsetExceedsSize; + if (offset > next_offset) return error.OffsetOrdering; } } }, @@ -571,6 +597,7 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat switch (@typeInfo(field.type)) { .bool, .int => { // Direct deserialize + if (i + @sizeOf(field.type) > serialized.len) return error.OffsetExceedsSize; try deserialize(field.type, serialized[i .. i + @sizeOf(field.type)], &@field(out.*, field.name), allocator); i += @sizeOf(field.type); }, @@ -578,9 +605,11 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat if (try comptime isFixedSizeObject(field.type)) { // Direct deserialize const field_serialized_size = try serializedFixedSize(field.type); + if (i + field_serialized_size > serialized.len) return error.OffsetExceedsSize; try deserialize(field.type, serialized[i .. i + field_serialized_size], &@field(out.*, field.name), allocator); i += field_serialized_size; } else { + if (i + 4 > serialized.len) return error.OffsetExceedsSize; try deserialize(u32, serialized[i .. i + 4], &indices[variable_field_index], allocator); i += 4; variable_field_index += 1; @@ -600,14 +629,20 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat switch (@typeInfo(field.type)) { .bool, .int => {}, // covered by the previous pass else => if (!try comptime isFixedSizeObject(field.type)) { - const end = if (last_index == n_var_fields - 1) serialized.len else indices[last_index + 1]; - try deserialize(field.type, serialized[indices[last_index]..end], &@field(out.*, field.name), allocator); + const start = @as(usize, indices[last_index]); + const end: usize = if (last_index == n_var_fields - 1) serialized.len else @as(usize, indices[last_index + 1]); + if (start > serialized.len or end > serialized.len) return error.OffsetExceedsSize; + if (start > end) return error.OffsetOrdering; + if (last_index > 0 and start < @as(usize, indices[last_index - 1])) return error.OffsetOrdering; + if (last_index == 0 and start != i) return error.OffsetOrdering; + try deserialize(field.type, serialized[start..end], &@field(out.*, field.name), allocator); last_index += 1; }, } } }, .@"union" => { + if (serialized.len < 1) return error.OffsetExceedsSize; // Read the type index var union_index: u8 = undefined; try deserialize(u8, serialized[0..1], &union_index, allocator); diff --git a/src/tests.zig b/src/tests.zig index 365413b..47c733e 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2300,6 +2300,30 @@ test "roundtrip: [2][4]u32 (nested fixed array) preserves inner elements" { try expect(std.mem.eql(u32, &out[1], &.{ 5, 6, 7, 8 })); } +test "deserialize struct: invalid first var offset returns error (no slice panic)" { + const S = struct { + a: u32, + b: []const u8, + }; + // Fixed prefix 4 bytes; claimed offset for `b` is past the buffer (Hive-style garbage). + const malformed = [_]u8{ 0, 0, 0, 0, 0xab, 0xaa, 0xab, 0xab }; + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &malformed, &out, std.testing.allocator)); +} + +test "deserialize struct: offset past buffer returns OffsetExceedsSize" { + const S = struct { + a: u32, + b: []const u8, + }; + var buf: [12]u8 = undefined; + buf[0..4].* = @as([4]u8, @bitCast(@as(u32, 0))); + buf[4..8].* = @as([4]u8, @bitCast(@as(u32, 100))); + buf[8..12].* = .{ 0, 0, 0, 0 }; + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); +} + test { _ = @import("beacon_tests.zig"); } diff --git a/src/utils.zig b/src/utils.zig index c699451..faaea7f 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -71,7 +71,9 @@ pub fn List(T: type, comptime N: usize) type { @panic("Use the optimized utils.Bitlist(N) instead of utils.List(bool, N)"); } else if (try lib.isFixedSizeObject(Self.Item)) { const pitch = try lib.serializedFixedSize(Self.Item); + if (serialized.len % pitch != 0) return error.OffsetOrdering; const n_items = serialized.len / pitch; + if (n_items > N) return error.OffsetExceedsSize; for (0..n_items) |i| { var item: Self.Item = undefined; @@ -81,15 +83,18 @@ pub fn List(T: type, comptime N: usize) type { } else { // Validate and decode dynamic list length const size = try Self.decodeDynamicLength(serialized); + const prefix_len = @as(usize, size) * 4; + if (prefix_len > serialized.len) return error.OffsetExceedsSize; - const indices = std.mem.bytesAsSlice(u32, serialized[0 .. size * 4]); + const indices = std.mem.bytesAsSlice(u32, serialized[0..prefix_len]); var i = @as(usize, 0); while (i < size) : (i += 1) { const end = if (i < size - 1) indices[i + 1] else serialized.len; const start = indices[i]; - if (start >= serialized.len or end > serialized.len) { + if (start > serialized.len or end > serialized.len) { return error.OffsetExceedsSize; } + if (start > end) return error.OffsetOrdering; if (i > 0 and start < indices[i - 1]) { return error.OffsetOrdering; } From 1aa1d5632edc195542bb4877587c5be3a5fb760a Mon Sep 17 00:00:00 2001 From: ch4r10t33r Date: Sat, 9 May 2026 14:05:47 +0100 Subject: [PATCH 2/3] review: tests, OffsetAlignment, drop redundant zon/checks (#63) - Remove minimum_zig_version from build.zig.zon (follow-up PR per maintainer). - Variable array: use .little; misalignment -> error.OffsetAlignment; drop redundant size vs len check. - Variable []T: .little; misalignment and n_items==0 -> OffsetAlignment. - Add regression tests for struct fixed overflow, nested truncation, ordering cases, variable array/slice paths, union empty guard (minInLength 0), List ragged and count>N; fix List test leaks with defer deinit. Addresses https://github.com/blockblaz/ssz.zig/pull/63#pullrequestreview-4254877666 --- build.zig.zon | 1 - src/lib.zig | 15 +++--- src/tests.zig | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 9 deletions(-) diff --git a/build.zig.zon b/build.zig.zon index f52a830..f335c95 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -2,6 +2,5 @@ .name = .ssz, .fingerprint = 0x1d34bd0ceb1dfc2d, .version = "0.0.9", - .minimum_zig_version = "0.16.0", .paths = .{""}, } diff --git a/src/lib.zig b/src/lib.zig index eaf4b08..e899db8 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -459,10 +459,9 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat // first variable index is also the size of the list // of indices. Recast that list as a []const u32. if (serialized.len < 4) return error.OffsetExceedsSize; - const offset_prefix = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little); - if (offset_prefix % @sizeOf(u32) != 0) return error.OffsetOrdering; + const offset_prefix = std.mem.readInt(u32, serialized[0..4], .little); + if (offset_prefix % @sizeOf(u32) != 0) return error.OffsetAlignment; const size = offset_prefix / @sizeOf(u32); - if (size > serialized.len / @sizeOf(u32)) return error.OffsetExceedsSize; if (offset_prefix > serialized.len) return error.OffsetExceedsSize; const indices = std.mem.bytesAsSlice(u32, serialized[0..offset_prefix]); var i = @as(usize, 0); @@ -522,17 +521,17 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat // read the first index, determine when the "variable size" list ends, // and determine the size of the item as a result. if (serialized.len < 4) return error.OffsetExceedsSize; - const first_offset_u32 = std.mem.readInt(u32, serialized[0..4], std.builtin.Endian.little); + const first_offset_u32 = std.mem.readInt(u32, serialized[0..4], .little); const first_offset = @as(usize, first_offset_u32); if (first_offset > serialized.len) return error.OffsetExceedsSize; - if (first_offset % @sizeOf(u32) != 0) return error.OffsetOrdering; + if (first_offset % @sizeOf(u32) != 0) return error.OffsetAlignment; const n_items = first_offset / @sizeOf(u32); - if (n_items == 0) return error.OffsetOrdering; + if (n_items == 0) return error.OffsetAlignment; var offset: usize = first_offset; var next_offset: usize = if (n_items == 1) serialized.len else blk: { if (serialized.len < 8) return error.OffsetExceedsSize; - const n = std.mem.readInt(u32, serialized[4..8], std.builtin.Endian.little); + const n = std.mem.readInt(u32, serialized[4..8], .little); break :blk @as(usize, n); }; if (next_offset > serialized.len) return error.OffsetExceedsSize; @@ -551,7 +550,7 @@ pub fn deserialize(T: type, serialized: []const u8, out: *T, allocator: ?Allocat else blk: { const rel = (i + 2) * 4; if (rel + 4 > serialized.len) return error.OffsetExceedsSize; - const n = std.mem.readInt(u32, serialized[rel..][0..4], std.builtin.Endian.little); + const n = std.mem.readInt(u32, serialized[rel..][0..4], .little); break :blk @as(usize, n); }; if (next_offset > serialized.len) return error.OffsetExceedsSize; diff --git a/src/tests.zig b/src/tests.zig index 47c733e..c76a809 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2324,6 +2324,150 @@ test "deserialize struct: offset past buffer returns OffsetExceedsSize" { try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); } +test "deserialize struct: fixed-field read past buffer" { + const S = struct { + a: u32, + b: u32, + c: []const u8, + }; + const buf = [_]u8{0} ** 10; + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize struct: nested variable container truncated in outer slice" { + const Inner = struct { + x: u32, + y: []const u8, + }; + const S = struct { + a: Inner, + }; + // Outer: u32 offset to `a` at 4; inner blob is 5 bytes (too short for u32 + offset u32). + var buf: [9]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 4, .little); + @memset(buf[4..9], 0); + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize struct: first variable slice must start after offset table" { + const S = struct { + a: u32, + b: []const u8, + }; + var buf: [16]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 0, .little); + std.mem.writeInt(u32, buf[4..8], 4, .little); + @memset(buf[8..16], 0); + var out: S = undefined; + try expectError(error.OffsetOrdering, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize struct: variable slice end before start" { + const S = struct { + a: u32, + b: []const u8, + c: []const u8, + }; + var buf: [24]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 0, .little); + std.mem.writeInt(u32, buf[4..8], 12, .little); + std.mem.writeInt(u32, buf[8..12], 10, .little); + @memset(buf[12..24], 0); + var out: S = undefined; + try expectError(error.OffsetOrdering, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize struct: third variable offset before second" { + const S = struct { + a: u32, + b: []const u8, + c: []const u8, + d: []const u8, + }; + var buf: [32]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 0, .little); + std.mem.writeInt(u32, buf[4..8], 16, .little); + std.mem.writeInt(u32, buf[8..12], 24, .little); + std.mem.writeInt(u32, buf[12..16], 23, .little); + @memset(buf[16..32], 0xaa); + var out: S = undefined; + try expectError(error.OffsetOrdering, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable array: misaligned offset prefix" { + var out: [1][]const u8 = undefined; + const buf = [_]u8{ 6, 0, 0, 0 }; + try expectError(error.OffsetAlignment, deserialize([1][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable array: offset prefix exceeds buffer" { + var out: [1][]const u8 = undefined; + const buf = [_]u8{ 8, 0, 0, 0 }; + try expectError(error.OffsetExceedsSize, deserialize([1][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable array: decreasing offsets in table" { + var out: [2][]const u8 = undefined; + var buf: [8]u8 = undefined; + // First word is offset-prefix length (8 bytes = two u32s); second word is end of first span (invalid < 8). + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 4, .little); + try expectError(error.OffsetOrdering, deserialize([2][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: zero first offset" { + var out: [][]const u8 = undefined; + const buf = [_]u8{ 0, 0, 0, 0 }; + try expectError(error.OffsetAlignment, deserialize([][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: misaligned first offset" { + var out: [][]const u8 = undefined; + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 6, .little); + @memset(buf[4..8], 0); + try expectError(error.OffsetAlignment, deserialize([][]const u8, buf[0..], &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: second offset out of buffer" { + var out: [][]const u8 = undefined; + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 100, .little); + try expectError(error.OffsetExceedsSize, deserialize([][]const u8, buf[0..], &out, std.testing.allocator)); +} + +test "deserialize tagged union: empty buffer hits union guard when minInLength is 0" { + const U = union(enum) { + a: u32, + b: u8, + + pub fn minInLength() usize { + return 0; + } + }; + var out: U = undefined; + try expectError(error.OffsetExceedsSize, deserialize(U, &.{}, &out, null)); +} + +test "utils.List fixed-size item: ragged serialized length" { + const L = utils.List(u64, 4); + var out: L = undefined; + defer out.deinit(); + const buf = [_]u8{0} ** 9; + try expectError(error.OffsetOrdering, L.sszDecode(buf[0..], &out, std.testing.allocator)); +} + +test "utils.List fixed-size item: element count exceeds N" { + const L = utils.List(u64, 2); + var out: L = undefined; + defer out.deinit(); + const buf = [_]u8{0} ** 24; + try expectError(error.OffsetExceedsSize, L.sszDecode(buf[0..], &out, std.testing.allocator)); +} + test { _ = @import("beacon_tests.zig"); } From fb636348383dafe3015f737331824e4f4d6cf61e Mon Sep 17 00:00:00 2001 From: Guillaume Ballet <3272758+gballet@users.noreply.github.com> Date: Tue, 19 May 2026 13:45:01 +0200 Subject: [PATCH 3/3] add coverage --- src/tests.zig | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/tests.zig b/src/tests.zig index c76a809..a19a63b 100644 --- a/src/tests.zig +++ b/src/tests.zig @@ -2468,6 +2468,112 @@ test "utils.List fixed-size item: element count exceeds N" { try expectError(error.OffsetExceedsSize, L.sszDecode(buf[0..], &out, std.testing.allocator)); } +test "deserialize variable array: buffer smaller than offset prefix" { + var out: [1][]const u8 = undefined; + const buf = [_]u8{0} ** 3; + try expectError(error.OffsetExceedsSize, deserialize([1][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable array: offset in table past buffer" { + var out: [2][]const u8 = undefined; + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 12, .little); + try expectError(error.OffsetExceedsSize, deserialize([2][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: buffer smaller than first offset slot" { + var out: [][]const u8 = undefined; + const buf = [_]u8{0} ** 3; + try expectError(error.OffsetExceedsSize, deserialize([][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: first offset exceeds buffer" { + var out: [][]const u8 = undefined; + const buf = [_]u8{ 100, 0, 0, 0 }; + try expectError(error.OffsetExceedsSize, deserialize([][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: first offset greater than second" { + var out: [][]const u8 = undefined; + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 4, .little); + try expectError(error.OffsetOrdering, deserialize([][]const u8, &buf, &out, std.testing.allocator)); +} + +test "deserialize variable slice of slices: later offset past buffer" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var out: [][]const u8 = undefined; + var buf: [16]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 12, .little); + std.mem.writeInt(u32, buf[4..8], 16, .little); + std.mem.writeInt(u32, buf[8..12], 100, .little); + @memset(buf[12..16], 0); + try expectError(error.OffsetExceedsSize, deserialize([][]const u8, &buf, &out, arena.allocator())); +} + +test "deserialize variable slice of slices: later offset decreases" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var out: [][]const u8 = undefined; + var buf: [20]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 12, .little); + std.mem.writeInt(u32, buf[4..8], 16, .little); + std.mem.writeInt(u32, buf[8..12], 14, .little); + @memset(buf[12..20], 0); + try expectError(error.OffsetOrdering, deserialize([][]const u8, &buf, &out, arena.allocator())); +} + +test "deserialize struct: bool/int field truncated below @sizeOf" { + const S = struct { + a: u32, + b: []const u8, + }; + const buf = [_]u8{0} ** 3; + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "deserialize struct: fixed-size composite field truncated" { + const S = struct { + a: [4]u32, + b: []const u8, + }; + const buf = [_]u8{0} ** 10; + var out: S = undefined; + try expectError(error.OffsetExceedsSize, deserialize(S, &buf, &out, std.testing.allocator)); +} + +test "utils.List dynamic-item: offset prefix exceeds buffer" { + const L = utils.List([]const u8, 4); + var out: L = undefined; + defer out.deinit(); + const buf = [_]u8{ 8, 0, 0, 0 }; + try expectError(error.OffsetExceedsSize, L.sszDecode(&buf, &out, std.testing.allocator)); +} + +test "utils.List dynamic-item: offset in table past buffer" { + const L = utils.List([]const u8, 4); + var out: L = undefined; + defer out.deinit(); + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 100, .little); + try expectError(error.OffsetExceedsSize, L.sszDecode(&buf, &out, std.testing.allocator)); +} + +test "utils.List dynamic-item: offsets decrease" { + const L = utils.List([]const u8, 4); + var out: L = undefined; + defer out.deinit(); + var buf: [8]u8 = undefined; + std.mem.writeInt(u32, buf[0..4], 8, .little); + std.mem.writeInt(u32, buf[4..8], 4, .little); + try expectError(error.OffsetOrdering, L.sszDecode(&buf, &out, std.testing.allocator)); +} + test { _ = @import("beacon_tests.zig"); }