Mengapa begitu sulit untuk membuat C kurang rentan terhadap buffer overflows?


23

Saya sedang melakukan kursus di perguruan tinggi, di mana salah satu lab adalah untuk melakukan eksploitasi buffer overflow pada kode yang mereka berikan kepada kami. Ini berkisar dari eksploitasi sederhana seperti mengubah alamat kembali untuk fungsi pada tumpukan untuk kembali ke fungsi yang berbeda, sampai kode yang mengubah register program / keadaan memori tetapi kemudian kembali ke fungsi yang Anda panggil, yang berarti bahwa fungsi yang Anda panggil benar-benar lupa akan exploit.

Saya melakukan riset tentang ini, dan jenis eksploitasi ini digunakan cukup banyak di mana-mana bahkan sekarang, dalam hal-hal seperti menjalankan homebrew di Wii , dan jailbreak yang tidak ditambatkan untuk iOS 4.3.1

Pertanyaan saya adalah mengapa masalah ini begitu sulit untuk diperbaiki? Sudah jelas ini adalah salah satu exploit besar yang digunakan untuk meretas ratusan hal, tetapi sepertinya akan sangat mudah untuk memperbaikinya dengan hanya memotong input apa saja melewati panjang yang diizinkan, dan hanya membersihkan semua input yang Anda ambil.

EDIT: Perspektif lain yang saya ingin jawaban untuk dipertimbangkan - mengapa pencipta C tidak memperbaiki masalah ini dengan mengimplementasikan kembali perpustakaan?

Jawaban:


35

Mereka memperbaiki perpustakaan.

Setiap modern yang C library standar berisi varian yang lebih aman dari strcpy, strcat, sprintf, dan sebagainya.

Pada sistem C99 - yang merupakan sebagian besar Unix - Anda akan menemukan ini dengan nama-nama seperti strncatdan snprintf, "n" menunjukkan bahwa dibutuhkan argumen yang ukuran buffer atau jumlah maksimum elemen untuk menyalin.

Fungsi-fungsi ini dapat digunakan untuk menangani banyak operasi dengan lebih aman, tetapi dalam retrospeksi kegunaannya tidak bagus. Sebagai contoh, beberapa snprintfimplementasi tidak menjamin bahwa buffer diakhiri oleh null. strncatmembutuhkan sejumlah elemen untuk disalin, tetapi banyak orang keliru melewati ukuran buffer dest.

Pada Windows, kita sering menemukan strcat_s, sprintf_s, yang "_s" akhiran yang menunjukkan "aman". Ini juga telah menemukan jalan mereka ke pustaka standar C di C11, dan memberikan kontrol lebih besar atas apa yang terjadi jika terjadi luapan (misalnya pemangkasan vs. penegasan).

Banyak vendor bahkan menyediakan lebih banyak alternatif non-standar seperti asprintfdi libc GNU, yang akan mengalokasikan buffer dengan ukuran yang sesuai secara otomatis.

Gagasan bahwa Anda dapat "memperbaiki C" adalah kesalahpahaman. Memperbaiki C bukan masalah - dan sudah dilakukan. Masalahnya adalah memperbaiki beberapa dekade kode C yang ditulis oleh programmer yang bodoh, lelah, atau tergesa-gesa, atau kode yang telah diangkut dari konteks di mana keamanan tidak menjadi masalah ke konteks di mana keamanan tidak. Tidak ada perubahan pada pustaka standar yang dapat memperbaiki kode ini, meskipun migrasi ke kompiler yang lebih baru dan pustaka standar sering dapat membantu mengidentifikasi masalah secara otomatis.


11
+1 untuk mengarahkan masalah pada programmer, bukan bahasa.
Nicol Bolas

8
@Nicol: Mengatakan "masalahnya adalah programmer" adalah reduksionis yang tidak adil. Masalahnya adalah bahwa selama bertahun-tahun (puluhan tahun) C membuatnya lebih mudah untuk menulis kode yang tidak aman daripada kode aman, terutama karena definisi kami tentang "aman" berkembang lebih cepat daripada standar bahasa apa pun, dan bahwa kode itu masih ada. Jika Anda ingin mencoba menguranginya menjadi satu kata benda, masalahnya adalah "1970-1999 libc", bukan "the programmer."

