بهینه‌سازی فرایند ارسال مجدد بسته‌ها در شبکه‌های گسترده (WAN) همواره چالشی بنیادین در مهندسی شبکه بوده است. برای چندین دهه، پروتکل کنترل انتقال (TCP) کاملاً بر روش تایید تجمعی (Cumulative ACKs) مطابق استاندارد RFC 793 تکیه داشت. در این الگو، چنانچه حتی یک بسته در میانه پنجره انتقال ناپدید می‌شد، فرستنده ناچار بود بسته مفقود شده و تمامی بسته‌های پس از آن را دوباره ارسال کند که منجر به اتلاف پهنای باند می‌گردید.

جهت رفع این نارسایی، کارگروه مهندسی اینترنت (IETF) سازوکار تایید گزینشی (TCP SACK) را در RFC 2018 استانداردسازی نمود. قابلیت SACK به گیرنده اجازه می‌دهد تا دامنه‌های ناپیوسته داده‌هایی را که با موفقیت دریافت شده‌اند اعلام کند تا فرستنده تنها بخش‌های مفقود را ارسال نماید.

با این وجود، پیاده‌سازی مشخصات تئوری در لایه‌های زیرین هسته سیستم‌عامل با چالش‌های امنیتی همراه است. در اواسط سال ۲۰۱۹، پژوهشگران امنیتی مجموعه‌ای از نقص‌های بحرانی را در پشته شبکه هسته لینوکس فاش ساختند که در رأس آن‌ها CVE-2019-11477 (مشهور به SACK Panic) قرار داشت. مهاجم با ارسال بسته‌های دستکاری‌شده با اندازه حداکثر قطعه (MSS) فوق‌العاده کوچک و بلوک‌های SACK پی‌درپی می‌توانست باعث سرریز عدد صحیح ۱۶ بیتی در صف بافر سوکت (sk_buff) هسته شده و کل سیستم‌عامل را بلادرنگ با توقف کامل (Kernel Panic) مواجه سازد.

بررسی عمیق سازوکار SACK Panic آشکار می‌سازد که چگونه بهینه‌سازی‌های لایه انتقال بر تخصیص حافظه هسته اثر می‌گذارند و چرا تونل‌سازی رمزنگاری‌شده لایه ۳ با پروتکل RiceGuard سدی استوار در برابر این حملات پدید می‌آورد.

+-------------------------------------------------------------------------+
|                مقایسه تایید تجمعی در برابر تایید گزینشی (RFC 2018)       |
+-------------------------------------------------------------------------+
| سناریو: بسته‌های ۱، ۲، ۴، ۵ دریافت شدند؛ بسته ۳ در مسیر گم شده است.     |
| ۱. تایید تجمعی (RFC 793): گیرنده پیام ACK=3 می‌فرستد. فرستنده بسته‌های   |
|    ۳ و ۴ و ۵ را مجدداً ارسال می‌کند؛ هدررفت پهنای باند شبکه.            |
| ۲. گزینه SACK (RFC 2018): گیرنده پیام ACK=3 به همراه بلوک SACK [4-6]   |
|    ارسال می‌کند. فرستنده فقط بسته ۳ را می‌فرستد. بهره‌وری کامل.        |
+-------------------------------------------------------------------------+

سیر تکاملی تایید بسته‌های TCP: تایید تجمعی در مقایسه با SACK (RFC 2018)

در مدل سنتی تایید تجمعی، فیلد شماره تایید در هدر ۲۰ بایتی TCP تنها شماره ترتیبی بایتی را که گیرنده در انتظار آن است مشخص می‌کند و فرض بر این است که کلیه بایت‌های پیشین دریافت شده‌اند. در هنگام افت چندین قطعه نامتوالی به دلیل سرریز بافر روترها، فرستنده در موقعیتی نامطلوب قرار می‌گرفت: یا ارسال مجدد یک بسته و انتظار برای یک زمان رفت‌وبرگشت (RTT)، یا ارسال سراسیمه کل پنجره داده.

استاندارد RFC 2018 با تعریف گزینه SACK در بخش گزینه‌های هدر TCP (Option Kind 5) این گلوگاه را از میان برداشت. گیرنده می‌تواند تا حداکثر چهار بلوک ناپیوسته از داده‌های نامنظم دریافت شده را گزارش نماید. هر بلوک با دو عدد صحیح ۳۲ بیتی تعریف می‌شود:

  • مرز چپ (Left Edge): نخستین شماره توالی بلوک داده دریافت‌شده.
  • مرز راست (Right Edge): شماره توالی درست پس از آخرین بایت معتبر آن بلوک.

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

