You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

原因:

  1. 解码时错误使用encoded.len分配decoded缓冲区,Base64解码后的实际长度远小于编码长度,多余空间被当作有效数据处理
  2. 循环条件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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 05:12:02