1
Masih menjadi tanggung jawab programmer untuk menggunakan alat yang mereka miliki sekarang untuk memperbaiki masalah ini. Ambil setengah hari atau lebih dan lakukan beberapa pencarian melalui kode sumber untuk hal-hal ini.
Nicol Bolas

1
@Nicol: Meskipun sepele untuk mendeteksi buffer overflow potensial, seringkali tidak sepele untuk memastikan itu adalah ancaman nyata, dan kurang sepele untuk mencari tahu apa yang harus terjadi jika buffer pernah diluap. Penanganan kesalahan sering tidak dipertimbangkan, tidak mungkin untuk "cepat" mengimplementasikan peningkatan karena Anda dapat mengubah perilaku modul dengan cara yang tidak terduga. Kami baru saja melakukan ini dalam basis kode legacy multi-juta baris, dan meskipun bernilai saat berolahraga itu menghabiskan banyak waktu (dan Uang).
mattnz

4
@NicolBolas: Tidak yakin jenis toko tempat Anda bekerja, tetapi tempat terakhir yang saya tulis C untuk penggunaan produksi diperlukan mengubah dokumen desain terperinci, memeriksanya, mengubah kode, mengubah rencana pengujian, meninjau rencana pengujian, melakukan pengujian lengkap uji sistem, meninjau hasil tes, lalu mensertifikasi ulang sistem di lokasi pelanggan. Ini untuk sistem telekomunikasi di benua berbeda yang ditulis untuk perusahaan yang tidak ada lagi. Terakhir saya tahu, sumbernya ada di arsip RCS pada kaset QIC yang seharusnya masih dapat dibaca, jika Anda dapat menemukan tape drive yang sesuai.
TMN

19

Ini tidak benar-benar tidak akurat untuk mengatakan bahwa C sebenarnya "rawan kesalahan" oleh desain . Selain dari beberapa kesalahan yang menyedihkan seperti gets, bahasa C tidak bisa benar-benar dengan cara lain tanpa kehilangan fitur utama yang menarik orang ke C di tempat pertama.

C dirancang sebagai bahasa sistem untuk bertindak sebagai semacam "rakitan portabel." Fitur utama dari bahasa C adalah bahwa tidak seperti bahasa tingkat yang lebih tinggi, kode C sering memetakan sangat dekat dengan kode mesin yang sebenarnya. Dengan kata lain, ++ibiasanya hanya sebuah incinstruksi, dan Anda dapat sering mendapatkan ide umum tentang apa yang akan dilakukan prosesor pada saat run-time dengan melihat kode C.

Tetapi menambahkan memeriksa batas implisit menambahkan banyak overhead tambahan - overhead yang programmer tidak meminta dan mungkin tidak mau. Overhead ini melampaui penyimpanan ekstra yang diperlukan untuk menyimpan panjang setiap array, atau instruksi tambahan untuk memeriksa batas array pada setiap akses array. Bagaimana dengan aritmatika pointer? Atau bagaimana jika Anda memiliki fungsi yang mengambil pointer? Lingkungan runtime tidak memiliki cara untuk mengetahui apakah pointer itu berada dalam batas blok memori yang dialokasikan secara sah. Untuk melacak hal ini, Anda memerlukan beberapa arsitektur runtime yang serius yang dapat memeriksa setiap penunjuk terhadap tabel blok memori yang saat ini dialokasikan, di mana saat ini kita sudah masuk ke wilayah runtime yang dikelola gaya Java / C # -style.


12
Jujur ketika orang bertanya mengapa C tidak "aman" itu membuat saya bertanya-tanya apakah mereka akan mengeluh bahwa perakitan tidak "aman".
Ben Brocka

5
Bahasa C sangat mirip perakitan portabel pada mesin Digital Equipment Corporation PDP-11. Pada saat yang sama mesin Burroughs memiliki batas array memeriksa di CPU, sehingga mereka sangat mudah untuk mendapatkan program masuk. Array memeriksa kehidupan perangkat keras di perangkat keras Rockwell Collins (kebanyakan digunakan dalam penerbangan.)
Tim Williscroft

15

