Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -521,8 +520,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:**
Expand Down
9 changes: 4 additions & 5 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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。

Expand Down Expand Up @@ -429,8 +428,8 @@ msgpack.MsgPackError.ExtDataTooLarge // 扩展类型数据过大

**安全限制**:

- 所有限制在内存分配**之前**强制执行
- 无效输入被立即拒绝,不消耗资源
- fixstr、str8、str16、str32 均在分配字符串内存、读取正文**之前**检查长度。
- 超限字符串返回 `StringTooLong`;此时仅消费了类型标记和长度前缀,正文尚未读取。
- 可配置限制允许针对特定环境调整(嵌入式、服务器等)

**内存安全**:
Expand Down
14 changes: 8 additions & 6 deletions src/msgpack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down Expand Up @@ -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 => {
Expand Down
62 changes: 29 additions & 33 deletions src/test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) ==========
Expand Down Expand Up @@ -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,
Expand All @@ -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" {
Expand Down
Loading