SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor

بیلد‌های بازتولیدپذیر واقعاً چه چیزی را اثبات می‌کنند؟

تفاوت هش فایل با بیلد بازتولیدپذیر چیست؟ چک‌سام فقط سلامت انتقال فایل را تایید می‌کند، اما بیلد بازتولیدپذیر ثابت می‌کند باینری دقیقاً از همان سورس‌کد تولید شده است.

اثبات بیلد‌های بازتولیدپذیر

بیلد‌های بازتولیدپذیر (Reproducible builds) تنها یک حقیقت محدود را اثبات می‌کنند: باینری که به دست شما رسیده، همان باینری است که دقیقاً از همین سورس‌کد تولید شده است. هر کسی می‌تواند همان سورس را بگیرد، دوباره آن را بیلد کند و بایت‌ها را با هم مقایسه نماید. با این کار، تأیید صحت دیگر عملی نیست که فقط ناشر قادر به انجام آن باشد.

پروژه Reproducible Builds آن را این‌گونه تعریف می‌کند: «یک بیلد زمانی بازتولیدپذیر است که با داشتن سورس‌کد، محیط بیلد و دستورالعمل‌های بیلد یکسان، هر طرفی بتواند کپی‌های بیت‌به‌بیت یکسانی از تمام آرتیفکت‌های مشخص‌شده ایجاد کند.» خودِ عمل مقایسه، یک هش (hash) است. تمام دشواری کار در این است که محیط و دستورالعمل‌ها را به اندازه‌ای دقیق و ثابت (pin) کنیم که دو ماشین متفاوت به نتیجه‌ای یکسان برسند.

چرا checksum به این پرسش پاسخ نمی‌دهد

یک checksum منتشرشده ثابت می‌کند فایلی که دانلود کرده‌اید با مقدار مرجع مطابقت دارد، بنابراین بدون خرابی به دست شما رسیده است. این مقدار هیچ چیزی درباره اینکه چه کسی فایل را تولید کرده است، نمی‌گوید. راهنمای تایید پروژه Apache درباره هش‌ها صریح است: «آن‌ها هیچ تضمینی در مورد اصالت فایل ارائه نمی‌دهند.» checksum مرجع باید از منبعی بیاید که بتوانید به آن اعتماد کنید، در غیر این صورت، مطابقت داشتن آن هیچ چیزی را ثابت نمی‌کند. امضای دیجیتال روی آن checksum ثابت می‌کند که فایل از طرف دارنده کلید آمده است، البته به شرطی که قبلاً بررسی کرده باشید که کلید واقعاً متعلق به ناشر است. هیچ‌کدام از این‌ها درباره آنچه پیش از ایجاد artifact رخ داده است، چیزی نمی‌گویند. اگر ماشین build ناشر breached شده باشد، فایل باینری مخرب دقیقاً مانند یک فایل سالم checksum و امضا می‌شود، بنابراین تمام بررسی‌های پایین‌دستی با موفقیت انجام می‌شوند. اگر یک maintainer از روی یک working tree که هرگز push نشده است build بگیرد، همین موضوع صادق است.

این همان شکاف موجود است. شما می‌توانید سورس‌کد را بخوانید، امضا را تایید کنید، checksum را بررسی کنید و همچنان در حال اجرای کدی باشید که هرگز در repository ظاهر نشده است. بنابراین، قابلیت بازتولید (reproducibility) موضوعی متفاوت از تایید دانلود در برابر checksum منتشرشده آن است. checksum از انتقال فایل محافظت می‌کند. بازسازی (rebuild) از هر آنچه پیش از انتقال رخ داده است، محافظت می‌کند.

این حمله تئوری نیست. نفوذ SolarWinds Orion در سال 2020 دقیقاً در همین موقعیت قرار داشت: سیستم build، artifactهای امضاشده‌ای را منتشر کرد که با سورس‌کدی که توسط دیگران بررسی شده بود، مطابقت نداشت. تمام بررسی‌های امضا با موفقیت انجام شد، زیرا امضاها از مرحله artifact شروع می‌شوند.

آنچه یک بیلد تکرارپذیر اثبات نمی‌کند

