چرا کامپایلر با ورودی یکسان خروجی یکسان نمیده
خلاصهٔ کاملتر
نویسنده میگه Anubis (همون سیستم proof-of-work برای محافظت از سایتها در برابر رباتها) داره یه روش مبتنی بر WebAssembly اضافه میکنه تا منطق چک هم روی کلاینت و هم سرور تو یه جا تعریف بشه. ولی اگه کاربر WebAssembly رو غیرفعال کرده باشه چی؟ راهحلش اینه که همون WASM رو با ابزار wasm2js به جاوااسکریپت کامپایل کنه؛ کندتره ولی بالاخره کار میکنه.
مشکل اینجاست که برای اعتماد، باید بیلد بازتولیدپذیر باشه، یعنی هرکسی همون نسخه رو بسازه دقیقاً همون بایتها رو بگیره. برای همین نویسنده تصمیم گرفت خودِ wasm2js رو (کامپایلشده به WASM با wasi-sdk) داخل مخزن بذاره. ولی به گفتهی خودش برخلاف تصورمون، کامپایلر یه تابع قطعی از ورودی به خروجی نیست و در عمل خیلی راحت خروجی ناهمسان تولید میکنه.
یکی از سادهترین منابع ناهمسانی، ماکروهای داخلی DATE و TIME هستن که خروجی رو با زمان اجرای کامپایلر مهر میزنن:
int main() {
std::cout << __DATE__ << " " << __TIME__ << std::endl;
return 0;
}با اینکه سورس عین هم بود، هر بار زمان متفاوتی تو خروجی مینشست. مشکل بعدی این بود که clang موقع بیلد بیسروصدا wasm-opt رو از $PATH صدا میزد، و چون نسخهی این ابزار روی ماشینهای مختلف فرق داشت (نسخهی قدیمی روی ماشین arm از پس دستورهای exception handling برنمیومد)، بیلد شکست میخورد. راهحل این یکی، پاسدادن --no-wasm-opt موقع لینک بود.
آزاردهندهترین مورد اما این بود که نسخهی clang مورد استفاده، تو مسیر مدیریت exception یه کدتولیدِ حساس به آدرس حافظه داشت؛ یعنی مقدار خام اشارهگرها روی ترتیب چند بلاک try_table اثر میذاشت و هر بیلد حدود ۲۹ بایت با بیلد بعدی فرق میکرد. همین روی معماریهای مختلف مثل x86_64 و arm64 هم بهخاطر ترتیب متفاوت پیمایش اشارهگر فرق میکرد.
برای دور زدنش دو کار کرد: تصادفیسازی فضای آدرس رو با setarch --addr-no-randomize خاموش کرد، و برای هر معماری چکسامهای sha256 مورد اعتماد ثبت کرد و یه job تو CI گذاشت که بازتولیدپذیری رو بررسی کنه. نویسنده میگه بازتولیدپذیری بین معماریها هنوز یه باگ سطحبالای LLVM ه که از پسش برنمیاد، ولی حداقل داخل هر معماری بیلد قطعی شده و فعلاً باید همین کافی باشه.
نکات کلیدی:
- کامپایلر با ورودی یکسان لزوماً خروجی بایتبهبایت یکسان نمیده.
- ماکروهای DATE و TIME خروجی رو با زمان بیلد ناهمسان میکنن.
- clang بیسروصدا wasm-opt رو از $PATH صدا میزنه؛ اختلاف نسخه بیلد رو میشکنه.
- کدتولیدِ وابسته به آدرس حافظه باعث اختلاف خروجی بین بیلدها و معماریها میشه.