Saya pikir masalah sebenarnya bukan bahwa jenis bug ini sulit untuk diperbaiki, tetapi mereka sangat mudah untuk dibuat: Jika Anda menggunakan strcpy, sprintfdan teman-teman dengan (tampaknya) cara paling sederhana yang dapat bekerja, maka Anda mungkin telah membuka pintu untuk buffer overflow. Dan tidak ada yang akan menyadarinya sampai seseorang mengeksploitasinya (kecuali jika Anda memiliki ulasan kode yang sangat baik). Sekarang tambahkan fakta bahwa ada banyak programmer biasa-biasa saja dan bahwa mereka hampir selalu berada di bawah tekanan waktu - dan Anda memiliki resep untuk kode yang begitu penuh dengan buffer overflow sehingga akan sulit untuk memperbaikinya semua hanya karena ada begitu banyak dari mereka dan mereka bersembunyi dengan sangat baik.


3
Anda tidak benar-benar membutuhkan "ulasan kode yang sangat bagus". Anda hanya perlu mencek sprintf, atau mendefinisikan ulang sprintf untuk sesuatu yang menggunakan sizeof () dan kesalahan pada ukuran pointer, atau lain-lain. Anda bahkan tidak memerlukan ulasan kode, Anda dapat melakukan hal-hal seperti ini dengan SCM commit kait dan grep.

1
@ JoWreschnig: sizeof(ptr)umumnya 4 atau 8. Itu batasan C lain: tidak ada cara untuk menentukan panjang array, hanya dengan memberikan pointer padanya.
MSalters

@MSalters: Ya, array int [1] atau char [4] atau apa pun yang mungkin positif palsu, tetapi dalam praktiknya Anda tidak pernah menangani buffer sebesar itu dengan fungsi-fungsi tersebut. (Saya tidak berbicara secara teoritis di sini - saya bekerja pada basis kode C yang besar selama empat tahun yang menggunakan pendekatan ini. Saya tidak pernah mencapai batasan sprintfing menjadi char [4].)

5
@ BlackJack: Kebanyakan programmer tidak bodoh - jika Anda memaksa mereka untuk melewati ukuran, mereka akan melewati yang benar. Hanya saja sebagian besar juga tidak akan lulus ukuran kecuali dipaksa. Anda bisa menulis makro yang akan mengembalikan panjang array jika statis atau berukuran otomatis, tetapi kesalahan jika diberi pointer. Kemudian Anda mendefinisikan kembali sprintf untuk memanggil snprintf dengan makro yang memberikan ukuran. Anda sekarang memiliki versi sprintf yang hanya berfungsi pada array dengan ukuran yang diketahui, dan memaksa programmer untuk memanggil snprintf dengan ukuran yang ditentukan secara manual.

1
Salah satu contoh sederhana dari makro semacam itu adalah #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))yang akan memicu pembagian waktu kompilasi-oleh-nol. Yang cerdas lain yang pertama kali saya lihat di Chromium adalah #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))yang memperdagangkan segelintir positif palsu untuk beberapa negatif palsu - sayangnya itu tidak berguna untuk char []. Anda dapat menggunakan berbagai ekstensi kompiler untuk membuatnya lebih andal, misalnya blogs.msdn.com/b/ce_base/archive/2007/05/08/… .

7

Sulit untuk memperbaiki buffer overflow karena C tidak menyediakan alat yang berguna untuk mengatasi masalah. Ini adalah kesalahan bahasa mendasar yang tidak dilindungi oleh buffer asli dan secara virtual, jika tidak sepenuhnya, tidak mungkin untuk menggantinya dengan produk unggulan, seperti yang dilakukan C ++ std::vectordan std::array, dan sulit bahkan dalam mode debug untuk menemukan buffer overflows.


13
"Kelemahan bahasa" adalah klaim yang sangat bias. Bahwa perpustakaan tidak menyediakan pemeriksaan batas adalah cacat; bahwa bahasa itu bukan pilihan sadar untuk menghindari overhead. Pilihan itu adalah bagian dari apa yang memungkinkan konstruksi tingkat tinggi suka std::vectordiimplementasikan secara efisien. Dan vector::operator[]membuat pilihan yang sama untuk kecepatan dibandingkan keselamatan. Keamanan dalam vectorberasal dari membuatnya lebih mudah untuk bergerak di sekitar ukuran, yang merupakan pendekatan yang sama dengan perpustakaan C modern

