| 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,6 +44,8 @@ pub const Fingerprint = packed struct(u64) { |
| 44 | pub const Hash = struct { | 44 | pub const Hash = struct { |
| 45 | /// Maximum size of a package hash. Unused bytes at the end are | 45 | /// Maximum size of a package hash. Unused bytes at the end are |
| 46 | /// filled with zeroes. | 46 | /// filled with zeroes. |
| 47 | /// | ||
| 48 | /// Assumed to be already validated. | ||
| 47 | bytes: [max_len]u8, | 49 | bytes: [max_len]u8, |
| 48 | 50 | ||
| 49 | pub const Algo = std.crypto.hash.sha2.Sha256; | 51 | pub const Algo = std.crypto.hash.sha2.Sha256; |
| ... | @@ -52,21 +54,35 @@ pub const Hash = struct { | ... | @@ -52,21 +54,35 @@ pub const Hash = struct { |
| 52 | /// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" | 54 | /// Example: "nnnn-vvvv-hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh" |
| 53 | pub const max_len = 32 + 1 + 32 + 1 + (32 + 32 + 200) / 6; | 55 | pub const max_len = 32 + 1 + 32 + 1 + (32 + 32 + 200) / 6; |
| 54 | 56 | ||
| 57 | /// Asserts `s` is valid. | ||
| 55 | pub fn fromSlice(s: []const u8) Hash { | 58 | pub fn fromSlice(s: []const u8) Hash { |
| 56 | assert(s.len <= max_len); | 59 | assert(validate(s) == .ok); |
| 57 | var result: Hash = undefined; | 60 | var result: Hash = undefined; |
| 58 | @memcpy(result.bytes[0..s.len], s); | 61 | @memcpy(result.bytes[0..s.len], s); |
| 59 | @memset(result.bytes[s.len..], 0); | 62 | @memset(result.bytes[s.len..], 0); |
| 60 | return result; | 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 | pub fn toSlice(ph: *const Hash) []const u8 { | 80 | pub fn toSlice(ph: *const Hash) []const u8 { |
| 64 | var end: usize = ph.bytes.len; | 81 | var end: usize = ph.bytes.len; |
| 65 | while (end > 0) { | 82 | while (true) { |
| 66 | end -= 1; | 83 | end -= 1; |
| 67 | if (ph.bytes[end] != 0) return ph.bytes[0 .. end + 1]; | 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 | pub fn eql(a: *const Hash, b: *const Hash) bool { | 88 | pub fn eql(a: *const Hash, b: *const Hash) bool { |
| ... | @@ -188,11 +204,6 @@ test Hash { | ... | @@ -188,11 +204,6 @@ test Hash { |
| 188 | try std.testing.expectEqualStrings("nasm-2.16.1-3-vrr-ygAAoADH9XG3tOdvPNuHen_d-XeHndOG-nNXmved", result.toSlice()); | 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 | test { | 207 | test { |
| 197 | _ = Fetch; | 208 | _ = Fetch; |
| 198 | } | 209 | } |
src/Package/Fetch.zig+1-1| ... | @@ -783,7 +783,7 @@ fn runResource( | ... | @@ -783,7 +783,7 @@ fn runResource( |
| 783 | const hash_tok = f.hash_tok.unwrap().?; | 783 | const hash_tok = f.hash_tok.unwrap().?; |
| 784 | if (!computed_package_hash.eql(&declared_hash)) { | 784 | if (!computed_package_hash.eql(&declared_hash)) { |
| 785 | return f.fail(hash_tok, try eb.printString( | 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 | .{ declared_hash.toSlice(), computed_package_hash.toSlice() }, | 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,12 +420,10 @@ const Parse = struct { |
| 420 | const ast = p.ast; | 420 | const ast = p.ast; |
| 421 | const tok = ast.nodeMainToken(node); | 421 | const tok = ast.nodeMainToken(node); |
| 422 | const h = try parseString(p, node); | 422 | const h = try parseString(p, node); |
| 423 | 423 | switch (Package.Hash.validate(h)) { | |
| 424 | if (h.len > Package.Hash.max_len) { | 424 | .ok => return h, |
| 425 | return fail(p, tok, "hash length exceeds maximum: {d}", .{h.len}); | 425 | else => |t| return fail(p, tok, "invalid hash: {t}", .{t}), |
| 426 | } | 426 | } |
| 427 | |||
| 428 | return h; | ||
| 429 | } | 427 | } |
| 430 | 428 | ||
| 431 | /// TODO: try to DRY this with AstGen.identifierTokenString | 429 | /// TODO: try to DRY this with AstGen.identifierTokenString |
| ... | @@ -632,7 +630,7 @@ test "basic" { | ... | @@ -632,7 +630,7 @@ test "basic" { |
| 632 | \\ .dependencies = .{ | 630 | \\ .dependencies = .{ |
| 633 | \\ .bar = .{ | 631 | \\ .bar = .{ |
| 634 | \\ .url = "https://example.com/baz.tar.gz", | 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,7 +662,7 @@ test "basic" { |
| 664 | manifest.dependencies.values()[0].location.url, | 662 | manifest.dependencies.values()[0].location.url, |
| 665 | ); | 663 | ); |
| 666 | try testing.expectEqualStrings( | 664 | try testing.expectEqualStrings( |
| 667 | "1220f1b680b6065fcfc94fe777f22e73bcb7e2767e5f4d99d4255fe76ded69c7a35f", | 665 | "libmp3lame-3.100.1-6-67wlF_KvEwDRCT3pTpcDzi5KGntWCEoM-WtvVPEWdlk5", |
| 668 | manifest.dependencies.values()[0].hash orelse return error.TestFailed, | 666 | manifest.dependencies.values()[0].hash orelse return error.TestFailed, |
| 669 | ); | 667 | ); |
| 670 | 668 |