این بخشی است که در مورد آن اغراق می‌شود، بنابراین در مورد محدودیت‌ها دقیق باشید.

  • این به معنای امن بودن سورس‌کد نیست. یک در پشتی (backdoor) که به‌صورت عمومی کامیت شده باشد، به‌طور تکرارپذیر بیلد می‌شود و هر بازسازی‌کننده‌ای آن را تأیید می‌کند، زیرا همه آن‌ها همان سورس‌کد مخرب را بیلد کرده‌اند. تکرارپذیری، هدف را به درخت سورس‌کد منتقل می‌کند. همچنان شخصی باید آن درخت را بررسی کند، به همین دلیل است که سیاست بازبینی، از جمله سیاست‌های مربوط به کد تولیدشده توسط هوش مصنوعی در پروژه‌های متن‌باز، یک کنترل مجزا باقی می‌ماند.
  • این به معنای امن بودن ورودی‌های شما نیست. وابستگی‌ها بخشی از چیزی هستند که شما بیلد می‌کنید. یک پکیج مخرب که در زمان بیلد فراخوانی می‌شود، در آرتیفکت نهایی کامپایل می‌گردد و هر بازسازی‌کننده‌ای که همان وابستگی را فراخوانی کند، با شما هم‌نظر خواهد بود. این همان روشی است که یک حمله به زنجیره تأمین npm به سرور می‌رسد و یک بیلد تکرارپذیر، آن را با دقت بازتولید می‌کند.
  • این به معنای صادق بودن زنجیره ابزار (toolchain) نیست. اگر کامپایلر آلوده باشد، هر بازسازی‌کننده‌ای که از آن کامپایلر استفاده کند، همان خروجی آلوده را تولید می‌کند و همه نتایج با هم مطابقت خواهند داشت. تکرارپذیری هزینه این حمله را افزایش می‌دهد، اما آن را شناسایی نمی‌کند.
  • این هیچ چیزی درباره آسیب‌پذیری‌ها نمی‌گوید. یک کتابخانه قدیمی که بیت‌به‌بیت بازتولید شده است، همچنان همان کتابخانه قدیمی با نقص‌های منتشرشده‌اش است، بنابراین بررسی سرور برای CVEهای شناخته‌شده را طبق برنامه زمانی خود ادامه دهید.

آنچه تکرارپذیری حذف می‌کند، یک موقعیت خاص برای مهاجم است: ماشین بیلد و کل مسیر از سورس‌کد تا فایل باینری. تا زمانی که یک پکیج تکرارپذیر نباشد، هیچ‌کس خارج از تیم ناشر نمی‌تواند آن مسیر را بررسی کند.

چرا یک منبع یکسان، بایت‌های متفاوتی تولید می‌کند

بیشتر نرم‌افزارها به‌صورت پیش‌فرض بازتولیدپذیر (reproducible) نیستند و دلایل آن ساده و خسته‌کننده است. کامپایلرها و فرمت‌های آرشیو، اطلاعاتی درباره ماشینی که آن‌ها را اجرا کرده است، ثبت می‌کنند.

  • برچسب زمانی (Timestamp). فرمت‌های tar، ar و zip زمان آخرین تغییر فایل را ذخیره می‌کنند، بنابراین ساختن فایل در ثانیه‌ای متفاوت، خروجی را تغییر می‌دهد.
  • مسیر (Path). اطلاعات دیباگ، مسیر مطلق دایرکتوری ساخت را ثبت می‌کنند؛ بنابراین فایلی که در /home/alice/src ساخته شده با فایلی که در /build/pkg ساخته شده متفاوت است، حتی اگر کد منبع کاملاً یکسان باشد.
  • ترتیب (Ordering). خواندن یک دایرکتوری، ورودی‌ها را بر اساس ترتیب فایل‌سیستم برمی‌گرداند، بنابراین ترتیب لینک‌دهی یا ترتیب اعضای آرشیو بین ماشین‌های مختلف تغییر می‌کند.
  • هویت (Identity). اسکریپت‌های ساخت، نام کاربری، نام میزبان (hostname) یا تنظیمات محلی (locale) شخصی که فایل را ساخته است، در آن درج می‌کنند.
  • تصمیمات زمان ساخت (Build-time decisions). تشخیص ویژگی‌های CPU یا استفاده از مقادیر تصادفی (seeding)، باعث می‌شود خروجی به‌جای منبع، به ماشین وابسته باشد.