کالبدشکافی ساختار SACK: قالب بلوک‌ها و توصیف‌کننده‌های مرز توالی

هدر TCP حداکثر ۴۰ بایت فضا برای گزینه‌های سفارشی دارد. گزینه پایه SACK شامل ۲ بایت اولیه است: نوع گزینه (Kind=5) و طول (Length). سپس هر بلوک افزوده به ۸ بایت حافظه نیاز دارد (دو فیلد ۳۲ بیتی مرزی).

از آنجا که ارتباطات امروزی معمولاً گزینه برچسب زمانی TCP Timestamps (RFC 7323) با اندازه ۱۰ بایت را فعال دارند، فضای باقی‌مانده محدود می‌گردد:

  • بدون برچسب زمانی: ۴۰ بایت منهای ۲ بایت برابر ۳۸ بایت آزاد؛ حداکثر پذیرش ۴ بلوک SACK (مصرف ۳۲ بایت).
  • با برچسب زمانی فعال: ۴۰ منهای ۱۰ منهای ۲ برابر ۲۸ بایت آزاد؛ حداکثر پذیرش ۳ بلوک SACK (مصرف ۲۴ بایت).

در همراهی با قابلیت تایید تکراری (D-SACK, RFC 2883)، بلوک نخست نشانگر بسته‌های تکراری بوده و امکان بررسی بازآرایی و خطاهای شبکه را فراهم می‌کند.

مدیریت بخش‌های نامنظم در هسته لینوکس: ساختار صف sk_buff

در هسته لینوکس، هر بسته تبادلی درون ساختاری بنیادی به نام بافر سوکت یا struct sk_buff (skb) محصور می‌شود. برای هر سوکت فعال، هسته صف‌های ارسال و بازفرست را به شکل یک لیست پیوندی دوطرفه مدیریت می‌کند.

در شبکه‌های پرسرعت با قابلیت Generic Segmentation Offload (GSO)، هسته برای هر فریم ۱۵۰۰ بایتی بافر مستقل نمی‌سازد، بلکه بسته‌های بزرگ تا ۶۴ کیلوبایت ایجاد کرده و خرد کردن آن به اندازه MTU را به کارت شبکه یا موتور پردازشی محول می‌نماید.

مکانیسم قطعه‌قطعه شدن بافر سوکت (skb) و الگوی مصرف حافظه رم

جهت محاسبه اینکه هر سوپربافر GSO نمایانگر چه تعداد فریم در کابل فیزیکی است، متغیری در ساختار skb_shared_info با نام gso_segs ثبت می‌شود:

struct skb_shared_info {
    __u16 gso_size;
    __u16 gso_segs;
    struct sk_buff *frag_list;
};

نکته اساسی این است: متغیر gso_segs به عنوان یک عدد صحیح ۱۶ بیتی بدون علامت (__u16) تعریف شده که بیشینه آن ۶۵,۵۳۵ است.

هنگامی که بسته‌ای با بلوک SACK تنها بخش کوچکی از یک سوپربافر در حال انتقال را تایید می‌کند، هسته نمی‌تواند بافر را آزاد کند. بنابراین تابع tcp_fragment() فراخوانی می‌شود تا شیء sk_buff به دو بخش تاییدشده و در انتظار تقسیم گردد.

+-------------------------------------------------------------------------+
|                     تجزیه ساختار SK_BUFF در هسته لینوکس                 |
+-------------------------------------------------------------------------+
| ۱. بافر GSO (۳۲ کیلوبایت): داده: ۳۲ کیلوبایت | gso_segs: ۲۳ قطعه         |
| ۲. ورود بلوک SACK: فراخوانی tcp_fragment() برای خرد کردن بافر           |
| ۳. سناریوی حمله: اندازه MSS=48 بایت صف را به هزاران ساختار skb خرد کرده  |
|    و به سرریز متغیر gso_segs می‌انجامد.                                  |
+-------------------------------------------------------------------------+

سازوکار آسیب‌پذیری SACK Panic (CVE-2019-11477): سرریز عدد صحیح ۱۶ بیتی

