Zig语言TLV Base64解码索引越界及内存分配错误排查
Zig TLV Base64编解码问题排查与修复
问题概述
编写的TLV转Base64编码功能正常,但解码阶段遇到两个问题:
- 解码触发panic,提示
index out of bounds: index 241, len 92 - 使用
E.calcSizeForSlice(encoded)分配内存时,出现错误联合类型无法转为usize的编译错误
原始代码
const std = @import("std"); pub const Tlv = struct { tag: u8, value: []const u8, pub fn init(tag: u8, value: []const u8) Tlv { return .{ .tag = tag, .value = value }; } pub fn write(tlv: Tlv, writer: anytype) !void { try writer.writeByte(tlv.tag); try writer.writeByte(@intCast(tlv.value.len)); _ = try writer.write(tlv.value); } pub fn encode(tlvs: []const Tlv, allocator: std.mem.Allocator) ![]const u8 { var buf = std.ArrayList(u8).init(allocator); defer buf.deinit(); const writer = buf.writer(); for (tlvs) |tlv| { try tlv.write(writer); } const E = std.base64.standard.Encoder; const encoded = try allocator.alloc(u8, E.calcSize(buf.items.len)); _ = E.encode(encoded, buf.items); return encoded; } pub fn decode(encoded: []const u8, allocator: std.mem.Allocator) !void { // !struct { []Tlv, []u8 } { var E = std.base64.standard.Decoder; std.debug.print("encoded size: {any} / {any}\n", .{E.calcSizeForSlice(encoded), encoded.len}); const decoded = try allocator.alloc(u8, encoded.len); defer allocator.free(decoded); try E.decode(decoded, encoded); var tlvs = std.ArrayList(Tlv).init(allocator); var i: usize = 0; while (i < decoded.len-1) { const len = decoded[i+1]; std.debug.print("TLV elements: {any} / {s}\n", .{ decoded[i], decoded[i+2..][0..len]}); try tlvs.append(.{.tag = decoded[i], .value = decoded[i+2..][0..len] }); i += 2 + len; } } }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); const allocator = arena.allocator(); const tlvs = [_]Tlv{ Tlv.init(1, "Bonz carpet"), Tlv.init(2, "310122393500003"), Tlv.init(3, "2022-04-25T15:30:00Z"), Tlv.init(4, "1200.00"), Tlv.init(5, "180.00"), }; const encoded = try Tlv.encode(&tlvs, allocator); defer allocator.free(encoded); std.debug.print("Base64: {s}\n", .{ encoded}); try Tlv.decode(encoded, allocator); }
运行报错信息
Base64: AQtCb256IGNhcnBldAIPMzEwMTIyMzkzNTAwMDAzAxQyMDIyLTA0LTI1VDE1OjMwOjAwWgQHMTIwMC4wMAUGMTgwLjAw encoded size: 69 / 92 TLV elements: 1 / Bonz carpet TLV elements: 2 / 310122393500003 TLV elements: 3 / 2022-04-25T15:30:00Z TLV elements: 4 / 1200.00 TLV elements: 5 / 180.00 thread 1 panic: index out of bounds: index 241, len 92 example.zig:40:85: 0x221ad8 in decode (output.s) std.debug.print("TLV elements: {any} / {s}\n", .{ decoded[i], decoded[i+2..][0..len]}); ^ example.zig:61:19: 0x222ff8 in main (output.s) try Tlv.decode(encoded, allocator); ^ /opt/compiler-explorer/zig-master/lib/std/start.zig:370:37: 0x21f9b5 in posixCallMainAndExit (output.s) var i: usize = 0; ^ /opt/compiler-explorer/zig-master/lib/std/start.zig:243:5: 0x21f4a1 in _start (output.s) asm volatile (switch (native_arch) { ^ ???:?:?: 0x0 in ??? (???) Program terminated with signal: SIGSEGV Exit code: 139
编译错误信息
error: expected type 'usize', found 'error{InvalidCharacter,InvalidPadding,NoSpaceLeft}!usize' try writer.writeByte(@intCast(tlv.value.len)); ~~~~~~^~~~~~~~~~ note: cannot convert error union to payload type note: consider using 'try', 'catch', or 'if'
问题分析与修复
问题1:索引越界panic
原因:
- 解码时错误使用
encoded.len分配decoded缓冲区,Base64解码后的实际长度远小于编码长度,多余空间被当作有效数据处理 - 循环条件
i < decoded.len-1存在漏洞,当i接近缓冲区末尾时,i+1可能越界,且未校验i+2+len是否超出缓冲区边界
修复:
- 使用
try E.calcSizeForSlice(encoded)获取正确的解码后长度,再分配缓冲区 - 修正循环条件,确保每次迭代中
i+2+len不超过有效解码数据的长度
问题2:错误联合类型转usize编译错误
原因:E.calcSizeForSlice返回error!usize错误联合类型,不能直接作为allocator.alloc的参数,必须显式处理错误
修复:
用try关键字提取错误联合中的有效usize值,再执行内存分配
修复后的完整代码
const std = @import("std"); pub const Tlv = struct { tag: u8, value: []const u8, pub fn init(tag: u8, value: []const u8) Tlv { return .{ .tag = tag, .value = value }; } pub fn write(tlv: Tlv, writer: anytype) !void { try writer.writeByte(tlv.tag); // 校验value长度不超过u8范围,避免溢出 if (tlv.value.len > std.math.maxInt(u8)) { return error.ValueLengthTooLong; } try writer.writeByte(@intCast(u8, tlv.value.len)); _ = try writer.write(tlv.value); } pub fn encode(tlvs: []const Tlv, allocator: std.mem.Allocator) ![]const u8 { var buf = std.ArrayList(u8).init(allocator); defer buf.deinit(); const writer = buf.writer(); for (tlvs) |tlv| { try tlv.write(writer); } const E = std.base64.standard.Encoder; const encoded = try allocator.alloc(u8, E.calcSize(buf.items.len)); _ = E.encode(encoded, buf.items); return encoded; } pub fn decode(encoded: []const u8, allocator: std.mem.Allocator) !std.ArrayList(Tlv) { const E = std.base64.standard.Decoder; // 正确计算解码后长度,处理错误联合 const decoded_len = try E.calcSizeForSlice(encoded); std.debug.print("decoded size: {any} / encoded size: {any}\n", .{decoded_len, encoded.len}); const decoded = try allocator.alloc(u8, decoded_len); errdefer allocator.free(decoded); const actual_decoded_len = try E.decode(decoded, encoded); // 只保留实际解码的有效数据 const valid_decoded = decoded[0..actual_decoded_len]; var tlvs = std.ArrayList(Tlv).init(allocator); errdefer tlvs.deinit(); var i: usize = 0; // 循环条件确保有足够空间读取tag和length字段 while (i + 2 <= valid_decoded.len) { const len = valid_decoded[i+1]; // 校验value字段是否超出缓冲区 if (i + 2 + len > valid_decoded.len) { return error.InvalidTlvFormat; } const value_slice = valid_decoded[i+2 .. i+2+len]; std.debug.print("TLV elements: {any} / {s}\n", .{ valid_decoded[i], value_slice }); try tlvs.append(.{ .tag = valid_decoded[i], .value = value_slice, }); i += 2 + len; } // 检查是否有未处理的剩余数据(可选,根据TLV规范决定) if (i != valid_decoded.len) { return error.InvalidTlvFormat; } return tlvs; } }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); // 自动释放arena内存,无需单独free const allocator = arena.allocator(); const tlvs = [_]Tlv{ Tlv.init(1, "Bonz carpet"), Tlv.init(2, "310122393500003"), Tlv.init(3, "2022-04-25T15:30:00Z"), Tlv.init(4, "1200.00"), Tlv.init(5, "180.00"), }; const encoded = try Tlv.encode(&tlvs, allocator); std.debug.print("Base64: {s}\n", .{encoded}); const decoded_tlvs = try Tlv.decode(encoded, allocator); defer decoded_tlvs.deinit(); std.debug.print("\nDecoded TLV count: {}\n", .{decoded_tlvs.items.len}); }
内容的提问来源于stack exchange,提问作者Hasan A Yousef
相关产品推荐
相关产品推荐

