Libseccomp: RFE: seccomp_rule_add menjadi sangat lambat sejak v2.4.0

Dibuat pada 1 Mei 2019  ·  23Komentar  ·  Sumber: seccomp/libseccomp

Halo,
masalah ini diperkenalkan oleh komit ce3dda9a1747cc6a4c044eafe5a2eb653c974919 antara v2.3.3 dan v2.4.0. Perhatikan contoh berikut: foo.c.zip .
Itu menambahkan sejumlah besar aturan. Dan bekerja sekitar 100 kali lebih lambat setelah komit yang disebutkan di atas.

foo.c waktu eksekusi menggunakan v2.4.1: 0.448
foo.c waktu eksekusi menggunakan v2.3.3: 0.077

Saya menggali sedikit dan menemukan bahwa db_col_transaction_start() menyalin koleksi filter yang sudah ada dan menggunakan arch_filter_rule_add() untuk menduplikasi aturan filter. Tapi arch_filter_rule_add() memanggil arch_syscall_translate() yang memanggil arch_syscall_resolve_name() yang bekerja di O(jumlah syscalls pada arsitektur yang diberikan). Jadi menambahkan satu aturan berfungsi setidaknya di O (jumlah aturan yang sudah ditambahkan * jumlah syscalls pada arsitektur yang digunakan) yang IMO benar-benar buruk.
Saya menghitung jumlah panggilan ke arch_filter_rule_add() dalam contoh di atas dan itu sama dengan 201152 .

Sebelum komit itu, jumlah panggilan ke arch_filter_rule_add() adalah 896 . Dan dari apa yang saya pahami dari kode, juga db_col_transaction_start() menyalin koleksi filter yang sudah ada dan tidak menggunakan arch_filter_rule_add(). Yang memberi kita estimasi: waktu menambahkan aturan di sekitar O(jumlah aturan yang sudah ditambahkan + jumlah panggilan sys pada arsitektur yang diberikan), yang jauh lebih baik.

Namun IMO seharusnya tidak terkait dengan jumlah aturan yang sudah ditambahkan, karena menambahkan n aturan berfungsi di O(n^2). Tapi itu adalah topik untuk diskusi yang berbeda, jadi seharusnya tidak menjadi masalah untuk filter kecil atau filter yang jarang dibuat.

Mengapa masalah ini penting?
Beberapa filter perlu mengeksekusi program PID (misalnya mengizinkan utas mengirim sinyal hanya ke dirinya sendiri). Jadi jika program yang dibatasi perlu dijalankan beberapa kali, itu menjadi overhead yang sangat terlihat. Saya memiliki filter sekitar 300 aturan dan overhead libseccomp sekitar 0,16 detik per proses sandbox (saya menjalankan proses puluhan kali).

Terima kasih sebelumnya atas bantuan Anda!

enhancement prioritlow

Komentar yang paling membantu

Kami melihat batas waktu dari pengguna karena perubahan ini. Itu benar-benar memperlambat segalanya dengan urutan besarnya.

Semua 23 komentar

Hai @varqox.

Ya, fungsi syscall resolver mungkin perlu ditingkatkan, bahkan jika Anda melihat kodenya, Anda akan melihat beberapa komentar seperti berikut:

/* XXX - plenty of room for future improvement here */

Jika Anda ingin melihat peningkatan kode itu, kami harus menggunakan bantuan!

Seperti yang disebutkan @pcmoore, ada banyak peluang untuk mempercepat _creation_ filter seccomp menggunakan libseccomp. Penelitian Anda di atas menguraikan salah satu dari beberapa area yang dapat menggunakan perbaikan. Ini belum menjadi perhatian bagi pengguna saya, jadi saya belum fokus padanya.

Mengenai kinerja _runtime_, saat ini saya sedang bekerja menggunakan pohon biner untuk filter besar seperti yang Anda berikan di foo.c. Hasil awal dengan pelanggan internal saya terlihat menjanjikan, tetapi saya ingin sekali melihat perubahannya. Lihat permintaan tarik https://github.com/seccomp/libseccomp/pull/152

Oke, saya melihat bahwa penyelesaian syscall dapat ditingkatkan, tetapi itu bukan akar penyebab masalah. Yaitu, seperti yang saya lihat, pembuatan snapshot di db_col_transaction_start() . Ada arch_filter_rule_add() disebut yang lambat karena menyelesaikan syscall, yang diselesaikan dalam aturan asli.