ریشه CVE-2019-11477 در نحوه تجمیع بخش‌های خرد شده در خلال پردازش صف ارسال مجدد نهفته است.

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

/* net/ipv4/tcp_input.c */
tcp_skb_pcount_add(prev, pcount);

از آنجا که فیلد gso_segs دارای پهنای ۱۶ بیت است، اگر شمار قطعات از سقف ۶۵,۵۳۵ فراتر رود، سرریز حسابی رخ داده و شمارنده به صفر بازمی‌گردد.

بلافاصله پس از آن، پشته شبکه شرط بازدارنده‌ای را در tcp_fragment() اجرا می‌کند:

BUG_ON(tcp_skb_pcount(skb) == 0);

دستور ماکروی BUG_ON() مکانیزم توقف اضطراری هسته است. به محض تحقق این شرط، سیستم‌عامل برای پیشگیری از تخریب حافظه بلافاصله متوقف شده و خطای مرگبار Kernel Panic رخ می‌دهد. مهاجم بدون احراز هویت می‌توانست سرور قربانی را از راه دور ساقط کند.

نقش مقادیر Micro-MSS در تشدید تخلیه بافرهای هسته سیستم‌عامل

مهاجم چگونه توانست بدون تبادل حجم بالای داده متغیر gso_segs را به بالاتر از ۶۵,۵۳۵ برساند؟ کلید این نفوذ، دستکاری گزینه Maximum Segment Size (MSS) در شروع اتصال TCP بود.

در تبادلات عادی، مقدار MSS بین ۱۴۰۰ تا ۱۴۶۰ بایت است. اما RFC 793 امکان اعلام مقادیر بسیار اندک را می‌دهد. کمترین حد پذیرفته‌شده در لینوکس مقدار ۴۸ بایت بود (سربرگ IPv4 به علاوه گزینه‌های ابتدایی TCP).

با MSS برابر ۴۸ بایت، هر قطعه حامل داده اندکی است. برای بافر ۳۲ کیلوبایتی خواهیم داشت:

$$ ext{gso_segs} = rac{32,768 ext{ Bytes}}{48 ext{ Bytes/Seg}} pprox 682 ext{ Segments}$$

مهاجم با ارسال بلوک‌های گزینشی SACK برای قطعات متناوب ۴۸ بایتی، صف هسته را به هزاران شیء sk_buff مینیاتوری خرد می‌کرد. هنگام ادغام، شمارنده به سرعت از ۶۵,۵۳۵ می‌گذشت و توقف هسته را کلید می‌زد.

افت شدید عملکرد SACK و تخلیه منابع پردازشی (CVE-2019-11478 و CVE-2019-11479)

همزمان با SACK Panic، دو آسیب‌پذیری منع سرویس مرتبط نیز شناسایی شد:

  1. CVE-2019-11478 (کندی مفرط SACK): مهاجم با خرد کردن عمدی صف، پیمایش لیست‌های با ده‌ها هزار عضو sk_buff را در هنگام اتمام زمان ارسال تحمیل کرده و مصرف پردازنده را به ۱۰۰٪ می‌رساند.
  2. CVE-2019-11479 (تخلیه منابع با MSS ناچیز): تحمیل تبادل با MSS معادل ۴۸ بایت، شمار بسته‌ها را تا ۳۰ برابر افزایش داده و بافر سوکت را می‌بلعید.

جریمه پردازشی نامتقارن و سربار پیمایش لیست‌های پیوندی در هسته

عدم تقارن محاسباتی در این شیوه بسیار نگران‌کننده بود. کلاینت نفوذگر تنها چند کیلوبایت ترافیک کنترلی ارسال می‌کرد؛ در حالی که سرور مقصد مگابایت‌ها حافظه هسته (kmalloc) را اختصاص داده و پردازنده را زیر بار وقفه‌های نرم‌افزاری (ksoftirqd) غرق می‌ساخت.

این حمله در محیط‌های ابری بدون برانگیختن سیستم‌های معمول پایش داس، سرورها را با بحران مواجه می‌کرد.

چگونگی مهار خطرات SACK از طریق تونل‌سازی لایه ۳ و پروتکل RiceGuard

اتصال مستقیم دستگاه‌ها در اینترنت عمومی، پشته لایه انتقال آن‌ها را در معرض پویش‌های مخرب قرار می‌دهد.

