From 8f61a7d34a33c2ec912c3f4404d69586e721c75a Mon Sep 17 00:00:00 2001 From: rpkak Date: Thu, 17 Sep 2026 08:59:38 +0200 Subject: [PATCH] libzigc: strnlen, wcsnlen: don't read past first zero Previously is was assumed, that all of str[0..max] is readable. --- lib/c/string.zig | 5 ++++- lib/c/wchar.zig | 7 +++++-- lib/std/c.zig | 2 ++ test/c/string.zig | 9 +++++++++ test/c/wchar.zig | 9 +++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/c/string.zig b/lib/c/string.zig index 2cd0c0467b46e06370f2e1d7de58a34db7d38584..94b94c65e9a022a4fd626be8ee48882ee7794e7b 100644 --- a/lib/c/string.zig +++ b/lib/c/string.zig @@ -224,7 +224,10 @@ fn stpncpy(noalias dst: [*]c_char, noalias src: [*:0]const c_char, max: usize) c } fn strnlen(str: [*:0]const c_char, max: usize) callconv(.c) usize { - return std.mem.findScalar(u8, @ptrCast(str[0..max]), 0) orelse max; + for (0..max) |i| { + if (str[i] == 0) return i; + } + return max; } fn memmem(haystack: *const anyopaque, haystack_len: usize, needle: *const anyopaque, needle_len: usize) callconv(.c) ?*anyopaque { diff --git a/lib/c/wchar.zig b/lib/c/wchar.zig index 24aca6b0c869d4900cd4724160c760a8bcfc98cb..eb9611514141e53665b333308c85aa26054257a7 100644 --- a/lib/c/wchar.zig +++ b/lib/c/wchar.zig @@ -79,11 +79,14 @@ fn wmemset(dest: [*]wchar_t, elem: wchar_t, len: usize) callconv(.c) [*]wchar_t } fn wcslen(str: [*:0]const wchar_t) callconv(.c) usize { - return wcsnlen(str, std.math.maxInt(usize)); + return std.mem.len(str); } fn wcsnlen(str: [*:0]const wchar_t, max: usize) callconv(.c) usize { - return std.mem.findScalar(wchar_t, str[0..max], 0) orelse max; + for (0..max) |i| { + if (str[i] == 0) return i; + } + return max; } fn wcscmp(a: [*:0]const wchar_t, b: [*:0]const wchar_t) callconv(.c) c_int { diff --git a/lib/std/c.zig b/lib/std/c.zig index d4f840d4e7b7d426b6bb2736a8634fd7eed57ce9..abadb78976db809b025e6b08e30b5963b9cd95bc 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -11471,7 +11471,9 @@ pub const strdup = switch (builtin.abi) { .msvc => private._strdup, else => private.strdup, }; +pub extern "c" fn strnlen(s: [*:0]const c_char, n: usize) callconv(.c) usize; pub extern "c" fn strndup(s: [*:0]const c_char, n: usize) ?[*:0]c_char; +pub extern "c" fn wcsnlen(s: [*:0]const wchar_t, n: usize) callconv(.c) usize; pub const wcsdup = switch (builtin.abi) { .msvc => private._wcsdup, else => private.wcsdup, diff --git a/test/c/string.zig b/test/c/string.zig index d9f048bbb6a7070c3583912bba96f5198bd5c81a..436333def598445365aa06e59890376d84f91ab7 100644 --- a/test/c/string.zig +++ b/test/c/string.zig @@ -50,3 +50,12 @@ test "strndup" { const copy3_u8: [*:0]u8 = @ptrCast(copy3); try testing.expectEqualStrings("Hello", std.mem.span(@as([*:0]const u8, copy3_u8))); } + +test "strnlen" { + const str: [*:0]const u8 = "Hello"; + try testing.expectEqual(3, c.strnlen(@ptrCast(str), 3)); + try testing.expectEqual(4, c.strnlen(@ptrCast(str), 4)); + try testing.expectEqual(5, c.strnlen(@ptrCast(str), 5)); + try testing.expectEqual(5, c.strnlen(@ptrCast(str), 6)); + try testing.expectEqual(5, c.strnlen(@ptrCast(str), 7)); +} diff --git a/test/c/wchar.zig b/test/c/wchar.zig index 1c84bd07bece2208d9ab0b1555401496ef28186d..a9e6030ec76b2837121f00d4c8848284c64b6a9b 100644 --- a/test/c/wchar.zig +++ b/test/c/wchar.zig @@ -32,3 +32,12 @@ test "wcsdup empty string" { defer c.free(cpy); try testing.expectEqual(@as(c.wchar_t, 0), cpy[0]); } + +test "wcsnlen" { + const wcs: [*:0]const c.wchar_t = &.{ 'H', 'e', 'l', 'l', 'o' }; + try testing.expectEqual(3, c.wcsnlen(wcs, 3)); + try testing.expectEqual(4, c.wcsnlen(wcs, 4)); + try testing.expectEqual(5, c.wcsnlen(wcs, 5)); + try testing.expectEqual(5, c.wcsnlen(wcs, 6)); + try testing.expectEqual(5, c.wcsnlen(wcs, 7)); +} -- 2.54.0