File dari memfd_create() pada awalnya merupakan file anonim yang mutable. Ukurannya dapat diubah, isinya dapat ditulis, dan file dapat dipetakan seperti file biasa, sementara storage-nya bersifat volatile dan dilepas setelah referensi terakhir hilang. File sealing menambahkan fase lain pada lifecycle tersebut: setelah data selesai diisi, kernel dapat menolak secara permanen kelas perubahan tertentu.
Transisi ini berguna saat satu process menyiapkan byte lalu menyerahkan file description yang sama ke process lain. Penerima dapat memeriksa seal yang melekat pada inode, bukan hanya mengandalkan kesepakatan bahwa pengirim tidak akan mengubah objek lagi.
Seal melekat pada inode
Sealing diaktifkan saat memfd dibuat dengan MFD_ALLOW_SEALING. Tanpa flag tersebut, memfd dimulai dengan F_SEAL_SEAL, sehingga seal tambahan tidak dapat dipasang.
int fd = memfd_create("snapshot", MFD_CLOEXEC | MFD_ALLOW_SEALING);
if (fd == -1)
abort();
if (ftruncate(fd, 4096) == -1)
abort();Kumpulan seal merupakan properti inode, bukan nomor descriptor tertentu. Descriptor hasil duplikasi maupun descriptor yang dikirim ke process lain karena itu melihat batas yang sama. Seal bersifat monotonic: F_ADD_SEALS dapat menambah pembatasan, tetapi tidak ada operasi untuk menghapusnya.
Sifat ini membuat sealing menjadi transisi state, bukan lock sementara.
tanpa seal
|
+--> SHRINK
| |
| +--> GROW
| |
| +--> WRITE
| |
| +--> SEAL
|
`-- pembatasan hanya bertambahF_SEAL_SEAL menutup transisi itu sendiri. Setelah seal tersebut terpasang, pemanggilan F_ADD_SEALS berikutnya gagal dengan EPERM.
Ukuran dan isi adalah properti terpisah
F_SEAL_SHRINK mencegah ukuran file diperkecil. F_SEAL_GROW mencegah ukurannya diperbesar. Keduanya terpisah karena sebuah protokol dapat perlu menutup satu arah perubahan lebih dahulu daripada arah lainnya.
Producer yang sudah menetapkan payload berukuran tetap dapat memasang keduanya:
int seals = F_SEAL_SHRINK | F_SEAL_GROW;
if (fcntl(fd, F_ADD_SEALS, seals) == -1)
abort();Setelah itu, operasi yang mencoba melewati batas ukuran tersebut akan gagal. Byte yang sudah ada masih dapat ditulis selama write seal belum dipasang.
Perbedaan ini penting pada format shared memory. Ukuran yang stabil mencegah peer melakukan truncate terhadap objek yang sudah dipetakan dan membuat process lain mengakses area di luar akhir file yang baru, tetapi kondisi itu belum membuat payload immutable.
F_SEAL_WRITE mensyaratkan shared mapping writable sudah dilepas
F_SEAL_WRITE memblokir perubahan isi file melalui operasi seperti write(). Seal ini juga mencegah pembuatan shared mapping writable baru.
Kernel tidak akan menambahkan F_SEAL_WRITE selama masih ada shared mapping writable. F_ADD_SEALS gagal dengan EBUSY pada kondisi tersebut. Producer yang mengisi objek melalui MAP_SHARED | PROT_WRITE perlu melepas mapping itu sebelum memasang seal.
void *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (p == MAP_FAILED)
abort();
memcpy(p, payload, payload_len);
if (munmap(p, 4096) == -1)
abort();
int seals = F_SEAL_SHRINK |
F_SEAL_GROW |
F_SEAL_WRITE |
F_SEAL_SEAL;
if (fcntl(fd, F_ADD_SEALS, seals) == -1)
abort();Urutan tersebut membentuk boundary publikasi yang jelas:
buat -> atur ukuran -> map writable -> isi -> unmap
|
v
pasang seal
|
v
kirim fdSetelah seluruh seal berhasil dipasang, holder berikutnya tidak dapat membuka kembali jalur mutasi yang dicakup oleh seal tersebut.
F_SEAL_FUTURE_WRITE membiarkan writer lama tetap aktif
F_SEAL_FUTURE_WRITE memiliki boundary yang lebih sempit. Seal ini memblokir write() berikutnya dan pembuatan shared mapping writable baru, tetapi shared mapping writable yang sudah ada saat seal dipasang masih dapat mengubah file.
Perilaku tersebut mendukung window update yang tetap dimiliki producer. Sebuah process dapat mempertahankan mapping writable yang sudah ada sambil mencegah process penerima membuat mapping writable baru.
mapping producer: writable sebelum seal
|
+---- tetap writable
|
F_SEAL_FUTURE_WRITE dipasang
|
+---- write() baru ditolak
`---- shared mmap writable baru ditolakKondisi ini tidak sama dengan publikasi immutable. Byte masih dapat berubah melalui mapping yang sudah ada. Consumer yang memerlukan isi stabil membutuhkan boundary lifecycle yang lebih kuat, misalnya melepas mapping writable lalu memasang F_SEAL_WRITE.
Perbedaan kedua seal tersebut terletak pada otoritas mutasi yang sudah ada. F_SEAL_WRITE mensyaratkan shared mapping writable tidak lagi ada ketika seal dipasang; F_SEAL_FUTURE_WRITE dapat hidup berdampingan dengan mapping yang sebelumnya sudah writable.
Penerima dapat memverifikasi kontrak
Process yang menerima memfd melalui UNIX domain socket dapat membaca mask seal saat ini dengan F_GET_SEALS.
int seals = fcntl(fd, F_GET_SEALS);
if (seals == -1)
abort();
int required = F_SEAL_SHRINK |
F_SEAL_GROW |
F_SEAL_WRITE |
F_SEAL_SEAL;
if ((seals & required) != required)
reject_object();Pemeriksaan ini mengubah asumsi mengenai objek menjadi state yang terlihat oleh kernel. Protokol dapat mewajibkan kumpulan seal tertentu sebelum memproses data yang harus tetap stabil.
Pemeriksaan harus sesuai dengan invariant yang dibutuhkan. F_SEAL_WRITE saja tidak melarang perubahan ukuran. F_SEAL_GROW | F_SEAL_SHRINK saja tidak melarang byte ditimpa. F_SEAL_SEAL hanya mencegah perubahan berikutnya pada kumpulan seal; seal itu sendiri tidak membekukan data file.
Transfer descriptor tidak melemahkan seal
memfd dapat dikirim antarprocess memakai SCM_RIGHTS melalui UNIX domain socket. Process penerima memperoleh descriptor yang merujuk file dasar yang sama. Karena seal merupakan properti inode, transfer tidak menghasilkan copy tanpa seal.
producer consumer
memfd fd
|
pasang seal
|
sendmsg(SCM_RIGHTS) ----------> recvmsg()
|
v
received fd
|
v
F_GET_SEALSProperti ini berbeda dari mengirim raw bytes lalu mempercayai field metadata terpisah yang menyatakan sumber bersifat immutable. Pembatasan tetap melekat pada objek kernel yang membawa data.
File seal tidak menyediakan authentication atau confidentiality. Penerima tetap memerlukan protokol untuk menentukan peer mana yang dipercaya mengirim objek, dan memfd tidak menyembunyikan isi dari process yang memang memperoleh akses. Sealing membatasi mutasi objek; mekanisme ini tidak menetapkan identitas peer.
Sealing mempersempit race surface pada shared memory
Shared memory yang mutable menyulitkan validasi ketika participant lain dapat mengubah byte di antara pemeriksaan dan pemakaian berikutnya. Menyalin payload ke private memory merupakan salah satu cara membuat snapshot lokal yang stabil. memfd dengan seal lengkap memberi boundary lain ketika berbagi backing object yang sama tetap diperlukan.
Jaminannya bersifat spesifik: setelah seal yang relevan berhasil dipasang, operasi yang dicakup seal tersebut ditolak oleh kernel. Jaminan itu tidak memvalidasi secara retroaktif byte yang ditulis sebelum sealing dan tidak menentukan makna format data.
Untuk protokol producer-consumer, batas tersebut memisahkan dua tanggung jawab. Producer bertanggung jawab membentuk byte yang valid sebelum publikasi. Kumpulan seal kernel kemudian dapat mempertahankan properti ukuran dan isi yang diharapkan consumer setelah publikasi.
Referensi
- Linux
memfd_create(2): https://man7.org/linux/man-pages/man2/memfd_create.2.html - Linux
F_GET_SEALS(2const): https://man7.org/linux/man-pages/man2/F_GET_SEALS.2const.html