Mulai kumpulkan kesalahan dalam fungsi salin

memcpy

Saya telah memperhatikan beberapa kali bahwa pemrogram membuat kesalahan dalam fungsi penyalinan data sederhana. Topik ini akan membutuhkan lebih banyak waktu di masa depan untuk mempelajari dan memilih bahan untuk menulis artikel yang menyeluruh. Tetapi saya ingin membagikan beberapa contoh yang baru-baru ini saya perhatikan.

Fenomena Baadera-Meinhof? Saya pikir bukan itu


Sebagai anggota tim PVS-Studio, saya menemukan sejumlah besar kesalahan yang kami temukan di berbagai proyek. Suka DevRel - Saya suka membicarakannya :). Hari ini saya memutuskan untuk membicarakan tentang fungsi penyalinan data yang diimplementasikan secara salah.

Saya telah menemukan fungsi yang gagal seperti itu lebih dari sekali. Tetapi saya tidak menuliskannya, karena saya tidak mementingkan hal ini. Namun, karena saya perhatikan tren ini, saatnya untuk mulai mengumpulkannya. Untuk memulai, saya akan membagikan dua kasus terakhir yang diperhatikan.

Seseorang mungkin berpendapat bahwa dua kasus - ini bukan keteraturan. Dan itu, mungkin, saya menarik perhatian mereka hanya karena mereka bertemu saya setelah waktu yang singkat dan memicu fenomena Baader-Meinhof .

Fenomena Baader-Meinhof, juga ilusi frekuensi, adalah distorsi kognitif di mana baru-baru ini ditemukan informasi yang muncul lagi setelah periode waktu yang pendek dianggap sebagai hal yang luar biasa sering diulang.

Saya pikir ini tidak benar. Saya sudah memiliki pengalaman pengamatan seperti itu tentang fungsi perbandingan, yang kemudian dikonfirmasi oleh materi yang dikumpulkan: " Evil hidup dalam fungsi perbandingan ."

Oke, mari kita langsung ke intinya. Pengantar untuk memberikan hanya dua contoh sejauh ini sudah terlalu lama :).

Contoh N1


Dalam artikel tentang verifikasi Zephyr RTOS, saya menggambarkan upaya yang gagal untuk mengimplementasikan fungsi strdup analog :

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    ((u8_t *)mntpt)[strlen(mntpt)] = '\0';
    memcpy(cpy_mntpt, mntpt, strlen(mntpt));
  }
  return cpy_mntpt;
}

Peringatan PVS-Studio: V575 [CWE-628] Fungsi 'memcpy' tidak menyalin seluruh string. Gunakan fungsi 'strcpy / strcpy_s' untuk mempertahankan terminal null. shell.c 427

Analyzer melaporkan bahwa fungsi memcpy menyalin baris, tetapi tidak menyalin terminal nol, dan ini sangat mencurigakan. Tampaknya terminal 0 ini disalin di sini:

((u8_t *)mntpt)[strlen(mntpt)] = '\0';

Tidak, ada kesalahan ketik di sini, karena terminal nol disalin ke dirinya sendiri. Perhatikan bahwa menulis ke array mntpt , bukan cpy_mntpt . Akibatnya, fungsi mntpt_prepare mengembalikan string yang tidak lengkap dengan nol terminal.

Bahkan, programmer ingin menulis seperti ini:

((u8_t *)cpy_mntpt)[strlen(mntpt)] = '\0';

Tidak jelas mengapa kode ini ditulis membingungkan dan tidak standar. Akibatnya, kesalahan serius dibuat dalam fungsi kecil dan tidak rumit. Kode ini dapat disederhanakan ke opsi berikut:

static char *mntpt_prepare(char *mntpt)
{
  char *cpy_mntpt;

  cpy_mntpt = k_malloc(strlen(mntpt) + 1);
  if (cpy_mntpt) {
    strcpy(cpy_mntpt, mntpt);
  }
  return cpy_mntpt;
}

Contoh N2


void myMemCpy(void *dest, void *src, size_t n) 
{ 
   char *csrc = (char *)src; 
   char *cdest = (char *)dest; 
   for (int i=0; i<n; i++) 
     cdest[i] = csrc[i]; 
}

Kami sendiri tidak mengidentifikasi kode ini menggunakan PVS-Studio, tetapi saya tidak sengaja bertemu di situs web StackOverflow: C dan analisis Kode statis: Apakah ini lebih aman daripada memcpy?

Namun, jika Anda memeriksa fungsi ini menggunakan alat analisa PVS-Studio, ia akan memperhatikan:

  • V104 Konversi implisit dari 'i' ke tipe memsize dalam ekspresi aritmatika: i <n test.cpp 26
  • V108 Jenis indeks salah: cdest [bukan tipe memsize]. Gunakan tipe memsize sebagai gantinya. test.cpp 27
  • V108 Jenis indeks salah: csrc [bukan tipe memsize]. Gunakan tipe memsize sebagai gantinya. test.cpp 27

Memang, kode ini mengandung cacat, seperti ditunjukkan dalam jawaban StackOverflow. Anda tidak dapat menggunakan variabel int sebagai indeks . Dalam program 64-bit, hampir pasti (kami tidak mempertimbangkan arsitektur eksotis), variabel int akan menjadi 32-bit dan fungsinya akan dapat menyalin tidak lebih dari INT_MAX byte. Itu tidak lebih dari 2 gigabytes.

Dengan penyangga yang lebih besar untuk disalin, overflow variabel tanda akan terjadi, yang dari sudut pandang C dan C ++ adalah perilaku yang tidak terdefinisi. Dan omong-omong, jangan mencoba menebak dengan tepat bagaimana kesalahan akan terwujud. Ini sebenarnya adalah topik yang sulit, yang dapat Anda baca di artikel " Perilaku tidak terdefinisi lebih dekat daripada yang Anda pikirkan ."

Sangat lucu bahwa kode ini muncul sebagai upaya untuk menghapus semacam peringatan dari penganalisa Checkmarx yang terjadi ketika fungsi memcpy dipanggil . Pemrogram tidak menghasilkan sesuatu yang lebih baik daripada membuat sepeda sendiri. Dan meskipun kesederhanaan fungsi salin, ternyata masih salah. Faktanya, orang tersebut kemungkinan besar melakukan hal yang lebih buruk daripada sebelumnya. Alih-alih memahami alasan peringatan itu, ia menutupi masalah dengan menulis fungsinya sendiri (membingungkan penganalisa). Plus menambahkan kesalahan menggunakan int untuk penghitung . Oh ya, kode seperti itu masih bisa menghambat optimasi. Tidak efisien menggunakan kode Anda sendiri alih-alih fungsi memcpy yang dioptimalkan secara efisien . Jangan lakukan ini :)

Kesimpulan


Ya, saya baru di awal perjalanan dan, mungkin, perlu waktu lebih dari satu tahun sebelum saya mengumpulkan materi untuk publikasi menyeluruh tentang topik ini. Sebenarnya, baru sekarang saya akan mulai menulis kasus-kasus seperti itu. Terima kasih atas perhatian Anda dan lihat apa yang menarik yang akan ditemukan oleh alat analisa PVS-Studio dalam kode C / C ++ / C # / Java Anda.



Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Andrey Karpov. Memulai Koleksi Bug Saya yang Ditemukan di Fungsi Salin .

All Articles