1
@ Charles: "C tidak menyediakan buffer yang berkembang secara dinamis sebagai bagian dari perpustakaan standar." Tidak, ini tidak ada hubungannya dengan itu. Pertama, C menyediakannya melalui realloc(C99 juga memungkinkan Anda untuk mengukur susunan array menggunakan ukuran runtime yang ditentukan tetapi konstan melalui variabel otomatis apa pun, hampir selalu lebih disukai daripada char buf[1024]). Kedua, masalahnya tidak ada hubungannya dengan memperluas buffer, itu berkaitan dengan apakah buffer membawa ukuran dengan mereka dan memeriksa ukuran itu ketika Anda mengaksesnya.

5
@ Jo: Masalahnya bukan karena array asli rusak. Itu karena mereka mustahil untuk diganti. Sebagai permulaan, vector::operator[]apakah batas melakukan pengecekan dalam mode debug - sesuatu yang tidak bisa dilakukan oleh array asli - dan kedua, tidak ada cara di C untuk menukar tipe array asli dengan yang dapat melakukan pemeriksaan batas, karena tidak ada templat dan tidak ada operator kelebihan beban. Di C ++, jika Anda ingin pindah dari T[]ke std::array, Anda bisa langsung saja menukar typedef. Di C, tidak ada cara untuk mencapai itu, dan tidak ada cara untuk menulis kelas dengan fungsionalitas yang setara, apalagi antarmuka.
DeadMG

3
@ Jo: Kecuali itu tidak pernah bisa berukuran statis, dan Anda tidak pernah bisa membuatnya generik. Tidak mungkin untuk menulis pustaka apa pun di C yang menyelesaikan peran yang sama std::vector<T>dan std::array<T, N>lakukan di C ++. Tidak akan ada cara untuk merancang dan menentukan perpustakaan apa pun, bahkan perpustakaan standar, yang dapat melakukan ini.
DeadMG

1
Saya tidak yakin apa yang Anda maksud dengan "ukurannya tidak akan pernah bisa statis." Seperti yang saya gunakan istilah itu, std::vectorjuga tidak pernah bisa berukuran statis. Sedangkan untuk generik, Anda dapat menjadikannya generik sebanyak yang diinginkan oleh C - sejumlah kecil operasi fundamental pada void * (tambah, hapus, ubah ukuran) dan semua yang ditulis secara khusus. Jika Anda akan mengeluh bahwa C tidak memiliki generik gaya C ++, itu jauh di luar lingkup penanganan buffer yang aman.

7

Masalahnya bukan dengan C bahasa .

IMO, satu-satunya kendala utama untuk diatasi adalah bahwa C hanya diajarkan secara buruk . Praktik buruk dan informasi yang salah selama puluhan tahun telah dilembagakan dalam buku pedoman dan catatan kuliah, meracuni pikiran setiap generasi baru programmer sejak awal. Siswa diberi deskripsi singkat tentang fungsi I / O "mudah" seperti gets1 atau scanfkemudian dibiarkan sendiri. Mereka tidak diberi tahu di mana atau bagaimana alat-alat itu bisa gagal, atau bagaimana mencegah kegagalan itu. Mereka tidak diberitahu tentang penggunaan fgetsdanstrtol/strtodkarena itu dianggap alat "canggih". Lalu mereka melepaskan dunia profesional untuk melampiaskan malapetaka mereka. Tidak banyak programmer yang lebih berpengalaman yang tahu lebih baik, karena mereka menerima pendidikan yang sama dengan otak yang rusak. Ini menjengkelkan. Saya melihat begitu banyak pertanyaan di sini dan di Stack Overflow dan di situs lain di mana sudah jelas bahwa orang yang mengajukan pertanyaan sedang diajarkan oleh seseorang yang tidak tahu apa yang mereka bicarakan , dan tentu saja Anda tidak bisa hanya mengatakan "Profesor Anda salah," karena dia seorang Profesor dan Anda hanya seorang pria di Internet.

