DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
腾讯云;让你的想法跑起来;轻量服务器 · 建站 / Docker / API;查看产品 →
晚安云;香港 CN2 ¥15/月

Go ascii85 解码没报错,非法分组却变成了另一串字节

五个 Ascii85 字符可能表示超过32位的值,Go受影响解码器会无符号回绕。接收不可信内容时,先用宽整数验证完整及补齐分组,再交给标准解码器。

ascii85.Decode 返回 err=nil,输入 uuuuu 却被解成另一组字节;把结果重新编码,也得不到原文本。如果这些字节会进入文件、摘要或协议字段,先停止接受该结果,检查分组是否超过 32 位范围。

五个 Ascii85 字符可以表达的组合比一个 uint32 更多。Go 1.27.3 的对应解码实现直接在 uint32 中乘以 85 累积,越界后会回绕;末尾不足五个字符时补齐,也可能触发同一问题。NewDecoder 使用相同解码逻辑,换成流式入口不能消除它。

在标准解码前检查分组数值

对于已取得完整、大小受限输入的应用,可以先执行与该 Ascii85 语法一致的范围预检。下面保留空白、z 缩写和末尾分组规则,用 uint64 完成累积:

func validateAscii85(src []byte) error {
    var value uint64
    count := 0
    for i, b := range src {
        switch {
        case b <= ' ':
            continue
        case b == 'z' && count == 0:
            continue
        case b >= '!' && b <= 'u':
            value = value*85 + uint64(b-'!')
            count++
        default:
            return ascii85.CorruptInputError(i)
        }
        if count == 5 {
            if value > 0xffffffff {
                return ascii85.CorruptInputError(i)
            }
            value, count = 0, 0
        }
    }
    if count == 1 {
        return ascii85.CorruptInputError(len(src))
    }
    if count > 1 {
        for i := count; i < 5; i++ {
            value = value*85 + 84
        }
        if value > 0xffffffff {
            return ascii85.CorruptInputError(len(src))
        }
    }
    return nil
}

需要导入 encoding/ascii85。只有预检通过才调用原来的 Decode,并继续检查其返回错误、消费字节数与输出长度;预检不替代应用的内容格式校验。它面向已经完整读取的输入,不是可直接拼在任意流式分块前面的状态机。

标准包不负责替应用拆掉文件容器或额外的 Adobe 包装标记。输入若来自这类格式,先按既有解析流程取得真正的编码正文,不用删除任意字符的方式“清洗”到预检通过。

保留边界用例和输入限制

验证最大合法完整分组、紧接其后的越界分组、普通短尾、越界短尾、z、空白和非法字符。还要检查业务需要的最大解码长度,不因增加预检而允许无限读取外部内容。

不要只比较重新编码后的字符串来证明合法性:被回绕的错误字节也能再次编码成另一串合法文本。更不能丢弃越界组后继续拼接,造成难以察觉的数据缺失。

后续若采用正式工具链修正,先确认完整分组和末尾补齐两条路径都具备范围检查,再决定是否撤回临时预检。应用仍应保留明确的拒绝和原始输入留存策略。