Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Security
Code

Mengamankan Kode Dengan Concurrency di Swift 4

Indonesian (Bahasa Indonesia) translation by Meyria (you can also view the original English article)

Dalam artikel saya sebelumnya tentang mengamankan kode di Swift, saya membahas kelemahan keamanan dasar di Swift seperti serangan injeksi. Sementara serangan injeksi biasa terjadi, ada beberapa cara lain yang bisa dikompromikan oleh aplikasi anda. Kerentanan yang umum namun kadang-kadang diabaikan adalah kondisi pacuan.

Swift 4 memperkenalkan Akses Eksklusif ke Memori, yang terdiri dari seperangkat aturan untuk mencegah area memori yang sama diakses pada waktu bersamaan. Misalnya, argumen inout di Swift memberitahukan sebuah metode bahwa ia dapat mengubah nilai parameter di dalam metode.

Tapi apa jadinya jika kita melewati variabel yang sama untuk berubah pada saat bersamaan?

Swift 4 telah melakukan perbaikan yang mencegah hal ini dari kompilasi. Tapi sementara Swift dapat menemukan skenario yang jelas ini pada saat kompilasi, ini sulit, terutama karena alasan kinerja, untuk menemukan masalah akses memori dalam kode yang berbarengan, dan sebagian besar kerentanan keamanan ada dalam bentuk kondisi pacuan.

Kondisi Pacuan

Segera setelah anda memiliki lebih dari satu thread yang perlu ditulis ke data yang sama pada saat bersamaan, kondisi pacuan bisa terjadi. Kondisi pacuan menyebabkan korupsi data. Untuk jenis serangan ini, kerentanan biasanya lebih halus - dan eksploitasi lebih kreatif. Misalnya, mungkin ada kemampuan untuk mengubah sumber daya bersama untuk mengubah alur kode keamanan yang terjadi di thread lain, atau dalam kasus status otentikasi, penyerang mungkin dapat memanfaatkan selisih waktu antara waktu pemeriksaan dan waktu penggunaan bendera.

Cara untuk menghindari kondisi pacuan adalah dengan menyinkronkan data. Sinkronisasi data biasanya berarti "mengunci" sehingga hanya satu thread yang dapat mengakses bagian kode tersebut setiap kali (dikatakan sebagai mutex—untuk saling pengecualian). Meskipun Anda dapat melakukannya secara eksplisit menggunakan class NSLock, ada kemungkinan kehilangan tempat dimana kode seharusnya disinkronkan. Melacak kunci dan apakah sudah terkunci atau tidak bisa menjadi sulit.

Grand Central Dispatch

Alih-alih menggunakan kunci primitif, anda dapat menggunakan Grand Central Dispatch (GCD)—Konkurensi modern API Apple yang dirancang untuk kinerja dan keamanan. Anda tidak perlu memikirkan kunci itu sendiri; itu bekerja untuk anda di balik layar.

Seperti yang dapat Anda lihat, ini adalah API yang cukup sederhana, jadi gunakan GCD sebagai pilihan pertama Anda saat merancang aplikasi Anda untuk konkurensi.

Pemeriksaan keamanan runtime Swift tidak dapat dilakukan di seluruh thread GCD karena menciptakan kinerja yang signifikan. Solusinya adalah dengan menggunakan alat Thread Sanitizer jika anda bekerja dengan banyak thread. Alat Thread Sanitizer sangat bagus dalam menemukan masalah yang mungkin tidak anda temukan dengan melihat kode itu sendiri. Hal ini dapat diaktifkan dengan membuka Product > Scheme > Edit Scheme > Diagnostics, dan memeriksa opsi Thread Sanitizer.

Jika desain aplikasi anda membuat anda bekerja dengan banyak thread, cara lain untuk melindungi diri dari masalah keamanan konkurensi adalah mencoba mendesain class anda agar terbebas kunci sehingga tidak perlu kode sinkronisasi. Ini memerlukan pemikiran nyata tentang desain antarmuka anda, dan bahkan dapat dianggap sebagai seni tersendiri dalam dan dari dirinya sendiri!

Main Thread Checker

Penting untuk disebutkan bahwa korupsi data juga dapat terjadi jika anda memperbarui UI pada thread manapun selain thread utama (thread lainnya disebut sebagai thread latar belakang).

Terkadang bahkan tidak jelas Anda berada di latar belakang. Misalnya, NSURLSession's delegateQueue, saat diset ke nil, secara default akan memanggil kembali pada thread latar belakang. Jika anda meperbarui UI atau menulis ke data anda di blok itu, ada kemungkinan bagus untuk kondisi pacuan. (Perbaiki ini dengan membungkus pembaruan UI di DispatchQueue.main.async {} atau lewat OperationQueue.main sebagai delegasi queue.)