Dan kemudian Anda memiliki kerumunan yang meremehkan jawaban yang dimulai dengan, "menurut standar bahasa ..." karena mereka bekerja di dunia nyata dan menurut mereka standar itu tidak berlaku untuk dunia nyata . Saya bisa berurusan dengan seseorang yang berpendidikan buruk, tetapi siapa pun yang bersikeras tidak tahu apa-apa hanya akan merusak industri ini.

Tidak akan ada masalah buffer overflow jika bahasa diajarkan dengan benar dengan penekanan pada penulisan kode aman. Ini bukan "sulit", itu bukan "maju", itu hanya berhati-hati.

Ya, ini sudah menjadi kata-kata kasar.


1 Yang, untungnya, akhirnya dicabut dari spesifikasi bahasa, meskipun akan mengintai dalam kode warisan senilai 40 tahun selamanya.


1
Sementara saya sebagian besar setuju dengan Anda, saya pikir Anda masih sedikit tidak adil. Apa yang kami anggap "aman" juga merupakan fungsi waktu (dan saya melihat Anda telah menjadi pengembang perangkat lunak profesional lebih lama dari saya, jadi saya yakin Anda sudah familiar dengan ini). Sepuluh tahun dari sekarang seseorang akan memiliki percakapan yang sama tentang mengapa semua orang di tahun 2012 menggunakan implementasi tabel hass yang dapat dilakukan oleh DoS, bukankah kita tahu apa-apa tentang keamanan? Jika ada masalah dalam mengajar, itu adalah masalah yang kita terlalu fokus pada pengajaran praktik "terbaik", dan bukan praktik terbaik itu sendiri yang berkembang.

1
Dan mari kita jujur. Anda bisa menulis kode aman dengan adil sprintf, tetapi itu tidak berarti bahasanya tidak cacat. C itu cacat dan yang cacat - seperti bahasa apapun - dan itu penting bahwa kita mengakui orang-orang cacat sehingga kami dapat terus memperbaikinya.

@ JoWreschnig - Meskipun saya setuju dengan poin yang lebih besar, saya pikir ada perbedaan kualitatif antara implementasi tabel hash yang dapat dilakukan oleh DoS dan buffer overruns. Yang pertama dapat dikaitkan dengan keadaan yang berkembang di sekitar Anda, tetapi yang kedua tidak memiliki alasan; buffer overruns adalah kesalahan pengkodean, titik. Ya, C tidak memiliki pelindung pisau dan akan memotong Anda jika Anda ceroboh; kita bisa berdebat apakah itu cacat dalam bahasa atau tidak. Itu yang ortogonal pada fakta bahwa sangat sedikit siswa diberi setiap instruksi keselamatan ketika mereka sedang belajar bahasa.
John Bode

5

Masalahnya adalah salah satu kepicikan manajerial daripada ketidakmampuan programmer. Ingat, aplikasi 90.000 baris hanya membutuhkan satu operasi tidak aman untuk sepenuhnya tidak aman. Hampir di luar kemungkinan bahwa aplikasi apa pun yang ditulis di atas penanganan string yang tidak aman secara fundamental akan 100% sempurna - yang berarti aplikasi tersebut tidak aman.

Masalahnya adalah bahwa biaya tidak aman tidak dibebankan ke penerima yang tepat (perusahaan yang menjual aplikasi hampir tidak pernah harus mengembalikan harga pembelian), atau tidak terlihat jelas pada saat keputusan dibuat ("Kita harus mengirim pada bulan Maret tidak peduli apa! "). Saya cukup yakin bahwa jika Anda memfaktorkan biaya jangka panjang dan biaya untuk pengguna Anda daripada keuntungan perusahaan Anda, menulis dalam C atau bahasa terkait akan jauh lebih mahal, mungkin sangat mahal sehingga jelas merupakan pilihan yang salah dalam banyak bidang di mana kebijaksanaan konvensional saat ini mengatakan bahwa itu adalah suatu keharusan. Tapi itu tidak akan berubah kecuali kewajiban perangkat lunak yang lebih ketat diperkenalkan - yang tidak diinginkan oleh siapa pun di industri ini.


-1: Menyalahkan manajemen sebagai akar dari semua kejahatan tidak terlalu konstruktif. Mengabaikan sejarah sedikit kurang begitu. Jawabannya hampir ditebus oleh kalimat terakhir.
mattnz