معماری تونل‌سازی لایه ۳ در سرویس VPNRice و پروتکل RiceGuard، پشته سیستم را کاملاً تفکیک می‌کند:

  1. جداسازی انتقال از طریق UDP: پروتکل RiceGuard ترافیک کاربر را در قالب دیتاگرام‌های UDP یکپارچه با رمزنگاری ChaCha20-Poly1305 بسته‌بندی می‌کند. شبکه‌های میانی تنها ترافیک استاندارد UDP را مشاهده کرده و دسترسی برای تزریق گزینه‌های مسموم TCP ندارند.
  2. مهار پارامتر MSS در درگاه شبکه: گیت‌وی‌های RiceGuard به شکل سخت‌گیرانه پارامتر MSS ورودی را بررسی کرده و هرگونه درخواست مشکوک زیر ۵۳۶ بایت را اصلاح یا رد می‌کنند.
  3. جداسازی وضعیت سوکت‌ها: با خاتمه یافتن تونل روی سرورهای مقاوم‌سازی‌شده، دستگاه‌های کاربران در شبکه‌های عمومی از گزند حملات مستقیم مصون می‌مانند.

کاربران در پلتفرم‌های مختلف می‌توانند از طریق صفحه دانلود VPN برنامه‌های مربوطه را دریافت نموده و ترافیک خود را ایمن سازند.

+-------------------------------------------------------------------------+
|                    دفاع لایه ۳ با پروتکل RICEGUARD                      |
+-------------------------------------------------------------------------+
| بدون حفاظت: [مهاجم] ── MSS=48 + بلوک مخرب SACK ──> [سقوط کامل هسته]     |
| با حفاظت:   [مهاجم] ── بسته‌های مینیاتوری ──> [گیت‌وی RiceGuard]         |
|                                              ├─ مهار سخت‌گیرانه MSS    |
|                                              ├─ حذف گزینه‌های مخرب     |
|                                              └─ تونل رمزنگاری UDP Noise|
|                                              └─> [میزبان داخلی امن]     |
+-------------------------------------------------------------------------+

ایمن‌سازی هسته و بازرسی بسته‌ها: تنظیمات Sysctl و مهار اندازه MSS

مدیران شبکه می‌توانند اقدامات اصلاحی را از طریق پارامترهای sysctl و فایروال لینوکس اعمال کنند.

۱. غیرفعال‌سازی موقت SACK (راهکار اضطراری)

در صورت تأخیر در به‌روزرسانی هسته، پردازش SACK موقتاً خاموش می‌شود:

# بررسی وضعیت فعلی SACK (۱ فعال، ۰ غیرفعال)
sysctl net.ipv4.tcp_sack

# غیرفعال کردن سراسری SACK
sudo sysctl -w net.ipv4.tcp_sack=0

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

۲. اعمال کف اندازه مجاز برای MSS

در نسخه‌های پس از اصلاح، متغیر tcp_min_snd_mss مانع پذیرش مقادیر غیرمنطقی می‌گردد:

# تنظیم حداقل مقدار مجاز ارسال MSS روی ۵۳۶ بایت (RFC 879)
sudo sysctl -w net.ipv4.tcp_min_snd_mss=536

۳. مهار MSS در فایروال با ابزار Iptables

پیکربندی گیت‌وی برای پالایش درخواست‌های غیرعادی:

# مسدودسازی بسته‌های SYN با اندازه MSS کمتر از ۵۳۶ بایت
sudo iptables -A INPUT -p tcp --tcp-flags SYN,RST SYN     -m tcpmss --mss 1:535 -j DROP

# انطباق خودکار MSS خروجی با مسیر انتقال MTU
sudo iptables -t mangle -A POSTROUTING -p tcp --tcp-flags SYN,RST SYN     -j TCPMSS --clamp-mss-to-pmtu

۴. ردیابی بسته‌ها به وسیله Tcpdump

ثبت و بازرسی بسته‌های مشکوک با ابزار خط فرمان:

# شکار بسته‌های SYN با مقدار MSS زیر ۵۰۰ بایت
sudo tcpdump -nn -i eth0 'tcp[tcpflags] & tcp-syn != 0 and tcp[20:2] < 500'

مقایسه معماری: پروتکل TCP استاندارد در برابر SACK ناامن و تونل محافظت‌شده