Saya melihatnya sebagai berikut: Kami ingin menduplikasi seluruh rangkaian filter saat ini (alias struct db_filter) dengan semua aturannya, jadi kami _construct_ semua filter dari awal alih-alih memanfaatkan apa yang sudah kami miliki dan hanya _copy_ semua filter. Kami tidak harus membangun dari awal, kami telah membangun filter sepenuhnya yang kami hanya ingin salinannya. Mungkin saya melewatkan sesuatu, tetapi sepertinya banyak perbaikan yang dapat dilakukan pada fungsi db_col_transaction_start() .

Dengan semua status dalam koleksi db libseccomp internal, menggandakannya bukanlah tugas yang sepele, membuat ulang koleksi dari aturan asli jauh lebih mudah (dari perspektif kode). Melacak aturan asli juga memungkinkan kami menawarkan kemampuan untuk "menghapus" aturan yang ada (kemungkinan fitur di masa mendatang).

Ini bukan untuk mengatakan bahwa kode transaksi tidak dapat ditingkatkan - itu pasti bisa - tetapi kode saat ini seperti itu karena suatu alasan, terutama kesederhanaan.

Kami melihat batas waktu dari pengguna karena perubahan ini. Itu benar-benar memperlambat segalanya dengan urutan besarnya.

Pemikiran lain, kita mungkin dapat mengubah ini sehingga kita hanya menduplikasi aturan pada awal transaksi, bukan keseluruhan pohon, dan hanya membuat ulang pohon pada transaksi yang gagal. Itu tidak sempurna, tetapi itu harus mengembalikan sebagian besar waktu.

Kami perlu melakukan sesuatu karena waktu mulai untuk container dan proses exec mengalami penurunan performa yang sangat besar dan menyebabkan orang memasang pin ke 2,3x

