authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-26 20:02:33-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-03-27 14:57:05+01:00
log053b5e3bddc086c43bc44e11a0106a2f15a0f3af
tree50bc6d1b9d049d4e2a9d257278e13c3601cb1948
parent2d3694ab42b73ba09124d5f1ddbfd9adaea79c47

Package.Manifest: add missing hash validation

closes #31225 reverts e96d86064eb81977f254fa8f36481b7d150cb3b6 which was an inadequate attempt to address the same problem (lack of hash validation).

3 files changed, 25 insertions(+), 16 deletions(-)

src/Package.zig+19-8
......@@ -44,6 +44,8 @@ pub const Fingerprint = packed struct(u64) {
4444pub const Hash = struct {
4545 /// Maximum size of a package hash. Unused bytes at the end are
4646 /// filled with zeroes.
47 ///
48 /// Assumed to be already validated.
4749 bytes: [max_len]u8,
4850
4951 pub const Algo = std.crypto.hash.sha2.Sha256;
......@@ -52,21 +54,35 @@ pub const Hash = struct {
5254 /// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
5355 pub const max_len = 32 + 1 + 32 + 1 + (32 + 32 + 200) / 6;
5456
57 /// Asserts `s` is valid.
5558 pub fn fromSlice(s: []const u8) Hash {
56 assert(s.len <= max_len);
59 assert(validate(s) == .ok);
5760 var result: Hash = undefined;
5861 @memcpy(result.bytes[0..s.len], s);
5962 @memset(result.bytes[s.len..], 0);
6063 return result;
6164 }
6265
66 pub const Validation = enum { ok, short, long, incomplete };
67
68 pub fn validate(s: []const u8) Validation {
69 if (s.len > max_len) return .long;
70 if (s.len < 44) return .short;
71 const n_dashes = std.mem.countScalar(u8, s[0 .. s.len - 44], '-');
72 if (n_dashes < 2) return .incomplete;
73 return .ok;
74 }
75
76 test validate {
77 try std.testing.expectEqual(.short, validate(""));
78 }
79
6380 pub fn toSlice(ph: *const Hash) []const u8 {
6481 var end: usize = ph.bytes.len;
65 while (end > 0) {
82 while (true) {
6683 end -= 1;
6784 if (ph.bytes[end] != 0) return ph.bytes[0 .. end + 1];
6885 }
69 return ph.bytes[0..0];
7086 }
7187
7288 pub fn eql(a: *const Hash, b: *const Hash) bool {
......@@ -188,11 +204,6 @@ test Hash {
188204 try std.testing.expectEqualStrings("nasm-2.16.1-3-vrr-ygAAoADH9XG3tOdvPNuHen_d-XeHndOG-nNXmved", result.toSlice());
189205}
190206
191test "empty hash" {
192 const hash = Hash.fromSlice("");
193 try std.testing.expectEqualStrings("", hash.toSlice());
194}
195
196207test {
197208 _ = Fetch;
198209}
src/Package/Fetch.zig+1-1
......@@ -783,7 +783,7 @@ fn runResource(
783783 const hash_tok = f.hash_tok.unwrap().?;
784784 if (!computed_package_hash.eql(&declared_hash)) {
785785 return f.fail(hash_tok, try eb.printString(
786 "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'",
786 "hash mismatch: manifest declares {s} but the fetched package has {s}",
787787 .{ declared_hash.toSlice(), computed_package_hash.toSlice() },
788788 ));
789789 }
src/Package/Manifest.zig+5-7
......@@ -420,12 +420,10 @@ const Parse = struct {
420420 const ast = p.ast;
421421 const tok = ast.nodeMainToken(node);
422422 const h = try parseString(p, node);
423
424 if (h.len > Package.Hash.max_len) {
425 return fail(p, tok, "hash length exceeds maximum: {d}", .{h.len});
423 switch (Package.Hash.validate(h)) {
424 .ok => return h,
425 else => |t| return fail(p, tok, "invalid hash: {t}", .{t}),
426426 }
427
428 return h;
429427 }
430428
431429 /// TODO: try to DRY this with AstGen.identifierTokenString
......@@ -632,7 +630,7 @@ test "basic" {
632630 \\ .dependencies = .{
633631 \\ .bar = .{
634632 \\ .url = "https://example.com/baz.tar.gz",
635 \\ .hash = "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f",
633 \\ .hash = "libmp3lame-3.100.1-6-67wlF_KvEwDRCT3pTpcDzi5KGntWCEoM-WtvVPEWdlk5",
636634 \\ },
637635 \\ },
638636 \\}
......@@ -664,7 +662,7 @@ test "basic" {
664662 manifest.dependencies.values()[0].location.url,
665663 );
666664 try testing.expectEqualStrings(
667 "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f",
665 "libmp3lame-3.100.1-6-67wlF_KvEwDRCT3pTpcDzi5KGntWCEoM-WtvVPEWdlk5",
668666 manifest.dependencies.values()[0].hash orelse return error.TestFailed,
669667 );
670668