From 719c71814f87f25f288de463df0c5130dcc22d2b Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Mon, 14 Sep 2026 14:11:05 +0800 Subject: [PATCH 1/2] fix: validate string lengths before allocation and body reads --- README.md | 4 ++-- README_CN.md | 4 ++-- src/msgpack.zig | 14 ++++++----- src/test.zig | 62 +++++++++++++++++++++++-------------------------- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 677aa4f..dbacb9d 100644 --- a/README.md +++ b/README.md @@ -521,8 +521,8 @@ This library uses an **iterative parser** (not recursive) to provide strong secu **Safety Limits:** -- All limits are enforced **before** memory allocation -- Invalid input is rejected immediately without resource consumption +- For fixstr, str8, str16, and str32, string lengths are checked **before** allocating or reading string contents. +- Oversized strings return `StringTooLong` after consuming the marker and length prefix; string contents remain unread. - Configurable limits allow tuning for specific environments (embedded, server, etc.) **Memory Safety:** diff --git a/README_CN.md b/README_CN.md index 7d788f6..13419f8 100644 --- a/README_CN.md +++ b/README_CN.md @@ -429,8 +429,8 @@ msgpack.MsgPackError.ExtDataTooLarge // 扩展类型数据过大 **安全限制**: -- 所有限制在内存分配**之前**强制执行 -- 无效输入被立即拒绝,不消耗资源 +- fixstr、str8、str16、str32 均在分配字符串内存、读取正文**之前**检查长度。 +- 超限字符串返回 `StringTooLong`;此时仅消费了类型标记和长度前缀,正文尚未读取。 - 可配置限制允许针对特定环境调整(嵌入式、服务器等) **内存安全**: diff --git a/src/msgpack.zig b/src/msgpack.zig index 51ac255..48979a1 100644 --- a/src/msgpack.zig +++ b/src/msgpack.zig @@ -2456,8 +2456,15 @@ pub fn PackWithLimits( } } + inline fn validateStrLength(len: usize) !void { + if (len > parse_limits.max_string_length) { + return MsgPackError.StringTooLong; + } + } + fn readFixStrValue(self: Self, allocator: Allocator, marker_u8: u8) ![]const u8 { const len: u8 = marker_u8 - @intFromEnum(Markers.FIXSTR); + try validateStrLength(len); const str = try self.readData(allocator, len); return str; @@ -2467,6 +2474,7 @@ pub fn PackWithLimits( /// Reduces code duplication for STR8/16/32 inline fn readStrValueGeneric(self: Self, comptime LenType: type, allocator: Allocator) ![]const u8 { const len = try self.readTypedInt(LenType); + try validateStrLength(len); return try self.readData(allocator, len); } @@ -2912,12 +2920,6 @@ pub fn PackWithLimits( .FIXSTR, .STR8, .STR16, .STR32 => { const val = try self.readStrValue(marker_u8, allocator); - // Validate string length - if (val.len > parse_limits.max_string_length) { - allocator.free(val); - return MsgPackError.StringTooLong; - } - current_payload = Payload{ .str = Str.init(val) }; }, .BIN8, .BIN16, .BIN32 => { diff --git a/src/test.zig b/src/test.zig index e5c0c83..179bc17 100644 --- a/src/test.zig +++ b/src/test.zig @@ -4171,29 +4171,15 @@ test "corrupted: nested arrays with mismatched counts" { } test "malicious: str32 with excessive length claim" { - var buffer: [1000]u8 = undefined; - var input_buf: [10]u8 = undefined; - - // str32 claiming 100MB (will be rejected by limit) - input_buf[0] = 0xdb; // str32 - input_buf[1] = 0x06; // 100MB = 0x06400000 - input_buf[2] = 0x40; - input_buf[3] = 0x00; - input_buf[4] = 0x00; - - var write_buffer = fixedBufferStream(&buffer); - var read_buffer = fixedBufferStream(&input_buf); + var input = [_]u8{ 0xdb, 0xff, 0xff, 0xff, 0xff }; + var output: [0]u8 = .{}; + var write_buffer = fixedBufferStream(&output); + var read_buffer = fixedBufferStream(&input); var p = pack.init(&write_buffer, &read_buffer); + var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 }); - const result = p.read(allocator); - if (result) |payload| { - payload.free(allocator); - try expect(false); // Should not succeed - } else |err| { - // Should be LengthReading (can't read 100MB) or StringTooLong - try expect(err == msgpack.MsgPackError.LengthReading or - err == msgpack.MsgPackError.StringTooLong); - } + try std.testing.expectError(msgpack.MsgPackError.StringTooLong, p.read(failing.allocator())); + try expect(!failing.has_induced_failure); } // ========== Tests for Generic Map Keys (Non-String Keys) ========== @@ -5525,7 +5511,7 @@ test "PackerIO: packIO convenience function" { // ParseLimits: error path coverage for limits not exercised elsewhere // ============================================================================ -test "iterative parser: string too long" { +test "string limit: reject before allocation or body read" { const custom_pack = msgpack.PackWithLimits( *bufferType, *bufferType, @@ -5536,17 +5522,27 @@ test "iterative parser: string too long" { .{ .max_string_length = 10 }, ); - // str8 marker (0xd9) + length=20 + 20 bytes of zeros - var arr: [256]u8 = std.mem.zeroes([256]u8); - arr[0] = 0xd9; - arr[1] = 20; - - var write_buffer = fixedBufferStream(&arr); - var read_buffer = fixedBufferStream(&arr); - var p = custom_pack.init(&write_buffer, &read_buffer); - - const result = p.read(allocator); - try std.testing.expectError(msgpack.MsgPackError.StringTooLong, result); + // Each string format has its own length-decoding path. + const headers = [_][]const u8{ + &.{0xab}, + &.{ 0xd9, 11 }, + &.{ 0xda, 0, 11 }, + &.{ 0xdb, 0, 0, 0, 11 }, + }; + for (headers) |header| { + var input: [16]u8 = undefined; + @memcpy(input[0..header.len], header); + @memset(input[header.len..], 'x'); + var output: [0]u8 = .{}; + var write_buffer = fixedBufferStream(&output); + var read_buffer = fixedBufferStream(&input); + var p = custom_pack.init(&write_buffer, &read_buffer); + var failing = std.testing.FailingAllocator.init(allocator, .{ .fail_index = 0 }); + + try std.testing.expectError(msgpack.MsgPackError.StringTooLong, p.read(failing.allocator())); + try expect(!failing.has_induced_failure); + try std.testing.expectEqual(header.len, read_buffer.pos); + } } test "iterative parser: bin too long" { From 913afd1cd1d2da98a1f706d1d1f7aa03b7433e49 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Mon, 14 Sep 2026 14:17:47 +0800 Subject: [PATCH 2/2] docs: recommend v0.0.17 for Zig 0.15 and older --- README.md | 5 ++--- README_CN.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index dbacb9d..57999f2 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,11 @@ This library is tested and optimized for all major platforms and architectures: | Zig Version | Library Version | Status | | -------------------- | --------------- | ------------------------------------- | -| 0.13 and older | 0.0.6 | Legacy support | -| 0.14.x / 0.15.x | Earlier releases | Not supported by the current version | +| 0.15.x and older | 0.0.17 | Legacy support | | 0.16.0 | Current | Supported with compatibility layer | | 0.17.0-dev | Current | Initial support; CI tracks `master` | -> **Note:** For Zig 0.13 and older versions, please use version `0.0.6` of this library. +> **Note:** For Zig 0.15.x and older versions, please use version `0.0.17` of this library. > **Note:** The current library requires Zig `0.16.0` or later. Zig `0.17.0-dev` is unreleased; compatibility may change as development continues. > **Note:** Zig 0.16+ removes `std.io.FixedBufferStream`, but this library provides a compatibility layer to maintain the same API across all supported versions. diff --git a/README_CN.md b/README_CN.md index 13419f8..62ea028 100644 --- a/README_CN.md +++ b/README_CN.md @@ -44,12 +44,11 @@ Zig 编程语言的 MessagePack 实现。此库提供了一种简单高效的方 | Zig 版本 | 库版本 | 状态 | | -------------------- | -------- | ----------------- | -| 0.13 及更早版本 | 0.0.6 | 旧版支持 | -| 0.14.x / 0.15.x | 历史版本 | 当前版本不再支持 | +| 0.15.x 及更早版本 | 0.0.17 | 旧版支持 | | 0.16.0 | 当前版本 | 通过兼容层支持 | | 0.17.0-dev | 当前版本 | 初步支持;CI 跟踪 `master` | -> **注意**: 对于 Zig 0.13 及更早版本,请使用本库的 `0.0.6` 版本。 +> **注意**: 如需支持 Zig 0.15.x 及更早版本,请使用本库的 `0.0.17` 版本。 > **注意**: 当前库要求 Zig `0.16.0` 或更高版本。Zig `0.17.0-dev` 尚未发布,兼容性可能随开发进展而变化。 > **注意**: Zig 0.16+ 移除了 `std.io.FixedBufferStream`,但本库提供了兼容层以在所有支持的版本中维持相同的 API。