Suatu yang baru di Xcode 9 dan diaktifkan secara default adalah Main Thread Checker (Product > Scheme > Edit Scheme > Diagnostics > Runtime API Checking > Main Thread Checker). Jika kode Anda tidak disinkronkan, masalah akan muncul pada Runtime Issues di navigator kiri Xcode, jadi perhatikan hal itu saat menguji aplikasi Anda.

Untuk kode keamanan, callback atau penangan penyelesaian apa pun yang anda tulis harus didokumentasikan apakah mereka kembali di thread utama atau tidak. Lebih baik lagi, ikuti desain API Apple terbaru yang memungkinkan anda melalui completionQueue dalam metode ini sehingga anda dapat dengan jelas memutuskan dan melihat thread apa yang mengembalikan blok penyelesaian

Contoh Pada Dunia-Nyata

Cukup bicara! Mari menyelami contohnya.

Di sini kita tidak memiliki sinkronisasi, namun lebih dari satu thread mengakses data secara bersamaan. Hal yang bagus tentang Thread Sanitizer adalah bahwa ia akan mendeteksi kasus seperti ini. Cara GCD modern untuk memperbaikinya adalah mengaitkan data anda dengan queue pengiriman serial.

Sekarang kode disinkronisasi dengan blok .async. Anda mungkin bertanya-tanya kapan harus memilih .async dan kapan menggunakan .sync. Anda dapat menggunakan .async ketika aplikasi anda tidak perlu menunggu sampai operasi di dalam blok selesai. Ini mungkin lebih baik dijelaskan dengan contoh.

Dalam contoh ini, thread yang meminta array transaksi jika itu berisi transaksi tertentu yang menyediakan output, sehingga perlu menunggu. Thread lain tidak mengambil tindakan apapun setelah menambahkan ke array transaksi, sehingga tidak perlu menunggu sampai blok selesai.

Blok sync dan async ini dapat dibungkus dalam metode yang mengembalikan data internal, seperti metode getter.

Penyebaran GCD memblok seluruh area kode anda bahwa akses data bersama bukanlah sebuah praktek yang baik karena lebih sulit untuk melacak semua tempat yang perlu disinkronkan. Hal ini jauh lebih baik untuk mencoba dan menyimpan semua fungsi ini di satu tempat.

Menggunakan metode aksesor desain yang baik adalah salah satu cara untuk memecahkan masalah ini. Menggunakan metode getter dan setter dan hanya menggunakan metode ini untuk mengakses data yang berarti bahwa anda dapat mensinkronisasinya di satu tempat. Ini menghindari memperbarui banyak bagian dari kode anda jika anda mengubah atau merefactor area GCD kode anda.

Structs

Sementara satu properti yang tersimpan dapat disinkronkan dalam suatu class, mengubah properti di struct benar-benar akan mempengaruhi struct seluruhnya. Swift 4 sekarang memasukkan perlindungan untuk metode yang bermutasi structs.

Mari kita lihat pertama kali apa yang tampak seperti korupsi struct (disebut  "Swift access race").

Dua metode dalam contoh mengubah properti yang disimpan, jadi mereka ditandai mutating. Mari kita katakan thread 1 dengan begin() dan thread 2 dengan finish(). Bahkan jika begin() hanya merubah id dan finish() hanya merubah timestamp, hal ini tetap pacuan akses. Sementara biasanya lebih baik untuk mengunci di dalam metode aksesor, ini tidak berlaku untuk structs sebagai struct seluruhnya menjadi eksklusif.

Salah satu solusinya adalah untuk mengubah struct ke suatu class ketika mengimplementasikan kode yang berbarengan. Jika Anda membutuhkan struct untuk beberapa alasan, anda bisa, dalam contoh ini, buat class Bank yang menyimpan structs Transaction. Kemudian pemanggil dari structs di dalam class dapat disinkronkan.

Berikut adalah contoh:

Access Control

Akan ada gunanya untuk memiliki perlindungan ini ketika antarmuka anda memperlihatkan sebuah objek bermutasi atau UnsafeMutablePointer ke data bersama, karena sekarang pengguna dari kelas anda dapat melakukan apapun yang mereka inginkan dengan data tanpa perlindungan GCD. Sebaliknya, kembalian salinan data di dalam getter. Hati-hati antarmuka desain dan enkapsulasi data itu penting, terutama saat merancang program yang berbarengan, untuk memastikan bahwa data bersama benar-benar dilindungi.

Pastikan variabel disinkronisasi ditandai dengan private, dibandingkan dengan open atau public, yang akan memungkinkan anggota dari file sumber untuk mengaksesnya. Satu perubahan yang menarik dalam Swift 4 adalah bahwa tingkatan lingkup akses private diperluas menjadi tersedia dalam ekstensi. Sebelumnya itu hanya bisa digunakan dalam melampirkan deklarasi, tetapi di Swift 4, variabel private dapat diakses di ekstensi, selama sebagai ekstensi dari deklarasi di file sumber yang sama.

