Pemecahan masalah yang biasa dalam kasus seperti itu adalah dengan hati-hati memeriksa siklus hidup objek yang terpengaruh: lihat bagaimana memori dialokasikan untuknya, bagaimana dibebaskan, bagaimana penghitung referensi diambil dan dilepaskan dengan benar, memberikan perhatian khusus pada jalur kesalahan. Namun, dalam kasus kami, objek berbeda dibungkus, dan memeriksa siklus hidup mereka tidak menemukan bug.
kmalloc-192 cache cukup populer di kernel, ia menggabungkan beberapa lusin objek berbeda. Bug dalam siklus hidup salah satunya adalah alasan yang paling mungkin untuk jenis bug ini. Bahkan hanya mencantumkan semua objek seperti itu cukup bermasalah, dan tidak ada pertanyaan untuk memeriksa semuanya. Laporan bug terus berdatangan, tetapi kami tidak berhasil menemukan penyebabnya dengan penyelidikan langsung. Diperlukan petunjuk.
Dari pihak kami, bug ini telah diselidiki oleh Andrey Ryabinin, seorang spesialis manajemen memori, yang dikenal luas di kalangan pengembang kernel yang sempit sebagai pengembang KASAN, sebuah teknologi luar biasa untuk menangkap kesalahan akses memori. Faktanya, KASANlah yang paling cocok untuk menemukan penyebab kutu kami. KASAN tidak termasuk dalam kernel asli RHEL7, tetapi Andrey memindahkan patch yang diperlukan kepada kami di OpenVz. Kami tidak menyertakan KASAN dalam versi produksi kernel kami, tetapi KASAN hadir dalam versi debug kernel dan secara aktif membantu QA kami dalam menemukan bug.
Selain KASAN, kernel debug menyertakan banyak fitur debug lain yang kami warisi dari Red Hat. Sebagai hasil dari debug, kernel menjadi agak lambat. QA mengatakan pengujian yang sama pada kernel debug membutuhkan waktu 4 kali lebih lama. Bagi kami, ini tidak mendasar, kami tidak mengukur kinerja di sana, tetapi mencari bug. Namun, perlambatan seperti itu tidak dapat diterima oleh pelanggan, dan permintaan kami untuk menempatkan kernel debug dalam produksi selalu ditolak.
Sebagai alternatif dari KASAN, klien diminta untuk mengaktifkan slub_debug di node yang terpengaruh... Teknologi ini juga memungkinkan deteksi kerusakan memori. Menggunakan zona merah dan keracunan memori untuk setiap objek, pengalokasi memori memeriksa untuk melihat apakah semuanya beres setiap kali mengalokasikan dan membebaskan memori. Jika terjadi kesalahan, ia mengeluarkan pesan kesalahan, jika memungkinkan, memperbaiki kerusakan yang terdeteksi dan memungkinkan kernel untuk terus bekerja. Selain itu, informasi tentang siapa yang terakhir mengalokasikan dan membebaskan sebuah objek disimpan, sehingga dalam kasus deteksi post-factum dari kerusakan memori, adalah mungkin untuk memahami "siapa" objek ini dalam "kehidupan lampau". Slub_debug dapat diaktifkan di baris perintah kernel pada kernel produksi, tetapi pemeriksaan ini juga menggunakan sumber daya memori dan cpu. Untuk pengembangan dan debugging QA, ini bagus, tetapi klien produksi menggunakannya tanpa banyak antusias.
Enam bulan telah berlalu, Tahun Baru semakin dekat. Pengujian lokal pada kernel debug dengan KASAN tidak menemukan masalah, kami tidak menerima laporan bug dari node dengan slub_debug diaktifkan, kami tidak dapat menemukan apa pun di bahan mentah dan kami tidak menemukan masalah. Andrey dimuati dengan tugas lain, sebaliknya saya mendapat celah dan saya diperintahkan untuk menganalisa laporan bug selanjutnya.
Setelah menganalisis crash dump, saya segera menemukan objek kmalloc-192 yang bermasalah: memorinya dipenuhi dengan semacam sampah, informasi yang dimiliki oleh berbagai jenis objek. Itu sangat mirip dengan konsekuensi penggunaan setelah bebas, namun, setelah memeriksa dengan cermat siklus hidup objek yang rusak di bahan mentah, saya juga tidak menemukan sesuatu yang mencurigakan.
Saya melihat-lihat laporan bug lama, mencoba menemukan beberapa petunjuk di sana, tetapi juga tidak berhasil.
Akhirnya saya kembali ke bug saya dan mulai melihat objek sebelumnya. Itu juga ternyata sedang digunakan, tetapi dari isinya itu sama sekali tidak dapat dipahami apa itu - tidak ada konstanta, referensi ke fungsi atau objek lain. Setelah melacak beberapa generasi referensi ke objek ini, saya akhirnya menemukan bahwa itu adalah bitmap yang menyusut. Objek ini adalah bagian dari teknik pengoptimalan untuk membebaskan memori kontainer. Teknologi ini awalnya dikembangkan untuk kernel kami, kemudian penulisnya Kirill Tkhai memasukkannya ke jalur utama linux.
"Hasilnya menunjukkan performa meningkat setidaknya dalam 548 kali lipat."
Beberapa ribu tambalan tersebut melengkapi kernel RHEL7 yang stabil pada batuan asli, membuat kernel Virtuozzo senyaman mungkin bagi para hoster. Jika memungkinkan, kami mencoba mengirim perkembangan kami ke jalur utama, karena ini mempermudah untuk menjaga kode dalam kondisi baik.
Mengikuti tautan, saya menemukan struktur yang menjelaskan bitmap saya. Descriptor percaya bahwa ukuran bitmap harus 240 byte, dan ini tidak mungkin benar dengan cara apapun, karena sebenarnya objek dialokasikan dari cache kmalloc-192.
Bingo!
Ternyata fungsi yang bekerja dengan bitmap mengakses memori di luar batas atasnya dan dapat mengubah konten objek berikutnya. Dalam kasus saya, ada refcount di awal objek, dan ketika bitmap membatalkannya, put selanjutnya menyebabkan pelepasan objek secara tiba-tiba. Kemudian, memori dialokasikan lagi untuk objek baru, yang inisialisasinya dianggap sebagai sampah oleh kode objek lama, yang cepat atau lambat pasti menyebabkan kerusakan node.