شما می‌توانید وقوع دلیل اول را در حدود ده ثانیه مشاهده کنید:

mkdir -p /tmp/rb && cd /tmp/rb
echo hello > a.txt && tar cf one.tar a.txt
sleep 2
echo hello > a.txt && tar cf two.tar a.txt
sha256sum one.tar two.tar

دو هش تولید شده متفاوت هستند زیرا هدر tar زمان تغییر فایل a.txt را ذخیره می‌کند و بازنویسی فایل، آن زمان را دو ثانیه جلو برده است. محتوای فایل بایت به بایت یکسان است. ثابت کردن (Pinning) متادیتا این مشکل را حل می‌کند:

export SOURCE_DATE_EPOCH=1700000000
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf three.tar a.txt
touch a.txt
tar --sort=name --mtime="@${SOURCE_DATE_EPOCH}" --owner=0 --group=0 --numeric-owner -cf four.tar a.txt
sha256sum three.tar four.tar

اکنون هش‌ها مطابقت دارند، زیرا هیچ فیلدی در هدر آرشیو از وضعیت فعلی ماشین تأثیر نمی‌گیرد. --sort=name ترتیب را اصلاح می‌کند، --mtime ساعت را ثابت نگه می‌دارد و فلگ‌های مالکیت مانع از ثبت شناسه کاربری (user id) شما می‌شوند.

بررسی تفاوت‌ها با diffoscope

هنگامی که دو بیلد با هم تفاوت دارند، sha256sum فقط به شما می‌گوید که آن‌ها متفاوت هستند و هیچ اطلاعات دیگری ارائه نمی‌دهد. ابزار diffoscope برای این ساخته شده است که دلیل تفاوت را به شکلی که برای انسان قابل خواندن باشد، بیان کند. این ابزار هر دو سمت را به‌صورت بازگشتی باز می‌کند، فرمت‌های باینری را به متن تبدیل کرده و متن‌ها را با هم مقایسه می‌کند. این ابزار از بسته‌های Debian، فایل‌های باینری ELF، آرشیوهای tar و ZIP، فایل‌های PDF، پایگاه‌داده‌های SQLite و ده‌ها فرمت دیگر پشتیبانی می‌کند.

sudo apt install -y diffoscope
diffoscope one.tar two.tar

برای جفت فایل tar در بالا، گزارش کوتاه است. اگر بخش‌های اضافی آن را حذف کنیم، به این شکل دیده می‌شود:

--- one.tar
+++ two.tar
├── file list
│ @@ -1 +1 @@
│ -rw-r--r-- 0/0  6 2026-08-18 10:14:02.000000 a.txt
│ +rw-r--r-- 0/0  6 2026-08-18 10:14:04.000000 a.txt

این تمام تشخیص است: اندازه یکسان، مسیر یکسان، مجوزهای یکسان، زمان تغییر متفاوت. یک بسته واقعی گزارش بسیار طولانی‌تری تولید می‌کند، بنابراین آن را در یک فایل بنویسید و در مرورگر باز کنید:

diffoscope --html report.html build1.changes build2.changes

ابزار diffoscope در صورت یکسان بودن ورودی‌ها کد خروجی 0، در صورت تفاوت کد 1 و در صورت بروز مشکل کد 2 را برمی‌گرداند؛ بنابراین بدون نیاز به اسکریپت wrapper، مستقیماً در CI job قابل استفاده است. روی یک VPS کوچک، به‌جای diffoscope، بسته diffoscope-minimal را نصب کنید: بسته کامل، مجموعه بزرگی از ابزارهای کمکی فرمت را نصب می‌کند که احتمالاً هرگز از آن‌ها استفاده نخواهید کرد.

آنچه SOURCE_DATE_EPOCH اصلاح می‌کند و محدودیت‌های آن

SOURCE_DATE_EPOCH یک متغیر محیطی است که یک عدد را در خود نگه می‌دارد: زمان آخرین تغییر در سورس، که بر حسب ثانیه از 1 ژانویه 1970 به وقت UTC محاسبه می‌شود. ابزار ساختی که از این متغیر پشتیبانی می‌کند، هر جا که قرار بود زمان فعلی را از سیستم‌عامل بپرسد، از این عدد استفاده می‌کند. آن را از طریق سیستم کنترل نسخه تنظیم کنید تا مقدار آن به جای زمان ساخت، تابع سورس باشد:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)

