| author | |
| committer | |
| log | 053b5e3bddc086c43bc44e11a0106a2f15a0f3af |
| tree | 50bc6d1b9d049d4e2a9d257278e13c3601cb1948 |
| parent | 2d3694ab42b73ba09124d5f1ddbfd9adaea79c47 |
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) { |
| 44 | 44 | pub const Hash = struct { |
| 45 | 45 | /// Maximum size of a package hash. Unused bytes at the end are |
| 46 | 46 | /// filled with zeroes. |
| 47 | /// | |
| 48 | /// Assumed to be already validated. | |
| 47 | 49 | bytes: [max_len]u8, |
| 48 | 50 | |
| 49 | 51 | pub const Algo = std.crypto.hash.sha2.Sha256; |
| ... | ... | @@ -52,21 +54,35 @@ pub const Hash = struct { |
| 52 | 54 | /// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" |
| 53 | 55 | pub const max_len = 32 + 1 + 32 + 1 + (32 + 32 + 200) / 6; |
| 54 | 56 | |
| 57 | /// Asserts `s` is valid. | |
| 55 | 58 | pub fn fromSlice(s: []const u8) Hash { |
| 56 | assert(s.len <= max_len); | |
| 59 | assert(validate(s) == .ok); | |
| 57 | 60 | var result: Hash = undefined; |
| 58 | 61 | @memcpy(result.bytes[0..s.len], s); |
| 59 | 62 | @memset(result.bytes[s.len..], 0); |
| 60 | 63 | return result; |
| 61 | 64 | } |
| 62 | 65 | |
| 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 | ||
| 63 | 80 | pub fn toSlice(ph: *const Hash) []const u8 { |
| 64 | 81 | var end: usize = ph.bytes.len; |
| 65 | while (end > 0) { | |
| 82 | while (true) { | |
| 66 | 83 | end -= 1; |
| 67 | 84 | if (ph.bytes[end] != 0) return ph.bytes[0 .. end + 1]; |
| 68 | 85 | } |
| 69 | return ph.bytes[0..0]; | |
| 70 | 86 | } |
| 71 | 87 | |
| 72 | 88 | pub fn eql(a: *const Hash, b: *const Hash) bool { |
| ... | ... | @@ -188,11 +204,6 @@ test Hash { |
| 188 | 204 | try std.testing.expectEqualStrings("nasm-2.16.1-3-vrr-ygAAoADH9XG3tOdvPNuHen_d-XeHndOG-nNXmved", result.toSlice()); |
| 189 | 205 | } |
| 190 | 206 | |
| 191 | test "empty hash" { | |
| 192 | const hash = Hash.fromSlice(""); | |
| 193 | try std.testing.expectEqualStrings("", hash.toSlice()); | |
| 194 | } | |
| 195 | ||
| 196 | 207 | test { |
| 197 | 208 | _ = Fetch; |
| 198 | 209 | } |
src/Package/Fetch.zig+1-1| ... | ... | @@ -783,7 +783,7 @@ fn runResource( |
| 783 | 783 | const hash_tok = f.hash_tok.unwrap().?; |
| 784 | 784 | if (!computed_package_hash.eql(&declared_hash)) { |
| 785 | 785 | 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}", | |
| 787 | 787 | .{ declared_hash.toSlice(), computed_package_hash.toSlice() }, |
| 788 | 788 | )); |
| 789 | 789 | } |
src/Package/Manifest.zig+5-7| ... | ... | @@ -420,12 +420,10 @@ const Parse = struct { |
| 420 | 420 | const ast = p.ast; |
| 421 | 421 | const tok = ast.nodeMainToken(node); |
| 422 | 422 | 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}), | |
| 426 | 426 | } |
| 427 | ||
| 428 | return h; | |
| 429 | 427 | } |
| 430 | 428 | |
| 431 | 429 | /// TODO: try to DRY this with AstGen.identifierTokenString |
| ... | ... | @@ -632,7 +630,7 @@ test "basic" { |
| 632 | 630 | \\ .dependencies = .{ |
| 633 | 631 | \\ .bar = .{ |
| 634 | 632 | \\ .url = "https://example.com/baz.tar.gz", |
| 635 | \\ .hash = "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f", | |
| 633 | \\ .hash = "libmp3lame-3.100.1-6-67wlF_KvEwDRCT3pTpcDzi5KGntWCEoM-WtvVPEWdlk5", | |
| 636 | 634 | \\ }, |
| 637 | 635 | \\ }, |
| 638 | 636 | \\} |
| ... | ... | @@ -664,7 +662,7 @@ test "basic" { |
| 664 | 662 | manifest.dependencies.values()[0].location.url, |
| 665 | 663 | ); |
| 666 | 664 | try testing.expectEqualStrings( |
| 667 | "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f", | |
| 665 | "libmp3lame-3.100.1-6-67wlF_KvEwDRCT3pTpcDzi5KGntWCEoM-WtvVPEWdlk5", | |
| 668 | 666 | manifest.dependencies.values()[0].hash orelse return error.TestFailed, |
| 669 | 667 | ); |
| 670 | 668 |