Tanggung jawab perangkat lunak yang lebih ketat dapat diperkenalkan oleh pengguna yang tertarik pada keamanan dan bersedia membayar untuk itu. Dapat diperdebatkan, itu dapat diperkenalkan dengan memiliki hukuman berat untuk pelanggaran keamanan. Solusi berbasis pasar akan berfungsi jika pengguna bersedia membayar untuk keamanan, tetapi mereka tidak.
David Thornley

4

Salah satu kekuatan besar menggunakan C adalah memungkinkan Anda memanipulasi memori dengan cara apa pun yang Anda inginkan.

Salah satu kelemahan besar menggunakan C adalah memungkinkan Anda memanipulasi memori dengan cara apa pun yang Anda inginkan.

Ada versi aman dari semua fungsi yang tidak aman. Namun, programmer dan kompiler tidak secara ketat menegakkan penggunaannya.


2

mengapa pencipta C tidak memperbaiki masalah ini dengan mengimplementasikan kembali perpustakaan?

Mungkin karena C ++ sudah melakukan ini, dan kompatibel dengan kode C. Jadi jika Anda ingin tipe string aman dalam kode C Anda, Anda hanya menggunakan std :: string dan menulis kode C Anda menggunakan kompiler C ++.

Subsistem memori yang mendasarinya dapat membantu untuk mencegah buffer overflows dengan memperkenalkan blok penjaga dan memeriksa validitasnya - sehingga semua alokasi memiliki 4 byte 'fefefefe' ditambahkan, ketika blok ini ditulis, sistem dapat melempar wobbler. Ini tidak dijamin untuk mencegah penulisan memori, tetapi akan menunjukkan bahwa ada sesuatu yang salah dan perlu diperbaiki.

Saya pikir masalahnya adalah rutinitas strcpy dll yang lama masih ada. Jika mereka dihapus demi strncpy dll maka itu akan membantu.


1
Menghapus strcpy dll. Sepenuhnya akan membuat jalur peningkatan tambahan semakin sulit, yang pada gilirannya akan menyebabkan orang tidak memperbarui sama sekali. Cara ini dilakukan sekarang Anda dapat beralih ke kompiler C11, kemudian mulai menggunakan varian _s, lalu ban varian non-_s, kemudian perbaiki penggunaan yang ada, selama periode waktu apa pun yang praktis dapat dijalankan.

-2

Sangat mudah untuk memahami mengapa masalah kelebihan tidak diperbaiki. C cacat di beberapa daerah. Pada saat itu kekurangan itu dipandang dapat ditoleransi atau bahkan sebagai fitur. Sekarang, beberapa dekade kemudian, kekurangan itu tidak dapat diperbaiki.

Beberapa bagian dari komunitas pemrograman tidak ingin lubang-lubang itu dipasang. Lihat saja semua perang api yang dimulai dari string, array, pointer, pengumpulan sampah ...


5
LOL, jawaban yang mengerikan dan salah arah.
Heath Hunnicutt

1
Untuk menjelaskan mengapa ini adalah jawaban yang buruk: C memang memiliki banyak kekurangan, tetapi membiarkan buffer overflows, dll tidak ada hubungannya dengan mereka, tetapi dengan persyaratan bahasa dasar. Tidak mungkin mendesain bahasa untuk melakukan pekerjaan C dan tidak mengizinkan buffer overflows. Beberapa bagian dari komunitas tidak mau melepaskan kemampuan yang memungkinkan C, seringkali dengan alasan yang bagus. Ada juga ketidaksepakatan tentang bagaimana menghindari beberapa masalah ini, menunjukkan bahwa kita tidak memiliki pemahaman yang lengkap tentang desain bahasa pemrograman, tidak lebih.
David Thornley

1
@ Davidvidhorn: Seseorang dapat merancang bahasa untuk melakukan pekerjaan C tetapi membuatnya sehingga cara-cara idiomatis normal dalam melakukan sesuatu setidaknya akan memungkinkan kompiler untuk memeriksa buffer overflow secara efisien, seandainya kompiler memilih untuk melakukannya. Ada perbedaan besar antara memiliki memcpy()tersedia dan memilikinya hanya cara standar menyalin segmen array secara efisien.
supercat
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.