جدول زیر تفاوت‌های کلیدی رویکردها را در مواجهه با شرایط واقعی نشان می‌دهد:

شاخص عملیاتی پروتکل TCP سنتی RFC 793 پشته SACK وصله‌نشده تونل ایمن RiceGuard
سرعت بازآفرینی بسته‌ها کند (ارسال مجدد کل پنجره) سریع (ارسال دقیق قطعه) بهینه (بازیابی بر بستر لایه ۳ UDP)
آسیب‌پذیری در برابر سقوط مصون (نبود منطق SACK) بحرانی (سرریز عدد صحیح) کاملاً مصون (مهار MSS و جداسازی)
فشار پردازنده زیر حمله پایین بحرانی (خرد شدن صف‌ها) بسیار ناچیز (شتاب‌دهنده سخت‌افزاری)
میزان مصرف رم بافر ثابت و محدود تصاعدی و خطرناک کاملاً مدیریت‌شده و پایدار
دستکاری تجهیزات میانی بالا (دید کامل هدرها) متوسط (حذف گزینه‌ها) غیرممکن (رمزنگاری یکپارچه سرتاسری)
کارایی در خطوط ناپایدار افت تا ۶۰ درصد حفظ کارایی مطلوب حفظ پایداری و بیشینه سرعت

راهکار نوین هسته لینوکس: صف‌های مبتنی بر درخت قرمز-سیاه (RB-Tree)

بحران SACK Panic منجر به بازطراحی زیرساخت پشته شبکه لینوکس توسط اریک دومازه گردید:

  1. بررسی سرریز شمارنده‌ها: تابع tcp_shifted_skb() ارتقا یافت تا مانع از چرخش فیلد gso_segs به مقدار صفر شود.
  2. جایگزینی لیست با درخت قرمز-سیاه: صف بسته‌های نامنظم از لیست خطی به درخت قرمز-سیاه (struct rb_node) انتقال یافت و پیچیدگی الگوریتم از $O(N)$ به $O(\log N)$ کاهش پیدا کرد که تهدید CVE-2019-11478 را رفع نمود.
  3. تثبیت کف اندازه MSS در کد منبع: هسته اجازه کوچک‌تر شدن قطعات به کمتر از ۴۸ بایت را در زمان اجرای tcp_fragment() نمی‌دهد.

این تغییرات بنیادی موجب شد مزایای عملکردی SACK بدون به خطر انداختن پایداری حافظه سیستم حفظ گردد.

چک‌لیست مهندسان شبکه برای ایمن‌سازی زیرساخت TCP SACK

برای اطمینان از تاب‌آوری زیرساخت شبکه در برابر حملات فرسایشی، اقدامات زیر پیشنهاد می‌شود:

  1. بررسی نگارش هسته سیستم‌ها: حصول اطمینان از ارتقای سرورها به نسخه‌های امن (لینوکس 4.19.52+، 5.1.11+ یا نگارش‌های اصلاح‌شده توزیع‌های معتبر).
  2. پیکربندی سد حفاظتی MSS: فعال‌سازی پالایش بسته‌ها در روترهای لبه و رد بسته‌های با MSS زیر ۵۳۶ بایت.
  3. بازبینی متغیرهای Sysctl: اطمینان از تنظیم مقدار net.ipv4.tcp_min_snd_mss بر روی ۵۳۶ در سرورهای متصل به اینترنت.
  4. جداسازی ترافیک ارتباطات دورکاری: استفاده از بسترهای رمزگذاری‌شده لایه ۳ نظیر RiceGuard برای خاتمه دادن ایمن نشست‌های TCP.
  5. پایش بار پردازشی SoftIRQ: تنظیم هشدارهای نظارتی پرومتئوس برای پردازه ksoftirqd جهت کشف زودهنگام حملات الگوریتمی صف.
  6. فعال‌سازی مکانیزم SYN-Cookies: همگام‌سازی تمهیدات با net.ipv4.tcp_syncookies=1 جهت حفظ بافر در مرحله شروع ارتباط.

تلفیق اقدامات استحکام‌بخشی هسته و به کارگیری معماری لایه ۳ رمزنگاری‌شده، متضمن بالاترین راندمان شبکه همزمان با دفع قطعی تهدیدات فرسایشی حافظه خواهد بود.