در بسته‌های Debian، ابزار debhelper آن را به‌طور خودکار از changelog استخراج و صادر می‌کند. تنظیم دستی آن در debian/rules به این صورت است:

export SOURCE_DATE_EPOCH ?= $(shell dpkg-parsechangelog -STimestamp)

پشتیبانی از این متغیر وابسته به هر ابزار است و هرگز سراسری نیست. cmake نسخه 3.8 و جدیدتر، gcc نسخه 7 و جدیدتر، rpm نسخه بالاتر از 4.13 و Docker buildx نسخه 0.10 و جدیدتر همگی آن را می‌خوانند. اسکریپت‌های شخصی شما تا زمانی که برای آن برنامه‌نویسی نشوند، از آن استفاده نمی‌کنند. اگر اسکریپتی date را فراخوانی می‌کند، متغیر را به آن تزریق کنید:

BUILD_DATE="$(date --utc --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y-%m-%d)"

هنگام پیاده‌سازی، یک قانون اهمیت دارد. اگر متغیر از قبل تنظیم شده باشد، آن مقدار تا جایی که به ساخت شما مربوط می‌شود، همان زمان فعلی است؛ بنابراین هر جا که قرار بود ساعت را بخوانید از آن استفاده کنید و آن را برای تمام فرآیندهایی که ساخت شما آغاز می‌کند، فعال نگه دارید. مستندات فنی این موضوع را به‌صراحت بیان می‌کنند: «فرآیندهای ساخت نباید این متغیر را برای فرآیندهای فرزند، در صورتی که از قبل موجود باشد، حذف کنند.»

ایمیج‌های کانتینر همین مشکل را در قالبی متفاوت دارند. Docker buildx نسخه 0.10 و جدیدتر، متغیر SOURCE_DATE_EPOCH را از shell شما به عنوان یک build argument به فرآیند ساخت منتقل می‌کند. برای بازنویسی timestamp فایل‌ها در لایه‌ها، به یک exporter نیاز است که BuildKit در نسخه 0.13 آن را اضافه کرد و روش مستندشده، نتیجه را به یک registry ارسال می‌کند:

export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct)
docker buildx build --output type=image,name=registry.example.com/app:1.0,push=true,rewrite-timestamp=true .

تست بیلد شخصی با استفاده از reprotest

ابزار reprotest سورس‌کد یکسان را دو بار بیلد می‌کند و در هر بار، محیط بیلد را به‌صورت هدفمند تغییر می‌دهد؛ سپس نتایج را با هم مقایسه می‌کند. هدف اصلی این ابزار، بررسی همین تغییرات (variations) است. به‌صورت پیش‌فرض، این ابزار متغیرهای محیطی، مسیر بیلد، نسخه کرنل، تصادفی‌سازی فضای آدرس، تعداد CPUها، زمان، کاربر و گروه، ترتیب فایل‌ها، نام دامنه و میزبان، دایرکتوری home، لوکال، مسیر فایل‌های اجرایی، منطقه زمانی و umask را تغییر می‌دهد.

sudo apt install -y reprotest
reprotest . -- null

هر چیزی که بعد از -- می‌آید، backend محیط بیلد را انتخاب می‌کند و null به معنای سیستمی است که در حال حاضر با آن کار می‌کنید. برای نگه داشتن دایرکتوری‌های موقت جهت بررسی، از -vv -d استفاده کنید، همان‌طور که در reprotest . -vv -- null -d آمده است. از reprotest auto -- null استفاده کنید تا ابزار خودش نوع سورس‌تری (source tree) که با آن مواجه است را تشخیص دهد.

برخی از تغییرات نیاز به دسترسی‌های ویژه یا بسته‌های اضافی دارند و در صورت عدم اجرا، با صدای بلند (خطاهای واضح) شکست می‌خورند. به‌جای اجرای کل فرآیند با دسترسی root، این موارد را غیرفعال کنید:

reprotest --vary=-user_group,-domain_host,-fileordering auto -- null

