1 poin oleh GN⁺ 2024-05-22 | 2 komentar | Bagikan ke WhatsApp
  • Dalam sistem yang membuat email, SMS, dan PDF dari templat dokumen, muncul masalah di mana titik menghilang hanya pada isi email yang dikirim ke pelanggan tertentu
  • Titik ada di sumber templat, pratinjau, dan PDF, tetapi masalah hanya dapat direproduksi pada panjang baris tertentu setelah nilai placeholder tiap pelanggan diganti dengan data nyata
  • Klien SMTP kustom membatasi panjang baris dan mendorong titik ke karakter pertama di baris baru, lalu server menghapus titik itu karena aturan dot transparency di SMTP
  • Perbaikannya dilakukan dengan membuat klien menambahkan satu titik lagi di depan baris yang dimulai dengan titik dan diikuti karakter lain
  • Karena tim lain yang memakai kode klien SMTP yang sama tidak memasang patch, email yang menampilkan premi bulanan $27.00 sebagai $2700 sempat terkirim ke sebagian pelanggan, dan bug itu segera diperbaiki

Masalah yang bermula dari sistem templat terpusat

  • Sekitar 7 tahun lalu, seorang klien ingin menggabungkan dokumen yang tersebar di berbagai templat Microsoft Word ke dalam satu sistem
  • Cara lama mengharuskan staf mengganti placeholder di dokumen secara manual dengan nama depan, nama belakang, dan lain-lain, sehingga templat dengan syarat lama, logo perusahaan lama, dan font yang salah ikut beredar dan sulit dikelola
  • Sistem baru dirancang untuk mengelola templat dokumen secara terpusat, lalu menggunakannya untuk membuat dokumen PDF, pesan teks, dan isi email
  • Pesan sambutan pelanggan baru juga bisa memiliki templat berbeda tergantung cara pengirimannya
    • Versi email dapat menggunakan tabel HTML dan gaya dasar
    • Versi untuk pengiriman pos dapat memuat infografik
    • Versi pesan teks hanya dapat berisi sapaan sambutan singkat

Titik yang hilang hanya untuk pelanggan tertentu

  • Setelah sistem berjalan beberapa bulan, atau bahkan lebih dari setahun, seorang administrator melaporkan bahwa satu titik hilang dari isi email yang dikirim ke pelanggan tertentu
  • Saat email yang sama dikirim ke pelanggan lain, titiknya tidak hilang, sehingga sulit dianggap sebagai kesalahan templat biasa
  • Di kode sumber templat, titik tersebut memang benar-benar ada
  • Saat menyalin templat produksi ke lingkungan lokal dan membuat pratinjau isi email, titik itu juga terlihat, dan pada versi keluaran yang dibuat dari templat yang sama pun tampil normal
  • Lingkungan lokal mengirim email ke localhost pada port tertentu, lalu email diperiksa dengan server SMTP palsu seperti SMTP4dev dan Outlook
  • Saat email pertama kali dikirim dari lokal dan diperiksa di Outlook, titik itu juga tampil normal

Kondisi reproduksi yang dibentuk oleh data per pelanggan

  • Saat membuat email, PDF, dan pesan teks, templat mengganti nilai placeholder seperti nama depan dan nama belakang pelanggan dengan data nyata
  • Karena itu, meskipun templatenya sama, panjang isi email yang dikirim ke tiap pelanggan bisa berbeda
  • Setelah nilai placeholder yang benar-benar dipakai untuk pelanggan bermasalah ditemukan dan email dikirim ulang di lingkungan lokal dengan nilai yang sama, gejala titik menghilang bisa dikonfirmasi di Outlook
  • Masalah ini bergantung pada panjang dan isi spesifik dari badan email pelanggan tersebut
  • Awalnya sempat diperiksa apakah karakter titik di templat ter-encode atau ternyata bukan titik biasa, tetapi itu bukan penyebabnya
  • Ketika posisi titik digeser satu karakter di dalam templat, titik itu kembali terlihat di Outlook, sehingga posisi baris menjadi petunjuk reproduksi

