Perjalanan panjang ke Tox-rs. Bagian 1

Logo keracunan

Hai semuanya!


Saya suka Tox dan menghormati para peserta proyek ini dan pekerjaan mereka. Dalam upaya membantu pengembang dan pengguna Tox, saya melihat ke dalam kode dan melihat potensi masalah yang dapat mengarah pada rasa aman yang salah. Sejak saya awalnya menerbitkan artikel ini pada tahun 2016 ( dalam bahasa Rusia ), banyak perbaikan telah dilakukan untuk Tox, dan saya memimpin sebuah tim yang menulis ulang perangkat lunak Tox aman dari awal menggunakan bahasa pemrograman Rust (lihat Tox-rs ). Saya sarankan menggunakan racun pada tahun 2019. Mari kita lihat apa yang sebenarnya membuat kita menulis ulang Tox di Rust.


Artikel asli 2016


Ada kecenderungan tidak sehat untuk melebih-lebihkan keamanan sistem E2E hanya atas dasar bahwa mereka E2E. Saya akan menyajikan fakta-fakta obyektif yang dilengkapi dengan komentar saya sendiri agar Anda menarik kesimpulan sendiri.


Spoiler: Pengembang Tox setuju dengan poin saya dan permintaan tarik kode sumber saya diterima.


Fakta No. 1. cabang utama gagal tes


Semuanya dimulai dengan artikel tentang Habr tentang menginstal node ( dalam bahasa Rusia ).
Dalam komentar, orang-orang mengeluh tentang kerumitan membangun dan menginstal node pada CentOS, jadi saya memutuskan untuk menulis sistem build di CMake. Setelah beberapa hari, saya siap untuk mempresentasikan PR saya ke komunitas Tox di Freenode, tetapi saya menemui kekurangan pemahaman:


seseorang telah berkontribusi pada cmake awalnya, tetapi pengembang lain tidak tahu bagaimana menggunakannya dan tidak dapat membuatnya membangun kode mereka, jadi mereka beralih ke autotools (sic!), yang mereka menjadi tahu lebih baik sekarang.

Saya mencatat bahwa kode yang gagal dalam tes di Travis CI masih diterima di cabang utama, tetapi mereka menjawab: "kami mengerti kami perlu melakukan sesuatu dengan tes, tetapi biarkan untuk sekarang."


Selanjutnya, saya menyelam lebih dalam untuk melihat kode dari utusan yang menarik ini.


Fakta No. 2. memset (ptr, 0, size) sebelum menelepon gratis


Mataku tertangkap


memset(c, 0, sizeof(Net_Crypto)); free(c); 

Jika Anda masih belum terbiasa dengan PVS-Studio dan artikelnya tentang fungsi memset PVS-Studio: kompiler dapat menghapus panggilan fungsi 'memset' jika wilayah memori tersebut tidak digunakan setelahnya. Logika kompiler sangat mudah: "Anda tidak akan menggunakan variabel ini setelah memanggil 'gratis', memset tidak akan mempengaruhi perilaku yang diamati, izinkan saya menghapus panggilan tidak berguna ini ke 'memset'".


Sebagai siswa yang rajin, saya mengganti setiap kemunculan memset($DST, 0, $SIZE) dengan sodium_memzero dan TEST CRASHED.


Fakta No. 3. Perbandingan kunci publik rentan terhadap serangan waktu


Ada fungsi khusus yang sangat bagus untuk membandingkan kunci publik di toxcore :


 /* compare 2 public keys of length crypto_box_PUBLICKEYBYTES, not vulnerable to timing attacks. returns 0 if both mem locations of length are equal, return -1 if they are not. */ int public_key_cmp(const uint8_t *pk1, const uint8_t *pk2) { return crypto_verify_32(pk1, pk2); } 

crypto_verify_32 - adalah fungsi dari pustaka NaCL / Sodium crypto, yang dapat membantu Anda menghindari serangan timing, karena ia bekerja dalam waktu yang konstan, sementara memcmp dapat merusak byte pertama yang tidak sama. Anda harus menggunakan crypto_verify_32 untuk membandingkan data sensitif seperti kunci.


Perbandingan string yang dilakukan byte-per-byte rentan terhadap eksploitasi oleh serangan timing, misalnya untuk memalsukan MAC (lihat kerentanan ini di perpustakaan Google Keyczar crypto).