هر موردی که reprotest گزارش می‌دهد، چیزی است که یک بازسازنده (rebuilder) بعداً به‌صورت عمومی و با نام پروژه شما گزارش خواهد کرد.

معنای نتیجه بازسازی‌کننده (rebuilder)

بازسازی‌کننده ماشینی است که متعلق به ناشر نیست. این ماشین سورس منتشرشده و محیط ساخت ثبت‌شده را دریافت می‌کند، بسته را دوباره می‌سازد و خروجی خود را با آرتیفکت موجود در آرشیو مقایسه می‌کند. این نتیجه تنها به این دلیل ارزشمند است که ماشین مذکور مستقل است.

دبیان محیط ساخت را در فایلی با نام .buildinfo ثبت می‌کند که dpkg-buildpackage آن را در کنار .deb می‌نویسد. فیلدها بخش جالب ماجرا هستند. Installed-Build-Depends تمام بسته‌های نصب‌شده‌ای که می‌توانند بر ساخت تأثیر بگذارند را با نسخه‌های دقیق فهرست می‌کند. Build-Path محل اجرای ساخت را ثبت می‌کند. Environment متغیرهای محیطی که اهمیت آن‌ها مشخص است را ثبت می‌کند. Checksums-Sha256 خروجی‌ها را پوشش می‌دهد. آن فایل دستورالعمل یک تلاش دوم است:

sudo apt install -y devscripts mmdebstrap
debrebuild --buildresult=./artifacts --builder=mmdebstrap hello_2.10-2_amd64.buildinfo

debrebuild فایل buildinfo را می‌خواند و نسخه‌های دقیق وابستگی‌های ذکر شده در آن را از snapshot.debian.org دریافت می‌کند، بنابراین بازسازی امروز می‌تواند از نسخه‌های بسته‌ای استفاده کند که در روز ساخت اصلی وجود داشته‌اند. سازنده mmdebstrap نیازی به تنظیم chroot یا دسترسی superuser ندارد. آرتیفکت‌های تولیدشده توسط آن را با استفاده از diffoscope با نسخه آرشیو مقایسه کنید.

آرچ لینوکس از rebuilderd استفاده می‌کند که این کار را به‌طور مداوم انجام داده و نتایج را منتشر می‌کند:

rebuildctl -H https://reproducible.archlinux.org pkgs ls --name rebuilderd

وضعیت‌ها شامل GOOD، BAD و UNKWN هستند و برداشت ساده از هر کدام در جهتی متفاوت اشتباه است. وضعیت GOOD به این معناست که یک طرف مستقل به همان بایت‌ها دست یافته است؛ این یک اظهارنظر قوی درباره فرآیند ساخت است، اما هیچ چیزی درباره سورس نمی‌گوید. وضعیت BAD تقریباً هرگز به معنای حمله نیست: دلیل معمول آن یک timestamp یا مسیری است که بسته‌بندی در ثابت‌سازی آن شکست خورده است، به همین دلیل است که rebuilderd می‌تواند یک گزارش diffoscope را به خطا پیوست کند. وضعیت UNKWN به این معناست که هیچ‌کس آن را تست نکرده است و یک بسته تست‌نشده، یک بسته تأییدشده نیست.

بنابراین قانون عملیاتی کوتاه است. نتیجه BAD دلیلی برای خواندن گزارش است. اگر گزارش نشان‌دهنده timestampها، مسیرهای ساخت یا ترتیب اعضا باشد، یک باگ بسته‌بندی ثبت کنید. اگر گزارش نشان‌دهنده کدهای اجرایی متفاوت بدون هیچ توضیح مشابهی است، استقرار آن بیلد را متوقف کرده و موضوع را ارجاع دهید.

میزان بازتولیدپذیری دبیان در حال حاضر چقدر است؟

نمودارDebian packages reproducible in the test framework, amd64
داده‌های پشت این نمودار
[
  {
    "label": "unstable",
    "percent_reproducible": 94.8,
    "tested_count": "41,490"
  },
  {
    "label": "forky",
    "percent_reproducible": 95.7,
    "tested_count": "39,529"
  },
  {
    "label": "experimental",
    "percent_reproducible": 72.5,
    "tested_count": "619"
  }
]

