Sunucunun şiddet düzeyini seçmesi
(cliffle.com)- Hubris, yalıtılmış görevlerin IPC ile iletişim kurduğu bir işletim sistemidir; 13. sistem çağrısı olan
REPLY_FAULT, sunucunun hatalı istemci isteklerini hata değeri yerine fault ile sonlandırabilmesini sağlar - İstemci açısından IPC bir fonksiyon çağrısı gibi görünür, ancak görevler ayrı derlendiği için yanlış işlem kodu, yorumlanamayan baytlar veya uygunsuz loaned memory durumlarının hepsini derleyici engelleyemez
- Normal bir Hubris programı, derleme ayarları ve üretilen Rust kodu sayesinde bu hatalarla neredeyse hiç karşılaşmaz; bu yüzden her çağrıda
Result<T, IpcError>veunwrap()zorunlu kılmak kod boyutunu ve çalışma zamanı maliyetini artırır - Çekirdek, sistem çağrısı önkoşullarını ihlal eden görevleri hata kodu vermeden anında öldürür;
REPLY_FAULTaynı fail-fast politikasını sunucu yanıtlarına genişletir - Bu tasarım yanlış API kullanımını hızla ortaya çıkarır, ancak rastgele IPC ve sistem çağrıları gönderen fuzz test veya chaos görevleri neredeyse hemen yeniden başlatıldığı için test yapmayı zorlaştırır
Hubris IPC ve REPLY_FAULTun konumu
- Hubris küçük, uygulamadan bağımsız bir çekirdeğe sahiptir; sürücüler, uygulama mantığı ve ağ yığını gibi kodların çoğunu ayrı derlenmiş yalıtılmış görevlerde tutar
- Görevler arası iletişim, çekirdeğin uyguladığı IPC sistem çağrılarıyla yapılır
RECV: en yüksek öncelikli gelen mesajı alır veya mesaj gelene kadar bloklanırSEND: çağıranı durdurur, mesajı ve denetimi alıcı göreve devreder, ardından yanıt alana kadar beklerREPLY: daha önceSENDyapan göreve yanıt ileterek yeniden çalışmasını sağlar
- Hubris’te istemci ve sunucu sabit kimlikler değil, görevlerin üstlendiği rollerdir
SENDkullanan görev istemci rolündedirRECVveREPLYkullanan görev sunucu rolündedir- Bir görev, bir göreve karşı sunucu iken başka bir göreve karşı istemci olabilir
Derleyicinin görev sınırlarında kaçırdığı hatalar
- Sıradan fonksiyon çağrılarında derleyici ve bağlayıcı, türleri ve çağrı hedeflerini büyük ölçüde garanti eder
- Bir Rust fonksiyonu
Stringargümanı alıyorsa, çağıranınboolgeçmesini derleyici engeller pet_catçağırmak isterkenfire_missilesçağırmak gibi hedef karışıklıkları da normalde yaşanmaz
- Bir Rust fonksiyonu
- Hubris IPC görev sınırlarını aşar ve her görev ayrı bir program olarak derlendiği için derleyici tüm IPC ilişkilerini doğrudan doğrulayamaz
- Bir IPC sunucusunun karşılaşabileceği hatalar kabaca üçe ayrılır
- Arayüze uymayan işlem kodu; örneğin yalnızca iki işlem bulunan bir arayüze “operation number 48” gelmesi
- Beklenen mesaj türü yerine yorumlanamayan bir bayt kümesi gelmesi ya da mesajın çok kısa veya çok uzun olması
- Gerekli loaned memory’nin olmaması ya da yazılabilir bellek gerekirken salt okunur bellek gelmesi
Normal programlara hata işlemeyi zorunlu kılmama nedeni
- Normal Hubris programlarında bu tür IPC hataları oluşmayacak şekilde yapılandırma yapılır
- Görev bağlantıları derleme sistemi ayarlarıyla yapılandırılır, bu yüzden birbirleriyle karıştırılmaları zordur
- İstemci, üretilen Rust koduyla IPC’yi kurar ve gönderir
- Sunucu da ayrı üretilmiş Rust koduyla sonucu işler
- Tüm IPC işlemleri
Result<T, IpcError>döndürecek şekilde tasarlanırsa normal programlar pratikte karşılaşamayacakları hatalar içinunwrap()koymak zorunda kalırunwrap()kod boyutu açısından yük getirir- Çalışma zamanında da hiç gerçekleşmeyecek hataları kontrol etme maliyeti doğar
- Üretilen kodun içine
unwrap()veyapanic!koymak panic konumunu merkezîleştirerek kod boyutu etkisini azaltabilir, ancak çalışma zamanı maliyeti aynen kalır - Genel hata kodlarını desteklemek için tüm işlemlerin aynı hata kodlama kurallarını izlemesi gerekir
- Tüm işlemler hata döndürebilmelidir
- Tüm işlemler bu hatayı aynı şekilde kodlamalıdır
- Başarısız olamayacak işlemler bile başarısız olabilir biçimde temsil edilmelidir
- Hubris tabanlı firmware’de gerçekten başarısız olamayacak işlemler sürekli bulundu; GPIO pin ayarı buna bir örnektir
Hubris çekirdeğinin agresif fault politikası
- Birçok işletim sistemi, sistem çağrısı önkoşulları ihlal edilse bile hata kodu döndürür ya da istisna/sinyal işleme fırsatı verir
- Unix’te açılmamış bir dosya tanıtıcısı
closeedilirse hata kodu döner opençağrısına yol adı yerine null pointer verilse bile hata kodu döner
- Unix’te açılmamış bir dosya tanıtıcısı
- Hubris, sistem çağrısı önkoşulları bozulduğunda ilgili görevi anında yok eder
- Görev artık komut yürütemez
- Görevin kendisine kurtarma veya devam etme fırsatı verilmez
- Uygulamanın supervisor görevi fault bildirimi alır ve genellikle görevi silip yeniden başlatır
- Çekirdeğin ürettiği fault bir synthetic faulttur
- null pointer dereference veya sıfıra bölme gibi CPU’nun ürettiği donanım fault’larına benzer
- Donanım fault’u işlemci mimarisi kurallarının ihlalinden, synthetic fault ise çekirdek kurallarının ihlalinden doğar
- Örneğin bir
SENDçağrısında alıcı görev indeksi uygulama aralığının dışındaysa veya mesaj işaretçisi erişim izni olmayan belleği gösteriyorsa synthetic fault oluşur - Hubris, kurtarılabilir veya devam edilebilir fault’lara izin vermez
- Donanım fault’u da olsa synthetic fault da olsa fault alan görev ölü duruma geçer
- Bu tercih, incelikli arıza modlarından kaçınmak ve sistem üzerine akıl yürütmeyi basitleştirmek içindir
Sunucunun istemciye fault ile yanıt verme biçimi
REPLY_FAULT, sunucunun istemciye normal yanıt yerine fault iletmesini sağlayan bir sistem çağrısıdır- Tipik
REPLYakışı şöyledir- İstemci
SENDkullandığında çekirdek istemci görevi alıcı görev için “waiting to send” durumunda işaretler - Alıcı görev
RECVkullandığında ilgili istemci “waiting for reply” durumuna geçer - Sunucu
REPLYçağırdığında istemci runnable durumuna döner
- İstemci
REPLY_FAULT,REPLYye benzer; ancak mesaj iletip çalıştırılabilir duruma getirmek yerine fault ileterek görevi ölü duruma sokar- Sunucu keyfi bir görevi öldüremez
REPLY_FAULTyalnızca ilgili sunucununRECVettiği ve henüzREPLYetmediği görevlerde kullanılabilir- Yalnızca belirli bir sunucunun yanıtını bekleyen istemciler üzerinde çalışır
- Hubris,
REPLY_FAULTu şu hata işlemleri için kullanır- Yanlış işlem kodu
- Bozuk, kesilmiş veya anlamsız mesaj
- İstemcinin doğru türde loaned memory göndermemesi
Uygulama hataları ve fail-fast deneyimi
REPLY_FAULTyalnızca IPC biçim hataları için değil, uygulamaya özgü hatalar için de kullanılabilir- Hubris IP yığını IP portlarını görevlere statik olarak atar
- Bir görev başka bir görevin IP portuna dokunmaya çalışırsa IP yığını ilgili göreve fault verir
- Bu yöntem pratikte oluşmaması gereken “teorik” hata işlemeyi azaltır ve yanlış kullanımı geliştirme sırasında hızla görünür kılar
REPLY_FAULT, Rust fonksiyon çağrısı önkoşulları ihlal edildiğinde genelliklepanic!oluşan modele benzer biçimde, sunucunun istemci süreci üzerinde süreçler arasıpanic!tetiklemesine yarayan bir araç olur- İstemcinin bunun için kod içermesi veya işbirliği yapması gerekmez
Güvenlik eğilimi ve testteki kısıtlar
- Eliza Weissman, Hubris’i “kötücül programlara karşı agresif biçimde hasmane” olarak tanımlar
- İstismar girişimleri çoğu zaman önce API hatası veya yanlış kullanım olarak ortaya çıktığından, hatalı davranan bileşenin durumunu silen bir sistemi istismar etmek daha zor olabilir
- Bu hipotez henüz test edilmedi
- Hubris exploit denemeleriyle ilgileniyorsanız iletişime geçmeniz isteniyor
- Gözlenen dezavantaj, sistemin fuzz test edilmesinin çok zor olmasıdır
- Rastgele IPC ve sistem çağrıları üreten küçük bir chaos görevi uygulandı, ancak neredeyse ne yaparsa yapsın hemen sıfırlanıyor
- Yararlı biçimde çalışması için, kararlarını her başlangıçta gözlemlenebilir şekilde değişen sistem uptime counter’a dayandırması gerekiyor
REPLY_FAULT, sunucunun istemcileri rastgele öldürerek chaos’u zorlaması için de bir yol sunar; ancak bu seçenek henüz tam olarak değerlendirilmedi- Tipik Hubris görevleri kasıtlı olarak hatalı IPC mesajlarını dinamik biçimde üretmediğinden, genellikle
REPLY_FAULTun varlığının farkında olmadan çalışabilir
1 yorum
Hacker News yorumları
REPLY_FAULT, sistem küçük ve sıkı tasarlanmışsa, uygulamaları da çoğunlukla tüm sistemi tasarlayan kişiler yazıyorsa iyi görünüyor
Ama bir uygulama geliştiricisi açısından, başka bir servisin herhangi bir anda benim sürecime anında öldüren bir hap döndürebildiği bir IPC modeliyle üçüncü taraf kodlara bağlanmak epey ürkütücü olurdu
Diğer uygulama geliştiricilerine o kadar güvenmiyorum. Dünya kötü sürücülerle ve yöneticilerin baskısı altındaki geliştiricilerin yaptığı arka plan süreçleriyle dolu; sırf saat 8'den önce çıkabilmek için uygunsuz olabilecek varsayılan REPLY_FAULT'lardan bolca koymaları çok olası
Tüm önkoşulları kolayca anlamak mümkün değildi; cihaza veya OS sürümüne göre değişebiliyordu
Kapsam elbette genişleme eğilimindedir, ama ana makinede ele alınması daha iyi olan işleri ille de gömülü denetleyicinin içindeki Hubris görevlerine itmek isteyeceklerini sanmıyorum
Sunucu “şu istemci hatalı” derse çekirdek o istemciyi öldürüyor. Asıl mesele, ikisinin birbirini anlamamış olması
Ayrıca “süreç” derken akla gelen şeyden farklı olabilir. Hubris'te iş parçacıklarının hepsi aynı adres alanını paylaşıyor
REPLY_FAULT zincirleme yayılıyor mu? Örneğin A, B'ye
SENDedip bekliyor; B de C'yeSENDedip bekliyor; CREPLY_FAULTyaparsa A da B ile birlikte ölür mü merak ediyorumÖyle değilse kötü niyetli bir görev deneyi yardımcı bir göreve devreder, olur biter. Tersi doğruysa da genel olarak epey kırılgan görünüyor; Hubris'i daha iyi bildiğimden değil
Üstelik
SENDdöngüsel veya karşılıklı olabiliyorsa bir görev yanlışlıkla kendi kendini öldürebilir. B → A → B gibi bir durumda bu, REPLY_FAULT kullanmamaya teşvik bile edebilirSunucunun istemciye karşı ateş edebilmesinin nedeni güvenlik değil, güvenilirlik. Hataların kasıtlı saldırılardan değil bug'lardan kaynaklandığı varsayılıyor; çekirdeğin aşırı tepkisi de geliştiricinin sorunu olabildiğince hızlı bulmasını sağlıyor
Elbette güvenlikle örtüştüğü yerler var; bir sürecin yapmaması gereken bir şeyi yapmaya çalıştığı durumlarda faydalı bir yedek savunma olabilir
Hubris ve hata ayıklayıcısı Humility, zamanım olsa ya da yapmam gereken bir görev çıksa derinlemesine kurcalamak isteyeceğim teknolojiler. Ne yazık ki şu anda mümkün değil
Tüm kodu tek bir ekibin yazdığı sistemlerde, istemci sadece tuhaf baktı diye onu yörüngeden vurup yok etme yaklaşımının yinelemeli geliştirme hızını artırabilmesi ilginç
Cebirsel etkileri okurken uyuyakaldıktan sonra sabah bu yazıyı okumak eğlenceli oldu. Biraz çarpıtarak bakarsak, bu sunucunun, istemcinin işleyemeyeceği bir etkiyi gerçekleştirmesine izin veren bir çekirdek
Kod yeniden kullanımı ve bileşim çok daha zorlaşacak gibi, ama yürütme modeli çok daha basitleşiyor. Statik gömülü sistemlerde bu kesinlikle doğru bir ödünleşim. Yeniden kullanım gerekiyorsa görevi her zaman vendor'layıp değiştirebilirsiniz
Aksine Unix'te görmezden gelinebilen çok fazla hata var ve kişisel olarak bunların önemli bir kısmının ölümcül sinyal üretmesi gerektiğini düşünüyorum. Öyle olsaydı genel yazılım kalitesi epey artardı
Örneğin geçersiz bir dosya tanımlayıcısına
close()çağırmak ölümcül olmayan bir hata olduğu için sıkça yok sayılıyor. Ama gerçekte, özellikle çok iş parçacıklı uygulamalarda, çok tehlikeli. Çoğu zaman yanlış dosya tanımlayıcısını kapatmak harmless biçimde başarısız olur; ama %1'inde bir loglama soketini, veritabanı kilit dosyasını ya da alakasız bir IPC bağlantısını kapatıverir. Herkesin nefret ettiği o kararsız yazılımlar böyle ortaya çıkarErrand of Mercy'deki şu repliği hatırlatıyor: “Çeşitli kurallar ve düzenlemeler olduğunu göreceksiniz. İlan edilecekler. Bunlardan en küçüğünü bile ihlal etmek ölümle cezalandırılır”
Bunu HTTP için 1 Nisan RFC'si haline getirmek lazım
HTTP 499 “Shame on you.” öneriyorum. 499 alan istemci, muhtemelen yalnızca
Strict: truegibi belirli bir başlıkla başlayan isteklerde, o isteği yayımlayan görevi dile özgü yöntemle sonlandırmalıBu bağlamda görülen “Bu da ne böyle… ama aslında, fena değil?” dengesini kusursuz yakalıyor
Çok keyifli bir okumaydı; bu tek supervisor yaklaşımı, eski startup'ımda uygulamayı her şeyi unwrap edecek şekilde yapılandırmamıza benziyor
Sevdiğim yazılardan biri olan https://medium.com/@mattklein123/crash-early-and-crash-often... de aklıma geldi
Bunun gerçekten fazla agresif olup olmadığını merak ediyorum
Linux’ta, soket üzerinden iletişim kurduğunuz başka bir programı yalnızca soketle doğrudan çökertmek mümkün değil; sokete hatalı veri gönderme durumu hariç
Ama öldürmek kesinlikle mümkün. root olarak çalışan herhangi bir şey başka bir şeyi öldürebilir, hatta yeniden başlatıp tüm sistemi de indirebilir
Biraz daha zor ve yaygın değil, ama en azından konteynerlerde root yetkisi yaygın. Elbette cgroup olduğu için daha da kısıtlanıyor, ama mesele bu
“Alırken hoşgörülü, gönderirken tutucu ol” şeklindeki yaygın bilgelikten de biraz farklı. Gerçi bu daha çok ağ sistemlerine bağlı bir söz olabilir
Yine de sistemin kabul ettikleri konusunda hoşgörülü olması kaçınılmaz olabilir. Aksi halde mevcut programları bozmadan API’yi hafifçe değiştirmenin bir yolu kalmaz, değil mi?
Bildiğim kadarıyla çalışma zamanında yeni tür süreçlere de izin vermiyor. Olası tüm çalıştırılabilir dosyaların derleme zamanında belirlenmiş olması gerekiyor
“Sorunu düzeltip görevi sürdürmenin bir yolu yok. Bu, incelikli hata kiplerinden kaçınmak ve sistem hakkında akıl yürütmeyi basitleştirmek için bilinçli bir seçimdi” kısmı bana Einstein’ın ünlü “Mümkün olduğunca basit, ama daha basit değil” sözünü hatırlatıyor
Bu tasarım sanki ikinci koşulu ihlal ediyor. Gerçek dünyanın karmaşasına hiç dayanıklı olmayan bir işletim ortamıyla ilgilenmiyorum; ticari olarak geçerli alanlar içinde de bunu kabul edecek bir yer pek bilmiyorum
Sonuçta init sistemine dönüp sürekli yeniden denemesini mi sağlayacağız? Peki hangi mekanizmayla meydana gelen fault’u anlayıp daha iyi bir şekilde tekrar deneyebiliriz?
Yine de inancın saflığını alkışlıyorum
Cliff’in blogda ayrıntılı yazdığı gibi, REPLY_FAULT başlangıçta belki fazla agresif olduğunu düşündüğümüz bir özellikti; ama sistemi kurup dağıtırken ve açıkçası debug ederken edindiğimiz deneyim, bunun sistemimizi kaprisli biçimde bozmak yerine daha sağlam hâle getireceğine bizi ikna etti
Buradaki düşünce tarzını ve pratikte nasıl göründüğünü [0] ve [1]’de daha fazla görebilirsiniz
[0] https://www.mattkeeter.com/blog/2024-03-25-packing/
[1] https://cliffle.com/blog/who-killed-the-network-switch/
Hobi projelerinde bile I2C bus’ın protokol bitlerinden biri karıştığında sık sık takılıp tüm sistemi indirdiğini gördüğüm için, bu tasarımın epey ilham verici olduğunu düşünüyorum
Anladığım kadarıyla bu, bilinen hata vakalarından, yani ele alınan hatalardan değil; protokol uyuşmazlıkları ve asla olmaması gereken şeylerle ilgili
Diğer yorumların da belirttiği gibi, amaca özel bir OS. Erlang ile UI yapmayacağınız gibi, Hubris de kapladığı alana gayet uygun görünüyor
Sebep bir bug, saldırı ya da bozuk donanım olabilir; her durumda devam edilmemeli. Çağıranda ciddi bir sorun vardır ve devam ederse sadece daha büyük zarar verir
Erlang/OTP’nin “let it crash” felsefesine biraz benziyor. Erlang oldukça fazla sayıda görev kritik donanımda kullanılıyor ve güvenilirliğiyle biliniyor; dolayısıyla pratikte o kadar büyük bir kusur olmayabilir
0xide sunucu rack’inin derinliklerinde çalışmak üzere yazılmış
“Kötüye kullanım girişimleri çoğu zaman önce API hatası ya da yanlış kullanım olarak ortaya çıktığından, herhangi bir hatalı davranışta yanlış davranan bileşenin durumunu silen bir sistemin kötüye kullanılması daha zor olmalıdır” kısmında, burada uygulamanın kabul ettiklerini biraz daha sıkı denetlemesi söz konusu
Bu yüzden güvenlik açısından bir avantaj var, ama düşünülen türden değil. Saldırganın ilerlemesini yok edip onu geri püskürtmek değil; eskiden daha arzu edilen hatalı bir duruma eklemlenebilen belirli hatalı durumların artık işe yaramaması
O zaman saldırgan bunu denemek yerine başka bir yer arar