From 057bf1afc9933e32bd35842d2464dab2164f06fb Mon Sep 17 00:00:00 2001 From: LemonBoy Date: Thu, 18 Feb 2021 20:28:59 +0100 Subject: [PATCH] std: Add more error checking in hexToBytes Prevent the function from turning into an endless loop that may or may not perform OOB accesses. --- lib/std/crypto/25519/ed25519.zig | 10 +++++----- lib/std/crypto/25519/ristretto255.zig | 2 +- lib/std/crypto/25519/x25519.zig | 4 ++-- lib/std/crypto/aes.zig | 8 ++++---- lib/std/crypto/blake3.zig | 2 +- lib/std/crypto/gimli.zig | 22 +++++++++++----------- lib/std/fmt.zig | 25 ++++++++++++++++++------- src/Cache.zig | 2 +- 8 files changed, 43 insertions(+), 32 deletions(-) diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index 420eb33a30..5c7ec0cdac 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -207,7 +207,7 @@ pub const Ed25519 = struct { test "ed25519 key pair creation" { var seed: [32]u8 = undefined; - try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); const key_pair = try Ed25519.KeyPair.create(seed); var buf: [256]u8 = undefined; std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); @@ -216,7 +216,7 @@ test "ed25519 key pair creation" { test "ed25519 signature" { var seed: [32]u8 = undefined; - try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); const key_pair = try Ed25519.KeyPair.create(seed); const sig = try Ed25519.sign("test", key_pair, null); @@ -339,11 +339,11 @@ test "ed25519 test vectors" { }; for (entries) |entry, i| { var msg: [entry.msg_hex.len / 2]u8 = undefined; - try fmt.hexToBytes(&msg, entry.msg_hex); + _ = try fmt.hexToBytes(&msg, entry.msg_hex); var public_key: [32]u8 = undefined; - try fmt.hexToBytes(&public_key, entry.public_key_hex); + _ = try fmt.hexToBytes(&public_key, entry.public_key_hex); var sig: [64]u8 = undefined; - try fmt.hexToBytes(&sig, entry.sig_hex); + _ = try fmt.hexToBytes(&sig, entry.sig_hex); if (entry.expected) |error_type| { std.testing.expectError(error_type, Ed25519.verify(sig, &msg, public_key)); } else { diff --git a/lib/std/crypto/25519/ristretto255.zig b/lib/std/crypto/25519/ristretto255.zig index 68fb938103..df85422f65 100644 --- a/lib/std/crypto/25519/ristretto255.zig +++ b/lib/std/crypto/25519/ristretto255.zig @@ -173,7 +173,7 @@ test "ristretto255" { std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{p.toBytes()}), "E2F2AE0A6ABC4E71A884A961C500515F58E30B6AA582DD8DB6A65945E08D2D76"); var r: [Ristretto255.encoded_length]u8 = undefined; - try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); + _ = try fmt.hexToBytes(r[0..], "6a493210f7499cd17fecb510ae0cea23a110e8d5b901f8acadd3095c73a3b919"); var q = try Ristretto255.fromBytes(r); q = q.dbl().add(p); std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{q.toBytes()}), "E882B131016B52C1D3337080187CF768423EFCCBB517BB495AB812C4160FF44E"); diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index 530637a451..5d0479bd4d 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -85,8 +85,8 @@ const htest = @import("../test.zig"); test "x25519 public key calculation from secret key" { var sk: [32]u8 = undefined; var pk_expected: [32]u8 = undefined; - try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); + _ = try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); + _ = try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); const pk_calculated = try X25519.recoverPublicKey(sk); std.testing.expectEqual(pk_calculated, pk_expected); } diff --git a/lib/std/crypto/aes.zig b/lib/std/crypto/aes.zig index d862bcf3fc..2a81492c8a 100644 --- a/lib/std/crypto/aes.zig +++ b/lib/std/crypto/aes.zig @@ -122,11 +122,11 @@ test "expand 128-bit key" { var exp: [16]u8 = undefined; for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_enc[i]); + _ = try std.fmt.hexToBytes(&exp, exp_enc[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_dec[i]); + _ = try std.fmt.hexToBytes(&exp, exp_dec[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } } @@ -144,11 +144,11 @@ test "expand 256-bit key" { var exp: [16]u8 = undefined; for (enc.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_enc[i]); + _ = try std.fmt.hexToBytes(&exp, exp_enc[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } for (dec.key_schedule.round_keys) |round_key, i| { - try std.fmt.hexToBytes(&exp, exp_dec[i]); + _ = try std.fmt.hexToBytes(&exp, exp_dec[i]); testing.expectEqualSlices(u8, &exp, &round_key.toBytes()); } } diff --git a/lib/std/crypto/blake3.zig b/lib/std/crypto/blake3.zig index 7a65487135..a10c50b074 100644 --- a/lib/std/crypto/blake3.zig +++ b/lib/std/crypto/blake3.zig @@ -663,7 +663,7 @@ fn testBlake3(hasher: *Blake3, input_len: usize, expected_hex: [262]u8) void { // Compare to expected value var expected_bytes: [expected_hex.len / 2]u8 = undefined; - fmt.hexToBytes(expected_bytes[0..], expected_hex[0..]) catch unreachable; + _ = fmt.hexToBytes(expected_bytes[0..], expected_hex[0..]) catch unreachable; testing.expectEqual(actual_bytes, expected_bytes); // Restore initial state diff --git a/lib/std/crypto/gimli.zig b/lib/std/crypto/gimli.zig index 4809b9b6f7..1c1d6c79db 100644 --- a/lib/std/crypto/gimli.zig +++ b/lib/std/crypto/gimli.zig @@ -270,7 +270,7 @@ pub fn hash(out: []u8, in: []const u8, options: Hash.Options) void { test "hash" { // a test vector (30) from NIST KAT submission. var msg: [58 / 2]u8 = undefined; - try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C"); + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C"); var md: [32]u8 = undefined; hash(&md, &msg, .{}); htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md); @@ -278,7 +278,7 @@ test "hash" { test "hash test vector 17" { var msg: [32 / 2]u8 = undefined; - try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F"); + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F"); var md: [32]u8 = undefined; hash(&md, &msg, .{}); htest.assertEqual("404C130AF1B9023A7908200919F690FFBB756D5176E056FFDE320016A37C7282", &md); @@ -286,7 +286,7 @@ test "hash test vector 17" { test "hash test vector 33" { var msg: [32]u8 = undefined; - try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + _ = try std.fmt.hexToBytes(&msg, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); var md: [32]u8 = undefined; hash(&md, &msg, .{}); htest.assertEqual("A8F4FA28708BDA7EFB4C1914CA4AFA9E475B82D588D36504F87DBB0ED9AB3C4B", &md); @@ -436,9 +436,9 @@ pub const Aead = struct { test "cipher" { var key: [32]u8 = undefined; - try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + _ = try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); var nonce: [16]u8 = undefined; - try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F"); + _ = try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F"); { // test vector (1) from NIST KAT submission. const ad: [0]u8 = undefined; const pt: [0]u8 = undefined; @@ -456,7 +456,7 @@ test "cipher" { { // test vector (34) from NIST KAT submission. const ad: [0]u8 = undefined; var pt: [2 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "00"); + _ = try std.fmt.hexToBytes(&pt, "00"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -470,9 +470,9 @@ test "cipher" { } { // test vector (106) from NIST KAT submission. var ad: [12 / 2]u8 = undefined; - try std.fmt.hexToBytes(&ad, "000102030405"); + _ = try std.fmt.hexToBytes(&ad, "000102030405"); var pt: [6 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102"); + _ = try std.fmt.hexToBytes(&pt, "000102"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -486,9 +486,9 @@ test "cipher" { } { // test vector (790) from NIST KAT submission. var ad: [60 / 2]u8 = undefined; - try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D"); + _ = try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D"); var pt: [46 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516"); + _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; @@ -503,7 +503,7 @@ test "cipher" { { // test vector (1057) from NIST KAT submission. const ad: [0]u8 = undefined; var pt: [64 / 2]u8 = undefined; - try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); + _ = try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F"); var ct: [pt.len]u8 = undefined; var tag: [16]u8 = undefined; diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 86d89a7578..e5ce457091 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1982,23 +1982,34 @@ test "bytes.hex" { pub const trim = @compileError("deprecated; use std.mem.trim with std.ascii.spaces instead"); pub const isWhiteSpace = @compileError("deprecated; use std.ascii.isSpace instead"); -pub fn hexToBytes(out: []u8, input: []const u8) !void { - if (out.len * 2 < input.len) +/// Decodes the sequence of bytes represented by the specified string of +/// hexadecimal characters. +/// Returns a slice of the output buffer containing the decoded bytes. +pub fn hexToBytes(out: []u8, input: []const u8) ![]u8 { + // Expect 0 or n pairs of hexadecimal digits. + if (input.len & 1 != 0) return error.InvalidLength; + if (out.len * 2 < input.len) + return error.NoSpaceLeft; var in_i: usize = 0; - while (in_i != input.len) : (in_i += 2) { + while (in_i < input.len) : (in_i += 2) { const hi = try charToDigit(input[in_i], 16); const lo = try charToDigit(input[in_i + 1], 16); out[in_i / 2] = (hi << 4) | lo; } + + return out[0 .. in_i / 2]; } test "hexToBytes" { - const test_hex_str = "909A312BB12ED1F819B3521AC4C1E896F2160507FFC1C8381E3B07BB16BD1706"; - var pb: [32]u8 = undefined; - try hexToBytes(pb[0..], test_hex_str); - try expectFmt(test_hex_str, "{X}", .{pb}); + var buf: [32]u8 = undefined; + try expectFmt("90" ** 32, "{X}", .{try hexToBytes(&buf, "90" ** 32)}); + try expectFmt("ABCD", "{X}", .{try hexToBytes(&buf, "ABCD")}); + try expectFmt("", "{X}", .{try hexToBytes(&buf, "")}); + std.testing.expectError(error.InvalidCharacter, hexToBytes(&buf, "012Z")); + std.testing.expectError(error.InvalidLength, hexToBytes(&buf, "AAA")); + std.testing.expectError(error.NoSpaceLeft, hexToBytes(buf[0..1], "ABAB")); } test "formatIntValue with comptime_int" { diff --git a/src/Cache.zig b/src/Cache.zig index f5ffb34dbe..57ff9227fa 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -317,7 +317,7 @@ pub const Manifest = struct { cache_hash_file.stat.size = fmt.parseInt(u64, size, 10) catch return error.InvalidFormat; cache_hash_file.stat.inode = fmt.parseInt(fs.File.INode, inode, 10) catch return error.InvalidFormat; cache_hash_file.stat.mtime = fmt.parseInt(i64, mtime_nsec_str, 10) catch return error.InvalidFormat; - std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; + _ = std.fmt.hexToBytes(&cache_hash_file.bin_digest, digest_str) catch return error.InvalidFormat; if (file_path.len == 0) { return error.InvalidFormat;