در تاریخ 2026-10-15، وضعیت شاخه unstable برای معماری amd64 نشان‌دهنده 94.8% بازتولیدپذیری در میان 41,490 بستهٔ آزمایش‌شده بود. شاخه experimental در سطح 72.5% قرار داشت که حاصل نمونه‌ای بسیار کوچک‌تر و جدیدتر شامل 619 بسته است؛ این نتیجه برای بسته‌هایی که هنوز کسی اصلاح آن‌ها را به پایان نرسانده، قابل انتظار است.

این ارقام از صفحه دبیان در tests.reproducible-builds.org استخراج شده‌اند که در تاریخ 2026-10-15 خوانده شده و دارای مهر زمانی "Last update: 2026-10-15 13:02 UTC" بوده است. این آمار تغییر می‌کنند. به‌جای نقل‌قول از این پاراگراف در شش ماه آینده، مستقیماً ردیاب (tracker) را بررسی کنید.

یک نکته مهم‌تر از درصد وجود دارد. این چارچوب، هر بسته را دو بار روی سخت‌افزار خود می‌سازد، محیط بین دو ساخت را تغییر می‌دهد و نتایج دوگانهٔ خود را با هم مقایسه می‌کند. این ابزار اندازه‌گیری می‌کند که آیا یک بسته قابلیت ساخت بازتولیدپذیر را دارد یا خیر. این یک بررسی برای تطابق .deb موجود در آرشیو با نسخه اصلی نیست؛ وظیفهٔ تطبیق با آرتیفکت منتشرشده بر عهدهٔ یک rebuilder جداگانه است. هر دو عدد مفید هستند. آن‌ها به پرسش‌های متفاوتی پاسخ می‌دهند، اما افراد اغلب عدد اول را به‌گونه‌ای نقل می‌کنند که گویی همان عدد دوم است.

اقدامات لازم روی سرور شخصی

شما قرار نیست یک توزیع لینوکسی جدید بسازید. بخش‌هایی که به یک سرور معمولی منتقل می‌شوند، کوچک و کم‌هزینه هستند.

  • زنجیره ابزار (toolchain) را ثابت کنید. یک base image که با تگ ارجاع داده شده باشد، ممکن است بدون اطلاع شما تغییر کند. به آن با استفاده از digest ارجاع دهید و digest را همراه با release ثبت کنید.
  • ورودی‌ها را ثبت کنید. فایل lockfile، مقدار digest ایمیج و نسخه کامپایلر را در کنار artifact نگه دارید. بیلدکردنی که محیط آن قابل بازسازی نباشد، قابل تکرار نیست و در نتیجه هرگز نمی‌توان آن را بررسی کرد.
  • در CI دو بار بیلد بگیرید و اگر خروجی‌ها متفاوت بودند، job را با خطا متوقف کنید. این کار هزینه یک بیلد اضافه را دارد اما nondeterminism را همان روزی که ایجاد می‌شود شناسایی می‌کند، نه یک سال بعد در زمان بروز حادثه.
  • مسیرهایی که کامپایلر در فایل تعبیه می‌کند را حذف کنید. برای Go، دستور go build -trimpath -buildvcs=false دایرکتوری بیلد و مهر زمانی کنترل نسخه را حذف می‌کند و go version -m ./app آنچه که واقعاً در فایل باینری قرار گرفته است را نمایش می‌دهد.
  • هشِ آنچه که مستقر (deploy) کرده‌اید را نگه دارید. زمانی که نیاز دارید بدانید آیا باینری در حال اجرا با یک نسخه از سورس‌کد مطابقت دارد یا خیر، این رکورد تنها چیزی است که می‌تواند پاسخگو باشد.

بررسی CI شامل چهار خط است:

set -eu
./build.sh && mv dist/app app.1
./build.sh && mv dist/app app.2
diffoscope --text - app.1 app.2

ابزار diffoscope در صورت تفاوت بین دو artifact با کد خروجی غیر صفر خارج می‌شود، بنابراین job به‌طور خودکار شکست می‌خورد و توضیحی خوانا در لاگ باقی می‌گذارد. این تمام ایده است که در مقیاس یک مخزن (repository) پیاده‌سازی شده: ادعای اینکه یک فایل باینری از یک سورس‌کد خاص تولید شده، باید چیزی باشد که ماشین دوم بتواند آن را تأیید کند.

FAQ