Basis kode dari proyek toxcore cukup luas, itulah sebabnya Tox dilahirkan dengan bug kerentanan waktu:


 bool id_equal(const uint8_t *dest, const uint8_t *src) { return memcmp(dest, src, crypto_box_PUBLICKEYBYTES) == 0; } 

Tapi itu belum semuanya. Pengembang masih lebih suka membandingkan kunci dengan caranya sendiri menggunakan tiga fungsi berbeda: id_equal atau public_key_cmp dan crypto_verify_32 .
Berikut ini adalah keluaran grep pendek dari DHT, routing bawang merah dan subsistem penting lainnya:


 if (memcmp(ping->to_ping[i].public_key, public_key, crypto_box_PUBLICKEYBYTES) == 0) { if (memcmp(public_key, onion_c->friends_list[i].real_public_key, crypto_box_PUBLICKEYBYTES) == 0) if (memcmp(public_key, onion_c->path_nodes_bs[i].public_key, crypto_box_PUBLICKEYBYTES) == 0) if (memcmp(dht_public_key, dht_public_key_temp, crypto_box_PUBLICKEYBYTES) != 0) if (Local_ip(ip_port.ip) && memcmp(friend_con->dht_temp_pk, public_key, crypto_box_PUBLICKEYBYTES) == 0) 

Fakta No. 4. increment_nonce dalam waktu yang tidak konstan


 /* Increment the given nonce by 1. */ void increment_nonce(uint8_t *nonce) { uint32_t i; for (i = crypto_box_NONCEBYTES; i != 0; --i) { ++nonce[i - 1]; if (nonce[i - 1] != 0) break; // <=== sic! } } 

Jika operasi tersebut melibatkan parameter rahasia, variasi waktu ini dapat membocorkan beberapa informasi. Dengan pengetahuan implementasi yang cukup, analisis statistik yang cermat bahkan dapat mengarah pada pemulihan total parameter rahasia.


Ada fungsi khusus dalam Sodium untuk meningkatkan nonces:


Documentssodium_increment () dapat digunakan untuk meningkatkan nonces dalam waktu yang konstan.
Kode
 void sodium_increment(unsigned char *n, const size_t nlen) { size_t i = 0U; uint_fast16_t c = 1U; for (; i < nlen; i++) { c += (uint_fast16_t) n[i]; n[i] = (unsigned char) c; c >>= 8; } } 

Telur paskah yang tidak disengaja ironis adalah bahwa fungsi increment_nonce terletak di file yang dimulai dengan kata-kata:


Kode ini harus sempurna. Kami tidak main-main dengan enkripsi.

Mari kita lihat lebih dekat file yang sempurna ini.


Fakta No. 5. Anda dapat menemukan kunci dan data pribadi di tumpukan


Kode merepotkan:


 /* Precomputes the shared key from their public_key and our secret_key. * This way we can avoid an expensive elliptic curve scalar multiply for each * encrypt/decrypt operation. * enc_key has to be crypto_box_BEFORENMBYTES bytes long. */ void encrypt_precompute(const uint8_t *public_key, const uint8_t *secret_key, uint8_t *enc_key) { crypto_box_beforenm(enc_key, public_key, secret_key); // Nacl/Sodium function } /* Encrypts plain of length length to encrypted of length + 16 using the * public key(32 bytes) of the receiver and the secret key of the sender and a 24 byte nonce. * * return -1 if there was a problem. * return length of encrypted data if everything was fine. */ int encrypt_data(const uint8_t *public_key, const uint8_t *secret_key, const uint8_t *nonce, const uint8_t *plain, uint32_t length, uint8_t *encrypted) { uint8_t k[crypto_box_BEFORENMBYTES]; encrypt_precompute(public_key, secret_key, k); // toxcore function return encrypt_data_symmetric(k, nonce, plain, length, encrypted); // toxcore function } 

encrypt_data_symmetric calls crypto_box_detached_afternm dari Nacl / Sodium, saya tidak akan memasukkan seluruh kode, ini adalah tautan untuk memeriksa sendiri.


Tampaknya sulit membuat kesalahan dalam empat baris kode, bukan?


Mari kita menggali Sodium:


 int crypto_box_detached(unsigned char *c, unsigned char *mac, const unsigned char *m, unsigned long long mlen, const unsigned char *n, const unsigned char *pk, const unsigned char *sk) { unsigned char k[crypto_box_BEFORENMBYTES]; int ret; (void) sizeof(int[crypto_box_BEFORENMBYTES >= crypto_secretbox_KEYBYTES ? 1 : -1]); if (crypto_box_beforenm(k, pk, sk) != 0) { return -1; } ret = crypto_box_detached_afternm(c, mac, m, mlen, n, k); sodium_memzero(k, sizeof k); return ret; } 

Menghapus semua cek yang kami dapatkan:


  unsigned char k[crypto_box_BEFORENMBYTES]; int ret; crypto_box_beforenm(k, pk, sk); ret = crypto_box_detached_afternm(c, mac, m, mlen, n, k); sodium_memzero(k, sizeof k); return ret; 

Apakah itu terlihat familier? Ya! Ini adalah kode fungsi yang sedikit dimodifikasi, mengenkripsi data dari toxcore, satu-satunya perbedaan adalah mereka lupa membersihkan kunci pada stack dengan sodium_memzero ... Dan ada juga kesalahan di: handle_TCP_handshake , handle_handshake , dan mungkin di tempat lain juga.


Fakta No. 6. Peringatan kompiler adalah untuk dummiez!


Para dev dari proyek toxcore dengan tegas menyangkal perlunya menghidupkan semua peringatan kompiler, atau mereka tidak tahu tentang mereka.


Fungsi yang tidak digunakan (Saya sangat senang dengan peringatan dalam pengujian):


 ../auto_tests/dht_test.c:351:12: warning: unused function 'test_addto_lists_ipv4' [-Wunused-function] START_TEST(test_addto_lists_ipv4) ^ ../auto_tests/dht_test.c:360:12: warning: unused function 'test_addto_lists_ipv6' [-Wunused-function] START_TEST(test_addto_lists_ipv6) ^ ../toxcore/TCP_server.c:1026:13: warning: unused function 'do_TCP_accept_new' [-Wunused-function] static void do_TCP_accept_new(TCP_Server *TCP_server) ^ ../toxcore/TCP_server.c:1110:13: warning: unused function 'do_TCP_incomming' [-Wunused-function] static void do_TCP_incomming(TCP_Server *TCP_server) ^ ../toxcore/TCP_server.c:1119:13: warning: unused function 'do_TCP_unconfirmed' [-Wunused-function] static void do_TCP_unconfirmed(TCP_Server *TCP_server) ^ 

 ../toxcore/Messenger.c:2040:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ ../toxcore/Messenger.c:2095:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ ../toxcore/Messenger.c:2110:28: warning: comparison of constant 256 with expression of type 'uint8_t' (aka 'unsigned char') is always false [-Wtautological-constant-out-of-range-compare] if (filenumber >= MAX_CONCURRENT_FILE_PIPES) ~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~ 

 ../auto_tests/TCP_test.c:205:24: warning: unsequenced modification and access to 'len' [-Wunsequenced] ck_assert_msg((len = recv(con->sock, data, length, 0)) == length, "wrong len %i\n", len); ^ ~~~ /usr/include/check.h:273:18: note: expanded from macro 'ck_assert_msg' _ck_assert_msg(expr, __FILE__, __LINE__,\ ^ 

Dan beberapa lusin peringatan tentang variabel yang tidak digunakan, perbandingan ditandatangani dan tidak ditandatangani, dan banyak lagi.


Kesimpulan saya


Kutipan dari repositori:


Kami ingin Tox sesederhana mungkin sambil tetap seaman mungkin.

Jika saya, seorang non-kriptografi, dapat menemukan bug mengerikan dalam sehari, bayangkan berapa banyak hal yang dapat ditemukan oleh spesialis kriptografi setelah sengaja menggali selama sebulan?




Versi awal Tox menimbulkan bahaya besar bagi pengguna yang mengandalkan keamanan Tox. Solusi eksklusif tidak layak dipercaya dan bahkan solusi open source tidak seaman yang Anda inginkan. Lihatlah pelanggaran keamanan terbaru di Matrix . Saat ini banyak bug diperbaiki dan Tox adalah pilihan terbaik yang tersedia untuk keamanan dan privasi bagi pengguna.


Lain kali saya akan memberi tahu Anda lebih lanjut tentang keadaan racun saat ini. Apa yang telah kami terapkan di Rust dan mengapa Anda harus mencobanya.


Reddit: komentar

Source: https://habr.com/ru/post/id447994/


All Articles