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、空白和非法字符。还要检查业务需要的最大解码长度,不因增加预检而允许无限读取外部内容。
不要只比较重新编码后的字符串来证明合法性:被回绕的错误字节也能再次编码成另一串合法文本。更不能丢弃越界组后继续拼接,造成难以察觉的数据缺失。
后续若采用正式工具链修正,先确认完整分组和末尾补齐两条路径都具备范围检查,再决定是否撤回临时预检。应用仍应保留明确的拒绝和原始输入留存策略。


