From 8a23ea6e94be10a479d2f8d12e3f38f39036e894 Mon Sep 17 00:00:00 2001 From: kbkpbot Date: Fri, 28 Mar 2025 17:10:43 +0800 Subject: [PATCH] ci, x.crypto.chacha20: fix overflow detected in the sanitized runs on the CI (#24064) --- vlib/x/crypto/chacha20/chacha.v | 59 +++----- vlib/x/crypto/chacha20/chacha_64bitctr_test.v | 134 ++++++++++++------ 2 files changed, 115 insertions(+), 78 deletions(-) diff --git a/vlib/x/crypto/chacha20/chacha.v b/vlib/x/crypto/chacha20/chacha.v index 2e3aa32a3e..9d3c49edb3 100644 --- a/vlib/x/crypto/chacha20/chacha.v +++ b/vlib/x/crypto/chacha20/chacha.v @@ -46,8 +46,6 @@ mut: key [8]u32 nonce [4]u32 - // Flag indicates whether this cipher's counter has reached the limit - overflow bool // Flag that tells whether this cipher was an extended XChaCha20 standard variant. // only make sense when mode == .standard extended bool @@ -170,12 +168,8 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) { // check for counter overflow num_blocks := (u64(src_len) + block_size - 1) / block_size - ctr := c.load_ctr() - max := c.max_ctr_value() - if c.overflow || ctr + num_blocks > max { - panic('chacha20: counter overflow') - } else if ctr + num_blocks == max { - c.overflow = true + if c.check_for_ctr_overflow(num_blocks) { + panic('chacha20: internal counter overflow') } // take the most full bytes of multiples block_size from the src, @@ -188,18 +182,6 @@ pub fn (mut c Cipher) xor_key_stream(mut dst []u8, src []u8) { idx += full src_len -= full - // we dont support bufsize - // FIXME: instead generates once block keystream again, i think we can panic here - if ctr + 1 > max { - c.block = []u8{len: block_size} - numblocks := (src_len + block_size - 1) / block_size - mut buf := c.block[block_size - numblocks * block_size..] - _ := copy(mut buf, src[idx..]) - c.chacha20_block_generic(mut buf, buf) - m := copy(mut dst[idx..], buf) - c.length = buf.len - m - return - } // If we have a partial block, pad it for chacha20_block_generic, and // keep the leftover keystream for the next invocation. if src_len > 0 { @@ -274,14 +256,10 @@ fn (mut c Cipher) chacha20_block_generic(mut dst []u8, src []u8) { if dst.len != src.len || dst.len % block_size != 0 { panic('chacha20: internal error: wrong dst and/or src length') } - // Safety checks to make sure increasing current cipher's counter - // by nr_block was not overflowing internal counter. - ctr := c.load_ctr() - max := c.max_ctr_value() - num_block := u64((src.len + block_size - 1) / block_size) - // FIXME: i think its can wrap - if ctr + num_block > max { - panic('Adding num_block to the current counter lead to overflow') + // check for counter overflow + num_blocks := u64((src.len + block_size - 1) / block_size) + if c.check_for_ctr_overflow(num_blocks) { + panic('chacha20: internal counter overflow') } // initializes ChaCha20 state @@ -431,7 +409,6 @@ pub fn (mut c Cipher) reset() { c.block.reset() } c.length = 0 - c.overflow = false c.precomp = false c.p1, c.p5, c.p9, c.p13 = u32(0), u32(0), u32(0), u32(0) @@ -440,14 +417,8 @@ pub fn (mut c Cipher) reset() { } // set_counter sets Cipher's counter +@[direct_array_access; inline] pub fn (mut c Cipher) set_counter(ctr u64) { - max_ctr := c.max_ctr_value() - - if c.overflow || ctr > max_ctr { - panic('counter overflow') - } else if ctr == max_ctr { - c.overflow = true - } match c.mode { .original { c.nonce[0] = u32(ctr) @@ -563,10 +534,11 @@ fn quarter_round(a u32, b u32, c u32, d u32) (u32, u32, u32, u32) { // Cipher's counter handling routine // // We define counter limit to simplify the access -const max_64bit_counter = u64(1 << 63) - 1 // not fully max_u64 +const max_64bit_counter = max_u64 const max_32bit_counter = u64(max_u32) // load_ctr loads underlying cipher's counter as u64 value. +@[direct_array_access; inline] fn (c Cipher) load_ctr() u64 { match c.mode { // In the original mode, counter was 64-bit size @@ -582,6 +554,7 @@ fn (c Cipher) load_ctr() u64 { } // max_ctr_value returns maximum value of cipher's counter. +@[inline] fn (c Cipher) max_ctr_value() u64 { match c.mode { .original { return max_64bit_counter } @@ -607,3 +580,15 @@ fn derive_xchacha20_key_nonce(key []u8, nonce []u8) !([]u8, []u8) { return new_key, new_nonce } + +@[direct_array_access; inline] +fn (c Cipher) check_for_ctr_overflow(add_value u64) bool { + // check for counter overflow + ctr := c.load_ctr() + sum := ctr + add_value + max := c.max_ctr_value() + if sum < ctr || sum < add_value || sum > max { + return true + } + return false +} diff --git a/vlib/x/crypto/chacha20/chacha_64bitctr_test.v b/vlib/x/crypto/chacha20/chacha_64bitctr_test.v index ece149f4b2..7e7505bed7 100644 --- a/vlib/x/crypto/chacha20/chacha_64bitctr_test.v +++ b/vlib/x/crypto/chacha20/chacha_64bitctr_test.v @@ -2,50 +2,88 @@ module chacha20 import encoding.hex +struct Test64BitXorKeyStream { + counter u64 + key []u8 + nonce []u8 + msgs [][]u8 + expected_output [][]u8 +} + +const ch20_64bitctr_xor_key_stream_testdata = [ + Test64BitXorKeyStream{ + counter: u64(0) + key: [u8(225), 2, 1, 178, 238, 127, 187, 188, 27, 237, 18, 62, 181, 65, 67, + 152, 13, 247, 147, 148, 101, 220, 185, 120, 234, 58, 144, 173, 3, 218, 193, 130] + nonce: [u8(153), 221, 244, 134, 99, 135, 243, 247] + msgs: [[u8(231), 121, 9, 28], + [u8(178), 221, 62, 9, 153, 189, 106, 12, 117, 47, 192, 81, 65, 112, 85, 57], + [u8(155), 202, 56, 16], [u8(227), 47, 226, 137], [u8(162), 77, 218, 52], + [u8(42), 250, 184, 196], + [u8(2), 129, 13, 136, 6, 12, 235, 183, 38, 178, 151, 243, 27, + 88, 97, 40], + [u8(248), 170, 168, 206], [u8(181), 220, 223, 139], + [u8(95), 108, 201, 227], [u8(38), 221, 147, 230], + [u8(98), 229, 5, 130, 13, 103, 248, 159, 240, 246, 56, 119, 160, 130, 82, 222], + 'hello hello hello'.bytes(), 'me me me'.bytes()] + expected_output: [[u8(14), 153, 137, 166], + [u8(132), 202, 57, 24, 78, 201, 133, 147, 175, 207, 224, 48, 197, 188, 230, 120], + [u8(243), 129, 27, 186], [u8(103), 184, 201, 233], + [u8(99), 236, 76, 148], [u8(12), 206, 236, 195], + [u8(72), 82, 44, 56, 164, 43, 98, 29, + 182, 98, 48, 235, 189, 181, 216, 152], + [u8(229), 19, 86, 45], [u8(109), 199, 143, 155], [u8(2), 33, 39, 189], + [u8(170), 82, 98, 126], + [u8(29), 236, 210, 171, 117, 132, 115, 18, 7, 18, 248, 97, 165, + 21, 147, 241], + [u8(211), 17, 84, 145, 207, 106, 145, 1, 245, 189, 8, 45, 71, 248, 44, 15, 201], + [u8(124), 195, 175, 137, 33, 119, 236, 120]] + }, + Test64BitXorKeyStream{ + counter: u64(0) + key: [u8(225), 2, 1, 178, 238, 127, 187, 188, 27, 237, 18, 62, 181, 65, 67, + 152, 13, 247, 147, 148, 101, 220, 185, 120, 234, 58, 144, 173, 3, 218, 193, 130] + nonce: [u8(255), 255, 255, 255, 255, 255, 255, 255] + msgs: [[u8(231), 121, 9, 28], + [u8(178), 221, 62, 9, 153, 189, 106, 12, 117, 47, 192, 81, 65, 112, 85, 57], + [u8(155), 202, 56, 16], [u8(227), 47, 226, 137], [u8(162), 77, 218, 52], + [u8(42), 250, 184, 196], + [u8(2), 129, 13, 136, 6, 12, 235, 183, 38, 178, 151, 243, 27, + 88, 97, 40], + [u8(248), 170, 168, 206], [u8(181), 220, 223, 139], + [u8(95), 108, 201, 227], [u8(38), 221, 147, 230], + [u8(98), 229, 5, 130, 13, 103, 248, 159, 240, 246, 56, 119, 160, 130, 82, 222], + 'hello hello hello'.bytes(), 'me me me'.bytes()] + expected_output: [[u8(162), 67, 82, 161], + [u8(138), 249, 111, 254, 34, 94, 136, 172, 147, 173, 214, 184, 250, 152, 22, 77], + [u8(179), 226, 192, 110], [u8(117), 28, 66, 178], + [u8(71), 179, 143, 86], [u8(234), 244, 13, 254], + [u8(70), 147, 151, 204, 172, 47, 185, + 46, 60, 122, 249, 255, 76, 140, 10, 92], + [u8(255), 163, 155, 167], [u8(114), 39, 46, 102], + [u8(35), 253, 18, 138], [u8(90), 176, 119, 92], + [u8(69), 177, 221, 87, 160, 165, 250, + 243, 112, 167, 125, 252, 75, 182, 253, 234], + [u8(73), 87, 169, 226, 132, 78, 156, 251, 140, 144, 194, 27, 162, 99, 14, 213, 138], + [u8(218), 93, 82, 52, 120, 105, 166, 160]] + }, +] + // This test `xor_key_stream` for cipher with 64-bit counter // This samples data, was generated with golang script shared at https://go.dev/play/p/MWKBO5dNcTq fn test_chacha20_xor_key_stream_with_64bit_counter() ! { - key := [u8(225), 2, 1, 178, 238, 127, 187, 188, 27, 237, 18, 62, 181, 65, 67, 152, 13, 247, - 147, 148, 101, 220, 185, 120, 234, 58, 144, 173, 3, 218, 193, 130] - nonce := [u8(153), 221, 244, 134, 99, 135, 243, 247] - - // creates original 64-bit counter ciphers - mut c := new_cipher(key, nonce)! - - series_of_msg := [[u8(231), 121, 9, 28], - [u8(178), 221, 62, 9, 153, 189, 106, 12, 117, 47, 192, 81, 65, 112, 85, 57], - [u8(155), 202, 56, 16], [u8(227), 47, 226, 137], [u8(162), 77, 218, 52], - [u8(42), 250, 184, 196], - [u8(2), 129, 13, 136, 6, 12, 235, 183, 38, 178, 151, 243, 27, 88, - 97, 40], - [u8(248), 170, 168, 206], [u8(181), 220, 223, 139], [u8(95), 108, 201, 227], - [u8(38), 221, 147, 230], - [u8(98), 229, 5, 130, 13, 103, 248, 159, 240, 246, 56, 119, 160, - 130, 82, 222], - 'hello hello hello'.bytes(), 'me me me'.bytes()] - - expected_output := [ - [u8(14), 153, 137, 166], - [u8(132), 202, 57, 24, 78, 201, 133, 147, 175, 207, 224, 48, 197, 188, 230, 120], - [u8(243), 129, 27, 186], - [u8(103), 184, 201, 233], - [u8(99), 236, 76, 148], - [u8(12), 206, 236, 195], - [u8(72), 82, 44, 56, 164, 43, 98, 29, 182, 98, 48, 235, 189, 181, 216, 152], - [u8(229), 19, 86, 45], - [u8(109), 199, 143, 155], - [u8(2), 33, 39, 189], - [u8(170), 82, 98, 126], - [u8(29), 236, 210, 171, 117, 132, 115, 18, 7, 18, 248, 97, 165, 21, 147, 241], - [u8(211), 17, 84, 145, 207, 106, 145, 1, 245, 189, 8, 45, 71, 248, 44, 15, 201], - [u8(124), 195, 175, 137, 33, 119, 236, 120], - ] - for i := 0; i < series_of_msg.len; i++ { - msg := series_of_msg[i] - exp := expected_output[i] - mut dst := []u8{len: msg.len} - c.xor_key_stream(mut dst, msg) - assert dst == exp + for item in ch20_64bitctr_xor_key_stream_testdata { + assert item.msgs.len == item.expected_output.len + // creates original 64-bit counter ciphers + mut c := new_cipher(item.key, item.nonce)! + c.set_counter(item.counter) + for i := 0; i < item.msgs.len; i++ { + msg := item.msgs[i] + exp := item.expected_output[i] + mut dst := []u8{len: msg.len} + c.xor_key_stream(mut dst, msg) + assert dst == exp + } } } @@ -231,4 +269,18 @@ const ch20_64bitctr_testdata = [ plaintext: '941998a93507ef7037eb04ff250aa2ac09ee4c7ca880b49bbb3ec34f476658c6bc29db656d40a0e9360721040bdc28617adab92574a730e8be8fb89964f8af011c8ff59ac09676643b7eef31d9fc85964b132dafce8e692ad7a921813bc1e1b3460fc1e26ee1754aa0cdfd5e4953571adac3c84b9678700590e5c976e240ffa01054e9badace91d5f39cd217d860af6ae6051089b0a32fa0e52680dbbb7afc99e20d42f89d87713f1c0fe51fc315948308d15cdeace2c46b8ffd6783b13034ca220e0381e5c77180ef844060f8eb33ade84c04602a509352af3d0222190643789acbbdd8a69a36abc68eb3175f1838333f970b94043811103ae1ef737e58577e8aa6aa41af187854e1924cc9ebcc785ba7252c57c75344a3b1dd8e6c0d3992a994516d2d5451e9059d044103c50e7f8e028b486664f986a496b197885d2de8525f879e7b7a0b5826eebd8412797a8af4345e0b63fb7957e05188750b02e796e055db133eb1bcd9e3f0e650e955e4efd496704166f6efa51737277d4c5cc0f45f' ciphertext: '2ae8f96aa8228a9fa1c3ab60ea1af2ed611b845ef023ff918cabc8faa0db09b7dca7e2f8329305fabf37f009b925d7d8a5cb336c31185c2929f52ad1fd84e64986cc8bdbef32ce4e04406e318a9020f3470ae2fbafd0c42cc3f2debf4fcb6398acd67415e8b2457e9668c86b179c44479d9ff157ddc20bb7c466ebcb18592eed2d68c6f074ad87c8e5098031e8fb9630545ae998b4da77908c30dc840112d93171619def1b04b62cde3e3a79f2295e84c63e5c80dbd1602be391f28ff4c7a360003280c868862f195fda2733b5f681c28d07893630205d821ec250d368f26dd9faa016e3a99d089d271ad2b4c4e246c7938d726d5a2e9f841e506aeea45824d4ce17b20dd36742e10ec972a72e512b0bbff9fe0ecb1d9bd6edc710a485ca5b01465cf8f916196af3e5577ef6f544984c29a91f8bd4312e927477efe66f36cb70a3bbd8d47a30707004fdae69f731e5e37b943b0469c3a819342cbb469bfebd171768579fcc3fddcf53a02b6f3bc6792a774048a3867b52f7691222992e48fa97' }, + Test64BitCounter{ + count: 18446744073709551614 // fffffffffffffffe + key: '35b7328ca227b329a4d9e0bc55239338034c08c5082383bc8e22d60410de959a' + nonce: 'afa330fba5925cb0' + plaintext: '17211d01b2acf4dc24ca3a83bf95b44c0ec432cecfd692d6af273a72ee0320ef7c609ec234ae3dc528ebff03357eaed795b8bd6cfb36411c2e291a1daafaebf67f6ccb94e1bfe017a6f04657312502eca502fbd41111a4242a8a9ad8571f487b5f75394b385caf21bcb85ec30de4088ed6c448739536eb50c8fa1b6f664db8cd' + ciphertext: 'd3980299fe1bb99ec70d42630c6c8d933fe2f161f1608ccc57a925818f2d6dbffc9d92a159f5883ce3d732b95521da45678529f8db08c22e7e01bebb4697039873880f8586d6e3be6048c346057959a63a3c17a5d96c158059bdf28963fe910b2ca900baf6ee879653f4f63ef0fe883afd83c540adbe21d97b736a478fc0cba4' + }, + Test64BitCounter{ + count: 18446744073709551615 // ffffffffffffffff + key: 'ad20701d42494e2d907838bc872e54511b0ed084fc64ec0a44d0269b331da9ab' + nonce: '31c9ac97f9023ed3' + plaintext: '63058a0f6c9e238838f3c3a68c1dbb9fa0028880f9bc9ce72531a630eb0608d351d7168470a5eda7537d6fb12f0e961967acd7d6e2d6eacc90cc3b00fd279575ef6a5c0175e7aedb0ae83ee0f7817fc8da2250dd7eac7005ce98159abf3f86eb89ed4299fd6ae6033c884190b3225676d7d5ba11f5e25a9a58ffcff4264d1c37' + ciphertext: '6cda3c0f72707d18ba81f92427407e5498737af0189efe7821e07b8a1323437764defbe120487d39327c0a2eff390ee58d7bceb3bdcf770c9448b3226019de7e933216080012663df8933d16be54e399aafe3f86594bd6b2f87fe9b8ca8767b37fdfab7968b5f1ce25ee8acb2c5d05825eb6cde9ce5a700c5e249976a3b03eea' + }, ]