Panjang baris SMTP dan dot transparency

  • Hasil debugging menunjukkan bahwa kode yang menyimpan email ke database tidak mengubah templat selain mengganti placeholder dengan data pelanggan
  • Setelah itu, ruang lingkup penyelidikan menyempit ke kode pekerjaan cron yang secara berkala mengambil target pengiriman dan mengirim email
  • Sebagian kode yang dipanggil pekerjaan cron diambil dari proyek sebelumnya, dan di dalamnya ada implementasi klien SMTP kustom
  • Kode tersebut memiliki fungsi untuk memecah baris agar setiap baris pada isi email tidak melebihi jumlah karakter tertentu
  • Perilaku ini berkaitan dengan batas panjang baris dalam spesifikasi SMTP
    • Panjang total maksimum satu baris teks adalah 1000 octets termasuk <CRLF>
    • Titik awal yang digandakan untuk transparansi tidak dihitung dalam perhitungan ini
    • Nilai ini dapat diperbesar oleh SMTP Service Extensions
  • Pada isi email yang bermasalah, batas panjang baris membuat titik berpindah menjadi karakter pertama di baris berikutnya
  • Dalam spesifikasi SMTP, penanda akhir data mail diperlakukan sebagai satu baris yang hanya berisi satu titik
  • Aturan dot transparency di SMTP menangani secara khusus baris isi yang dimulai dengan titik
    • Klien SMTP harus memeriksa sebelum mengirim setiap baris teks mail apakah karakter pertamanya adalah titik, dan jika ya, harus menambahkan satu titik lagi di awal baris
    • Server SMTP akan menganggap baris yang hanya berisi satu titik sebagai akhir data mail
    • Jika karakter pertama adalah titik dan ada karakter lain pada baris yang sama, server akan menghapus karakter pertama itu
  • Klien SMTP kustom tidak melakukan penambahan satu titik lagi ketika sebuah baris dimulai dengan titik, sehingga server SMTP penerima menghapus titik pertama dan titik asli di isi email pun hilang

Perbaikan dan dampak yang baru terlihat belakangan

  • Perbaikannya adalah membuat klien menambahkan satu titik lagi saat sebuah baris dimulai dengan titik dan masih memiliki karakter lain pada baris yang sama
  • Dengan cara ini, meskipun server SMTP penerima menghapus titik pertama, titik asli tetap tersisa di isi email
  • Saat email asli dikirim ulang secara lokal dengan informasi penerima yang sama seperti pelanggan bermasalah, titik itu tidak lagi hilang
  • Setelah perbaikan dirilis, tim lain juga diberi tahu tentang bug tersebut karena mereka memakai kode klien SMTP yang sama dari proyek sebelumnya
  • Beberapa bulan kemudian, sistem milik tim lain ternyata masih belum dipatch dan mengirim banyak email penting yang memberi tahu pelanggan tentang premi bulanan baru mereka
  • Pada sebagian email, titik pemisah desimal pada premi bulanan tepat berada di posisi karakter pertama sebuah baris lalu menghilang
  • Akibatnya, sebagian pelanggan menerima email yang membuat premi baru mereka tampak seperti $2700, bukan $27.00
  • Bug ini bergantung pada panjang setiap baris isi email, dan hanya terjadi pada sebagian pelanggan yang panjang nama depan dan nama belakangnya tepat cocok dengan kondisi tersebut
  • Karena penyebabnya sudah diketahui, kode tim tersebut langsung dipatch

2 komentar

 
surfindia 2024-05-22