Ada baiknya bila Anda dapat berkonsultasi dengan penulis kode!
Melihat kodenya dengan Kirill, kami segera menemukan akar penyebab dari perbedaan yang terdeteksi. Saat jumlah penampung meningkat, bitmap seharusnya meningkat, tetapi kami meninggalkan salah satu kasus dan, sebagai hasilnya, terkadang melewatkan bitmap pengubahan ukuran. Dalam pengujian lokal kami, situasi ini tidak ditemukan, dan dalam versi tambalan yang dikirim Kirill ke jalur utama, kodenya dikerjakan ulang, dan tidak ada bug di sana.
Dengan 4 percobaan, bersama-sama, Kirill dan saya menulis tambalan seperti itu , kami menjalankannya dalam tes lokal selama sebulan, dan pada akhir Februari kami merilis pembaruan dengan kernel tetap. Kami secara selektif memeriksa crash dump lainnya, juga menemukan bitmap yang salah di lingkungan tersebut, merayakan kemenangan dan menghapus bug lama secara diam-diam.
Namun, wanita tua itu terus jatuh dan jatuh. Tetesan dari jenis laporan bug ini telah menyusut, tetapi belum sepenuhnya kering.
Secara umum, ini sudah diharapkan. Klien kami adalah penghosting. Mereka sangat tidak suka me-reboot node mereka, karena reboot == downtime == kehilangan uang. Kami juga tidak suka sering merilis kernel. Rilis resmi pembaruan adalah prosedur yang agak melelahkan yang membutuhkan menjalankan banyak tes berbeda. Oleh karena itu, kernel stabil baru dirilis kira-kira setiap tiga bulan.
Untuk memastikan pengiriman segera dari perbaikan bug ke node produksi klien, kami menggunakan tambalan langsung ReadyKernel. Menurut pendapat saya, tidak ada orang lain yang melakukan ini kecuali kami. Virtuozzo 7 menggunakan strategi yang tidak biasa untuk menggunakan pola hidup.
Biasanya, lifepatch hanya sebagai pengaman. Di negara kita, 3/4 dari perbaikannya adalah perbaikan bug. Perbaikan untuk bug yang telah ditemukan atau mungkin mudah ditemukan oleh pelanggan kami di masa mendatang. Secara efektif, hal-hal seperti itu hanya dapat dilakukan untuk kit distribusi Anda: tanpa umpan balik dari pengguna, Anda tidak dapat memahami apa yang penting bagi mereka dan apa yang tidak.
Tambalan hidup tentu saja bukan obat mujarab. Biasanya tidak mungkin untuk menambal semuanya secara berurutan - teknologinya tidak memungkinkan. Fungsionalitas baru juga tidak ditambahkan dengan cara ini. Namun, sebagian besar bug diperbaiki dengan tambalan satu baris yang paling sederhana, yang sangat baik untuk tambalan hidup. Dalam kasus yang lebih kompleks, tambalan asli harus "dimodifikasi secara kreatif dengan file", terkadang mesin tambalan langsung bermasalah, tetapi ahli tambalan hidup kita Zhenya Shatokhin tahu pekerjaannya dengan sempurna. Baru-baru ini, misalnya, dia menggalibug mempesona di kpatch , yang, untuk alasan yang bagus, umumnya layak untuk menulis opera terpisah.
Saat perbaikan bug yang sesuai terakumulasi, biasanya setiap satu atau dua minggu, Zhenya meluncurkan serangkaian patch langsung ReadyKernel lainnya. Setelah rilis, mereka langsung terbang ke node klien dan mencegah serangan di rake yang sudah kita ketahui. Dan semua ini tanpa me-reboot node klien. Dan sering-seringlah melepaskan kernel yang tidak perlu. Manfaat berkelanjutan.
Namun, seringkali tambalan langsung tiba di klien terlambat: masalah yang ditutup telah terjadi, tetapi simpul, bagaimanapun, belum macet.
Itulah mengapa munculnya laporan bug baru dengan masalah yang telah kami perbaiki tidak terduga bagi kami. Parsing mereka berulang kali menunjukkan gejala yang sudah dikenal: kernel lama, sampah di kmalloc-192, bitmap "salah" di depannya, dan patch langsung yang tidak dimuat atau dimuat terlambat dengan perbaikan.
Salah satu kasus tersebut adalah OVZ-7188 dari FastVPS , yang datang kepada kami pada akhir Februari. “Terima kasih banyak atas laporan bugnya. Belasungkawa kami. Sangat mirip dengan masalah yang diketahui. Sayang sekali tidak ada patch langsung di OpenVZ. Tunggu rilis kernel yang stabil, beralihlah ke Virtuozzo atau gunakan kernel yang tidak stabil dengan perbaikan bug. "
Laporan bug adalah salah satu hal paling berharga yang diberikan OpenVZ kepada kita. Meneliti mereka memberi kita kesempatan untuk menemukan masalah serius sebelum klien gemuk masuk. Oleh karena itu, terlepas dari masalah yang diketahui, saya tetap meminta untuk mengisi crash dumps untuk kami.
Parsing yang pertama agak mengecilkan hati saya: bitmap yang "salah" di depan objek kmalloc-192 yang "bengkok" tidak ditemukan.
Beberapa saat kemudian, masalah tersebut muncul kembali di kernel baru. Dan kemudian yang lainnya, yang lainnya dan yang lainnya.
Ups!
Bagaimana? Belum diperbaiki? Saya memeriksa ulang bahan bakunya - semuanya baik-baik saja, tambalan sudah terpasang, tidak ada yang hilang.
Lagi korupsi? Di tempat yang sama?
Saya harus memikirkannya lagi.
(Apa ini? Lihat di sini )
Di setiap tempat pembuangan kecelakaan baru, penyelidikan kembali menemukan objek kmalloc-192. Secara umum, objek seperti itu terlihat cukup normal, tetapi pada awal objek, alamat yang salah ditemukan setiap saat. Melacak hubungan objek, saya menemukan bahwa dua byte internal dibatalkan di alamat.
in all cases corrupted pointer contains nulls in 2 middle bytes: (mask 0xffffffff0000ffff)
0xffff9e2400003d80
0xffff969b00005b40
0xffff919100007000
0xffff90f30000ccc0
Dalam kasus pertama yang terdaftar, alih-alih alamat "salah" 0xffff9e2400003d80, alamat "benar" 0xffff9e24740a3d80 seharusnya. Situasi serupa ditemukan pada kasus lain.
Ternyata beberapa kode asing membatalkan objek kami dengan 2 byte. Skenario yang paling mungkin adalah use-after-free, ketika sebuah objek, setelah dibebaskan, mengosongkan beberapa field dalam byte pertamanya. Saya memeriksa objek yang paling sering digunakan, tetapi tidak ditemukan sesuatu yang mencurigakan. Lagi-lagi jalan buntu.
FastVPSatas permintaan kami, saya menjalankan kernel debug dengan KASAN selama seminggu, tetapi tidak membantu, masalahnya tidak pernah muncul kembali. Kami meminta untuk mendaftarkan slub_debug, tetapi ini memerlukan boot ulang, dan prosesnya memakan waktu lama. Pada bulan Maret-April, node mengalami error beberapa kali, tetapi slub_debug dinonaktifkan, dan ini tidak memberi kami informasi baru.
Dan kemudian ada jeda, masalah berhenti berkembang biak. April berakhir, Mei berlalu - tidak ada musim gugur baru.
Penantian berakhir pada 7 Juni - akhirnya masalah menghantam inti dengan mengaktifkan slub_debug. Saat memeriksa zona merah saat membebaskan objek slub_debug, saya menemukan dua nol byte di luar batas atasnya. Dengan kata lain, ternyata itu tidak digunakan setelah bebas, objek sebelumnya lagi-lagi pelakunya. Ada struct nf_ct_ext yang tampak normal. Struktur ini mengacu pada pelacakan koneksi, deskripsi koneksi jaringan yang digunakan firewall.
Namun, masih belum jelas mengapa ini terjadi.
Saya mulai mengintip di conntrack: di salah satu wadah seseorang mengetuk port terbuka 1720 menggunakan ipv6. Berdasarkan port dan protokol, saya menemukan nf_conntrack_helper yang sesuai.
static struct nf_conntrack_helper nf_conntrack_helper_q931[] __read_mostly = {
{
.name = "Q.931",
.me = THIS_MODULE,
.data_len = sizeof(struct nf_ct_h323_master),
.tuple.src.l3num = AF_INET, <<<<<<<< IPv4
.tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT),
.tuple.dst.protonum = IPPROTO_TCP,
.help = q931_help,
.expect_policy = &q931_exp_policy,
},
{
.name = "Q.931",
.me = THIS_MODULE,
.tuple.src.l3num = AF_INET6, <<<<<<<< IPv6
.tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT),
.tuple.dst.protonum = IPPROTO_TCP,
.help = q931_help,
.expect_policy = &q931_exp_policy,
},
};
Membandingkan struktur, saya perhatikan bahwa pembantu ipv6 tidak mendefinisikan .data_len. Saya mulai mencari tahu dari mana asalnya, saya menemukan patch 2012.
commit 1afc56794e03229fa53cfa3c5012704d226e1dec
Penulis: Pablo Neira Ayuso <pablo@netfilter.org>
Tanggal: Kam 7 Jun 12:11:50 2012 +0200
netfilter: nf_ct_helper: menerapkan data pribadi variabel panjang pembantu
Patch ini menggunakan ekstensi conntrack variabel panjang baru.
Alih-alih menggunakan union nf_conntrack_help yang berisi semua
informasi data pribadi helper, kami mengalokasikan
area panjang variabel untuk menyimpan data pembantu pribadi.
Patch ini mencakup modifikasi dari semua pembantu yang ada.
Ini juga mencakup beberapa header include untuk menghindari kompilasi
peringatan.
Patch menambahkan bidang .data_len baru ke helper, yang menunjukkan berapa banyak memori yang diperlukan penangan koneksi jaringan yang sesuai. Tambalan itu seharusnya mendefinisikan .data_len untuk semua nf_conntrack_helpers yang tersedia saat itu, tetapi melewatkan struktur yang saya temukan.
Hasilnya, ternyata koneksi melalui ipv6 ke port terbuka 1720 meluncurkan fungsi q931_help (), ia menulis ke struktur yang tidak ada yang mengalokasikan memori. Pemindaian port sederhana membatalkan beberapa byte, transmisi pesan protokol normal memenuhi struktur dengan informasi yang lebih bermakna, tetapi dalam kasus apa pun, memori orang lain rusak dan cepat atau lambat ini menyebabkan crash node.
Florian Westphal mendesain ulang kode itu lagi pada 2017dan menghapus .data_len, dan masalah yang saya temukan tidak diperhatikan.
Terlepas dari kenyataan bahwa bug tidak lagi ditemukan di jalur utama kernel linux saat ini, masalahnya diwarisi oleh kernel dari sekelompok distribusi linux, termasuk RHEL7 / CentOS7, SLES 11 & 12, Oracle Unbreakable Enterprise Kernel 3 & 4, Debian 8 & 9, dan Ubuntu 14.04 & 16.04 LTS.
Bug direproduksi dengan mudah pada node pengujian, baik di inti kami maupun di RHEL7 asli. Keamanan eksplisit: kerusakan memori yang dikelola dari jarak jauh. Dimana port 1720 ipv6 terbuka - praktis ping kematian.
Pada tanggal 9 Juni saya membuat tambalan satu baris dengan deskripsi yang tidak jelas dan mengirimkannya ke jalur utama. Saya mengirim deskripsi rinci ke Red Hat Bugzilla dan menulisnya secara terpisah ke Red Hat Security.
Peristiwa lebih lanjut berkembang tanpa partisipasi saya.
Pada tanggal 15 Juni, Zhenya Shatokhin merilis patch live ReadyKernel untuk kernel lama kami.
https://readykernel.com/patch/Virtuozzo-7/readykernel-patch-131.10-108.0-1.vl7/
Pada tanggal 18 Juni kami merilis kernel stabil baru di Virtuozzo dan OpenVz.
https://virtuozzosupport.force.com/s/article/VZA-2020-043
Pada 24 Juni, Red Hat Security menetapkan ID CVE ke bug
https://access.redhat.com/security/cve/CVE-2020-14305
Masalah menerima dampak sedang dengan Skor CVSS v3 8.1 yang sangat tinggi dan selama beberapa hari berikutnya, distribusi
SUSE lain menanggapi bug topi publik https://bugzilla.suse.com/show_bug.cgi?id=CVE-2020-14305
Debian https: / /security-tracker.debian.org/tracker/CVE-2020-14305
Ubuntuhttps://people.canonical.com/~ubuntu-security/cve/2020/CVE-2020-14305.html
Pada tanggal 6 Juli KernelCare merilis livepatch untuk para distributor yang terpengaruh.
https://blog.kernelcare.com/new-kernel-vulnerability-found-by-virtuozzo-live-patched-by-kernelcare
Pada tanggal 9 Juli, masalah ini telah diperbaiki pada kernel Linux yang stabil 4.9.230 dan 4.4.230.
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=linux-4.9.y&id=396ba2fc4f27ef6c44bbc0098bfddf4da76dc4c9
Distribusi, bagaimanapun, masih belum menutup lubang ...
“Lihat, Kostya,” kataku pada rekanku Kostya Khorenko, “cangkang kita menabrak kawah yang sama dua kali! Saya dan satu akses-luar-akhir-objek terakhir kali saya bertemu nepoymi, dan di sini ia mengunjungi kami dua kali berturut-turut. Katakan padaku, apakah itu seperti probabilitas persegi? Atau tidak persegi?
- Kemungkinannya persegi, ya. Tapi di sini Anda harus melihat - peristiwa apa probabilitasnya? Probabilitas kuadrat dari peristiwa di mana bug yang tidak biasa ditemukan tepat 2 kali berturut-turut. Itu berturut-turut.
Yah, Kostya pintar, dia lebih tahu.