Tidak hanya variabel pada risiko korupsi data tetapi pada file juga. Menggunakan class dasar FileManager, yang merupakan thread-aman, dan memeriksa bendera hasil operasi file sebelum melanjutkannya dalam kode Anda.

Berinteraksi dengan Objective-C

Banyak objek Objective-C memiliki rekan yang bisa berubah yang digambarkan oleh judul mereka. NSString yang bisa berubah versi bernama NSMutableString, NSArray's adalah NSMutableArray, dan seterusnya. Selain fakta bahwa benda-benda ini dapat bermutasi di luar sinkronisasi, jenis pointer yang datang dari Objective-C juga menumbangkan opsi Swift. Ada kesempatan baik bahwa anda bisa mengharapkan sebuah objek di Swift, tetapi dari Objective-C itu dikembalikan sebagai nil.

Jika aplikasi crash, ini memberikan wawasan berharga tentang logika internal. Dalam kasus ini, bisa jadi bahwa input pengguna tidak diperksa dengan benar dan bahwa area alur aplikasi patut dilihat untuk dicoba dan dieksploitasi.

Solusinya di sini adalah untuk memperbarui kode Objective-C Anda untuk menyertakan anotasi nullability. Kita dapat mengambil sedikit pengalihan di sini karena saran ini berlaku untuk interoperabilitas yang aman pada umumnya, baik antara Swift dan Objective-C atau antara dua bahasa pemrograman lainnya.

Awali variabel Objective-C Anda dengan nullable saat nil dapat dikembalikan, dan nonnull jika seharusnya tidak.

Anda juga dapat menambahkan nullable dan nonnull ke daftar atribut dari properti Objective-C.

Alat Analyzer Statik di Xcode selalu bagus untuk menemukan bug Objective-C. Sekarang dengan anotasi nullability, di Xcode 9 Anda dapat menggunakan Analyzer Statik pada kode Objective-C Anda dan akan menemukan inkonsistensi nullability pada file Anda. Lakukan ini dengan menavigasi ke Product > Perform Action > Analyze.

Meskipun diaktifkan secara default, Anda juga dapat mengontrol cek nullability di LLVM dengan flag -Wnullability*.

Cek nullability bagus untuk menemukan masalah saat waktu kompilasi, tetapi mereka tidak menemukan masalah runtime. Sebagai contoh, kadang-kadang kita menganggap di bagian kode kita bahwa nilai opsional akan selalu ada dan menggunakan pembuka paksa ! di atasnya. Ini adalah pilihan tersirat secara implisit, tetapi sebenarnya tidak ada jaminan bahwa itu akan selalu ada. Lagi pula, jika ditandai dengan pilihan, kemungkinan besar akan nihil di beberapa titik. Oleh karena itu, sebaiknya hindari pembukaan paksa dengan !. Sebagai gantinya, solusi elegan adalah memeriksa saat runtime seperti ini:

Untuk lebih membantu Anda, ada fitur baru yang ditambahkan pada Xcode 9 untuk melakukan pengecekan nullabilitas saat runtime. Ini adalah bagian dari Undefined Behavior Sanitizer, dan meskipun tidak diaktifkan secara default, Anda dapat mengaktifkannya dengan membuka Build Settings > Undefined Behavior Sanitizer dan mengatur Yes untuk Enable Nullability Anotation Checks.

Keterbacaan

Praktik bagus untuk menulis metode Anda hanya dengan satu entri dan satu titik keluar. Tidak hanya bagus untuk keterbacaan, tapi juga untuk dukungan multithreading lanjutan.

Katakanlah sebuah class dirancang tanpa concurrency dalam pikiran. Kemudian kebutuhan berubah sehingga sekarang harus mendukung .lock() dan .unlock() metode NSLock. Ketika tiba saatnya untuk menempatkan kunci di sekitar bagian kode Anda, Anda mungkin perlu menulis ulang banyak metode Anda hanya untuk menjadikan thread yang aman. Sangat mudah untuk melewatkan return tersembunyi di tengah metode yang kemudian seharusnya mengunci contoh NSLock Anda, yang kemudian dapat menyebabkan kondisi pacuan. Selain itu, pernyataan seperti return tidak akan secara otomatis membuka kunci. Bagian lain dari kode Anda yang mengasumsikan kunci  telah terkunci dan mencoba mengunci lagi akan menjadikan aplikasi buntu (aplikasi akan membeku dan akhirnya dihentikan oleh sistem). Gangguan juga bisa menjadi kerentanan keamanan dalam kode multithread jika file kerja sementara tidak pernah dibersihkan sebelum thread berakhir. Jika kode Anda memiliki struktur ini:

Anda malah bisa menyimpan Boolean, memperbaruinya sepanjang jalan dan kemudian mengembalikannya di akhir metode. Kemudian kode sinkronisasi bisa dengan mudah dibungkus dalam metode tanpa banyak kerja.

Metode .unlock() harus dipanggil dari thread yang sama dengan yang dipanggil .lock(), jika tidak, hasilnya menghasilkan perilaku yang tidak terdefinisi.

Pengujian

Seringkali, menemukan dan memperbaiki kerentanan dalam kode bersamaan datang dengan pencarian bug. Bila Anda menemukan bug, itu seperti memegang cermin ke diri Anda sendiri—sebuah kesempatan belajar yang hebat. Jika Anda lupa melakukan sinkronisasi di satu tempat, kemungkinan kesalahan yang sama ada di tempat lain dalam kode. Meluangkan waktu untuk memeriksa sisa kode Anda untuk kesalahan yang sama saat Anda menemukan bug adalah cara yang sangat efisien untuk mencegah kerentanan keamanan yang akan terus muncul berulang kali di rilis aplikasi mendatang.

Sebenarnya, banyak jailbreak IOS baru-baru ini disebabkan oleh kesalahan pengkodean berulang yang ditemukan di Apple's IOKit. Setelah mengetahui gaya pengembang, Anda dapat memeriksa bagian kode lainnya untuk bug serupa.

Menemukan bug adalah motivasi yang baik untuk penggunaan kembali kode. Mengetahui bahwa Anda memperbaiki masalah di satu tempat dan tidak harus pergi mencari semua kejadian yang sama dalam copy / paste kode bisa sangat melegakan.

Kondisi pacuan bisa jadi rumit untuk ditemukan saat pengujian karena ingatan mungkin harus rusak hanya dengan "cara yang benar" untuk melihat masalahnya, dan terkadang masalah tersebut muncul lama kemudian dalam eksekusi aplikasi.

Saat Anda menguji, tutup semua kode Anda. Pergi melalui setiap aliran dan kasus dan uji setiap baris kode setidaknya satu kali. Terkadang membantu memasukkan data acak (memberi masukan), atau memilih nilai ekstrim dengan harapan menemukan tepi kasus yang tidak akan jelas dari melihat kode atau menggunakan aplikasi dengan cara yang biasa. Ini, bersama dengan alat Xcode baru yang tersedia, bisa pergi jauh untuk mencegah kerentanan keamanan. Meskipun tidak ada kode yang 100% aman, mengikuti rutinitas, seperti awal fungsional tes, unit tes, sistem tes, stress dan regresi tes, akan benar-benar melunasi.

Selain melakukan debug pada aplikasi Anda, satu hal yang berbeda untuk konfigurasi rilis (konfigurasi untuk aplikasi yang dipublikasikan di store) adalah bahwa pengoptimalan kode disertakan. Misalnya, apa yang menurut compiler adalah operasi yang tidak terpakai dapat dioptimalkan, atau variabel mungkin tidak bertahan lebih lama dari yang diperlukan pada blok yang bersamaan. Untuk aplikasi yang dipublikasikan, kode Anda benar-benar diubah, atau berbeda dengan yang Anda tes. Ini berarti bahwa bug dapat dikenalkan hanya ada setelah Anda merilis aplikasi Anda.

Jika Anda tidak menggunakan konfigurasi tes, pastikan Anda menguji aplikasi Anda pada mode rilis dengan menavigasikan ke Product > Scheme > Edit Scheme. Pilih Run dari daftar di sebelah kiri, dan di panel Info di sebelah kanan, ubah Build Configuration ke Release. Meskipun bagus untuk menutupi keseluruhan aplikasi Anda dalam mode ini, ketahuilah bahwa karena pengoptimalan, titik balik dan debugger tidak akan berperilaku seperti yang diharapkan. Misalnya, deskripsi variabel mungkin tidak tersedia meskipun kode dijalankan dengan benar.

Kesimpulan

Dalam posting ini, kita melihat kondisi pacuan dan bagaimana menghindarinya dengan cara mengkode dengan aman dan menggunakan alat seperti Thread Sanitizer. Kita juga berbicara tentang Akses Eksklusif ke Memori, yang merupakan tambahan yang bagus untuk Swift 4. Pastikan ini disetel ke Full Enforcement di Build Settings > Exclusive Access to Memory!

Ingat bahwa penegakan ini hanya berlaku untuk mode debug, dan jika Anda masih menggunakan Swift 3.2, banyak penegasan yang dibahas hanya dalam bentuk peringatan. Jadi perhatikan secara serius, atau lebih baik lagi, gunakan semua fitur baru yang ada dengan mengadopsi Swift 4 hari ini!

Dan saat Anda berada di sini, periksa beberapa posting saya yang lain tentang mengamankan kode untuk iOS dan Swift!


Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.