Sepertinya di judul, period dipahami sebagai periode waktu, bukan tanda titik, haha

 
GN⁺ 2024-05-22
Komentar Hacker News
  • Kode ini tampaknya mengimplementasikan sebagian klien SMTP secara langsung, dan akar masalahnya sepertinya ada di situ
    Mengimplementasikan protokol dengan tepat itu sulit, dan bug seperti yang dibahas di artikel cukup sering terjadi
    Jika memakai library klien SMTP yang diimplementasikan dengan benar, teks masukan akan dienkode sesuai spesifikasi SMTP tanpa peduli posisi titik di dalam teks, dan lapisan templat tidak perlu memikirkan SMTP

    • Masalah sebenarnya bukan pada protokol itu sendiri, melainkan pada pendekatan koboi dalam menanganinya
      Menerima teks lalu mengodekannya agar sesuai dengan spesifikasi SMTP tidaklah sulit, tetapi orang harus tahu sejak awal bahwa pemrosesan itu memang diperlukan
      Banyak bug dan kerentanan keamanan seperti SQL injection dan XSS juga lahir dari kesalahan yang sama, yaitu menyambung-nyambung string
      Dalam kueri SQL, pengikatan nilai ke templat kueri harus terjadi di “ruang SQL”, bukan di ruang string tanpa tipe; membuat string SQL terserialisasi secara langsung seperti SELECT * FROM foo WHERE foo.bar = + $userData adalah cara yang salah
      Templat HTML juga sama: jika ditangani pada level pohon dokumen alih-alih sebagai representasi string, banyak kerentanan bodoh bisa dihindari
      Untuk menghindari titik yang hilang dalam email, jangan menyuntikkan teks tak berstruktur di tengah pipeline SMTP; hormati level abstraksi yang sedang dipakai
      Terkait ini, langsec juga layak dilihat
    • Masalah sebenarnya adalah SMTP merupakan protokol “teks polos” yang memuat sinyal in-band
      Karena “baris yang hanya berisi satu titik” didefinisikan bukan sebagai baris literal, melainkan sebagai urutan kontrol, hal seperti ini bisa terjadi
      SMTP adalah contoh desain yang rumit tanpa perlu, dan bug implementasinya mencerminkan kerumitan itu
      Bukan berarti saya menyarankan untuk mengimplementasikannya sendiri, tetapi SMTP juga seharusnya tidak sampai sesulit itu untuk diimplementasikan dengan benar sendirian
    • Kedengarannya ide bagus, tetapi Anda mengambil satu library SMTP lalu library itu menarik 5 dependensi, masing-masing menarik 3 dependensi transitif lagi, dan salah satunya ternyata proyek kotak perkakas serbaguna yang sebenarnya cuma dipakai 1% saja
      Sisanya cuma beban mati, tetapi karena ada fungsi yang bahkan tidak dipanggil, ia tetap menarik 20 dependensi tambahan, dan tahu-tahu codebase membengkak beberapa MB serta Anda menerima peringatan CVE dari library yang bahkan tidak sadar sedang dipakai
    • https://en.wikipedia.org/wiki/Jamie_Zawinski#Zawinski's_Law
  • Sisi teknisnya sudah dibahas lebih baik oleh orang lain, tetapi ini mengingatkan saya pada anekdot tentang betapa pentingnya hal kecil seperti satu titik di akhir kalimat
    Di Jerman tempat saya bekerja, umum untuk meminta “Zeugnis”, yaitu surat rekomendasi saat keluar kerja yang berisi tugas yang dijalankan dan penilaian terhadap karyawan, dan ini dokumen penting yang biasanya diminta saat melamar kerja
    Tentu saja tidak mungkin seorang karyawan mau menerima kalimat seperti “orang ini malas, jangan dipekerjakan”, jadi muncul semacam kode yang disebut “Zeugnissprache”, disamarkan seperti pujian
    Salah satu kodenya konon jika kalimat terakhir tidak diakhiri titik, artinya “abaikan semua yang tertulis di sini, orang ini buruk sekali”
    Setelah pekerjaan saya sebelumnya, saya meminta pengacara meninjau Zeugnis saya, dan meskipun semua penilaiannya positif, entah karena ceroboh atau bukan, kalimat terakhirnya memang tidak punya titik

    • Gagasan bahwa surat rekomendasi memakai kode rahasia itu cuma legenda urban
      Staf HR tidak punya insentif untuk membuat kode rahasia bersama para pesaing potensial mereka, dan tidak masuk akal juga membayangkan itu bisa diajarkan ke staf HR baru sambil tetap dirahasiakan
      Legenda seperti ini muncul karena pihak HR tidak bisa menulis terlalu blak-blakan bahwa seseorang itu bermasalah
      Jika tidak ada hal positif yang bisa ditulis, mereka akhirnya memuji kelebihan biasa seperti ketepatan waktu; ini lebih mirip gaya bahasa HR versi “bless their heart” daripada kode rahasia
      Kalau sampai digugat, mereka masih punya celah untuk berkata, “Yang Mulia, itu pujian! Dia memang selalu datang tepat waktu!”
    • “Kalau kalimat terakhir tidak punya titik, berarti abaikan semua yang tertulis di sini, orang ini buruk sekali” — ya, apalagi yang mungkin bisa salah?
    • Saya jadi penasaran apakah dokumen evaluasi kinerjanya juga tidak punya titik
  • Saya tidak paham kenapa sebuah pekerjaan cron yang mengirim email harus mengimplementasikan klien SMTP sendiri
    Tinggal pakai program seperti mail dari mailutils
    Bahkan dari sudut pandang deliverability, membuat sendiri interaksi SMTP minimal di atas socket itu sudah sulit sejak awal
    Sekarang Anda tidak bisa begitu saja terhubung langsung ke sembarang host penukar email untuk mengirim surat; biasanya harus terhubung ke host relay SMTP tertentu yang disediakan ISP
    Itu berarti harus mengimplementasikan juga koneksi TLS, autentikasi, dan seterusnya
    Sedikit ironisnya, cron sendiri sebenarnya sudah bisa mengirim email
    Output dari pekerjaan cron dikirim lewat email kepada pemiliknya, dan di beberapa cron alamat penerimanya bisa diubah lewat hal seperti variabel MAILTO di crontab

    • Memang idealnya saat mengirim email kita memakai MTA milik host, tetapi nyatanya banyak sekali hal yang justru membenamkan klien SMTP mereka sendiri
      Alasannya, meski pengguna “barang itu” mungkin bisa memasukkan alamat server SMTP, program tersebut tidak punya cara untuk percaya bahwa MTA pengirim seperti sendmail sudah dikonfigurasi dengan benar
      Di perusahaan besar ada banyak server yang memang tidak bisa mengirim email sistem, dan kondisinya kadang berada di luar kendali pembuat program maupun pengguna
      Konfigurasi email adalah bidang khusus dengan prasyarat seperti DNS, enkripsi, dan kebijakan, jadi itu terlalu berat jika tujuan satu-satunya cuma ingin sesuatu seperti aplikasi dompet bisa mengirim email
      “Hubungi administrator sistem” sering kali bukan pilihan yang realistis, baik di skala besar maupun kecil
      Bukan alasan yang bagus, tetapi itulah alasan yang sebenarnya
    • Tidak ada dunia di mana kita bisa yakin sistem sudah punya biner untuk mengirim email, tetapi saya juga tetap tidak akan membuatnya sendiri
      Di hampir semua bahasa, sekalipun tidak ada di pustaka standar, ada banyak library klien SMTP yang cukup bagus untuk dipakai
    • Ini juga terasa seperti contoh Hukum Zawinski, bukan?
  • Ada dua kebiasaan buruk besar yang terlihat di sini
    Yang pertama, seperti sudah ditunjukkan banyak orang, adalah jangan mengimplementasikan standar secara asal-asalan
    Jika memang harus mengimplementasikannya sendiri, lakukan dengan perhatian dan ketelitian yang diperlukan, atau gunakan library yang sudah ada
    Yang kedua adalah jangan melakukan vendoring dependensi
    Library yang digunakan harus diperbarui secara berkala dan tepat waktu, bukan hanya “saat diperlukan”
    Jika pembaruan ditunda atau malah dihindari, bug yang sudah diperbaiki di upstream bisa menjadi masalah besar bagi orang-orang yang merasa cukup memperbarui hanya ketika masalahnya terlihat langsung

    • Alternatif untuk tidak melakukan vendoring dependensi tampak lebih buruk
      Dalam kasus itu, stabilitas aplikasi terekspos pada perubahan upstream, dan perubahan itu bisa merusak kode
      Memang kita mungkin tidak langsung mendapatkan perbaikan, tetapi menurut saya lebih baik tahu bahwa saya melakukan perubahan karena perbaikan yang saya butuhkan daripada memasukkan ketidakstabilan dan risiko tambahan yang tidak diinginkan
      Saya cenderung ke prinsip “kalau tidak rusak, jangan diperbaiki”
  • Belakangan ini, tampaknya banyak orang tidak mempelajari protokol dasar dengan berinteraksi langsung lewat terminal
    SMTP tampaknya memang dirancang dengan mempertimbangkan interaksi manual seperti itu, dan sebagai orang yang pernah benar-benar memakainya seperti itu untuk sementara waktu, “satu baris berisi satu titik” untuk mengakhiri pesan terasa terpatri permanen dalam ingatan
    Terkait hal itu, penanganan escape juga tampak seperti konsep yang asing bagi banyak programmer
    Banyak orang melihat situasi di atas tanpa bertanya, “Kalau ingin mengirim email yang punya baris berisi satu titik saja, bagaimana?” tetapi ada juga kelompok besar lain yang akan merasa pertanyaan itu sangat logis dan mudah dipahami

    • Saya malah akan terkejut jika dalam 30 tahun terakhir ada proporsi berarti dari developer software yang belajar dengan cara seperti itu
  • Dot stuffing memang diperlukan
    SMTP https://www.rfc-editor.org/rfc/rfc5321#section-4.5.2
    Ada juga di POP3 https://www.rfc-editor.org/rfc/rfc1939#page-8

  • Ini mengingatkan saya pada pengalaman men-debug implementasi protokol jaringan. Tepatnya AppleTalk NBP untuk orang-orang lama
    Saya sudah mengodekan semuanya, tetapi paket saya ditolak, atau lebih tepatnya dibuang diam-diam, sementara paket dari implementasi Apple yang asli lolos
    Saya menaruh paket yang benar dan yang salah di layar komputer dan membandingkannya byte demi byte, tetapi tidak bisa menemukan masalahnya, dan dari awal sampai akhir semuanya sama persis termasuk checksum
    Saat waktunya pulang dan saya memutuskan untuk mencetak hal-hal bodoh itu agar dilihat nanti, begitu dicetak kesalahannya langsung terlihat
    Versi saya dua halaman, sementara implementasi yang benar hanya satu halaman
    Ternyata saya tidak mengosongkan buffer dengan benar sebelum mengirim data. Begitulah mbuf
    Sampai sekarang pun kalau dipikirkan masih lucu

  • Baris “We are happy to welcome you to our family.” sama sekali tidak mendekati batas panjang baris
    Sepertinya ada hal lain yang terjadi, misalnya keseluruhannya sebenarnya berupa lampiran HTML MIME
    Bisa jadi bentuknya seperti ... lots of text ... lalu diikuti We are happy to welcome you to our family.
    Tetapi kalau HTML dipotong menjadi baris secara sembarangan, tag-nya akan rusak

    • Itu hanya contoh, dan maaf karena tidak bisa memberikan contoh nyata dengan jumlah karakter yang tepat
    • Kebanyakan orang yang menerima email HTML rusak seperti ini akan menyadari bahwa format emailnya aneh, lalu menurunkan penilaian atas kompetensi perusahaan, menganggapnya sebagai perusahaan yang bahkan tidak bisa menulis email yang mudah dibaca dengan benar, lalu lanjut ke email berikutnya
      Sangat mungkin perusahaan itu sendiri lewat begitu saja tanpa pernah menyadari masalah kecil tersebut
    • Jika memakai soft line break dari quoted-printable encoding, baris bisa dipecah sembarangan tanpa merusak tag HTML
      Quoted-printable seharusnya memiliki batas maksimum 78 karakter termasuk CRLF, tetapi klien email umumnya cukup toleran
  • Jika seseorang baru pertama kali mendengar dot stuffing, mungkin sulit percaya ada kengerian lain apa saja di dunia email
    Ada pelipatan header, penanganan tanda kutip di local part, hal-hal seperti IPv6 literal, dan sebagainya

    • Saya penasaran, apa masalahnya dengan IPv6 literal?
  • Begitu membaca bagian bahwa itu adalah kode klien SMTP yang dipinjam dari proyek sebelumnya, saya langsung menduga akhirnya akan berujung pada tim lain yang ternyata belum menambal bug ini