# Pemindaian Pickle

Pickle adalah format serialisasi yang banyak digunakan dalam ML. Yang paling menonjol, format ini merupakan format default untuk bobot model PyTorch.

Terdapat serangan eksekusi kode arbitrer yang berbahaya yang dapat dilakukan saat Anda memuat file pickle. Kami menyarankan untuk memuat model dari pengguna dan organisasi yang Anda percaya, mengandalkan commit yang ditandatangani, dan/atau memuat model dari format TF atau Jax dengan mekanisme konversi otomatis `from_tf=True`. Kami juga memitigasi masalah ini dengan menampilkan/"memverifikasi" daftar impor di setiap file pickle, langsung di Hub. Terakhir, kami sedang bereksperimen dengan format serialisasi baru yang sederhana untuk bobot yang disebut [`safetensors`](https://github.com/huggingface/safetensors).

## Apa itu pickle?

Dari [dokumentasi resmi](https://docs.python.org/3/library/pickle.html) :

> Modul `pickle` mengimplementasikan protokol biner untuk serialisasi dan deserialisasi struktur objek Python.

Artinya, pickle adalah protokol serialisasi, sesuatu yang Anda gunakan untuk berbagi data secara efisien di antara berbagai pihak.

Kami menyebut pickle sebagai file biner yang dihasilkan saat proses pickling.

Pada intinya, pickle pada dasarnya adalah tumpukan instruksi atau opcode. Seperti yang mungkin Anda tebak, format ini tidak dapat dibaca oleh manusia. Opcode dihasilkan saat proses pickling dan dibaca secara berurutan saat proses unpickling. Berdasarkan opcode, aksi tertentu akan dieksekusi.

Berikut adalah contoh kecil:

```python
import pickle
import pickletools

var = "data I want to share with a friend"

# store the pickle data in a file named 'payload.pkl'
with open('payload.pkl', 'wb') as f:
    pickle.dump(var, f)

# disassemble the pickle
# and print the instructions to the command line
with open('payload.pkl', 'rb') as f:
    pickletools.dis(f)
```

Saat Anda menjalankan ini, file pickle akan dibuat dan instruksi berikut akan dicetak di terminal Anda:

```python
    0: \x80 PROTO      4
    2: \x95 FRAME      48
   11: \x8c SHORT_BINUNICODE 'data I want to share with a friend'
   57: \x94 MEMOIZE    (as 0)
   58: .    STOP
highest protocol among opcodes = 4
```

Jangan terlalu khawatir tentang instruksi untuk saat ini, cukup ketahui bahwa modul [pickletools](https://docs.python.org/3/library/pickletools.html) sangat berguna untuk menganalisis pickle. Modul ini memungkinkan Anda membaca instruksi dalam file ***tanpa*** mengeksekusi kode apa pun.

Pickle bukan sekadar protokol serialisasi; ia memberikan fleksibilitas lebih dengan memungkinkan pengguna untuk menjalankan kode Python pada saat de-serialisasi. Kedengarannya tidak baik, bukan?

## Mengapa hal ini berbahaya?

Seperti yang telah kami sebutkan di atas, de-serialisasi pickle berarti kode dapat dieksekusi. Namun, hal ini memiliki beberapa keterbatasan: Anda hanya dapat merujuk fungsi dan kelas dari modul tingkat atas; Anda tidak dapat menyematkannya di dalam file pickle itu sendiri.

Kembali ke papan gambar:

```python
import pickle
import pickletools

class Data:
    def __init__(self, important_stuff: str):
        self.important_stuff = important_stuff

d = Data("42")

with open('payload.pkl', 'wb') as f:
    pickle.dump(d, f)
```

Saat kami menjalankan skrip ini, kami mendapatkan `payload.pkl` lagi. Saat kami memeriksa isi file:

```bash

# cat payload.pkl
__main__Data)}important_stuff42sb.%

# hexyl payload.pkl
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 80 04 95 33 00 00 00 00 ┊ 00 00 00 8c 08 5f 5f 6d │×•×30000┊000×•__m│
│00000010│ 61 69 6e 5f 5f 94 8c 04 ┊ 44 61 74 61 94 93 94 29 │ain__××•┊Data×××)│
│00000020│ 81 94 7d 94 8c 0f 69 6d ┊ 70 6f 72 74 61 6e 74 5f │××}××•im┊portant_│
│00000030│ 73 74 75 66 66 94 8c 02 ┊ 34 32 94 73 62 2e       │stuff××•┊42×sb.  │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
```

Kita dapat melihat bahwa tidak ada banyak di dalamnya, hanya beberapa opcode dan data terkait. Anda mungkin berpikir, jadi apa masalahnya dengan pickle?

Mari kita coba hal lain:

```python
from fickling.pickle import Pickled
import pickle

# Create a malicious pickle
data = "my friend needs to know this"

pickle_bin = pickle.dumps(data)

p = Pickled.load(pickle_bin)

p.insert_python_exec('print("you\'ve been pwned !")')

with open('payload.pkl', 'wb') as f:
    p.dump(f)

# innocently unpickle and get your friend's data
with open('payload.pkl', 'rb') as f:
    data = pickle.load(f)
    print(data)
```

Di sini kami menggunakan pustaka [fickling](https://github.com/trailofbits/fickling) untuk kesederhanaan. Pustaka ini memungkinkan kami menambahkan instruksi pickle untuk mengeksekusi kode yang terkandung dalam sebuah string melalui fungsi `exec`. Inilah cara Anda menghindari fakta bahwa Anda tidak dapat mendefinisikan fungsi atau kelas di pickle Anda: Anda menjalankan exec pada kode python yang disimpan sebagai string.

Saat Anda menjalankan ini, akan dibuat `payload.pkl` dan mencetak berikut:

```
you've been pwned !
my friend needs to know this
```

Jika kita memeriksa isi file pickle, kita mendapatkan:

```bash
# cat payload.pkl
c__builtin__
exec
(Vprint("you've been pwned !")
tR my friend needs to know this.%

# hexyl payload.pkl
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 63 5f 5f 62 75 69 6c 74 ┊ 69 6e 5f 5f 0a 65 78 65 │c__built┊in___exe│
│00000010│ 63 0a 28 56 70 72 69 6e ┊ 74 28 22 79 6f 75 27 76 │c_(Vprin┊t("you'v│
│00000020│ 65 20 62 65 65 6e 20 70 ┊ 77 6e 65 64 20 21 22 29 │e been p┊wned !")│
│00000030│ 0a 74 52 80 04 95 20 00 ┊ 00 00 00 00 00 00 8c 1c │_tR×•× 0┊000000×•│
│00000040│ 6d 79 20 66 72 69 65 6e ┊ 64 20 6e 65 65 64 73 20 │my frien┊d needs │
│00000050│ 74 6f 20 6b 6e 6f 77 20 ┊ 74 68 69 73 94 2e       │to know ┊this×.  │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
```

Pada dasarnya, inilah yang terjadi saat Anda melakukan unpickle:

```python
# ...
opcodes_stack = [exec_func, "malicious argument", "REDUCE"]
opcode = stack.pop()
if opcode == "REDUCE":
    arg = opcodes_stack.pop()
    callable = opcodes_stack.pop()
    opcodes_stack.append(callable(arg))
# ...
```

Instruksi yang menimbulkan ancaman adalah `STACK_GLOBAL`, `GLOBAL`, dan `REDUCE`.

`REDUCE` adalah instruksi yang memberitahu unpickler untuk mengeksekusi fungsi dengan argumen yang diberikan, dan instruksi `*GLOBAL` memberitahu unpickler untuk `import` sesuatu.

Sebagai kesimpulan, pickle berbahaya karena:

- saat mengimpor modul python, kode arbitrer dapat dieksekusi
- Anda dapat mengimpor fungsi bawaan seperti `eval` atau `exec`, yang dapat digunakan untuk mengeksekusi kode arbitrer
- saat menginstansiasi sebuah objek, konstruktor dapat dipanggil

Inilah mengapa sebagian besar dokumentasi yang menggunakan pickle menyatakan untuk tidak melakukan unpickle data dari sumber yang tidak tepercaya.

## Strategi Mitigasi

***Jangan gunakan pickle***

Saran yang bagus, Luc, tetapi pickle digunakan secara luas dan tidak akan segera ditinggalkan: menemukan format baru yang memuaskan semua pihak dan memulai perubahan tersebut akan memakan waktu.

Jadi, apa yang bisa kita lakukan untuk saat ini?

### Muat file dari pengguna dan organisasi yang Anda percaya

Di Hub, Anda memiliki kemampuan untuk [menandatangani commit Anda dengan kunci GPG](./security-gpg). Hal ini **tidak** menjamin bahwa file Anda aman, tetapi menjamin asal file tersebut.

Jika Anda mengenal dan mempercayai pengguna A, serta commit yang berisi file di Hub ditandatangani oleh kunci GPG pengguna A, maka cukup aman untuk berasumsi bahwa Anda dapat mempercayai file tersebut.

### Muat bobot model dari TF atau Flax

Checkpoint TensorFlow dan Flax tidak terpengaruh, dan dapat dimuat dalam arsitektur PyTorch menggunakan kwarg `from_tf` dan `from_flax` untuk metode `from_pretrained` untuk menghindari masalah ini.

E.g.:

```python
from transformers import AutoModel

model = AutoModel.from_pretrained("google-bert/bert-base-cased", from_flax=True)
```

### Gunakan format serialisasi Anda sendiri

- [MsgPack](https://msgpack.org/index.html)
- [Protobuf](https://developers.google.com/protocol-buffers)
- [Cap'n'proto](https://capnproto.org/)
- [Avro](https://avro.apache.org/)
- [safetensors](https://github.com/huggingface/safetensors)

Format terakhir ini, `safetensors`, adalah format serialisasi sederhana yang sedang kami kerjakan dan uji coba saat ini! Tolong bantu atau berkontribusi jika Anda bisa 🔥.

### Perbaiki `torch.load/save`

Saat ini sedang berlangsung diskusi terbuka di PyTorch mengenai [Cara aman untuk memuat hanya bobot dari file *.pt secara default](https://github.com/pytorch/pytorch/issues/52181) – silakan ikut berkontribusi di sana!

### Security Scanner Hub

#### Yang kita miliki saat ini

Kami telah membuat pemindai keamanan yang memindai setiap file yang diunggah ke Hub dan menjalankan pemeriksaan keamanan. Pada saat penulisan ini, pemindai tersebut menjalankan dua jenis pemindaian:

- Pemindaian ClamAV
- Pindai Impor Pickle

Untuk pemindaian ClamAV, file diproses melalui antivirus open-source [ClamAV](https://www.clamav.net). Meskipun ini mencakup banyak file berbahaya, metode ini tidak mencakup eksploit pickle.

Kami telah mengimplementasikan pemindaian Pickle Import, yang mengekstrak daftar impor yang dirujuk dalam file pickle. Setiap kali Anda mengunggah `pytorch_model.bin` atau file pickled lainnya, pemindaian ini akan dijalankan.

Di hub, daftar impor akan ditampilkan di samping setiap file yang berisi impor. Jika ada impor yang terlihat mencurigakan, impor tersebut akan disorot.

Kami mendapatkan data ini berkat [`pickletools.genops`](https://docs.python.org/3/library/pickletools.html#pickletools.genops) yang memungkinkan kami membaca file tanpa mengeksekusi kode yang berpotensi berbahaya.

Perhatikan bahwa hal ini memungkinkan untuk mengetahui apakah, saat melakukan unpickling pada sebuah file, akan terjadi `REDUCE` pada fungsi yang berpotensi berbahaya yang diimpor oleh `*GLOBAL`.

***Penafian***: ini tidak 100% bebas dari kesalahan. Tanggung jawab Anda sebagai pengguna untuk memeriksa apakah sesuatu aman atau tidak. Kami tidak secara aktif mengaudit paket python untuk keamanan; daftar impor aman/tidak aman yang kami miliki dipelihara dengan upaya terbaik. Silakan hubungi kami jika Anda merasa sesuatu tidak aman, dan kami akan menandainya sebagai demikian, dengan mengirimkan email ke website di huggingface.co

#### Solusi potensial

Kita bisa memikirkan pembuatan [Unpickler](https://docs.python.org/3/library/pickle.html#pickle.Unpickler) kustom seperti [ini](https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/serial.py). Namun, seperti yang dapat kita lihat dalam [eksploit canggih](https://ctftime.org/writeup/16723) ini, hal itu tidak akan berhasil.

Untungnya, selalu ada jejak dari impor `eval`, sehingga membaca opcode secara langsung seharusnya memungkinkan untuk menangkap penggunaan yang berbahaya.

Solusi yang saya usulkan saat ini adalah membuat file yang menyerupai `.gitignore` tetapi untuk impor.

File ini akan menjadi daftar putih impor yang akan menandai file `pytorch_model.bin` sebagai berbahaya jika terdapat impor yang tidak termasuk dalam daftar putih.

Kita bisa membayangkan adanya format mirip regex di mana, misalnya, semua submodul numpy dapat diizinkan melalui baris sederhana seperti: `numpy.*`.

## Bacaan Lanjutan

[pickle - serialisasi objek Python - dokumentasi Python 3.10.6](https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled)

[Pickle Berbahaya - Serialisasi Python yang Merusak](https://intoli.com/blog/dangerous-pickles/)

[GitHub - trailofbits/fickling: Dekompiler dan analis statis untuk pickling Python](https://github.com/trailofbits/fickling)

[Exploitasi Python pickles](https://davidhamann.de/2020/04/05/exploiting-python-pickle/)

[cpython/pickletools.py di 3.10 · python/cpython](https://github.com/python/cpython/blob/3.10/Lib/pickletools.py)

[cpython/pickle.py di 3.10 · python/cpython](https://github.com/python/cpython/blob/3.10/Lib/pickle.py)

[CrypTen/serial.py di main · facebookresearch/CrypTen](https://github.com/facebookresearch/CrypTen/blob/main/crypten/common/serial.py)

[CTFtime.org / Balsn CTF 2019 / pyshv1 / Writeup](https://ctftime.org/writeup/16723)

[Memulihkan modul pickle Python](https://github.com/moreati/pickle-fuzz)

