پروتکلهای پیشرفته ارتباطی بر پایههای قدرتمند ریاضی نظیر منحنی بیضوی Curve25519 برای تبادل کلید و الگوریتم ChaCha20-Poly1305 برای رمزگذاری دادهها استوار هستند. با این وجود، استحکام این الگوریتمها زمانی که کلیدهای محرمانه در فضای حافظه اصلی برنامه به صورت متنی و بدون محافظت باقی بمانند، کارایی عملی خود را از دست میدهد.
در ساختار متداول سرویسهای ارتباطی لینوکس، برنامهها کلیدهای بلندمدت و کلیدهای موقت نشستها را در بخش حافظه پویا (Heap) ذخیره میکنند. این روش اطلاعات حیاتی را در معرض خطرات متعددی قرار میدهد: خواندن مستقیم حافظه از طریق فایل /proc/$PID/mem توسط بدافزارهای دارای دسترسی، ایجاد فایلهای گزارش خرابی حافظه (Core Dump)، انتقال صفحات حافظه به بخش سواپ (Swap) ذخیرهسازی و پایش متغیرها توسط ابزار ptrace(2).
سازوکار حلقه کلید هسته لینوکس (keyrings(7) و رابط keyctl(2)) با انتقال ذخیرهسازی کلیدها از حافظه برنامه به ساختارهای امن در فضای هسته لینوکس، راهکاری بنیادین برای مهار این آسیبپذیریها ارائه میدهد.
مسیرهای نفوذ به حافظه برنامههای کاربردی
برای درک اهمیت جداسازی کلیدها در سطح هسته، خطرات پیشروی یک سرویس ارتباطی معمول را بررسی میکنیم:
[ بدافزار نفوذی / مهاجم دارای دسترسی محلی ]
|
+---> ptrace(PTRACE_PEEKDATA, PID) -------> [ حافظه پویای سرویس (Heap) ]
| |
+---> خواندن فایل /proc/$PID/mem ----------> [ کلید خصوصی Curve25519 ]
| [ کلیدهای نشست ChaCha20 ]
+---> پارتیشن حافظه سواپ (Swap) <----------+ (انتقال صفحات غیرفعال)
|
+---> فایل گزارش خرابی Core Dump <---------+ (توقف ناگهانی برنامه)
- پایش حافظه در حال اجرا: مهاجمی که به دسترسیهای سیستمی دست یافته باشد میتواند با استفاده از ابزارهای خطایابی یا خواندن مستقیم
/proc/$PID/mem، حافظه برنامه را بررسی کرده و کلیدهای متنی را استخراج نماید. - ماندگاری اطلاعات در گزارشهای خطا: در صورت بروز خطای اجرایی، سیستمعامل ممکن است تصویری کامل از حافظه برنامه را در قالب فایل Core Dump روی دیسک ذخیره کند که میتواند منجر به باقی ماندن کلیدها روی حافظه دائمی شود.
- انتقال به حافظه سواپ دیسک: در شرایط کمبود حافظه رم، سیستمعامل صفحات حافظه غیرفعال را به پارتیشن سواپ روی دیسک منتقل میکند. در صورتی که حافظه توسط توابعی مانند
mlock(2)تثبیت نشده باشد، کلیدها بدون رمزگذاری روی دیسک نوشته میشوند.
ساختار سلسلهمراتبی دستههای کلید (Keyrings)
هسته لینوکس کلیدها را در مجموعههایی با ساختار درختی نگهداری میکند که هر دسته به یک سطح امنیتی اختصاص دارد:
[ Thread Keyring: ویژه رشته پردازشی جاری ]
^
| ارثبری
[ Process Keyring: مشترک میان تمام رشتههای فرایند ]
^
| ارثبری
[ Session Keyring: متعلق به نشست کاری یا سرویس ]
^
| ارثبری
[ User Keyring: متصل به شناسه کاربری سیستم UID ]
- Thread Keyring: فقط برای رشته در حال اجرا معتبر است و با پایان آن حذف میگردد.
- Process Keyring: میان تمام رشتههای یک فرایند به اشتراک گذاشته میشود و با توقف برنامه پاک میشود.
- Session Keyring: به تمام زیرفرآیندهای مشتق شده از یک نشست سیستمی به ارث میرسد.
- User Keyring: به شناسه کاربر متصل است و در بازههای ورود و خروج کاربر در حافظه باقی میماند.
هسته لینوکس با این کلیدها به عنوان اشیای سیستمی مجزا (struct key) در فضای حافظه محافظتشده PAGE_KERNEL رفتار میکند. برنامههای کاربردی صرفا با شناسههای عددی ۳۲ بیتی (key_serial_t) با کلیدها ارتباط برقرار میکنند که مانع از دسترسی مستقیم به نشانی فیزیکی حافظه میشود.
پیادهسازی سازوکار امن با کتابخانه keyctl
یک سرویس امن ارتباطی در زمان راهاندازی کلیدها را مستقیما در حلقه کلید هسته بارگذاری میکند:
#include <sys/types.h>
#include <keyutils.h>
#include <stdio.h>
#include <string.h>
/* ذخیره کلید خصوصی Curve25519 در دسته کلید فرایند */
key_serial_t store_vpn_private_key(const unsigned char *raw_key, size_t key_len) {
key_serial_t key_id;
/* افزودن کلید به دسته کلید اختصاصی فرایند */
key_id = add_key("user",
"vpnrice:curve25519_priv",
raw_key,
key_len,
KEY_SPEC_PROCESS_KEYRING);
if (key_id == -1) {
perror("خطا در add_key");
return -1;
}
/* تنظیم دسترسیها: مشاهده، خواندن و جستجو برای فرایند مالک */
keyctl_setperm(key_id, KEY_POS_VIEW | KEY_POS_READ | KEY_POS_SEARCH);
/* تعیین مهلت زمانی برای حذف خودکار کلید (مثلا ۳۶۰۰ ثانیه) */
keyctl_set_timeout(key_id, 3600);
return key_id;
}
تنظیم دقیق سطح دسترسیها
دستور keyctl_setperm() سطوح دسترسی را برای گروههای مختلف مشخص مینماید:
| پرچم دسترسی | کاربرد عملیاتی | مزیت امنیتی |
|---|---|---|
KEY_POS_VIEW |
مشاهده مشخصات و عنوان کلید | بررسی وجود کلید بدون دسترسی به متن اصلی |
KEY_POS_READ |
خواندن محتوای کلید | منحصرا در اختیار رشته پردازشی مالک کلید |
KEY_POS_WRITE |
بهروزرسانی محتوای کلید | جلوگیری از تغییرات غیرمجاز در کلیدهای فعال |
KEY_POS_SEARCH |
جستجوی شناسه بر اساس نام | امکان بازیابی کلید از دستههای سلسلهمراتبی |
KEY_POS_LINK |
اتصال کلید به دستههای دیگر | ممانعت از انتشار اشتباه کلید در دستههای عمومی |
KEY_POS_SETATTR |
ویرایش دسترسیها یا زمان انقضا | قفل کردن تنظیمات امنیتی کلیدها |
پس از پایان زمان اعتبار، هسته لینوکس ارجاع به کلید را حذف کرده و فورا تابع memzero_explicit() را برای پاکسازی کامل حافظه فراخوانی میکند.
ارزیابی مقایسهای سطوح امنیت حافظه
نگهداری کلیدها در هسته لینوکس تحولی اساسی در لایههای حفاظتی ایجاد میکند:
| شاخص ارزیابی | نگهداری در حافظه معمولی برنامه (malloc) |
ساختار حلقه کلید هسته (keyctl) |
|---|---|---|
| محل ذخیرهسازی | حافظه مجازی فضای کاربر | فضای اختصاصی هسته (PAGE_KERNEL) |
| روش دسترسی | اشارهگر مستقیم به حافظه | شناسه عددی ۳۲ بیتی (key_serial_t) |
آسیبپذیری /proc/$PID/mem |
کلیدها به راحتی قابل خواندن هستند | ایمن (کلید در نقشه حافظه برنامه وجود ندارد) |
| نشت اطلاعات در Core Dump | ذخیره کامل در فایل خروجی خطا | ایمن (حافظه هسته در گزارشها وارد نمیشود) |
| نشت به حافظه سواپ | احتمال بالا در صورت عدم استفاده از mlock |
ایمن به دلیل مدیریت غیرقابل انتقال حافظه هسته |
| انقضای خودکار کلید | نیازمند برنامهنویسی تایمر در برنامه | مدیریت مستقیم توسط هسته با keyctl_set_timeout |
| پاکسازی فیزیکی حافظه | استفاده از memset (احتمال نادیده گرفتن) |
پاکسازی تضمینشده با memzero_explicit() |
یکپارچهسازی در خدمات VPNRice و پروتکل RiceGuard
در ساختار امنیتی VPNRice، سازوکار Kernel Keyring در سرورهای دروازه و کارخواههای مبتنی بر پروتکل RiceGuard تعبیه شده است:
[ سرویس کنترلی RiceGuard ]
|
| 1. add_key("user", "riceguard:ephemeral_session", ...)
v
[ زیرسیستم Linux Kernel Keyring ]
| (کلید در حافظه امن PAGE_KERNEL قرار دارد)
|
| 2. تبادل امن از طریق Crypto API هسته
v
[ موتور هدایت ترافیک RiceGuard Anycast ]
- مدیریت نشستهای دورهای: در زمان تبادل مجدد کلیدها، RiceGuard کلیدهای جدید را با مهلت دقیق ۱۲۰ ثانیه بر اساس استانداردهای WireGuard ثبت میکند تا با آغاز نشست تازه، کلیدهای پیشین فورا از حافظه محو شوند.
- عدم ماندگاری در فضای کاربری: پس از انتقال کلیدها به حلقه هسته، بافرهای اولیه توسط تابع
explicit_bzero()بازنویسی میشوند تا اثری از اطلاعات محرمانه در حافظه برنامه باقی نماند. - تفکیک کانتینرها: روی سرورهای اشتراکی، برای هر محیط ایزوله یک حلقه کلید مجزا تعریف میشود تا دسترسی میان بخشهای مختلف به طور کامل مسدود گردد.
دستورهای مدیریتی و پایش در سیستم لینوکس
مدیران سیستم میتوانند با استفاده از دستورهای خط فرمان وضعیت کلیدهای فعال و زمان اعتبار آنها را بررسی کنند:
# مشاهده ساختار درختی کلیدهای فرایند جاری
keyctl show @p
# فهرست کلیدهای ثبتشده همراه با زمان باقیمانده
keyctl list @p
# بررسی آمار کلی کلیدهای فعال در هسته
cat /proc/keys
جداسازی ذخیرهسازی کلیدها از فضای آسیبپذیر برنامههای کاربردی، لایهای نفوذناپذیر برای زیرساختهای نوین شبکه به ارمغان میآورد.