Saya tidak akan berkomentar lebih lanjut tentang sifat masalah _"besar"_, perspektif itu telah dibuat beberapa kali dan saya menganggapnya relatif dan tergantung pada kasus penggunaan. Namun, saya ingin mengingatkan semua orang bahwa rilis libseccomp sebelum v2.4 rentan terhadap potensi kerentanan yang telah dipublikasikan (masalah #139).

Bagi mereka yang khawatir tentang masalah ini, saat ini ditandai untuk rilis v2.5.

Anda membuat refactoring dan memiliki dampak kinerja "besar" dalam rilis kecil dan Anda tidak membantu dengan mengatakan ini bergantung pada kasus penggunaan. Harap anggap ini serius karena orang-orang akan mulai memperhatikan sebagai pembaruan distro ke 2.4

@crosbymichael perubahan itu tidak hanya refactoring, itu perlu untuk memperbaiki masalah dan mendukung perubahan di kernel (terutama kebutuhan untuk mendukung syscalls panggilan multipleks dan langsung, misalnya syscalls soket pada 32-bit x86).

Saya _tidak_ meniup ini, saya terus memikirkan cara untuk memecahkan masalah ini (lihat komentar saya di atas), dan fakta bahwa saya telah menandai ini sebagai sesuatu untuk rilis kecil berikutnya. Pada titik ini sulit bagi saya untuk tidak menganggap komentar Anda sebagai menghasut, jika itu bukan niat Anda, saya sarankan untuk lebih berhati-hati saat berkomentar di masa mendatang. Jika Anda tidak puas dengan kemajuan masalah ini, Anda selalu dapat membantu dengan mengirimkan tambalan/PR untuk ditinjau.

Catatan untuk diri sendiri dan siapa pun yang mempertimbangkan untuk mencoba menyelesaikan ini ...

Saya baru-baru ini diingatkan mengapa kami melakukan apa yang kami lakukan sehubungan dengan transaksi (salin semuanya di depan); kami melakukan ini karena kami harus dapat mengembalikan transaksi tanpa gagal. Mengapa?
Operasi seccomp_rule_add() normal perlu menjaga filter tetap utuh bahkan jika terjadi kegagalan; jika kita gagal dalam transaksi multi-bagian (mis. panggilan syscall socket/ipc pada x86/s390/s390x/dll.) sebagai bagian dari penambahan aturan normal, kita HARUS dapat kembali ke filter pada awal transaksi tanpa gagal ( terlepas dari tekanan memori, dll.).

Menduplikasi pohon tanpa aturan akan terus menjadi tantangan karena sifat pohon dan tautan di dalam pohon, tetapi kita mungkin dapat memilih secara selektif kapan kita perlu membuat transaksi internal, melewatkannya untuk banyak kasus ketika tidak diperlukan.

Saya menghabiskan lebih banyak waktu untuk melihat ini dan karena cara kami secara destruktif memodifikasi pohon keputusan selama penambahan aturan, saya tidak yakin kita dapat menghindari membungkus penambahan aturan dengan transaksi. Ini berarti daripada menemukan cara untuk membatasi penggunaan transaksi kita secara internal, kita perlu menemukan cara untuk mempercepatnya, untungnya saya pikir saya mungkin telah menemukan solusi: pohon bayangan.

Saat ini kami membangun pohon baru setiap kali kami membuat transaksi baru dan membuangnya jika berhasil, yang seperti yang telah kami lihat bisa sangat lambat dalam beberapa kasus penggunaan. Pikiran saya adalah bahwa alih-alih membuang pohon duplikat pada komit, kami mencoba menambahkan aturan yang baru saja kami tambahkan ke pohon duplikat (menjadikannya salinan dari filter saat ini) dan menyimpannya sebagai "transaksi bayangan" untuk mempercepat berikutnya cuplikan transaksi. Beberapa catatan:

  • db_col_transaction_start() harus mencoba menggunakan transaksi bayangan jika ada, tetapi jika tidak, itu harus mundur ke perilaku saat ini.
  • db_col_transaction_abort() harus berperilaku sama seperti sekarang; ini berarti transaksi yang gagal akan menghapus transaksi bayangan (perlu pohon untuk memulihkan filter), tetapi transaksi yang berhasil berikutnya akan memulihkan bayangan. Transaksi yang gagal harus cukup jarang sehingga ini seharusnya tidak menjadi masalah besar.
  • Kita mungkin perlu menghapus transaksi bayangan pada operasi lain, misalnya operasi arch/ABI?, tetapi itu adalah sesuatu yang perlu kita periksa. Terlepas dari itu, menyelesaikan transaksi bayangan seharusnya sepele.
  • Ini memiliki keuntungan tidak hanya mempercepat penambahan aturan, tetapi mempercepat transaksi secara umum. Ini mungkin tidak signifikan sekarang, tetapi akan berguna ketika kami mengekspos fungsionalitas transaksi kepada pengguna (ini akan diperlukan jika kami ingin melakukan mekanisme seperti "janji" BSD).

Saya punya waktu setelah makan malam malam ini, jadi saya segera menerapkan ide transaksi bayangan di atas. Kodenya masih kasar, dan pengujian saya (di bawah) bahkan lebih kasar, tetapi sepertinya kita melihat beberapa peningkatan kinerja dengan pendekatan ini:

  • Biaya pengujian dasar
# time for i in {0..20000}; do /bin/true; done
real    0m10.479s
user    0m7.641s
sys     0m3.924s
  • Cabang utama saat ini
# time for i in {0..20000}; do ./42-sim-adv_chains > /dev/null; done

real    0m16.303s
user    0m12.584s
sys     0m4.501s
  • Ditambal
time for i in {0..20000}; do ./42-sim-adv_chains > /dev/null; done

real    0m15.021s
user    0m11.540s
sys     0m4.387s

Jika kami mengurangi biaya pengujian, kami melihat peningkatan kinerja sekitar 20% pada "pengujian" ini, tetapi saya berharap manfaat untuk kumpulan filter kompleks menjadi lebih baik (jauh lebih baik?) daripada ini.

@varqox dan/atau @crosbymichael setelah saya membersihkan patch sedikit dan membuat PR, apakah Anda dapat menguji ini di lingkungan Anda?

Contoh kasus uji saya sudah ada di sini:

Halo,
masalah ini diperkenalkan oleh komit ce3dda9 antara v2.3.3 dan v2.4.0. Perhatikan contoh berikut: foo.c.zip .
Itu menambahkan sejumlah besar aturan. Dan bekerja sekitar 100 kali lebih lambat setelah komit yang disebutkan di atas.

foo.c waktu eksekusi menggunakan v2.4.1: 0.448
foo.c waktu eksekusi menggunakan v2.3.3: 0.077

Tapi begitu PR siap, saya bisa mengujinya di lingkungan saya.

Hai @varqox , ya saya melihat Anda memasukkan kasus uji dalam laporan asli, tetapi saya lebih tertarik untuk mendengar bagaimana kinerjanya dalam penggunaan nyata . Jika Anda dapat mencoba PR #180 dan melaporkan kembali, saya akan sangat menghargainya - terima kasih!

Hai @pcmoore ,

Terima kasih telah membuat PR ini.
Saya membuat dan menguji PR #180 Anda, hasilnya menjanjikan untuk test case saya. Saya melihat masalah ini karena pelanggan menggunakan pemeriksaan kesehatan buruh pelabuhan dan mengalami masalah kinerja di libseccomp 2.4.x .
Dalam kasus pengujian saya, kinerja PR ini sebanding dengan libseccomp 2.3.3 . Detailnya seperti di bawah ini:

Lingkungan

Ubuntu 19.04 VM (2 CPU, memori 2G) di MacBook Pro (15 inci, pertengahan 2015)
Kernel 5.0.0-32-generik
Docker CE 19.03.2

Kasus cobaan:

Siapkan 20 wadah:

for i in $(seq 1 20)
do
  docker run -d --name bb$i busybox sleep 3d
done

Jalankan pengujian dengan menembakkan docker exec pada semua container secara bersamaan

for i in $(seq 1 20)
do 
  /usr/bin/time -f "%E real" docker exec bb$i true & 
done

Hasil

libsecomp 2.3.3

0:01.05 real
0:01.12 real
0:01.16 real
0:01.20 real
0:01.23 real
0:01.27 real
0:01.31 real
0:01.35 real
0:01.37 real
0:01.38 real
0:01.40 real
0:01.41 real
0:01.40 real
0:01.40 real
0:01.45 real
0:01.46 real
0:01.47 real
0:01.48 real
0:01.48 real
0:01.49 real

libsecomp 2.4.1

0:00.98 real
0:01.63 real
0:01.67 real
0:01.95 real
0:02.55 real
0:02.70 real
0:02.70 real
0:02.96 real
0:03.04 real
0:03.16 real
0:03.17 real
0:03.21 real
0:03.23 real
0:03.27 real
0:03.24 real
0:03.29 real
0:03.27 real
0:03.29 real
0:03.28 real
0:03.27 real

Membangun PR Anda

0:00.95 real
0:01.12 real
0:01.20 real
0:01.23 real
0:01.28 real
0:01.29 real
0:01.31 real
0:01.37 real
0:01.38 real
0:01.40 real
0:01.43 real
0:01.43 real
0:01.44 real
0:01.45 real
0:01.42 real
0:01.47 real
0:01.48 real
0:01.48 real
0:01.48 real
0:01.50 real

catatan lain-lain

  • Saya menetapkan 2.4.1 di AC_INIT di configure.ac sebelum saya membuat PR ini.
  • Custom build ini dipasang di /usr/local/lib , saya memverifikasinya dengan menjalankan ldd /usr/bin/runc untuk memastikan custom build digunakan selama pengujian.
  • Saya menjalankan tes beberapa kali, ada perbedaan hasil yang sangat kecil. Jadi saya yakin itu bukan hasil yang tidak disengaja.

Itu bagus, terima kasih atas bantuannya @xinfengliu!

Hai @pcmoore ,
Terima kasih untuk PR ini.
Dalam kasus saya, ini mengembalikan kinerja libseccomp ke tingkat yang sebanding dengan v2.3.3.

Hasil

foo.c

g++ foo.c -lseccomp -o foo -O3
for ((i=0; i<10; ++i)); do time ./foo; done 

libsecomp 2.3.3

./foo  0.01s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.020 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total
./foo  0.02s user 0.00s system 98% cpu 0.018 total
./foo  0.02s user 0.00s system 98% cpu 0.019 total

Rata-rata: 0.0188 s

libsecomp 2.4.2

./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total
./foo  0.19s user 0.00s system 99% cpu 0.193 total
./foo  0.19s user 0.00s system 99% cpu 0.196 total
./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.20s user 0.00s system 99% cpu 0.196 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total
./foo  0.20s user 0.00s system 99% cpu 0.197 total
./foo  0.19s user 0.00s system 99% cpu 0.195 total
./foo  0.19s user 0.00s system 99% cpu 0.194 total

Rata-rata: 0.1949 s

PR #180

./foo  0.01s user 0.01s system 98% cpu 0.012 total
./foo  0.01s user 0.00s system 97% cpu 0.013 total
./foo  0.01s user 0.00s system 96% cpu 0.013 total
./foo  0.01s user 0.01s system 97% cpu 0.014 total
./foo  0.01s user 0.00s system 97% cpu 0.012 total
./foo  0.01s user 0.00s system 98% cpu 0.013 total
./foo  0.01s user 0.00s system 98% cpu 0.012 total
./foo  0.01s user 0.00s system 98% cpu 0.013 total
./foo  0.01s user 0.00s system 97% cpu 0.013 total
./foo  0.01s user 0.00s system 97% cpu 0.011 total

Rata-rata: 0.0126 s

Sepertinya PR ini memberikan beberapa percepatan lebih dari v2.3.3 dalam tes sintetis ini.

Membangun dan memuat (dari seccomp_init() ke seccomp_load()) filter di kotak pasir saya ditambah beberapa overhead inisialisasi kotak pasir

libsecomp 2.3.3

Measured: 0.0052 s
Measured: 0.0040 s
Measured: 0.0046 s
Measured: 0.0042 s
Measured: 0.0038 s
Measured: 0.0038 s
Measured: 0.0039 s
Measured: 0.0036 s
Measured: 0.0042 s
Measured: 0.0044 s
Measured: 0.0036 s
Measured: 0.0037 s
Measured: 0.0044 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0040 s
Measured: 0.0037 s
Measured: 0.0043 s
Measured: 0.0042 s
Measured: 0.0035 s
Measured: 0.0034 s
Measured: 0.0038 s
Measured: 0.0035 s
Measured: 0.0035 s
Measured: 0.0037 s
Measured: 0.0038 s

Rata-rata: 0.0039 s

libsecomp 2.4.2

Measured: 0.0496 s
Measured: 0.0480 s
Measured: 0.0474 s
Measured: 0.0475 s
Measured: 0.0479 s
Measured: 0.0479 s
Measured: 0.0492 s
Measured: 0.0485 s
Measured: 0.0491 s
Measured: 0.0490 s
Measured: 0.0484 s
Measured: 0.0483 s
Measured: 0.0480 s
Measured: 0.0482 s
Measured: 0.0474 s
Measured: 0.0483 s
Measured: 0.0507 s
Measured: 0.0472 s
Measured: 0.0482 s
Measured: 0.0471 s
Measured: 0.0498 s
Measured: 0.0489 s
Measured: 0.0474 s
Measured: 0.0494 s
Measured: 0.0483 s
Measured: 0.0498 s
Measured: 0.0492 s

Rata-rata: 0.0466 s

PR #180

Measured: 0.0058 s
Measured: 0.0059 s
Measured: 0.0054 s
Measured: 0.0046 s
Measured: 0.0059 s
Measured: 0.0048 s
Measured: 0.0045 s
Measured: 0.0051 s
Measured: 0.0052 s
Measured: 0.0053 s
Measured: 0.0048 s
Measured: 0.0048 s
Measured: 0.0045 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0059 s
Measured: 0.0044 s
Measured: 0.0046 s
Measured: 0.0046 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0062 s
Measured: 0.0047 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0044 s
Measured: 0.0044 s

Rata-rata: 0.0049 s

Meskipun dalam PR uji sintetis memberikan waktu yang lebih baik daripada v2.3.3, di dunia nyata ini sedikit lebih lambat (mungkin karena aturan yang lebih rumit dan menjalankan seccomp_merge() untuk menggabungkan dua filter besar). Namun, masih memberikan kecepatan sekitar sepuluh kali lipat lebih dari v2.4.2.

Terima kasih telah memverifikasi kinerja @varqox! Segera setelah @drakenclimber menanggapi putaran terakhir komentar (dan saya memperbaiki masalah yang tersisa yang mungkin dia kemukakan), kami akan menggabungkannya.

Ah, tidak apa-apa, saya baru menyadari bahwa @drakenclimber menandai PR sebagai disetujui. Saya akan melanjutkan dan menggabungkannya sekarang.

Saya baru saja menggabungkan PR #180 jadi saya pikir kami dapat menandai ini sebagai ditutup, jika ada yang melihat ada masalah kinerja yang tersisa, jangan ragu untuk berkomentar dan/atau membuka kembali. Terima kasih semuanya atas kesabaran dan bantuan Anda!

@pcmoore apakah Anda berencana untuk segera merilis dengan perubahan itu?

Saat ini merupakan bagian dari tonggak rilis libseccomp v2.5, Anda dapat melacak kemajuan kami menuju rilis v2.5 menggunakan tautan di bawah ini:

Apakah halaman ini membantu?
0 / 5 - 0 peringkat