آیا build قابل‌تکرار (reproducible) به معنای امن بودن نرم‌افزار است؟

خیر. این ویژگی فقط ثابت می‌کند که فایل باینری با سورس‌کد مطابقت دارد و نه بیشتر. یک backdoor که در سورس‌کد عمومی commit شده باشد، به‌صورت قابل‌تکرار build می‌شود و هر کسی که آن را دوباره build کند، همان سورس مخرب را تأیید خواهد کرد. بسته‌ای که دارای یک CVE شناخته‌شده است، به‌طور کامل بازتولید می‌شود و همچنان آسیب‌پذیر باقی می‌ماند. قابلیت تکرارپذیری، یکی از موقعیت‌های حمله (یعنی ماشین build و مسیر تبدیل سورس به باینری) را حذف می‌کند. خواندن سورس‌کد و ردیابی آسیب‌پذیری‌ها وظایف جداگانه‌ای هستند که تکرارپذیری آن‌ها را برای شما انجام نمی‌دهد.

چرا با اینکه هیچ تغییری در سورس ایجاد نشده، دو build من با هم متفاوت هستند؟

تقریباً همیشه دلیل آن یک timestamp، یک مسیر (path) یا ترتیب فایل‌ها است. فرمت‌های آرشیو مانند tar و zip زمان آخرین تغییر فایل را ذخیره می‌کنند، بنابراین checkout کردن در ثانیه‌ای متفاوت، بایت‌های متفاوتی تولید می‌کند. اطلاعات دیباگ، دایرکتوری مطلق build را ثبت می‌کنند، بنابراین /home/alice/src و /build/pkg از کدهای یکسان، باینری‌های متفاوتی تولید می‌کنند. خواندن دایرکتوری‌ها، ورودی‌ها را بر اساس ترتیب فایل‌سیستم برمی‌گرداند، بنابراین فایل‌های object در خط لینک ممکن است در ماشین‌های مختلف ترتیب متفاوتی داشته باشند. دستور diffoscope build1 build2 را اجرا کنید تا گزارش به شما بگوید کدام‌یک از این موارد باعث تفاوت شده است، به‌جای اینکه حدس بزنید.

متغیر SOURCE_DATE_EPOCH چیست و آیا باید آن را تنظیم کنم؟

این یک متغیر محیطی استاندارد است که شامل یک عدد می‌شود: زمان آخرین تغییر سورس‌کد به ثانیه از تاریخ 1 ژانویه 1970 به وقت UTC. ابزارهایی که از این متغیر پشتیبانی می‌کنند، هر جا که قرار باشد ساعت سیستم را بخوانند، از این مقدار استفاده می‌کنند. آن را از طریق سیستم کنترل نسخه با export SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct) تنظیم کنید. این فرآیند خودکار نیست و یک راه‌حل کلی محسوب نمی‌شود. فقط ابزارهایی که آن را پیاده‌سازی کرده‌اند، این متغیر را می‌خوانند و اسکریپت‌های build شما باید خودشان آن را بخوانند؛ بنابراین اسکریپتی که date را فراخوانی می‌کند، تا زمانی که شما آن را تغییر ندهید، همچنان زمان فعلی را درج خواهد کرد.

وقتی یک rebuilder وضعیت BAD گزارش می‌دهد، چه باید کرد؟

پیش از انجام هر کار دیگری، گزارش را بخوانید. نتیجه BAD به این معناست که یک rebuild مستقل، بایت‌های یکسانی تولید نکرده است و دلیل معمول آن، غیرقطعی بودن (nondeterminism) در بسته‌بندی است، نه یک حمله. ابزار rebuilderd دقیقاً به همین دلیل می‌تواند یک گزارش diffoscope تولید کند. اگر تفاوت‌ها مربوط به timestampها، مسیرهای build یا ترتیب فایل‌ها باشد، این یک باگ در بسته‌بندی است که ارزش گزارش کردن دارد. اگر تفاوت در کد اجرایی است و چنین توضیحی برای آن وجود ندارد، استقرار آن build را متوقف کنید، آرتیفکت‌ها را نگه دارید و موضوع را به ناشر ارجاع دهید.

آیا این مقاله مفید بود؟

#reproducible-builds#supply-chain#diffoscope#debian#verification