Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
.name = .ssz,
.fingerprint = 0x1d34bd0ceb1dfc2d,
.version = "0.0.9",
.minimum_zig_version = "0.16.0",
Comment thread
ch4r10t33r marked this conversation as resolved.
Outdated
.paths = .{""},
}
59 changes: 47 additions & 12 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment thread
ch4r10t33r marked this conversation as resolved.
Outdated
if (offset_prefix % @sizeOf(u32) != 0) return error.OffsetOrdering;
Comment thread
ch4r10t33r marked this conversation as resolved.
Outdated
const size = offset_prefix / @sizeOf(u32);
if (size > serialized.len / @sizeOf(u32)) return error.OffsetExceedsSize;
if (offset_prefix > serialized.len) return error.OffsetExceedsSize;
Comment thread
ch4r10t33r marked this conversation as resolved.
Outdated
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;
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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;
}
}
},
Expand Down Expand Up @@ -571,16 +597,19 @@ 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);
},
else => {
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;
Expand All @@ -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);
Expand Down
24 changes: 24 additions & 0 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
9 changes: 7 additions & 2 deletions src/utils.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down
Loading